<?php

declare(strict_types=1);

final class MyronBundleImpactAnalysisService
{
    public function __construct(
        private readonly PDO $pdo,
        private readonly MyronSiteTypeRegistryRepository $siteTypes,
        private readonly MyronBundleActivationRepository $bundles,
    ) {
    }

    /**
     * @return array<string, mixed>
     */
    public function analyze(string $bundleId): array
    {
        $bundle = $this->siteTypes->findSiteTypeById($bundleId);
        if (!is_array($bundle)) {
            throw new InvalidArgumentException('Bundle not found.');
        }
        $state = $this->bundles->findBundleState($bundleId);
        $isPrimary = is_array($state) && ($state['is_primary'] ?? false) === true;

        $pageStatement = $this->pdo->prepare(
            'SELECT
                pages.id AS page_id,
                pages.title,
                pages.slug,
                pages.path,
                pages.publication_state,
                pages.current_published_version_id,
                page_versions.title AS version_title
             FROM pages
             LEFT JOIN page_versions ON page_versions.id = pages.current_published_version_id
             WHERE pages.source_bundle_id = :bundle_id
               AND (pages.disabled_by_bundle_removal_at IS NULL OR pages.disabled_by_bundle_removal_at = \'\')
             ORDER BY pages.path ASC, pages.id ASC'
        );
        $pageStatement->execute([':bundle_id' => trim($bundleId)]);
        $pageRows = array_values(array_filter($pageStatement->fetchAll(PDO::FETCH_ASSOC), 'is_array'));
        $pageIds = array_values(array_map(static fn (array $row): string => (string) ($row['page_id'] ?? ''), $pageRows));
        $menuItems = $this->menuItemsForPages($pageIds);
        $menuByPage = [];
        foreach ($menuItems as $item) {
            $menuByPage[(string) ($item['page_id'] ?? '')] = true;
        }

        $pagesOwned = [];
        $counts = [
            'published_in_menu' => 0,
            'published_not_in_menu' => 0,
            'draft' => 0,
        ];
        foreach ($pageRows as $row) {
            $pageId = (string) ($row['page_id'] ?? '');
            $stateName = (string) ($row['publication_state'] ?? 'draft');
            $inMenu = isset($menuByPage[$pageId]);
            if ($stateName === 'published') {
                $counts[$inMenu ? 'published_in_menu' : 'published_not_in_menu']++;
            } else {
                $counts['draft']++;
            }
            $pagesOwned[] = [
                'page_id' => $pageId,
                'title' => (string) (($row['version_title'] ?? '') ?: ($row['title'] ?? '')),
                'slug' => (string) ($row['path'] ?? ''),
                'publish_state' => $stateName,
                'in_menu' => $inMenu,
            ];
        }

        $payload = [
            'schema_version' => 1,
            'bundle_id' => trim($bundleId),
            'bundle_label' => MyronSiteTypeRegistryRepository::displayLabelForSiteType($bundle),
            'is_primary' => $isPrimary,
            'pages_owned' => $pagesOwned,
            'categorized_counts' => $counts,
            'menu_items_affected' => $menuItems,
            'internal_links_warning' => $this->internalLinksWarnings($pageRows),
            'mic_references_warning' => $this->micReferenceWarnings($pageRows),
        ];
        if ($isPrimary) {
            $payload['primary_warning'] = true;
        }

        return $payload;
    }

    /**
     * @param list<string> $pageIds
     * @return list<array<string, mixed>>
     */
    private function menuItemsForPages(array $pageIds): array
    {
        $pageIds = array_values(array_filter($pageIds, static fn (string $id): bool => $id !== ''));
        if ($pageIds === []) {
            return [];
        }
        $placeholders = implode(',', array_fill(0, count($pageIds), '?'));
        $statement = $this->pdo->prepare(
            'SELECT
                menu_items.id AS menu_item_id,
                menu_items.menu_id,
                menu_items.page_id,
                menu_items.label,
                menu_items.sort_order,
                menu_items.parent_menu_item_id,
                menu_items.created_at,
                menu_items.updated_at
             FROM menu_items
             WHERE menu_items.page_id IN (' . $placeholders . ')
             ORDER BY menu_items.menu_id ASC, menu_items.sort_order ASC, menu_items.id ASC'
        );
        $statement->execute($pageIds);

        return array_values(array_filter($statement->fetchAll(PDO::FETCH_ASSOC), 'is_array'));
    }

    /**
     * @param list<array<string, mixed>> $ownedPages
     * @return list<array<string, mixed>>
     */
    private function internalLinksWarnings(array $ownedPages): array
    {
        $ownedIds = array_fill_keys(array_map(static fn (array $row): string => (string) ($row['page_id'] ?? ''), $ownedPages), true);
        $warnings = [];
        foreach ($ownedPages as $owned) {
            $slug = myronNormalizePath((string) ($owned['path'] ?? ''));
            $statement = $this->pdo->prepare(
                'SELECT pages.id AS page_id, pages.title AS page_title
                 FROM page_versions
                 INNER JOIN pages ON pages.id = page_versions.page_id
                 WHERE page_versions.content_json LIKE :needle
                 ORDER BY pages.path ASC'
            );
            $statement->execute([':needle' => '%' . $slug . '%']);
            foreach ($statement->fetchAll(PDO::FETCH_ASSOC) as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $pageId = (string) ($row['page_id'] ?? '');
                if ($pageId === '' || isset($ownedIds[$pageId])) {
                    continue;
                }
                $warnings[$pageId]['page_id'] = $pageId;
                $warnings[$pageId]['page_title'] = (string) ($row['page_title'] ?? '');
                $warnings[$pageId]['matched_slugs'][$slug] = true;
            }
        }

        return array_values(array_map(
            static function (array $warning): array {
                $warning['matched_slugs'] = array_keys($warning['matched_slugs'] ?? []);
                return $warning;
            },
            $warnings
        ));
    }

    /**
     * @param list<array<string, mixed>> $ownedPages
     * @return list<array<string, mixed>>
     */
    private function micReferenceWarnings(array $ownedPages): array
    {
        $ownedIds = array_fill_keys(array_map(static fn (array $row): string => (string) ($row['page_id'] ?? ''), $ownedPages), true);
        $warnings = [];
        foreach (array_keys($ownedIds) as $pageId) {
            if ($pageId === '') {
                continue;
            }
            foreach (['[mic-page-' . $pageId . ']', '[mic-page-' . myronNormalizeSlug($pageId) . ']'] as $token) {
                $statement = $this->pdo->prepare(
                    'SELECT pages.id AS page_id, pages.title AS page_title
                     FROM page_versions
                     INNER JOIN pages ON pages.id = page_versions.page_id
                     WHERE page_versions.content_json LIKE :needle
                     ORDER BY pages.path ASC'
                );
                $statement->execute([':needle' => '%' . $token . '%']);
                foreach ($statement->fetchAll(PDO::FETCH_ASSOC) as $row) {
                    if (!is_array($row)) {
                        continue;
                    }
                    $referencingPageId = (string) ($row['page_id'] ?? '');
                    if ($referencingPageId === '' || isset($ownedIds[$referencingPageId])) {
                        continue;
                    }
                    $warnings[] = [
                        'referencing_page_id' => $referencingPageId,
                        'referencing_page_title' => (string) ($row['page_title'] ?? ''),
                        'mic_token' => $token,
                    ];
                }
            }
        }

        return $warnings;
    }
}
