<?php

declare(strict_types=1);

trait MyronContentTemplatesTrait
{
    /**
     * @return list<array<string, mixed>>
     */
    public function listTemplates(): array
    {
        $statement = $this->pdo->query(
            'SELECT
                page_templates.id,
                page_templates.template_key,
                page_templates.name,
                page_templates.description,
                page_templates.source_template_id,
                page_templates.source_bundle_id,
                page_templates.chrome_json,
                page_templates.header_capabilities_json,
                page_templates.current_version_id,
                page_template_versions.version_number,
                page_template_versions.view_name,
                page_template_versions.schema_json
             FROM page_templates
             LEFT JOIN page_template_versions ON page_template_versions.id = page_templates.current_version_id
             ORDER BY page_templates.name ASC'
        );

        $rows = $statement->fetchAll();
        return array_map(
            fn (array $row): array => $this->decorateTemplateRow($row),
            $rows
        );
    }

    /**
     * @return array<string, list<array<string, mixed>>>
     */
    public function listTemplatesBySourceTemplateId(): array
    {
        $index = [];
        foreach ($this->listTemplates() as $template) {
            $sourceTemplateId = trim((string) ($template['source_template_id'] ?? ''));
            if ($sourceTemplateId === '') {
                continue;
            }

            $index[$sourceTemplateId] ??= [];
            $index[$sourceTemplateId][] = $template;
        }

        return $index;
    }

    /**
     * @return array<string, mixed>|null
     */
    public function findRuntimeTemplateBySourceTemplateId(string $sourceTemplateId): ?array
    {
        $statement = $this->pdo->prepare(
            'SELECT
                page_templates.id,
                page_templates.template_key,
                page_templates.name,
                page_templates.description,
                page_templates.source_template_id,
                page_templates.source_bundle_id,
                page_templates.chrome_json,
                page_templates.header_capabilities_json,
                page_templates.current_version_id,
                page_template_versions.version_number,
                page_template_versions.view_name,
                page_template_versions.schema_json
             FROM page_templates
             LEFT JOIN page_template_versions ON page_template_versions.id = page_templates.current_version_id
             WHERE page_templates.source_template_id = :source_template_id
             LIMIT 1'
        );
        $statement->execute([':source_template_id' => trim($sourceTemplateId)]);
        $row = $statement->fetch();
        if (!is_array($row)) {
            return null;
        }

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

    /**
     * @return array<string, mixed>|null
     */
    public function findRuntimeTemplateByIdOrSourceTemplateId(string $templateRef): ?array
    {
        $ref = trim($templateRef);
        if ($ref === '') {
            return null;
        }

        $statement = $this->pdo->prepare(
            'SELECT
                page_templates.id,
                page_templates.template_key,
                page_templates.name,
                page_templates.description,
                page_templates.source_template_id,
                page_templates.source_bundle_id,
                page_templates.chrome_json,
                page_templates.header_capabilities_json,
                page_templates.current_version_id,
                page_template_versions.version_number,
                page_template_versions.view_name,
                page_template_versions.schema_json
             FROM page_templates
             LEFT JOIN page_template_versions ON page_template_versions.id = page_templates.current_version_id
             WHERE page_templates.id = :ref OR page_templates.source_template_id = :ref
             LIMIT 1'
        );
        $statement->execute([':ref' => $ref]);
        $row = $statement->fetch();

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

    /**
     * @param array<string, mixed> $row
     * @return array<string, mixed>
     */
    private function decorateTemplateRow(array $row): array
    {
        $row['schema'] = json_decode((string) $row['schema_json'], true, 512, JSON_THROW_ON_ERROR);
        $row['chrome'] = trim((string) ($row['chrome_json'] ?? '')) !== ''
            ? json_decode((string) $row['chrome_json'], true, 512, JSON_THROW_ON_ERROR)
            : null;
        $row['headerCapabilities'] = trim((string) ($row['header_capabilities_json'] ?? '')) !== ''
            ? json_decode((string) $row['header_capabilities_json'], true, 512, JSON_THROW_ON_ERROR)
            : [];
        $roleSourceId = $this->templateRoleSourceId($row);
        $row['template_class'] = MyronTemplateDefinitionRepository::templateClassForTemplateId($roleSourceId);
        $row['template_purpose'] = MyronTemplateDefinitionRepository::templatePurposeForTemplateId($roleSourceId);
        $row['source_site_type'] = $this->templateSourceSiteType($roleSourceId);
        $row['template_lifecycle'] = $this->templateLifecycleForRow($row);
        $row['is_custom_site_template'] = (bool) ($row['template_lifecycle']['isCustomSiteTemplate'] ?? false);
        $row['is_available_page_template'] = (bool) ($row['template_lifecycle']['isAvailablePageTemplate'] ?? true);

        return $row;
    }

    /**
     * @param array<string, mixed> $row
     * @return array<string, mixed>
     */
    private function templateLifecycleForRow(array $row): array
    {
        $schema = is_array($row['schema'] ?? null) ? $row['schema'] : [];
        $metadata = is_array($schema['templateLifecycle'] ?? null) ? $schema['templateLifecycle'] : [];
        $sourceTemplateId = trim((string) ($row['source_template_id'] ?? ''));
        $isCustomSource = $sourceTemplateId !== '' && str_contains($sourceTemplateId, '::custom::');
        $kind = trim((string) ($metadata['kind'] ?? ''));

        if ($kind === '') {
            $kind = $isCustomSource ? 'page_template' : 'page_template';
        }

        $isCustomSiteTemplate = $kind === 'custom_site_template';
        $isAvailablePageTemplate = array_key_exists('availableAsPageTemplate', $metadata)
            ? (bool) $metadata['availableAsPageTemplate']
            : true;

        return [
            'kind' => $kind,
            'baseTemplateId' => trim((string) ($metadata['baseTemplateId'] ?? ($schema['sourceTemplateBaseId'] ?? ''))),
            'createdFrom' => trim((string) ($metadata['createdFrom'] ?? ($isCustomSource ? 'packaged_site_template' : 'runtime_seed'))),
            'availableAsPageTemplate' => $isAvailablePageTemplate,
            'isCustomSiteTemplate' => $isCustomSiteTemplate,
            'isAvailablePageTemplate' => !$isCustomSiteTemplate || $isAvailablePageTemplate,
            'registeredAt' => trim((string) ($metadata['registeredAt'] ?? '')),
        ];
    }

    /**
     * @return list<array<string, mixed>>
     */
    public function listCustomSiteTemplates(): array
    {
        return array_values(array_filter(
            $this->listTemplates(),
            static fn (array $template): bool => (bool) ($template['is_custom_site_template'] ?? false)
        ));
    }

    /**
     * @return list<array<string, mixed>>
     */
    public function listAvailablePageTemplates(): array
    {
        return array_values(array_filter(
            $this->listTemplates(),
            static fn (array $template): bool => (bool) ($template['is_available_page_template'] ?? true)
        ));
    }

    /**
     * @param array<string, mixed> $row
     */
    private function templateRoleSourceId(array $row): string
    {
        foreach (['source_template_id', 'template_key', 'id'] as $field) {
            $candidate = trim((string) ($row[$field] ?? ''));
            if ($candidate !== '') {
                return $candidate;
            }
        }

        return '';
    }

    private function templateSourceSiteType(string $templateId): string
    {
        $normalized = trim($templateId);
        if ($normalized === '') {
            return '';
        }

        $parts = explode('.', $normalized);
        if (($parts[0] ?? '') === 'template') {
            array_shift($parts);
        }

        if ($parts !== [] && preg_match('/^v[0-9]+$/', (string) end($parts)) === 1) {
            array_pop($parts);
        }

        return $parts !== [] ? (string) end($parts) : '';
    }

    /**
     * @param list<array<string, mixed>> $sections
     */
    public function updateRuntimeTemplateSections(string $runtimeTemplateId, array $sections): array
    {
        $template = $this->requireTemplate($runtimeTemplateId);
        $versionId = trim((string) ($template['current_version_id'] ?? ''));
        if ($versionId === '') {
            throw new InvalidArgumentException('Runtime template has no editable current version.');
        }

        $statement = $this->pdo->prepare(
            'SELECT schema_json
             FROM page_template_versions
             WHERE id = :version_id
             LIMIT 1'
        );
        $statement->execute([':version_id' => $versionId]);
        $schemaJson = $statement->fetchColumn();
        if (!is_string($schemaJson) || trim($schemaJson) === '') {
            throw new InvalidArgumentException('Runtime template schema is unavailable.');
        }

        $schema = json_decode($schemaJson, true, 512, JSON_THROW_ON_ERROR);
        if (!is_array($schema)) {
            throw new InvalidArgumentException('Runtime template schema is invalid.');
        }

        $schema['sections'] = array_values($sections);
        if (is_array($schema['bridge'] ?? null)) {
            $schema['bridge']['bodySectionCount'] = count($schema['sections']);
        }

        $update = $this->pdo->prepare(
            'UPDATE page_template_versions
             SET schema_json = :schema_json
             WHERE id = :version_id'
        );
        $update->execute([
            ':schema_json' => json_encode($schema, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES),
            ':version_id' => $versionId,
        ]);

        $touchTemplate = $this->pdo->prepare(
            'UPDATE page_templates
             SET updated_at = :updated_at
             WHERE id = :template_id'
        );
            $touchTemplate->execute([
                ':updated_at' => myronNow(),
                ':template_id' => $runtimeTemplateId,
            ]);

        myronInvalidatePublicPageCache('runtime template sections changed');

            $updated = $this->findRuntimeTemplateBySourceTemplateId((string) ($template['source_template_id'] ?? ''));
            return is_array($updated) ? $updated : $template;
        }

    /**
     * Persist a runtime template composition and its default content as one schema update.
     *
     * @param list<array<string, mixed>> $sections
     * @param array<string, mixed> $defaultContent
     */
    public function updateRuntimeTemplateComposition(
        string $runtimeTemplateId,
        array $sections,
        array $defaultContent
    ): array {
        $template = $this->findTemplateWithSchemaById($runtimeTemplateId);
        if (!is_array($template)) {
            throw new InvalidArgumentException('Selected template does not exist.');
        }

        $schema = is_array($template['schema'] ?? null) ? $template['schema'] : [];
        $schema['sections'] = array_values($sections);
        $schema['defaultContent'] = $defaultContent;
        if (is_array($schema['bridge'] ?? null)) {
            $schema['bridge']['bodySectionCount'] = count($schema['sections']);
        }

        return $this->updateRuntimeTemplateSchema($runtimeTemplateId, $schema);
    }

    public function updateRuntimeTemplateSectionEditability(string $runtimeTemplateId, int $position, bool $contentEditable): array
    {
        $template = $this->findTemplateWithSchemaById($runtimeTemplateId);
        if (!is_array($template)) {
            throw new InvalidArgumentException('Selected template does not exist.');
        }
        $schema = is_array($template['schema'] ?? null) ? $template['schema'] : [];
        $sections = array_values(array_filter(
            is_array($schema['sections'] ?? null) ? $schema['sections'] : [],
            static fn (mixed $section): bool => is_array($section)
        ));

        if (!isset($sections[$position])) {
            throw new InvalidArgumentException('Runtime template section position is invalid.');
        }

        $sections[$position]['editing'] = is_array($sections[$position]['editing'] ?? null) ? $sections[$position]['editing'] : [];
        $sections[$position]['editing']['contentEditable'] = $contentEditable;

        return $this->updateRuntimeTemplateSections($runtimeTemplateId, $sections);
    }

    /**
     * @param array<string, mixed> $patch
     */
    public function updateRuntimeTemplateSectionDesign(string $runtimeTemplateId, int $position, array $patch): array
    {
        $template = $this->findTemplateWithSchemaById($runtimeTemplateId);
        if (!is_array($template)) {
            throw new InvalidArgumentException('Selected template does not exist.');
        }

        $schema = is_array($template['schema'] ?? null) ? $template['schema'] : [];
        $sections = array_values(array_filter(
            is_array($schema['sections'] ?? null) ? $schema['sections'] : [],
            static fn (mixed $section): bool => is_array($section)
        ));

        if (!isset($sections[$position])) {
            throw new InvalidArgumentException('Runtime template section position is invalid.');
        }

        $sections[$position]['sectionUuid'] = myronSectionUuid($sections[$position], $position);
        foreach ($patch as $key => $value) {
            if ($value === null) {
                unset($sections[$position][$key]);
                continue;
            }
            $sections[$position][$key] = $value;
        }

        return $this->updateRuntimeTemplateSections($runtimeTemplateId, $sections);
    }

    public function updateRuntimeTemplateBlogRailPlacement(string $runtimeTemplateId, string $placement): array
    {
        $template = $this->findTemplateWithSchemaById($runtimeTemplateId);
        if (!is_array($template)) {
            throw new InvalidArgumentException('Selected template does not exist.');
        }

        $normalized = myronNormalizeSlug($placement);
        if (!in_array($normalized, ['left', 'right', 'none'], true)) {
            throw new InvalidArgumentException('Blog rail placement must be left, right, or none.');
        }

        $schema = is_array($template['schema'] ?? null) ? $template['schema'] : [];
        $sourceTemplateId = trim((string) (($schema['sourceTemplateId'] ?? '') ?: ($template['source_template_id'] ?? '')));
        if (function_exists('myronRailDesignActionService') && $sourceTemplateId !== '') {
            try {
                myronRailDesignActionService()->setTemplateAssignment(
                    $sourceTemplateId,
                    MyronRailDesignRepository::DEFAULT_RAIL,
                    $normalized,
                    new MyronActionCallerContext('system', 'runtime-template', [])
                );
            } catch (Throwable) {
                // Keep legacy schema placement persistence available during upgrades.
            }
        }
        $schema['blogRail'] = is_array($schema['blogRail'] ?? null) ? $schema['blogRail'] : [];
        $schema['blogRail']['railId'] = MyronRailDesignRepository::DEFAULT_RAIL;
        $schema['blogRail']['placement'] = $normalized;

        return $this->updateRuntimeTemplateSchema((string) ($template['id'] ?? $runtimeTemplateId), $schema);
    }

    /**
     * @param array<string, mixed> $schema
     */
    private function updateRuntimeTemplateSchema(string $runtimeTemplateId, array $schema): array
    {
        $template = $this->requireTemplate($runtimeTemplateId);
        $versionId = trim((string) ($template['current_version_id'] ?? ''));
        if ($versionId === '') {
            throw new InvalidArgumentException('Runtime template has no editable current version.');
        }

        $update = $this->pdo->prepare(
            'UPDATE page_template_versions
             SET schema_json = :schema_json
             WHERE id = :version_id'
        );
        $update->execute([
            ':schema_json' => json_encode($schema, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES),
            ':version_id' => $versionId,
        ]);

        $touchTemplate = $this->pdo->prepare(
            'UPDATE page_templates
             SET updated_at = :updated_at
             WHERE id = :template_id'
        );
            $touchTemplate->execute([
                ':updated_at' => myronNow(),
                ':template_id' => $runtimeTemplateId,
            ]);

        myronInvalidatePublicPageCache('runtime template schema changed');

            $updated = $this->findRuntimeTemplateBySourceTemplateId((string) ($template['source_template_id'] ?? ''));
            return is_array($updated) ? $updated : $template;
        }

    /**
     * @return array<string, mixed>|null
     */
    private function findTemplateWithSchemaById(string $runtimeTemplateId): ?array
    {
        $statement = $this->pdo->prepare(
            'SELECT
                page_templates.id,
                page_templates.template_key,
                page_templates.name,
                page_templates.description,
                page_templates.source_template_id,
                page_templates.source_bundle_id,
                page_templates.chrome_json,
                page_templates.header_capabilities_json,
                page_templates.current_version_id,
                page_template_versions.version_number,
                page_template_versions.view_name,
                page_template_versions.schema_json
             FROM page_templates
             LEFT JOIN page_template_versions ON page_template_versions.id = page_templates.current_version_id
             WHERE page_templates.id = :template_id
             LIMIT 1'
        );
        $statement->execute([':template_id' => trim($runtimeTemplateId)]);
        $row = $statement->fetch();

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

    /**
     * @param array<string, mixed> $content
     */
    public function updateRuntimeTemplateDefaultContent(string $runtimeTemplateId, array $content): array
    {
        $template = $this->requireTemplate($runtimeTemplateId);
        $versionId = trim((string) ($template['current_version_id'] ?? ''));
        if ($versionId === '') {
            throw new InvalidArgumentException('Runtime template has no editable current version.');
        }

        $statement = $this->pdo->prepare(
            'SELECT schema_json
             FROM page_template_versions
             WHERE id = :version_id
             LIMIT 1'
        );
        $statement->execute([':version_id' => $versionId]);
        $schemaJson = $statement->fetchColumn();
        if (!is_string($schemaJson) || trim($schemaJson) === '') {
            throw new InvalidArgumentException('Runtime template schema is unavailable.');
        }

        $schema = json_decode($schemaJson, true, 512, JSON_THROW_ON_ERROR);
        if (!is_array($schema)) {
            throw new InvalidArgumentException('Runtime template schema is invalid.');
        }
        $schema['defaultContent'] = $content;

        $update = $this->pdo->prepare(
            'UPDATE page_template_versions
             SET schema_json = :schema_json
             WHERE id = :version_id'
        );
        $update->execute([
            ':schema_json' => json_encode($schema, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES),
            ':version_id' => $versionId,
        ]);

        $touchTemplate = $this->pdo->prepare(
            'UPDATE page_templates
             SET updated_at = :updated_at
             WHERE id = :template_id'
        );
            $touchTemplate->execute([
                ':updated_at' => myronNow(),
                ':template_id' => $runtimeTemplateId,
            ]);

        myronInvalidatePublicPageCache('runtime template default content changed');

            $updated = $this->findRuntimeTemplateBySourceTemplateId((string) ($template['source_template_id'] ?? ''));
            return is_array($updated) ? $updated : $template;
        }
}
