<?php

declare(strict_types=1);

final class MyronSectionContentMigrationService
{
    /**
     * @param list<array<string, mixed>> $sourceSections
     * @param list<array<string, mixed>> $destinationSections
     * @param array<string, mixed> $sourceContent
     */
    public function report(
        array $sourceSections,
        array $destinationSections,
        array $sourceContent,
        string $context = 'pages'
    ): MyronSectionContentMigrationReport {
        $sourceRows = $this->indexedSections($sourceSections, $sourceContent);
        $destinationRows = $this->indexedSections($destinationSections, []);
        $usedSource = [];
        $mapped = [];
        $unmatchedDestination = [];

        foreach ($destinationRows as $destination) {
            $source = $this->bestSourceForDestination($destination, $sourceRows, $usedSource);
            if (!is_array($source)) {
                $unmatchedDestination[] = $this->reportRow($destination);
                continue;
            }

            $usedSource[(string) $source['instanceKey']] = true;
            $mapped[] = [
                'source' => $this->reportRow($source),
                'destination' => $this->reportRow($destination),
                'matchedBy' => (string) $source['matchedBy'],
                'fields' => array_values(array_keys(is_array($source['content'] ?? null) ? $source['content'] : [])),
            ];
        }

        $unmatchedSource = [];
        foreach ($sourceRows as $source) {
            if (!isset($usedSource[(string) $source['instanceKey']])) {
                $unmatchedSource[] = $this->reportRow($source);
            }
        }

        return new MyronSectionContentMigrationReport($context, $mapped, $unmatchedSource, $unmatchedDestination);
    }

    /**
     * @param list<array<string, mixed>> $sourceSections
     * @param list<array<string, mixed>> $destinationSections
     * @param array<string, mixed> $sourceContent
     * @return array<string, mixed>
     */
    public function remapContent(array $sourceSections, array $destinationSections, array $sourceContent): array
    {
        $report = $this->report($sourceSections, $destinationSections, $sourceContent)->toArray();
        $sourceRows = $this->indexedSections($sourceSections, $sourceContent);
        $sourceByInstance = [];
        foreach ($sourceRows as $source) {
            $sourceByInstance[(string) $source['instanceKey']] = $source;
        }

        $remapped = [
            'eyebrow' => trim((string) ($sourceContent['eyebrow'] ?? '')),
            'headline' => trim((string) ($sourceContent['headline'] ?? '')),
            'intro' => trim((string) ($sourceContent['intro'] ?? '')),
            'body' => trim((string) ($sourceContent['body'] ?? '')),
            'ctaLabel' => trim((string) ($sourceContent['ctaLabel'] ?? '')),
            'ctaHref' => trim((string) ($sourceContent['ctaHref'] ?? '')),
            '_schemaVersion' => 2,
            'sections' => [],
        ];

        foreach ($report['mapped'] as $mapping) {
            if (!is_array($mapping)) {
                continue;
            }
            $sourceKey = (string) ($mapping['source']['instanceKey'] ?? '');
            $destinationKey = (string) ($mapping['destination']['instanceKey'] ?? '');
            if ($sourceKey === '' || $destinationKey === '') {
                continue;
            }
            $content = is_array($sourceByInstance[$sourceKey]['content'] ?? null) ? $sourceByInstance[$sourceKey]['content'] : [];
            if ($content !== []) {
                $remapped['sections'][$destinationKey] = $content;
            }
        }

        return $remapped;
    }

    /**
     * @param list<array<string, mixed>> $sections
     * @param array<string, mixed> $content
     * @return list<array<string, mixed>>
     */
    private function indexedSections(array $sections, array $content): array
    {
        $rows = [];
        foreach ($sections as $position => $section) {
            if (!is_array($section)) {
                continue;
            }
            $instanceKey = myronSectionInstanceKey($section, (int) $position);
            $rows[] = [
                'instanceKey' => $instanceKey,
                'position' => (int) $position,
                'sectionKey' => trim((string) ($section['sectionKey'] ?? '')),
                'componentId' => trim((string) ($section['componentId'] ?? '')),
                'variant' => trim((string) ($section['variant'] ?? '')),
                'content' => myronSectionContentForInstance($content, $section, (int) $position),
                'matchedBy' => '',
            ];
        }

        return $rows;
    }

    /**
     * @param list<array<string, mixed>> $sourceRows
     * @param array<string, true> $usedSource
     * @return array<string, mixed>|null
     */
    private function bestSourceForDestination(array $destination, array $sourceRows, array $usedSource): ?array
    {
        foreach (['instanceKey', 'sectionKeyComponentVariant', 'sectionKey', 'componentVariant'] as $strategy) {
            foreach ($sourceRows as $source) {
                $sourceKey = (string) ($source['instanceKey'] ?? '');
                if ($sourceKey === '' || isset($usedSource[$sourceKey])) {
                    continue;
                }
                if ($this->matches($source, $destination, $strategy)) {
                    $source['matchedBy'] = $strategy;
                    return $source;
                }
            }
        }

        return null;
    }

    private function matches(array $source, array $destination, string $strategy): bool
    {
        return match ($strategy) {
            'instanceKey' => (string) $source['instanceKey'] === (string) $destination['instanceKey'],
            'sectionKeyComponentVariant' => (string) $source['sectionKey'] !== ''
                && (string) $source['sectionKey'] === (string) $destination['sectionKey']
                && (string) $source['componentId'] === (string) $destination['componentId']
                && (string) $source['variant'] === (string) $destination['variant'],
            'sectionKey' => (string) $source['sectionKey'] !== ''
                && (string) $source['sectionKey'] === (string) $destination['sectionKey'],
            'componentVariant' => (string) $source['componentId'] !== ''
                && (string) $source['componentId'] === (string) $destination['componentId']
                && (string) $source['variant'] === (string) $destination['variant'],
            default => false,
        };
    }

    /**
     * @return array<string, mixed>
     */
    private function reportRow(array $row): array
    {
        return [
            'instanceKey' => (string) ($row['instanceKey'] ?? ''),
            'position' => (int) ($row['position'] ?? 0),
            'sectionKey' => (string) ($row['sectionKey'] ?? ''),
            'componentId' => (string) ($row['componentId'] ?? ''),
            'variant' => (string) ($row['variant'] ?? ''),
        ];
    }
}
