<?php

declare(strict_types=1);

namespace PpPhp\Core\Licensing;

/**
 * The one place any feature asks "is this install entitled to X?".
 *
 * A capability is a server-side feature a plan may grant (see PlanRegistry) — the
 * platform's own AI, Email, SMS, storage, and so on. The Owner turns capabilities
 * on per plan; a licence issued on that plan carries the granted keys inside its
 * signed lease; here is where the client reads them back and where feature code
 * gates on them:
 *
 *     if (Capability::has('server_ai')) { …use the server AI… }
 *     else { …offer an upgrade… }
 *
 * This class ships to clients (it is the CHECK). PlanRegistry — which DEFINES the
 * plans and their grants — is authority-only. Both share this CATALOG so the
 * labels a customer sees match the toggles the Owner set.
 */
final class Capability
{
    /**
     * Every capability the platform knows about. Add a key here when you build a
     * new server-side feature; plans then toggle it, and it is enforceable at once.
     * Keys are STABLE — they travel in the signed lease and are what code checks.
     *
     * @var array<string,array{label:string,desc:string,icon:string,group:string}>
     */
    public const CATALOG = [
        'server_ai'        => ['label' => 'Server AI',        'desc' => "Use the platform server's AI service (no key of their own).", 'icon' => 'stars',          'group' => 'Server services'],
        'server_email'     => ['label' => 'Server Email',     'desc' => 'Send email through the platform server.',                     'icon' => 'envelope-at',    'group' => 'Server services'],
        'server_sms'       => ['label' => 'Server SMS',       'desc' => 'Send SMS through the platform server.',                       'icon' => 'chat-dots',      'group' => 'Server services'],
        'server_storage'   => ['label' => 'Server Storage',   'desc' => 'Store files on the platform-provided cloud storage.',         'icon' => 'hdd',            'group' => 'Server services'],
        'priority_support' => ['label' => 'Priority support', 'desc' => 'Flagged for priority in the support queue.',                  'icon' => 'life-preserver', 'group' => 'Service level'],
    ];

    /** @return array<int,string> the known capability keys */
    public static function keys(): array
    {
        return array_keys(self::CATALOG);
    }

    public static function isKnown(string $cap): bool
    {
        return isset(self::CATALOG[$cap]);
    }

    public static function label(string $cap): string
    {
        return self::CATALOG[$cap]['label'] ?? ucwords(str_replace('_', ' ', $cap));
    }

    /** @return array{label:string,desc:string,icon:string,group:string}|null */
    public static function meta(string $cap): ?array
    {
        return self::CATALOG[$cap] ?? null;
    }

    /**
     * True when capabilities are actually GATED on this install — i.e. a real,
     * enforced client edition. On the vendor's own authority, or an unstamped /
     * dev build where nothing is enforced, capabilities are not restricted.
     */
    public static function gatedHere(): bool
    {
        try {
            if (!class_exists(LicenseGate::class)) return false;
            if (!LicenseGate::enforced()) return false;   // dev / unstamped — no gating
            if (LicenseGate::isAuthority()) return false;  // the licence server itself
            return true;
        } catch (\Throwable $e) {
            return false;
        }
    }

    /**
     * The single entitlement check. True when this install may use $cap:
     *   • not gated here (authority / dev)            → always true
     *   • gated, but the licence is invalid/expired   → false
     *   • gated + valid                               → whatever the plan granted
     */
    public static function has(string $cap): bool
    {
        try {
            if (!self::gatedHere()) return true;
            if (LicenseGate::locked()) return false;
            return class_exists(LicenseClient::class) && (new LicenseClient())->can($cap);
        } catch (\Throwable $e) {
            return false;
        }
    }

    /**
     * The capability keys granted on this install (for display / iteration). On a
     * non-gated copy that is the whole catalogue; on a gated client it is the plan's
     * grant from the lease, or none when the licence is not valid.
     *
     * @return array<int,string>
     */
    public static function granted(): array
    {
        try {
            if (!self::gatedHere()) return self::keys();
            if (LicenseGate::locked()) return [];
            return class_exists(LicenseClient::class) ? (new LicenseClient())->capabilities() : [];
        } catch (\Throwable $e) {
            return [];
        }
    }

    /**
     * Guard a server-side feature in one line. Returns null when allowed, or a
     * short, customer-facing reason string when not — so a caller can:
     *   if ($why = Capability::deny('server_ai')) { return error($why); }
     */
    public static function deny(string $cap): ?string
    {
        if (self::has($cap)) return null;
        return 'Your plan does not include ' . self::label($cap) . '. Contact your provider to upgrade.';
    }
}
