<?php

declare(strict_types=1);

/**
 * APO-269 — POST /api/v1/search/rebuild
 *
 * Operator-callable backfill / drift-repair action. Walks every currently-
 * published page, runs it through the indexer, and reports a summary.
 * Identical logic to the apo_269_backfill_search_index migration; the
 * action exists so the operator can re-run on demand when index drift
 * is suspected, without redeploying.
 */
final class MyronRebuildSearchIndexAction implements MyronAction
{
    public function __construct(
        private readonly PDO $pdo,
        private readonly MyronSearchIndexer $indexer
    ) {
    }

    public function metadata(): MyronActionMetadata
    {
        return new MyronActionMetadata(
            uri: '/api/v1/search/rebuild',
            method: 'POST',
            domain: 'administration',
            resource: 'search-index',
            verb: 'rebuild',
            inputSchema: [
                'type' => 'object',
                'properties' => [],
            ],
            outputSchema: [
                'type' => 'object',
                'properties' => [
                    'processed' => ['type' => 'integer'],
                    'indexed' => ['type' => 'integer'],
                    'skipped' => ['type' => 'integer'],
                    'errors' => ['type' => 'integer'],
                ],
            ],
            requiredScope: MyronApiScopes::ADMINISTRATION_WRITE,
            summary: 'Walk every published page and re-index into search_documents. Idempotent recovery tool when write-through indexing drifts.',
            sideEffects: ['upserts rows in search_documents for every published page'],
            audienceClass: 2,
            snapshotInclude: false,
            exposeToMcp: true,
        );
    }

    public function execute(array $input, MyronActionCallerContext $caller): MyronActionResult
    {
        $entitlement = $this->requireCapability('search.public');
        if ($entitlement !== null) {
            return $entitlement;
        }
        $summary = ['processed' => 0, 'indexed' => 0, 'skipped' => 0, 'errors' => 0];
        $pageIds = $this->indexer->listAllPublishedPageIds();
        foreach ($pageIds as $pageId) {
            $summary['processed']++;
            try {
                $this->indexer->indexPageById($pageId);
                if ($this->indexedCount($pageId) > 0) {
                    $summary['indexed']++;
                } else {
                    $summary['skipped']++;
                }
            } catch (Throwable $e) {
                $summary['errors']++;
            }
        }
        return MyronActionResult::ok($summary);
    }

    private function indexedCount(string $pageId): int
    {
        $statement = $this->pdo->prepare("SELECT COUNT(*) FROM search_documents WHERE entity_type = 'page' AND entity_id = :id");
        $statement->execute([':id' => $pageId]);
        return (int) $statement->fetchColumn();
    }

    private function requireCapability(string $capability): ?MyronActionResult
    {
        if (!function_exists('myronEntitlementGate')) {
            return null;
        }
        try {
            $decision = myronEntitlementGate()->evaluate($capability);
        } catch (Throwable) {
            return MyronActionResult::error('entitlement_denied', 'This feature is not available for the current license.');
        }
        return in_array((string) ($decision['outcome'] ?? ''), ['allowed', 'warn'], true)
            ? null
            : MyronActionResult::error('entitlement_denied', 'This feature is not available for the current license.');
    }
}
