<?php

declare(strict_types=1);

/**
 * APO-194 Card 2 — Template duplication action service.
 *
 * Extracted from the `$createCustomTemplate` closure in the Site Type Studio
 * admin route so that BOTH the admin form path AND the new API path
 * POST /api/v1/design/templates/duplicate share identical logic.
 *
 * Note: the `runtimeSchemaForDefinition` capability is also a closure in the
 * admin route. To preserve the closure surface without copying its body, the
 * service takes a `schemaResolver` callable in its constructor. Callers
 * provide the resolver (admin path passes the route's closure; future API
 * dispatcher path can pass a substrate-equivalent resolver).
 */
final class MyronTemplateDuplicationActionService
{
    /**
     * @param callable $schemaResolver fn(array $definition): array — produces the runtime schema map.
     */
    public function __construct(
        private readonly PDO $pdo,
        private $schemaResolver,
    ) {
    }

    /**
     * @param array<string, mixed> $definition  source template definition
     * @return array{id: string, source_template_id: string, lifecycle: array<string, mixed>}
     */
    public function duplicate(array $definition, string $name, MyronActionCallerContext $caller): array
    {
        $sourceTemplateId = trim((string) ($definition['id'] ?? ''));
        $sourceBundleId   = trim((string) ($definition['bundleId'] ?? ''));
        $displayName      = trim($name);
        if ($sourceTemplateId === '' || $displayName === '') {
            throw new InvalidArgumentException('A source template and template name are required.');
        }

        $slug = substr(myronNormalizeSlug($displayName), 0, 72) ?: 'custom';
        $candidateSourceTemplateId = $sourceTemplateId . '::custom::' . $slug;
        $counter = 2;
        while (true) {
            $exists = $this->pdo->prepare('SELECT id FROM page_templates WHERE source_template_id = :source_template_id LIMIT 1');
            $exists->execute([':source_template_id' => $candidateSourceTemplateId]);
            if ($exists->fetchColumn() === false) {
                break;
            }
            $candidateSourceTemplateId = $sourceTemplateId . '::custom::' . $slug . '-' . $counter;
            $counter++;
        }

        $templateId = myronGenerateId('tpl');
        $versionId  = myronGenerateId('tplver');
        $now        = myronNow();

        $resolver = $this->schemaResolver;
        $schema   = (array) $resolver($definition);
        $schema['sourceTemplateId']     = $candidateSourceTemplateId;
        $schema['sourceTemplateBaseId'] = $sourceTemplateId;
        $schema['defaultContent']       = [];
        $schema['templateLifecycle']    = [
            'kind' => 'custom_site_template',
            'createdFrom' => 'packaged_site_template',
            'baseTemplateId' => $sourceTemplateId,
            'availableAsPageTemplate' => false,
            'createdBy' => $caller->callerId,
            'createdAt' => $now,
            'registeredAt' => '',
        ];

        $this->pdo->beginTransaction();
        try {
            $insertTemplate = $this->pdo->prepare(
                'INSERT INTO page_templates (
                    id, template_key, name, description, source_template_id, source_bundle_id,
                    chrome_json, header_capabilities_json, current_version_id, created_at, updated_at
                 ) VALUES (
                    :id, :template_key, :name, :description, :source_template_id, :source_bundle_id,
                    :chrome_json, :header_capabilities_json, :current_version_id, :created_at, :updated_at
                 )'
            );
            $insertTemplate->execute([
                ':id'                    => $templateId,
                ':template_key'          => myronNormalizeSlug(str_replace(['template.', '.v1'], ['', ''], $sourceTemplateId)) . '-' . $slug,
                ':name'                  => $displayName,
                ':description'           => 'Custom Site Template derived from ' . $sourceTemplateId . '.',
                ':source_template_id'    => $candidateSourceTemplateId,
                ':source_bundle_id'      => $sourceBundleId,
                ':chrome_json'           => json_encode($schema['chrome'] ?? [], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES),
                ':header_capabilities_json' => json_encode($schema['headerCapabilities'] ?? [], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES),
                ':current_version_id'    => $versionId,
                ':created_at'            => $now,
                ':updated_at'            => $now,
            ]);

            $insertVersion = $this->pdo->prepare(
                'INSERT INTO page_template_versions (
                    id, template_id, version_number, view_name, schema_json, created_at
                 ) VALUES (
                    :id, :template_id, :version_number, :view_name, :schema_json, :created_at
                 )'
            );
            $insertVersion->execute([
                ':id'             => $versionId,
                ':template_id'    => $templateId,
                ':version_number' => 1,
                ':view_name'      => 'default',
                ':schema_json'    => json_encode($schema, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES),
                ':created_at'     => $now,
            ]);

            $this->pdo->commit();
        } catch (Throwable $exception) {
            $this->pdo->rollBack();
            throw $exception;
        }

        return [
            'id'                 => $templateId,
            'source_template_id' => $candidateSourceTemplateId,
            'lifecycle'          => $schema['templateLifecycle'],
        ];
    }

    /**
     * @return array{id: string, source_template_id: string, lifecycle: array<string, mixed>}
     */
    public function createBlank(string $name, string $description, string $siteTypeCode, MyronActionCallerContext $caller): array
    {
        $displayName = trim($name);
        if ($displayName === '') {
            throw new InvalidArgumentException('A Custom Site Template name is required.');
        }

        $slug = substr(myronNormalizeSlug($displayName), 0, 72) ?: 'custom-site-template';
        $sourceTemplateId = 'template.custom.' . $slug . '.v1::custom::' . $slug;
        $counter = 2;
        while (true) {
            $exists = $this->pdo->prepare('SELECT id FROM page_templates WHERE source_template_id = :source_template_id LIMIT 1');
            $exists->execute([':source_template_id' => $sourceTemplateId]);
            if ($exists->fetchColumn() === false) {
                break;
            }
            $sourceTemplateId = 'template.custom.' . $slug . '-' . $counter . '.v1::custom::' . $slug . '-' . $counter;
            $counter++;
        }

        $templateId = myronGenerateId('tpl');
        $versionId = myronGenerateId('tplver');
        $now = myronNow();
        $normalizedSiteType = myronNormalizeSlug($siteTypeCode);
        $schema = [
            'sourceTemplateId' => $sourceTemplateId,
            'sourceTemplateBaseId' => '',
            'sourceBundleId' => $normalizedSiteType !== '' ? 'custom.' . $normalizedSiteType : 'custom',
            'chrome' => [],
            'headerCapabilities' => [],
            'bridge' => [
                'normalizedBodySections' => true,
                'legacyChromeSectionCount' => 0,
                'bodySectionCount' => 0,
            ],
            'regions' => [
                ['key' => 'eyebrow', 'label' => 'Eyebrow', 'type' => 'text', 'richtext' => true],
                ['key' => 'headline', 'label' => 'Headline', 'type' => 'text', 'richtext' => true],
                ['key' => 'intro', 'label' => 'Intro', 'type' => 'textarea', 'richtext' => true],
                ['key' => 'body', 'label' => 'Body', 'type' => 'textarea', 'richtext' => true],
                ['key' => 'ctaLabel', 'label' => 'CTA label', 'type' => 'text', 'richtext' => true],
                ['key' => 'ctaHref', 'label' => 'CTA href', 'type' => 'text'],
            ],
            'sections' => [],
            'defaultContent' => [],
            'templateLifecycle' => [
                'kind' => 'custom_site_template',
                'createdFrom' => 'blank_custom_site_template',
                'baseTemplateId' => '',
                'availableAsPageTemplate' => false,
                'createdBy' => $caller->callerId,
                'createdAt' => $now,
                'registeredAt' => '',
            ],
        ];

        $this->pdo->beginTransaction();
        try {
            $insertTemplate = $this->pdo->prepare(
                'INSERT INTO page_templates (
                    id, template_key, name, description, source_template_id, source_bundle_id,
                    chrome_json, header_capabilities_json, current_version_id, created_at, updated_at
                 ) VALUES (
                    :id, :template_key, :name, :description, :source_template_id, :source_bundle_id,
                    :chrome_json, :header_capabilities_json, :current_version_id, :created_at, :updated_at
                 )'
            );
            $insertTemplate->execute([
                ':id' => $templateId,
                ':template_key' => 'custom-site-template-' . $slug,
                ':name' => $displayName,
                ':description' => trim($description) !== '' ? trim($description) : 'Operator-owned Custom Site Template.',
                ':source_template_id' => $sourceTemplateId,
                ':source_bundle_id' => (string) $schema['sourceBundleId'],
                ':chrome_json' => json_encode([], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES),
                ':header_capabilities_json' => json_encode([], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES),
                ':current_version_id' => $versionId,
                ':created_at' => $now,
                ':updated_at' => $now,
            ]);

            $insertVersion = $this->pdo->prepare(
                'INSERT INTO page_template_versions (
                    id, template_id, version_number, view_name, schema_json, created_at
                 ) VALUES (
                    :id, :template_id, :version_number, :view_name, :schema_json, :created_at
                 )'
            );
            $insertVersion->execute([
                ':id' => $versionId,
                ':template_id' => $templateId,
                ':version_number' => 1,
                ':view_name' => 'default',
                ':schema_json' => json_encode($schema, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES),
                ':created_at' => $now,
            ]);

            $this->pdo->commit();
        } catch (Throwable $exception) {
            $this->pdo->rollBack();
            throw $exception;
        }

        return [
            'id' => $templateId,
            'source_template_id' => $sourceTemplateId,
            'lifecycle' => $schema['templateLifecycle'],
        ];
    }

    /**
     * @return array{id: string, source_template_id: string, lifecycle: array<string, mixed>}
     */
    public function makeAvailableAsPageTemplate(string $templateRef, MyronActionCallerContext $caller): array
    {
        $template = $this->findRuntimeTemplate($templateRef);
        if (!is_array($template)) {
            throw new InvalidArgumentException('Choose a valid Custom Site Template.');
        }

        $schema = json_decode((string) ($template['schema_json'] ?? '{}'), true, 512, JSON_THROW_ON_ERROR);
        if (!is_array($schema)) {
            throw new InvalidArgumentException('Custom Site Template schema is invalid.');
        }
        $lifecycle = is_array($schema['templateLifecycle'] ?? null) ? $schema['templateLifecycle'] : [];
        if (($lifecycle['kind'] ?? '') !== 'custom_site_template') {
            throw new InvalidArgumentException('Only Custom Site Templates can be made available as Page Templates.');
        }

        $lifecycle['availableAsPageTemplate'] = true;
        if (trim((string) ($lifecycle['registeredAt'] ?? '')) === '') {
            $lifecycle['registeredAt'] = myronNow();
        }
        $lifecycle['registeredBy'] = $caller->callerId;
        $schema['templateLifecycle'] = $lifecycle;

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

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

        return [
            'id' => (string) $template['id'],
            'source_template_id' => (string) $template['source_template_id'],
            'lifecycle' => $lifecycle,
        ];
    }

    /**
     * @return array{id: string, source_template_id: string, deleted: bool}
     */
    public function deleteCustomSiteTemplate(string $templateRef, MyronActionCallerContext $caller): array
    {
        $template = $this->findRuntimeTemplate($templateRef);
        if (!is_array($template)) {
            throw new InvalidArgumentException('Choose a valid Custom Site Template.');
        }

        $schema = json_decode((string) ($template['schema_json'] ?? '{}'), true, 512, JSON_THROW_ON_ERROR);
        if (!is_array($schema)) {
            throw new InvalidArgumentException('Custom Site Template schema is invalid.');
        }
        $lifecycle = is_array($schema['templateLifecycle'] ?? null) ? $schema['templateLifecycle'] : [];
        if (($lifecycle['kind'] ?? '') !== 'custom_site_template') {
            throw new InvalidArgumentException('Only Custom Site Templates can be deleted here.');
        }

        $templateId = (string) ($template['id'] ?? '');
        $sourceTemplateId = (string) ($template['source_template_id'] ?? '');
        if ($templateId === '') {
            throw new InvalidArgumentException('Custom Site Template is missing an id.');
        }

        $pageCount = $this->countPagesUsingTemplate($templateId, $sourceTemplateId);
        if ($pageCount > 0) {
            throw new InvalidArgumentException('This Custom Site Template is used by ' . $pageCount . ' page' . ($pageCount === 1 ? '' : 's') . ' and cannot be deleted.');
        }

        $versionIds = $this->templateVersionIds($templateId);
        if ($versionIds !== []) {
            $versionPlaceholders = implode(', ', array_fill(0, count($versionIds), '?'));
            $versionUsage = $this->pdo->prepare(
                'SELECT COUNT(*) FROM page_versions WHERE template_version_id IN (' . $versionPlaceholders . ')'
            );
            $versionUsage->execute($versionIds);
            $versionUsageCount = (int) $versionUsage->fetchColumn();
            if ($versionUsageCount > 0) {
                throw new InvalidArgumentException('This Custom Site Template has page versions and cannot be deleted.');
            }
        }

        $this->pdo->beginTransaction();
        try {
            $deleteAssignments = $this->pdo->prepare(
                'DELETE FROM template_chrome_assignments WHERE template_id = :template_id OR template_id = :source_template_id'
            );
            $deleteAssignments->execute([
                ':template_id' => $templateId,
                ':source_template_id' => $sourceTemplateId,
            ]);

            $deleteVersions = $this->pdo->prepare('DELETE FROM page_template_versions WHERE template_id = :template_id');
            $deleteVersions->execute([':template_id' => $templateId]);

            $deleteTemplate = $this->pdo->prepare('DELETE FROM page_templates WHERE id = :template_id');
            $deleteTemplate->execute([':template_id' => $templateId]);

            $this->pdo->commit();
        } catch (Throwable $exception) {
            $this->pdo->rollBack();
            throw $exception;
        }

        return [
            'id' => $templateId,
            'source_template_id' => $sourceTemplateId,
            'deleted' => true,
        ];
    }

    /**
     * @return list<string>
     */
    private function templateVersionIds(string $templateId): array
    {
        $statement = $this->pdo->prepare('SELECT id FROM page_template_versions WHERE template_id = :template_id');
        $statement->execute([':template_id' => trim($templateId)]);

        return array_values(array_map('strval', $statement->fetchAll(PDO::FETCH_COLUMN)));
    }

    private function countPagesUsingTemplate(string $templateId, string $sourceTemplateId): int
    {
        $statement = $this->pdo->prepare(
            'SELECT COUNT(*) FROM pages WHERE template_id = :template_id OR source_template_id = :source_template_id'
        );
        $statement->execute([
            ':template_id' => trim($templateId),
            ':source_template_id' => trim($sourceTemplateId),
        ]);

        return (int) $statement->fetchColumn();
    }

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

        $statement = $this->pdo->prepare(
            'SELECT page_templates.*, 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) ? $row : null;
    }
}
