<?php

namespace App\Services;

use App\Models\Song;
use App\Models\SongArrangement;
use App\Services\DTO\ParsedCcliSection;
use App\Support\CcliLabels;
use Illuminate\Support\Collection;

final class CcliTranslationPairingService
{
    public function __construct(
        private readonly CcliPasteParser $parser,
    ) {}

    /**
     * Pair a CCLI paste as translation for an existing local song.
     * Returns mapping for UI review (does NOT save to DB).
     *
     * @return array{
     *     song: Song,
     *     mapping: array<int, array{local_label: string, ccli_label: string|null, distributed_lines: string[]}>,
     *     unmatched_labels: string[],
     *     distributed_text: string
     * }
     */
    public function pair(Song $localSong, string $ccliRawText, string $arrangementName = 'normal'): array
    {
        $parsed = $this->parser->parse($ccliRawText);

        $localSong->loadMissing(['arrangements.arrangementSections.section.slides', 'arrangements.arrangementSections.section.label']);

        $arrangement = $this->findArrangement($localSong, $arrangementName);

        if ($arrangement === null) {
            return [
                'song' => $localSong,
                'mapping' => [],
                'unmatched_labels' => [],
                'distributed_text' => '',
            ];
        }

        $ccliByCanonical = $this->sectionsByCanonicalLabel($parsed->sections);
        $mapping = [];
        $unmatchedLabels = [];
        $allDistributedLines = [];

        foreach ($arrangement->arrangementSections->sortBy('order') as $arrangementSection) {
            $section = $arrangementSection->section;
            $label = $section?->label;

            if ($section === null || $label === null) {
                continue;
            }

            $localCanonical = $this->canonicalLabel($label->name, null);
            $matchedSection = $ccliByCanonical[$localCanonical] ?? null;
            $slides = $section->slides->sortBy('order')->values();

            if ($matchedSection === null) {
                $unmatchedLabels[] = $label->name;
                $distributedLines = array_fill(0, max($slides->count(), 1), '');
            } else {
                $distributedLines = $this->distributeLines(
                    $matchedSection->linesTranslated ?? $matchedSection->lines,
                    $slides,
                );
            }

            $allDistributedLines = array_merge($allDistributedLines, $distributedLines);
            $mapping[] = [
                'local_label' => $label->name,
                'ccli_label' => $matchedSection?->label,
                'distributed_lines' => $distributedLines,
            ];
        }

        return [
            'song' => $localSong,
            'mapping' => $mapping,
            'unmatched_labels' => $unmatchedLabels,
            'distributed_text' => implode("\n", $allDistributedLines),
        ];
    }

    private function findArrangement(Song $localSong, string $arrangementName): ?SongArrangement
    {
        return $localSong->arrangements->where('name', $arrangementName)->first()
            ?? $localSong->arrangements->where('is_default', true)->first()
            ?? $localSong->arrangements->first();
    }

    /**
     * @param  ParsedCcliSection[]  $sections
     * @return array<string, ParsedCcliSection>
     */
    private function sectionsByCanonicalLabel(array $sections): array
    {
        $byCanonical = [];

        foreach ($sections as $section) {
            $canonical = $this->canonicalLabel($section->kind, $section->number);
            $byCanonical[$canonical] ??= $section;
        }

        return $byCanonical;
    }

    private function canonicalLabel(string $kind, ?string $number): string
    {
        $label = trim($kind.' '.($number ?? ''));

        return mb_strtolower(CcliLabels::normalizeLabelName($label));
    }

    /**
     * Distribute CCLI lines into local slide slots, preserving each local slide line count.
     *
     * @param  string[]  $lines
     * @param  Collection<int, mixed>  $slides
     * @return string[]
     */
    private function distributeLines(array $lines, Collection $slides): array
    {
        if ($slides->isEmpty()) {
            return $lines;
        }

        $distributed = [];
        $offset = 0;
        $lastSlideIndex = $slides->count() - 1;

        foreach ($slides as $index => $slide) {
            $lineCount = max(count(explode("\n", $slide->text_content ?? '')), 1);
            $chunk = array_slice($lines, $offset, $lineCount);
            $offset += $lineCount;

            if ($index === $lastSlideIndex && $offset < count($lines)) {
                $chunk = array_merge($chunk, array_slice($lines, $offset));
            }

            $distributed[] = implode("\n", $chunk);
        }

        return $distributed;
    }
}
