<?php

declare(strict_types=1);

// DesignSystemRegistry loaded by the app-local JSONM runtime.

final class MyronOnboardingStateRepository
{
    public function __construct(
        private readonly PDO $pdo,
        private readonly MyronSiteTypeRegistryRepository $siteTypes,
        private readonly MyronBundleActivationRepository $bundles,
        ?string $themeDirectory = null,
        private readonly ?DesignSystemRegistry $designSystems = null,
        private readonly ?MyronSiteSetupSelectionService $selectionService = null,
    ) {}

    /**
     * @return array<string, mixed>
     */
    public function getState(): array
    {
        $statement = $this->pdo->query('SELECT * FROM onboarding_state WHERE id = 1 LIMIT 1');
        $row = $statement ? $statement->fetch() : false;

        if (!is_array($row)) {
            return $this->decorateState([
                'primary_bundle_id' => $this->bundles->getPrimaryBundleId() ?? '',
                'archetype_theme_id' => '',
                'applied_design_system_id' => '',
                'setup_status' => 'pending',
                'setup_completed_at' => '',
                'updated_by_user_id' => '',
                'updated_at' => '',
            ]);
        }

        return $this->decorateState($row);
    }

    /**
     * @return list<array<string, string>>
     */
    public function listThemeOptions(): array
    {
        return $this->designSystemRegistry()->listSelectableDefinitions();
    }

    /**
     * @param array<string, mixed> $input
     * @return array<string, mixed>
     */
    public function saveState(array $input, string $actorId): array
    {
        $actor = trim($actorId);
        if ($actor === '') {
            throw new InvalidArgumentException('An actor id is required for site setup changes.');
        }

        $primaryBundleId = trim((string) ($input['primary_bundle_id'] ?? ''));
        $themeId = trim((string) ($input['archetype_theme_id'] ?? ''));
        $activeBundleIds = array_values(array_map(
            'strval',
            is_array($input['active_bundle_ids'] ?? null) ? $input['active_bundle_ids'] : [$primaryBundleId]
        ));
        $selectionManifest = null;
        if (array_key_exists('selection_manifest', $input)) {
            $rawManifest = is_array($input['selection_manifest'] ?? null) ? $input['selection_manifest'] : [];
            $rawManifest['primary_bundle_id'] = $primaryBundleId;
            $selectionManifest = $this->selectionService()->normalizeSelectionManifest($rawManifest);
            $activeBundleIds = array_values(array_map('strval', (array) ($selectionManifest['included_bundle_ids'] ?? [$primaryBundleId])));
        }
        $applyTheme = ($input['apply_theme'] ?? false) === true || (string) ($input['apply_theme'] ?? '') === '1';

        $primary = $this->siteTypes->findSiteTypeById($primaryBundleId);
        if (!is_array($primary)) {
            throw new InvalidArgumentException('Primary bundle `' . $primaryBundleId . '` does not exist.');
        }
        if ((string) ($primary['bundleType'] ?? '') !== 'primary') {
            throw new InvalidArgumentException('Primary bundle `' . $primaryBundleId . '` must be a primary site type.');
        }

        $theme = $themeId !== '' ? $this->findThemeOption($themeId) : null;
        if ($themeId !== '' && $theme === null) {
            throw new InvalidArgumentException('Design system `' . $themeId . '` does not exist.');
        }
        if ($applyTheme && $theme === null) {
            throw new InvalidArgumentException('A registered design system selection is required before it can be applied.');
        }

        $this->bundles->replaceStateWithAudit($primaryBundleId, $activeBundleIds, $actor, 'site-setup');

        $appliedDesignSystemId = '';
        if ($applyTheme && $theme !== null) {
            $record = $this->designSystemRegistry()->applySelectable($themeId);
            $appliedDesignSystemId = (string) ($record['id'] ?? $themeId);
        }

        $now = myronNow();
        $existing = $this->pdo->query('SELECT id, applied_design_system_id, selection_manifest_json FROM onboarding_state WHERE id = 1 LIMIT 1');
        $existingRow = $existing ? $existing->fetch() : false;
        $existingAppliedId = is_array($existingRow) ? trim((string) ($existingRow['applied_design_system_id'] ?? '')) : '';
        $persistedAppliedId = $appliedDesignSystemId !== '' ? $appliedDesignSystemId : $existingAppliedId;
        $existingManifestJson = is_array($existingRow) ? trim((string) ($existingRow['selection_manifest_json'] ?? '')) : '';
        $selectionManifestJson = $selectionManifest !== null
            ? json_encode($selectionManifest, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES)
            : $existingManifestJson;

        $sql = is_array($existingRow)
            ? 'UPDATE onboarding_state
               SET primary_bundle_id = :primary_bundle_id,
                   archetype_theme_id = :archetype_theme_id,
                   applied_design_system_id = :applied_design_system_id,
                   selection_manifest_json = :selection_manifest_json,
                   setup_status = :setup_status,
                   setup_completed_at = :setup_completed_at,
                   updated_by_user_id = :updated_by_user_id,
                   updated_at = :updated_at
               WHERE id = 1'
            : 'INSERT INTO onboarding_state (
                    id,
                    primary_bundle_id,
                    archetype_theme_id,
                    applied_design_system_id,
                    selection_manifest_json,
                    setup_status,
                    setup_completed_at,
                    updated_by_user_id,
                    updated_at
               ) VALUES (
                    1,
                    :primary_bundle_id,
                    :archetype_theme_id,
                    :applied_design_system_id,
                    :selection_manifest_json,
                    :setup_status,
                    :setup_completed_at,
                    :updated_by_user_id,
                    :updated_at
               )';
        $statement = $this->pdo->prepare($sql);
        $statement->execute([
            ':primary_bundle_id' => $primaryBundleId,
            ':archetype_theme_id' => $themeId,
            ':applied_design_system_id' => $persistedAppliedId,
            ':selection_manifest_json' => $selectionManifestJson,
            ':setup_status' => 'configured',
            ':setup_completed_at' => $now,
            ':updated_by_user_id' => $actor,
            ':updated_at' => $now,
        ]);

        return $this->getState();
    }

    /**
     * @return array<string, string>|null
     */
    public function findThemeOption(string $themeId): ?array
    {
        $normalized = trim($themeId);
        if ($normalized === '') {
            return null;
        }

        foreach ($this->listThemeOptions() as $theme) {
            if ((string) $theme['id'] === $normalized) {
                return $theme;
            }
        }

        return null;
    }

    /**
     * APO-205-011 Fix B — record the most-recently-applied design system on the
     * onboarding_state row so reciprocal-telemetry reads (administration.site-
     * setup-state.get, compatibility/configured) reflect the apply.
     *
     * Distinct from saveState() which is the full-wizard write. This is a
     * targeted post-apply hook callable from MyronDefinitionsActionService —
     * the canonical apply path (both MCP action layer and admin POST route).
     *
     * Idempotent: an empty $appliedDesignSystemId leaves the field unchanged.
     * If no row exists yet (pre-wizard install), a minimal row is created with
     * the current primary_bundle_id; the wizard remains the canonical
     * setup_completed_at writer.
     */
    public function recordAppliedDesignSystem(string $appliedDesignSystemId, string $archetypeThemeId, string $actorId): void
    {
        $appliedId = trim($appliedDesignSystemId);
        if ($appliedId === '') {
            return;
        }
        $themeId = trim($archetypeThemeId);
        $actor   = trim($actorId);
        $now     = myronNow();

        $existing = $this->pdo->query('SELECT id, primary_bundle_id, archetype_theme_id, setup_status FROM onboarding_state WHERE id = 1 LIMIT 1');
        $row = $existing ? $existing->fetch() : false;

        if (is_array($row)) {
            // UPDATE — preserve archetype_theme_id when no new id is provided
            // (the apply may be a packaged archetype re-apply where the wizard's
            // archetype selection still stands).
            $effectiveThemeId = $themeId !== '' ? $themeId : (string) ($row['archetype_theme_id'] ?? '');
            $statement = $this->pdo->prepare(
                'UPDATE onboarding_state
                 SET applied_design_system_id = :applied_design_system_id,
                     archetype_theme_id       = :archetype_theme_id,
                     updated_by_user_id       = :updated_by_user_id,
                     updated_at               = :updated_at
                 WHERE id = 1'
            );
            $statement->execute([
                ':applied_design_system_id' => $appliedId,
                ':archetype_theme_id'       => $effectiveThemeId,
                ':updated_by_user_id'       => $actor,
                ':updated_at'               => $now,
            ]);
            return;
        }

        // INSERT — minimal row; the wizard remains the canonical writer for
        // setup_completed_at and selection_manifest_json.
        $primaryBundleId = (string) ($this->bundles->getPrimaryBundleId() ?? '');
        $statement = $this->pdo->prepare(
            'INSERT INTO onboarding_state (
                id, primary_bundle_id, archetype_theme_id, applied_design_system_id,
                selection_manifest_json, setup_status, setup_completed_at,
                updated_by_user_id, updated_at
             ) VALUES (
                1, :primary_bundle_id, :archetype_theme_id, :applied_design_system_id,
                "", "pending", "",
                :updated_by_user_id, :updated_at
             )'
        );
        $statement->execute([
            ':primary_bundle_id'        => $primaryBundleId,
            ':archetype_theme_id'       => $themeId,
            ':applied_design_system_id' => $appliedId,
            ':updated_by_user_id'       => $actor,
            ':updated_at'               => $now,
        ]);
    }

    /**
     * @return array<string, mixed>
     */
    public function resetState(): array
    {
        $this->pdo->exec('DELETE FROM onboarding_state WHERE id = 1');

        return $this->getState();
    }

    /**
     * @param array<string, mixed> $row
     * @return array<string, mixed>
     */
    private function decorateState(array $row): array
    {
        $primaryBundleId = trim((string) ($row['primary_bundle_id'] ?? ''));
        $themeId = trim((string) ($row['archetype_theme_id'] ?? ''));

        return [
            'primary_bundle_id' => $primaryBundleId,
            'primary_bundle' => $primaryBundleId !== '' ? $this->siteTypes->findSiteTypeById($primaryBundleId) : null,
            'archetype_theme_id' => $themeId,
            'archetype_theme' => $themeId !== '' ? $this->findThemeOption($themeId) : null,
            'applied_design_system_id' => trim((string) ($row['applied_design_system_id'] ?? '')),
            'selection_manifest' => $this->decodeManifest((string) ($row['selection_manifest_json'] ?? '')),
            'setup_status' => trim((string) ($row['setup_status'] ?? 'pending')) ?: 'pending',
            'setup_completed_at' => trim((string) ($row['setup_completed_at'] ?? '')),
            'updated_by_user_id' => trim((string) ($row['updated_by_user_id'] ?? '')),
            'updated_at' => trim((string) ($row['updated_at'] ?? '')),
        ];
    }

    private function selectionService(): MyronSiteSetupSelectionService
    {
        return $this->selectionService ?? new MyronSiteSetupSelectionService($this->pdo, $this->siteTypes, myronTemplateDefinitionRepository());
    }

    private function designSystemRegistry(): DesignSystemRegistry
    {
        return $this->designSystems ?? new DesignSystemRegistry();
    }

    /**
     * @return array<string, mixed>|null
     */
    private function decodeManifest(string $json): ?array
    {
        if (trim($json) === '') {
            return null;
        }

        $decoded = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
        return is_array($decoded) ? $decoded : null;
    }
}
