<?php

declare(strict_types=1);

/**
 * APO-186 Card 5 — Manifest discovery endpoint.
 *
 * URI:    GET /api/v1/manifest
 * Scope:  null (public; AI clients discover the surface BEFORE authenticating)
 *
 * Self-describes — its own metadata appears in the catalog it emits, so AI
 * clients can introspect the introspection endpoint. Filters via query
 * params: ?domain=<group>, ?scope=<scope>. Public actions appear in any
 * scope filter so AI clients always see the discovery surface.
 *
 * Manifest is regenerated every request from the live registry — never
 * cached, never persisted. Adding a registered action = automatically
 * appears.
 */
final class MyronGetManifestAction implements MyronAction
{
    public function __construct(
        private readonly MyronActionRegistry $registry,
    ) {
    }

    public function metadata(): MyronActionMetadata
    {
        return new MyronActionMetadata(
            uri:           '/api/v1/manifest',
            method:        'GET',
            domain:        'system',
            resource:      'manifest',
            verb:          'read',
            inputSchema:   [
                'type'       => 'object',
                'properties' => [
                    'domain' => ['type' => 'string'],
                    'scope'  => ['type' => 'string'],
                ],
            ],
            outputSchema:  [
                'type'       => 'object',
                'properties' => [
                    'manifest' => ['type' => 'object'],
                ],
            ],
            requiredScope: null,
            summary:       'Discover the action catalog: every API action available, its URI, method, input/output schemas, required scope, side effects, and AI-prompt-context summary.',
            sideEffects:   [],
            preconditions: [],
            postconditions: [],
            audienceClass:  2,
        );
    }

    public function execute(array $input, MyronActionCallerContext $caller): MyronActionResult
    {
        $domainFilter = isset($input['domain']) && is_string($input['domain'])
            ? trim($input['domain'])
            : '';
        $scopeFilter = isset($input['scope']) && is_string($input['scope'])
            ? trim($input['scope'])
            : '';

        $entries = [];
        foreach ($this->registry->all() as $action) {
            $meta = $action->metadata();

            if ($domainFilter !== '' && $meta->domain !== $domainFilter) {
                continue;
            }

            if ($scopeFilter !== '') {
                // Public actions (no requiredScope) appear in every scope filter
                // so AI clients always see introspection endpoints.
                if ($meta->requiredScope !== null && $meta->requiredScope !== $scopeFilter) {
                    continue;
                }
            }

            $entries[] = $meta->toArray();
        }

        // Stable ordering: alphabetical by URI within each domain.
        usort($entries, static function (array $a, array $b): int {
            $cmpDomain = strcmp((string) $a['domain'], (string) $b['domain']);
            if ($cmpDomain !== 0) {
                return $cmpDomain;
            }
            return strcmp((string) $a['uri'], (string) $b['uri']);
        });

        return MyronActionResult::ok([
            'manifest' => [
                'version'     => '1',
                'generatedAt' => date('Y-m-d\TH:i:s\Z'),
                'actions'     => $entries,
            ],
        ]);
    }
}
