<?php

namespace App\Jobs;

use App\Models\Video;
use App\Models\VideoAudioTrack;
use App\Services\LlmLyricsService;
use App\Services\NasSyncService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;

/**
 * Bake heavy emoji decoration into the saved lyrics JSON using the active LLM.
 * Original words are preserved verbatim; emojis are layered on top (in-line +
 * trailing, multiple per line) per the admin's decoration prompt.
 *
 * Runs as its own job so a flaky LLM call can never delay or fail a successful
 * transcription. Safe to re-run — already-decorated lines are skipped, so a
 * second pass only fills in gaps.
 */
class DecorateLyricsJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $timeout = 600;
    public int $tries = 1;

    /** Languages written without spaces between words (mirrors transcribe.py). */
    private const SPACELESS_LANGS = ['th', 'zh', 'ja', 'lo', 'my', 'km', 'yue', 'wuu'];

    public function __construct(public int $videoId, public ?int $trackId = null)
    {
        $this->onQueue('video-processing');
    }

    public function handle(LlmLyricsService $llm, NasSyncService $nas): void
    {
        // Two-layer toggle: the admin's per-pipeline switch (Lyrics Pipeline
        // page) gates this job, and the LLM-service-level switch (AI/LLM page)
        // gates the LLM call inside it. Either being OFF skips decoration.
        if (\App\Models\Setting::get('lyrics_llm_decorate', 'true') !== 'true') return;
        if (! $llm->decorateEnabled()) return;

        $video = Video::find($this->videoId);
        if (! $video) return;

        $track = $this->trackId ? VideoAudioTrack::find($this->trackId) : null;
        if ($this->trackId && ! $track) return;

        $data = $nas->getLyrics($video, $track);
        if (! is_array($data) || empty($data['lines'])) return;
        if (($data['status'] ?? null) !== 'ready') return;

        // Decorate only lines that haven't been decorated yet — a re-run fills
        // gaps cheaply instead of re-stamping the whole song.
        $texts   = [];
        $indices = [];
        foreach ($data['lines'] as $i => $ln) {
            if (! empty($ln['decorated'])) continue;
            $t = (string) ($ln['text'] ?? '');
            if (trim($t) === '') continue;
            $texts[]   = $t;
            $indices[] = $i;
        }
        if (! $texts) return;

        try {
            $decorated = $llm->decorateLines($texts);
        } catch (\Throwable $e) {
            Log::warning('DecorateLyricsJob: LLM call failed: ' . $e->getMessage(), [
                'video_id' => $this->videoId, 'track_id' => $this->trackId,
            ]);
            return;
        }
        if (! $decorated) return;

        $applied = 0;
        foreach ($decorated as $localIdx => $newText) {
            if (! isset($indices[$localIdx])) continue;
            $globalIdx = $indices[$localIdx];
            $line = &$data['lines'][$globalIdx];

            $line['text']       = $newText;
            $line['decorated']  = true;
            // The words array no longer matches the new (emoji-laced) text. We
            // redistribute the existing [start,end] window evenly across the
            // new tokens so the karaoke word-highlight still tracks the audio.
            // Tokens that are pure emoji get the same per-slot timing as words.
            $lang = (string) ($line['lang'] ?? ($data['language'] ?? 'en'));
            $line['words'] = $this->redistributeWords(
                (float) ($line['start'] ?? 0),
                (float) ($line['end']   ?? 0),
                $newText, $lang
            );
            unset($line);
            $applied++;
        }

        if (! $applied) return;

        $data['decorated_at'] = now()->toIso8601String();
        $nas->putLyrics($video, $track, $data);

        Log::info('DecorateLyricsJob: done', [
            'video_id' => $this->videoId, 'track_id' => $this->trackId,
            'decorated' => $applied,
        ]);
    }

    /**
     * Evenly distribute [start,end] across the line's tokens. Words for spaced
     * languages, characters for spaceless scripts (Thai/CJK/…). Used after
     * decoration so the karaoke word-highlight still tracks the audio.
     */
    private function redistributeWords(float $start, float $end, string $text, string $lang): array
    {
        if ($text === '' || $end <= $start) return [];
        $spaceless = in_array($lang, self::SPACELESS_LANGS, true);
        $tokens = $spaceless
            ? preg_split('//u', $text, -1, PREG_SPLIT_NO_EMPTY)
            : preg_split('/\s+/u', trim($text), -1, PREG_SPLIT_NO_EMPTY);
        $n = count($tokens ?: []);
        if ($n === 0) return [];
        $slot = ($end - $start) / $n;
        $out  = [];
        foreach ($tokens as $i => $t) {
            $out[] = [
                'start' => round($start + $i * $slot, 3),
                'end'   => round($start + ($i + 1) * $slot, 3),
                'text'  => $t,
            ];
        }
        return $out;
    }
}
