<?php

declare(strict_types=1);

final class MyronFooterDesignRepository
{
    public const DEFAULT_VARIANT = 'footer.standard';

    private const VALID_VARIANTS = [
        'footer.standard',
        'footer.portfolio',
        'footer.editorial',
        'footer.mega',
        'footer.ecom',
        'footer.local-business',
        'footer.conversion',
        'footer.creator',
        'footer.campaign',
        'footer.checkout',
    ];

    public function __construct(private readonly PDO $pdo)
    {
    }

    /**
     * @return list<string>
     */
    public function validVariants(): array
    {
        return self::VALID_VARIANTS;
    }

    /**
     * @return list<array<string, mixed>>
     */
    public function listVariants(): array
    {
        $this->ensureSeedRows();
        $statement = $this->pdo->query('SELECT * FROM footer_designs ORDER BY variant_id ASC');
        $rows = $statement ? $statement->fetchAll(PDO::FETCH_ASSOC) : [];
        $byVariant = [];
        foreach ($rows as $row) {
            if (is_array($row)) {
                $hydrated = $this->hydrate($row);
                $byVariant[(string) $hydrated['variant_id']] = $hydrated;
            }
        }

        $ordered = [];
        foreach (self::VALID_VARIANTS as $variantId) {
            if (isset($byVariant[$variantId])) {
                $ordered[] = $byVariant[$variantId];
            }
        }

        return $ordered;
    }

    /**
     * @return list<array<string, mixed>>
     */
    public function listActivatedSorted(): array
    {
        $rows = array_values(array_filter(
            $this->listVariants(),
            static fn (array $row): bool => (bool) ($row['is_activated'] ?? false)
        ));
        usort($rows, static fn (array $a, array $b): int => strcmp((string) $a['variant_id'], (string) $b['variant_id']));
        return $rows;
    }

    /**
     * @return array<string, mixed>
     */
    public function getDefault(): array
    {
        $this->ensureSeedRows();
        $statement = $this->pdo->query('SELECT * FROM footer_designs WHERE is_default = 1 LIMIT 1');
        $row = $statement ? $statement->fetch(PDO::FETCH_ASSOC) : false;
        if (is_array($row)) {
            return $this->hydrate($row);
        }

        $fallback = $this->getByVariantId(self::DEFAULT_VARIANT);
        $this->setDefault((string) $fallback['variant_id']);
        return $this->getByVariantId((string) $fallback['variant_id']);
    }

    /**
     * @return array<string, mixed>
     */
    public function getByVariantId(string $variantId): array
    {
        $variantId = $this->normalizeVariant($variantId);
        $this->ensureSeedRows();
        $statement = $this->pdo->prepare('SELECT * FROM footer_designs WHERE variant_id = :variant_id LIMIT 1');
        $statement->execute([':variant_id' => $variantId]);
        $row = $statement->fetch(PDO::FETCH_ASSOC);
        if (is_array($row)) {
            return $this->hydrate($row);
        }

        $this->insertVariant($variantId, false, false, [], [], '');
        return $this->getByVariantId($variantId);
    }

    public function saveActivation(string $variantId, bool $isActivated, string $updatedByUserId = ''): void
    {
        $variantId = $this->normalizeVariant($variantId);
        $row = $this->getByVariantId($variantId);
        if (!$isActivated && (bool) ($row['is_default'] ?? false)) {
            throw new RuntimeException('Set a new default before deactivating this variant.');
        }

        $statement = $this->pdo->prepare(
            'UPDATE footer_designs
             SET is_activated = :is_activated,
                 updated_by_user_id = :updated_by_user_id,
                 updated_at = :updated_at
             WHERE variant_id = :variant_id'
        );
        $statement->execute([
            ':is_activated' => $isActivated ? 1 : 0,
            ':updated_by_user_id' => $updatedByUserId,
            ':updated_at' => myronNow(),
            ':variant_id' => $variantId,
        ]);
    }

    public function setDefault(string $variantId, string $updatedByUserId = ''): void
    {
        $variantId = $this->normalizeVariant($variantId);
        $row = $this->getByVariantId($variantId);
        if (!(bool) ($row['is_activated'] ?? false)) {
            throw new RuntimeException('Only activated footer variants can be set as default.');
        }

        $now = myronNow();
        $this->pdo->beginTransaction();
        try {
            $this->pdo->exec('UPDATE footer_designs SET is_default = 0');
            $statement = $this->pdo->prepare(
                'UPDATE footer_designs
                 SET is_default = 1,
                     updated_by_user_id = :updated_by_user_id,
                     updated_at = :updated_at
                 WHERE variant_id = :variant_id'
            );
            $statement->execute([
                ':updated_by_user_id' => $updatedByUserId,
                ':updated_at' => $now,
                ':variant_id' => $variantId,
            ]);
            $this->pdo->commit();
        } catch (Throwable $exception) {
            if ($this->pdo->inTransaction()) {
                $this->pdo->rollBack();
            }
            throw $exception;
        }
    }

    /**
     * @return array{zone_visibility:array<string,bool>,zone_content:array<string,array<string,mixed>>}
     */
    public function getZoneConfigForVariant(string $variantId): array
    {
        $row = $this->getByVariantId($variantId);
        return [
            'zone_visibility' => is_array($row['zone_visibility'] ?? null) ? $row['zone_visibility'] : [],
            'zone_content' => is_array($row['zone_content'] ?? null) ? $row['zone_content'] : [],
        ];
    }

    /**
     * @param array<string, mixed> $visibility
     * @param array<string, mixed> $contentByZone
     */
    public function saveConfiguration(string $variantId, array $visibility, array $contentByZone, string $updatedByUserId): void
    {
        $variantId = $this->normalizeVariant($variantId);
        $normalizedVisibility = [];
        foreach ($visibility as $zoneId => $value) {
            $normalizedVisibility[trim((string) $zoneId)] = (bool) $value;
        }

        $normalizedContent = [];
        foreach ($contentByZone as $zoneId => $content) {
            if (is_array($content)) {
                $normalizedContent[trim((string) $zoneId)] = $this->normalizeZoneContentValues($content);
            }
        }

        $statement = $this->pdo->prepare(
            'UPDATE footer_designs
             SET zone_visibility_json = :zone_visibility_json,
                 zone_content_json = :zone_content_json,
                 updated_by_user_id = :updated_by_user_id,
                 updated_at = :updated_at
             WHERE variant_id = :variant_id'
        );
        $statement->execute([
            ':zone_visibility_json' => json_encode($normalizedVisibility, JSON_UNESCAPED_UNICODE),
            ':zone_content_json' => json_encode($normalizedContent, JSON_UNESCAPED_UNICODE),
            ':updated_by_user_id' => $updatedByUserId,
            ':updated_at' => myronNow(),
            ':variant_id' => $variantId,
        ]);
    }

    public function recommendedVariantForSiteType(string $bundleId, MyronSiteTypeRegistryRepository $siteTypes): string
    {
        $siteType = $siteTypes->findSiteTypeById($bundleId);
        if (!is_array($siteType)) {
            return self::DEFAULT_VARIANT;
        }

        $variant = trim((string) ($siteType['footerVariant'] ?? ''));
        return in_array($variant, self::VALID_VARIANTS, true) ? $variant : self::DEFAULT_VARIANT;
    }

    public function seedFromSiteType(string $bundleId, MyronSiteTypeRegistryRepository $siteTypes, string $updatedByUserId): string
    {
        $recommended = $this->recommendedVariantForSiteType($bundleId, $siteTypes);
        if ($this->isUntouchedDefault()) {
            $this->saveActivation($recommended, true, $updatedByUserId);
            $this->setDefault($recommended, $updatedByUserId);
            $this->applySeedContent($recommended, $bundleId, $updatedByUserId);
        }

        return $recommended;
    }

    /**
     * APO-179 — author the merged (per-variant generic + per-site-type overlay) content
     * into footer_designs.zone_content_json on a fresh seed of an untouched default.
     * This runs only inside seedFromSiteType's isUntouchedDefault gate, so operator
     * customization is preserved by the existing repo invariants.
     */
    private function applySeedContent(string $variantId, string $bundleId, string $updatedByUserId): void
    {
        if (!function_exists('myronFooterContentSeedService')) {
            return;
        }
        $service = myronFooterContentSeedService();
        $merged  = $service->seedContentForSiteTypeAndVariant($bundleId, $variantId);
        if ($merged === []) {
            return;
        }

        $row = $this->getByVariantId($variantId);
        $existingVisibility = is_array($row['zone_visibility'] ?? null) ? $row['zone_visibility'] : [];
        $this->saveConfiguration($variantId, $existingVisibility, $merged, $updatedByUserId);
    }

    public function isUntouchedDefault(): bool
    {
        $default = $this->getDefault();
        return (string) ($default['variant_id'] ?? self::DEFAULT_VARIANT) === self::DEFAULT_VARIANT
            && $this->hasCustomizedZoneContent(is_array($default['zone_content'] ?? null) ? $default['zone_content'] : []) === false;
    }

    public function applyRecommendation(string $bundleId, string $variant, string $updatedByUserId): void
    {
        $variant = $this->normalizeVariant($variant);
        $this->saveActivation($variant, true, $updatedByUserId);
        $this->setDefault($variant, $updatedByUserId);
        $this->clearDismissedRecommendation($bundleId, $variant, $updatedByUserId);
    }

    public function dismissRecommendation(string $bundleId, string $variant, string $updatedByUserId): void
    {
        myronFooterDesignSettingsRepository()->dismissRecommendation($bundleId, $variant, $updatedByUserId);
    }

    public function isRecommendationDismissed(string $bundleId, string $variant): bool
    {
        return myronFooterDesignSettingsRepository()->isRecommendationDismissed($bundleId, $variant);
    }

    private function clearDismissedRecommendation(string $bundleId, string $variant, string $updatedByUserId): void
    {
        myronFooterDesignSettingsRepository()->clearDismissedRecommendation($bundleId, $variant, $updatedByUserId);
    }

    private function ensureSeedRows(): void
    {
        $count = (int) ($this->pdo->query('SELECT COUNT(*) FROM footer_designs')?->fetchColumn() ?: 0);
        if ($count > 0) {
            return;
        }

        $this->insertVariant(self::DEFAULT_VARIANT, true, true, [], [], '');
        foreach (self::VALID_VARIANTS as $variantId) {
            if ($variantId !== self::DEFAULT_VARIANT) {
                $this->insertVariant($variantId, false, false, [], [], '');
            }
        }
    }

    /**
     * @param array<string, mixed> $visibility
     * @param array<string, mixed> $content
     */
    private function insertVariant(string $variantId, bool $activated, bool $default, array $visibility, array $content, string $userId): void
    {
        $now = myronNow();
        $statement = $this->pdo->prepare(
            'INSERT INTO footer_designs (
                id, variant_id, is_activated, zone_visibility_json, zone_content_json,
                is_default, created_at, updated_at, created_by_user_id, updated_by_user_id
             )
             SELECT :id, :variant_id, :is_activated, :zone_visibility_json, :zone_content_json,
                    :is_default, :created_at, :updated_at, :created_by_user_id, :updated_by_user_id
             WHERE NOT EXISTS (SELECT 1 FROM footer_designs WHERE variant_id = :variant_id_existing)'
        );
        $statement->execute([
            ':id' => 'fd_' . str_replace(['footer.', '-'], ['', '_'], $variantId),
            ':variant_id' => $variantId,
            ':is_activated' => $activated ? 1 : 0,
            ':zone_visibility_json' => json_encode($visibility, JSON_UNESCAPED_UNICODE),
            ':zone_content_json' => json_encode($content, JSON_UNESCAPED_UNICODE),
            ':is_default' => $default ? 1 : 0,
            ':created_at' => $now,
            ':updated_at' => $now,
            ':created_by_user_id' => $userId,
            ':updated_by_user_id' => $userId,
            ':variant_id_existing' => $variantId,
        ]);
    }

    /**
     * @param array<string, mixed> $row
     * @return array<string, mixed>
     */
    private function hydrate(array $row): array
    {
        $variantId = (string) ($row['variant_id'] ?? self::DEFAULT_VARIANT);
        return [
            'id' => (string) ($row['id'] ?? ''),
            'variant_id' => $variantId,
            // APO-205 Card 2 — every footer row carries its human-readable label
            // alongside the variant code so AI clients answering "what footer is
            // active?" don't have to maintain a parallel lookup table.
            'variant_label' => $this->labelForVariant($variantId),
            'is_activated' => (bool) ((int) ($row['is_activated'] ?? 0)),
            'is_default' => (bool) ((int) ($row['is_default'] ?? 0)),
            'zone_visibility' => $this->decodeVisibility((string) ($row['zone_visibility_json'] ?? '{}')),
            'zone_content' => $this->decodeZoneContentJson((string) ($row['zone_content_json'] ?? '{}')),
            'created_at' => (string) ($row['created_at'] ?? ''),
            'updated_at' => (string) ($row['updated_at'] ?? ''),
        ];
    }

    private function labelForVariant(string $variantId): string
    {
        if (function_exists('myronFooterVariantCompositionService')) {
            $service = myronFooterVariantCompositionService();
            return $service->labelForVariant($variantId);
        }
        // Defensive fallback (e.g. CLI bootstrap before helpers register).
        return ucwords(str_replace(['footer.', '-'], ['', ' '], $variantId));
    }

    private function normalizeVariant(string $variantId): string
    {
        $variantId = trim($variantId);
        return in_array($variantId, self::VALID_VARIANTS, true) ? $variantId : self::DEFAULT_VARIANT;
    }

    /**
     * @return array<string, bool>
     */
    private function decodeVisibility(string $raw): array
    {
        $decoded = json_decode($raw !== '' ? $raw : '{}', true);
        if (!is_array($decoded)) {
            return [];
        }

        $visibility = [];
        foreach ($decoded as $zoneId => $value) {
            $visibility[trim((string) $zoneId)] = (bool) $value;
        }
        return $visibility;
    }

    /**
     * @return array<string, array<string, mixed>>
     */
    private function decodeZoneContentJson(string $raw): array
    {
        $decoded = json_decode($raw !== '' ? $raw : '{}', true);
        if (!is_array($decoded)) {
            return [];
        }

        $content = [];
        foreach ($decoded as $zoneId => $zoneContent) {
            if (is_array($zoneContent)) {
                $content[trim((string) $zoneId)] = $zoneContent;
            }
        }
        return $content;
    }

    /**
     * @param array<string, mixed> $content
     * @return array<string, mixed>
     */
    private function normalizeZoneContentValues(array $content): array
    {
        $normalized = [];
        foreach ($content as $key => $value) {
            if (is_array($value)) {
                $cleanItems = [];
                $items = array_values(array_filter($value, 'is_array'));
                usort($items, static fn (array $a, array $b): int => ((int) ($a['__order'] ?? 0)) <=> ((int) ($b['__order'] ?? 0)));
                foreach ($items as $item) {
                    $hasValue = false;
                    foreach ($item as $itemKey => $itemVal) {
                        if (str_starts_with((string) $itemKey, '__')) {
                            continue;
                        }
                        if (trim((string) $itemVal) !== '') {
                            $hasValue = true;
                            break;
                        }
                    }
                    if ($hasValue && empty($item['__remove'])) {
                        unset($item['__order'], $item['__remove']);
                        $cleanItems[] = $item;
                    }
                }
                $normalized[(string) $key] = $cleanItems;
            } else {
                $normalized[(string) $key] = trim((string) $value);
            }
        }

        return $normalized;
    }

    /**
     * @param array<string, array<string, mixed>> $contentByZone
     */
    private function hasCustomizedZoneContent(array $contentByZone): bool
    {
        foreach ($contentByZone as $content) {
            foreach ($content as $value) {
                if (is_array($value) && $value !== []) {
                    return true;
                }
                if (!is_array($value) && trim((string) $value) !== '') {
                    return true;
                }
            }
        }
        return false;
    }
}
