<?php

declare(strict_types=1);

/**
 * APO-186 Card 4 — Permission scope vocabulary.
 *
 * Six base scopes mirror the existing myronUserCan{Access,Manage}{Domain}
 * role checks in bootstrap.php. AI-as-user-with-role: the scope vocabulary
 * the AI bearer-token caller holds is the same vocabulary the human role
 * model already gates on.
 *
 * Sub-scopes follow `{domain}.{resource}.{verb}` shape (e.g.
 * `design.tokens.write`, `content.pages.publish`). Base scopes auto-include
 * sub-scopes by namespace prefix — holding `design.write` means the caller
 * has every `design.*.write` sub-scope. (Sub-scopes are introduced incrementally
 * in later milestones; the Foundation only ships the six base scopes.)
 */
final class MyronApiScopes
{
    public const DESIGN_READ          = 'design.read';
    public const DESIGN_WRITE         = 'design.write';
    public const CONTENT_READ         = 'content.read';
    public const CONTENT_WRITE        = 'content.write';
    public const ADMINISTRATION_READ  = 'administration.read';
    public const ADMINISTRATION_WRITE = 'administration.write';
    public const ADMINISTRATION_DESTRUCTIVE = 'administration.destructive';
    public const DEPLOYMENT_PUSH      = 'deployment.push';
    public const DEPLOYMENT_RECEIVE   = 'deployment.receive';

    public const ALL_BASE = [
        self::DESIGN_READ,
        self::DESIGN_WRITE,
        self::CONTENT_READ,
        self::CONTENT_WRITE,
        self::ADMINISTRATION_READ,
        self::ADMINISTRATION_WRITE,
    ];

    /**
     * Determine whether $heldScopes covers $requiredScope.
     *
     * Direct match wins. Otherwise, if a base scope `{domain}.{verb}` is held
     * and the required scope is a sub-scope `{domain}.{resource}.{verb}` with
     * the same {domain}.{verb} suffix, the base scope authorises the
     * sub-scope per the namespace inclusion rule.
     *
     * @param list<string> $heldScopes
     */
    public static function covers(array $heldScopes, string $requiredScope): bool
    {
        if (in_array($requiredScope, $heldScopes, true)) {
            return true;
        }
        // Sub-scope inclusion: required = `domain.resource.verb`; held base = `domain.verb`.
        $parts = explode('.', $requiredScope);
        if (count($parts) >= 3) {
            $domain = $parts[0];
            $verb   = end($parts);
            $base   = $domain . '.' . $verb;
            if (in_array($base, $heldScopes, true)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Parse a comma-separated env value into a normalised list of scope strings.
     *
     * @return list<string>
     */
    public static function parseList(string $raw): array
    {
        $items = array_map('trim', explode(',', $raw));
        return array_values(array_filter($items, static fn (string $s): bool => $s !== ''));
    }
}
