<?php

namespace App\Support;

/**
 * Build an ASS (Advanced SubStation Alpha) subtitle file with word-level
 * karaoke timing from a lyrics JSON payload (the shape produced by
 * ml/transcribe.py). Burned into the downloadable mp4 via libass.
 *
 * Karaoke fill uses ASS \k tags: each word's \k duration (centiseconds) is the
 * time it waits before the highlight reaches it, so the cumulative \k before a
 * word equals its onset relative to the line start. PrimaryColour is the sung
 * (filled) colour, SecondaryColour the not-yet-sung colour.
 */
class LyricsAss
{
    /** Render at the same canvas the slideshow uses. */
    private const W = 1280;
    private const H = 720;

    /**
     * Write an .ass file for the given lyrics data. Returns true on success,
     * false when there are no usable timed lines (caller should skip burning).
     */
    public static function write(array $lyrics, string $outPath): bool
    {
        $lines = $lyrics['lines'] ?? [];
        if (! is_array($lines) || ! $lines) return false;

        $events = [];
        foreach ($lines as $ln) {
            $start = $ln['start'] ?? null;
            $end   = $ln['end'] ?? null;
            if ($start === null || $end === null) continue;

            $words = (isset($ln['words']) && is_array($ln['words']) && $ln['words']) ? $ln['words'] : null;
            $text  = $words
                ? self::karaokeText($words, (float) $start)
                : self::escape((string) ($ln['text'] ?? ''));
            if ($text === '') continue;

            // Hold the line a touch past its last word for readability.
            // Fields per the Format line: Layer,Start,End,Style,Name,MarginL,MarginR,Effect,Text
            $events[] = 'Dialogue: 0,' . self::ts((float) $start) . ',' . self::ts((float) $end + 0.4)
                . ',Lyrics,,0,0,,' . $text;
        }
        if (! $events) return false;

        $ass = self::header() . implode("\n", $events) . "\n";
        return file_put_contents($outPath, $ass) !== false;
    }

    private static function karaokeText(array $words, float $lineStart): string
    {
        $out  = '';
        $lead = (int) round((((float) $words[0]['start']) - $lineStart) * 100);
        if ($lead > 0) $out .= '{\k' . $lead . '}';

        $n = count($words);
        foreach ($words as $i => $w) {
            $wStart = (float) $w['start'];
            $wEnd   = (float) $w['end'];
            $dur    = max(1, (int) round(($wEnd - $wStart) * 100));
            $out   .= '{\k' . $dur . '}' . self::escape((string) $w['text']);

            if ($i < $n - 1) {
                $gap = (int) round((((float) $words[$i + 1]['start']) - $wEnd) * 100);
                $out .= ($gap > 0 ? '{\k' . $gap . '}' : '') . ' ';
            }
        }
        return $out;
    }

    /** ASS timestamp: H:MM:SS.cc (centiseconds). */
    private static function ts(float $t): string
    {
        if ($t < 0) $t = 0;
        $cs = (int) round($t * 100);
        $h  = intdiv($cs, 360000);
        $m  = intdiv($cs % 360000, 6000);
        $s  = intdiv($cs % 6000, 100);
        $c  = $cs % 100;
        return sprintf('%d:%02d:%02d.%02d', $h, $m, $s, $c);
    }

    private static function escape(string $s): string
    {
        // Strip ASS override delimiters and collapse newlines.
        $s = str_replace(['{', '}', '\\'], ['(', ')', '/'], $s);
        return str_replace(["\r\n", "\n", "\r"], ' ', $s);
    }

    private static function header(): string
    {
        // Colours are &HAABBGGRR. Primary = sung (white), Secondary = unsung
        // (translucent grey), heavy outline + shadow for legibility over artwork.
        return "[Script Info]\n"
            . "ScriptType: v4.00+\n"
            . 'PlayResX: ' . self::W . "\n"
            . 'PlayResY: ' . self::H . "\n"
            . "WrapStyle: 0\n"
            . "ScaledBorderAndShadow: yes\n\n"
            . "[V4+ Styles]\n"
            . "Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\n"
            . "Style: Lyrics,Sans,54,&H00FFFFFF,&H64C8C8C8,&H00101010,&H80000000,-1,0,0,0,100,100,0,0,1,3,2,2,80,80,70,1\n\n"
            . "[Events]\n"
            . "Format: Layer, Start, End, Style, Name, MarginL, MarginR, Effect, Text\n";
    }
}
