<?php

declare(strict_types=1);

final class MyronCustomSectionTemplateRepository
{
    public function __construct(
        private readonly PDO $pdo,
    ) {
    }

    /**
     * @return list<array<string, mixed>>
     */
    public function listTemplates(?string $jtbdGroup = null, bool $includeInactive = false): array
    {
        $sql = 'SELECT * FROM custom_section_templates';
        $params = [];
        $where = [];

        if (!$includeInactive) {
            $where[] = 'lifecycle_status = :lifecycle_status';
            $params[':lifecycle_status'] = 'active';
        }

        if ($jtbdGroup !== null && trim($jtbdGroup) !== '') {
            $where[] = 'jtbd_group = :jtbd_group';
            $params[':jtbd_group'] = trim($jtbdGroup);
        }

        if ($where !== []) {
            $sql .= ' WHERE ' . implode(' AND ', $where);
        }

        $sql .= ' ORDER BY jtbd_group ASC, section_key ASC';
        $statement = $this->pdo->prepare($sql);
        $statement->execute($params);
        $rows = $statement->fetchAll();

        if (!is_array($rows)) {
            return [];
        }

        return array_values(array_map(fn (array $row): array => $this->hydrate($row), $rows));
    }

    public function findBySectionKey(string $sectionKey, bool $includeInactive = false): ?array
    {
        $sql = 'SELECT * FROM custom_section_templates WHERE section_key = :section_key';
        if (!$includeInactive) {
            $sql .= ' AND lifecycle_status = :lifecycle_status';
        }
        $sql .= ' LIMIT 1';

        $statement = $this->pdo->prepare($sql);
        $params = [':section_key' => self::normalizeSectionKey($sectionKey)];
        if (!$includeInactive) {
            $params[':lifecycle_status'] = 'active';
        }
        $statement->execute($params);
        $row = $statement->fetch();

        return is_array($row) ? $this->hydrate($row) : null;
    }

    /**
     * @param array<string, mixed> $record
     * @return array<string, mixed>
     */
    public function save(array $record, string $actorId = ''): array
    {
        $sectionKey = self::normalizeSectionKey((string) ($record['sectionKey'] ?? ''));
        if ($sectionKey === '') {
            throw new InvalidArgumentException('Custom section templates require a sectionKey.');
        }

        $label = trim((string) ($record['label'] ?? ''));
        if ($label === '') {
            throw new InvalidArgumentException('Custom section templates require a label.');
        }

        $componentId = trim((string) ($record['primaryComponentId'] ?? $record['componentId'] ?? ''));
        if ($componentId === '') {
            throw new InvalidArgumentException('Custom section templates require a primary component.');
        }

        $variant = self::normalizeVariant((string) ($record['variant'] ?? 'default'));
        $defaultContent = is_array($record['defaultContent'] ?? null) ? $record['defaultContent'] : [];
        $editingContract = self::normalizeEditingContract(is_array($record['editing'] ?? null) ? $record['editing'] : []);
        $editable = self::normalizeNullableBool($record['defaultContentEditable'] ?? null);
        $status = self::normalizeLifecycleStatus((string) ($record['lifecycleStatus'] ?? 'active'));
        $now = myronNow();

        $payload = [
            'sectionKey' => $sectionKey,
            'label' => $label,
            'description' => trim((string) ($record['description'] ?? '')),
            'jtbdGroup' => trim((string) ($record['jtbdGroup'] ?? 'custom')),
            'sectionType' => trim((string) ($record['sectionType'] ?? 'custom')),
            'primaryComponents' => [$componentId],
            'componentId' => $componentId,
            'variant' => $variant,
            'remediationStatus' => 'operator-owned',
            'defaultContent' => $defaultContent,
            'defaultContentEditable' => $editable,
            'editing' => $editingContract,
            'lifecycleStatus' => $status,
            'sourceAuthority' => 'operator-owned-custom-section-template',
        ];

        $params = [
            ':section_key' => $sectionKey,
            ':label' => $payload['label'],
            ':description' => $payload['description'],
            ':jtbd_group' => $payload['jtbdGroup'],
            ':section_type' => $payload['sectionType'],
            ':primary_component_id' => $componentId,
            ':variant' => $variant,
            ':default_content_json' => json_encode($defaultContent, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES),
            ':default_content_editable' => $editable,
            ':editing_contract_json' => json_encode($editingContract, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES),
            ':lifecycle_status' => $status,
            ':record_json' => json_encode($payload, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES),
            ':updated_by_user_id' => trim($actorId),
            ':updated_at' => $now,
        ];

        if ($this->findBySectionKey($sectionKey, true) !== null) {
            $statement = $this->pdo->prepare(
                'UPDATE custom_section_templates
                 SET label = :label,
                     description = :description,
                     jtbd_group = :jtbd_group,
                     section_type = :section_type,
                     primary_component_id = :primary_component_id,
                     variant = :variant,
                     default_content_json = :default_content_json,
                     default_content_editable = :default_content_editable,
                     editing_contract_json = :editing_contract_json,
                     lifecycle_status = :lifecycle_status,
                     record_json = :record_json,
                     updated_by_user_id = :updated_by_user_id,
                     updated_at = :updated_at
                 WHERE section_key = :section_key'
            );
            $statement->execute($params);
        } else {
            $statement = $this->pdo->prepare(
                'INSERT INTO custom_section_templates (
                    section_key,
                    label,
                    description,
                    jtbd_group,
                    section_type,
                    primary_component_id,
                    variant,
                    default_content_json,
                    default_content_editable,
                    editing_contract_json,
                    lifecycle_status,
                    record_json,
                    created_by_user_id,
                    updated_by_user_id,
                    created_at,
                    updated_at
                 ) VALUES (
                    :section_key,
                    :label,
                    :description,
                    :jtbd_group,
                    :section_type,
                    :primary_component_id,
                    :variant,
                    :default_content_json,
                    :default_content_editable,
                    :editing_contract_json,
                    :lifecycle_status,
                    :record_json,
                    :created_by_user_id,
                    :updated_by_user_id,
                    :created_at,
                    :updated_at
                 )'
            );
            $params[':created_by_user_id'] = trim($actorId);
            $params[':created_at'] = $now;
            $statement->execute($params);
        }

        $saved = $this->findBySectionKey($sectionKey, true);
        if (!is_array($saved)) {
            throw new RuntimeException('Failed to reload custom section template `' . $sectionKey . '`.');
        }

        return $saved;
    }

    public function delete(string $sectionKey): void
    {
        $normalized = self::normalizeSectionKey($sectionKey);
        if ($normalized === '') {
            throw new InvalidArgumentException('sectionKey is required.');
        }
        $statement = $this->pdo->prepare('DELETE FROM custom_section_templates WHERE section_key = :section_key');
        $statement->execute([':section_key' => $normalized]);
    }

    /**
     * @return list<array<string, mixed>>
     */
    public function usagesForSectionKey(string $sectionKey): array
    {
        $normalized = self::normalizeSectionKey($sectionKey);
        if ($normalized === '') {
            return [];
        }

        $usages = [];
        $statement = $this->pdo->query(
            'SELECT page_templates.id AS template_id,
                    page_templates.template_key,
                    page_templates.name,
                    page_templates.source_template_id,
                    page_template_versions.schema_json
             FROM page_templates
             LEFT JOIN page_template_versions ON page_template_versions.id = page_templates.current_version_id'
        );
        $rows = $statement ? $statement->fetchAll() : [];
        foreach (is_array($rows) ? $rows : [] as $row) {
            if (!is_array($row)) {
                continue;
            }
            $schema = json_decode((string) ($row['schema_json'] ?? '{}'), true);
            if (!is_array($schema)) {
                continue;
            }
            foreach (is_array($schema['sections'] ?? null) ? $schema['sections'] : [] as $position => $section) {
                if (!is_array($section) || trim((string) ($section['sectionKey'] ?? '')) !== $normalized) {
                    continue;
                }
                $usages[] = [
                    'type' => 'template-section',
                    'templateId' => (string) ($row['template_id'] ?? ''),
                    'templateKey' => (string) ($row['template_key'] ?? ''),
                    'templateName' => (string) ($row['name'] ?? ''),
                    'sourceTemplateId' => (string) ($row['source_template_id'] ?? ''),
                    'position' => (int) $position,
                ];
            }
        }

        if ($this->tableExists('section_variants')) {
            $variantStatement = $this->pdo->prepare('SELECT id, name FROM section_variants WHERE section_key = :section_key');
            $variantStatement->execute([':section_key' => $normalized]);
            foreach ($variantStatement->fetchAll() ?: [] as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $usages[] = [
                    'type' => 'section-variant',
                    'variantId' => (string) ($row['id'] ?? ''),
                    'variantName' => (string) ($row['name'] ?? ''),
                ];
            }
        }

        return $usages;
    }

    public static function normalizeSectionKey(string $value): string
    {
        return myronNormalizeSlug($value);
    }

    public static function normalizeVariant(string $value): string
    {
        $normalized = trim($value);
        return $normalized !== '' ? $normalized : 'default';
    }

    /**
     * @param array<string, mixed> $contract
     * @return array{fields:list<array<string, mixed>>,repeaters:list<array<string, mixed>>}
     */
    public static function normalizeEditingContract(array $contract): array
    {
        $fields = array_values(array_filter(
            is_array($contract['fields'] ?? null) ? $contract['fields'] : [],
            static fn (mixed $field): bool => is_array($field) && trim((string) ($field['key'] ?? $field['name'] ?? '')) !== ''
        ));
        $repeaters = array_values(array_filter(
            is_array($contract['repeaters'] ?? null) ? $contract['repeaters'] : [],
            static fn (mixed $repeater): bool => is_array($repeater) && trim((string) ($repeater['key'] ?? $repeater['name'] ?? '')) !== ''
        ));

        return [
            'fields' => $fields,
            'repeaters' => $repeaters,
        ];
    }

    private static function normalizeNullableBool(mixed $value): ?int
    {
        if ($value === null || $value === '') {
            return null;
        }

        return filter_var($value, FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE) ? 1 : 0;
    }

    private static function normalizeLifecycleStatus(string $status): string
    {
        $normalized = trim($status);
        return in_array($normalized, ['active', 'inactive'], true) ? $normalized : 'active';
    }

    /**
     * @param array<string, mixed> $row
     * @return array<string, mixed>
     */
    private function hydrate(array $row): array
    {
        $record = json_decode((string) ($row['record_json'] ?? '{}'), true, 512, JSON_THROW_ON_ERROR);
        if (!is_array($record)) {
            $record = [];
        }
        $defaultContent = json_decode((string) ($row['default_content_json'] ?? '{}'), true, 512, JSON_THROW_ON_ERROR);
        $editing = json_decode((string) ($row['editing_contract_json'] ?? '{}'), true, 512, JSON_THROW_ON_ERROR);

        $componentId = (string) ($row['primary_component_id'] ?? '');
        $record['sectionKey'] = (string) ($row['section_key'] ?? '');
        $record['label'] = (string) ($row['label'] ?? '');
        $record['description'] = (string) ($row['description'] ?? '');
        $record['jtbdGroup'] = (string) ($row['jtbd_group'] ?? '');
        $record['sectionType'] = (string) ($row['section_type'] ?? '');
        $record['primaryComponents'] = [$componentId];
        $record['componentId'] = $componentId;
        $record['variant'] = (string) ($row['variant'] ?? 'default');
        $record['remediationStatus'] = 'operator-owned';
        $record['defaultContent'] = is_array($defaultContent) ? $defaultContent : [];
        $record['defaultContentEditable'] = $row['default_content_editable'] === null || $row['default_content_editable'] === ''
            ? null
            : ((int) $row['default_content_editable'] === 1 ? 1 : 0);
        $record['editing'] = self::normalizeEditingContract(is_array($editing) ? $editing : []);
        $record['_custom'] = [
            'sourceAuthority' => 'operator-owned-custom-section-template',
            'lifecycleStatus' => (string) ($row['lifecycle_status'] ?? ''),
            'createdByUserId' => (string) ($row['created_by_user_id'] ?? ''),
            'updatedByUserId' => (string) ($row['updated_by_user_id'] ?? ''),
            'createdAt' => (string) ($row['created_at'] ?? ''),
            'updatedAt' => (string) ($row['updated_at'] ?? ''),
        ];
        $record['_registry'] = [
            'sectionKey' => $record['sectionKey'],
            'label' => $record['label'],
            'jtbdGroup' => $record['jtbdGroup'],
            'sectionType' => $record['sectionType'],
            'primaryComponents' => $record['primaryComponents'],
            'remediationStatus' => 'operator-owned',
            'seededAt' => '',
            'updatedAt' => (string) ($row['updated_at'] ?? ''),
            'sourceAuthority' => 'operator-owned-custom-section-template',
        ];

        return $record;
    }

    private function tableExists(string $table): bool
    {
        try {
            $statement = $this->pdo->query('SELECT 1 FROM ' . $table . ' LIMIT 1');
            return $statement !== false;
        } catch (Throwable) {
            return false;
        }
    }
}
