<?php

declare(strict_types=1);

final class MyronEntitlementPresentationPolicy
{
    public const STATE_AVAILABLE = 'available';
    public const STATE_LOCKED = 'locked';
    public const STATE_HIDDEN = 'hidden';

    /**
     * Site types that ship free with Core. Every other site type is gated by the
     * single `site_types.catalog` capability, granted on any paid Module activation.
     *
     * @var list<string>
     */
    private const CORE_SITE_TYPE_CODES = ['biz', 'blog'];

    public const SITE_TYPE_CATALOG_CAPABILITY = 'site_types.catalog';

    public function __construct(
        private readonly ?MyronEntitlementGate $gate = null,
        private readonly ?MyronEntitlementSnapshot $snapshot = null,
    ) {
    }

    /**
     * @return array{state:string, capability:string, reason:string, remediationLabel:string, remediationRoute:string, remediationQuery:array<string,string>}
     */
    public function decisionForCapability(string $capability): array
    {
        $capability = trim($capability);
        if ($capability === '') {
            return $this->decision(self::STATE_AVAILABLE, '', 'no_capability_required');
        }

        $evaluation = $this->evaluate($capability);
        if (in_array((string) ($evaluation['outcome'] ?? ''), ['allowed', 'warn'], true)) {
            return $this->decision(self::STATE_AVAILABLE, $capability, (string) ($evaluation['reason'] ?? 'entitled'));
        }

        $definition = MyronCapabilityCatalog::definitions()[$capability] ?? null;
        if (is_array($definition) && ($definition['visibility'] ?? 'visible') === 'hidden') {
            return $this->decision(self::STATE_HIDDEN, $capability, (string) ($evaluation['reason'] ?? 'capability_not_visible'));
        }

        return $this->decision(self::STATE_LOCKED, $capability, (string) ($evaluation['reason'] ?? 'capability_not_entitled'));
    }

    public function allowsCapability(string $capability): bool
    {
        return $this->decisionForCapability($capability)['state'] === self::STATE_AVAILABLE;
    }

    /**
     * @param array<string, mixed> $siteType
     */
    public function allowsSiteType(array $siteType): bool
    {
        $capability = self::capabilityForSiteType($siteType);
        return $capability === '' || $this->allowsCapability($capability);
    }

    /**
     * @param list<array<string, mixed>> $siteTypes
     * @return list<array<string, mixed>>
     */
    public function visibleSiteTypes(array $siteTypes): array
    {
        return array_values(array_filter($siteTypes, fn (array $siteType): bool => $this->allowsSiteType($siteType)));
    }

    /**
     * @param array<string, mixed> $siteType
     */
    public function assertSiteTypeAllowed(array $siteType): void
    {
        if ($this->allowsSiteType($siteType)) {
            return;
        }

        $label = trim((string) ($siteType['name'] ?? $siteType['id'] ?? 'This site type'));
        throw new InvalidArgumentException($label . ' is not available for the current license.');
    }

    /**
     * @param array<string, mixed>|string $siteType
     */
    public static function capabilityForSiteType(array|string $siteType): string
    {
        if (is_array($siteType)) {
            $code = trim((string) ($siteType['code'] ?? ''));
            $bundleId = trim((string) ($siteType['id'] ?? $siteType['bundleId'] ?? ''));
            if ($code === '' && str_starts_with($bundleId, 'site-type.')) {
                $code = substr($bundleId, strlen('site-type.'));
            }
        } else {
            $raw = strtolower(trim($siteType));
            $code = str_starts_with($raw, 'site-type.') ? substr($raw, strlen('site-type.')) : $raw;
        }

        $code = strtolower(trim((string) $code));
        if ($code === '' || in_array($code, ['custom', 'universal'], true)) {
            return '';
        }

        // Core site types are free with Core; all others are gated behind the single
        // catalog capability rather than per-type personal/pro tiers.
        if (in_array($code, self::CORE_SITE_TYPE_CODES, true)) {
            return '';
        }

        return self::SITE_TYPE_CATALOG_CAPABILITY;
    }

    public static function isModuleCapability(string $capability): bool
    {
        $capability = trim($capability);
        if ($capability === 'modules.install' || $capability === self::SITE_TYPE_CATALOG_CAPABILITY) {
            return true;
        }

        $definition = MyronCapabilityCatalog::definitions()[$capability] ?? null;
        return is_array($definition)
            && (str_starts_with((string) ($definition['package'] ?? ''), 'pro-')
                || (is_array($definition['modules'] ?? null) && $definition['modules'] !== []));
    }

    /**
     * @return array{label:string, route:string, query:array<string,string>}
     */
    public function remediationForCapability(string $capability): array
    {
        if (self::isModuleCapability($capability)) {
            return [
                'label' => 'Open Modules',
                'route' => 'administration.licenseManager',
                'query' => ['tab' => 'redeem'],
            ];
        }

        return [
            'label' => 'Open License',
            'route' => 'administration.license',
            'query' => [],
        ];
    }

    /**
     * @return array{state:string, capability:string, reason:string, remediationLabel:string, remediationRoute:string, remediationQuery:array<string,string>}
     */
    private function decision(string $state, string $capability, string $reason): array
    {
        $remediation = $this->remediationForCapability($capability);
        return [
            'state' => $state,
            'capability' => $capability,
            'reason' => $reason,
            'remediationLabel' => $remediation['label'],
            'remediationRoute' => $remediation['route'],
            'remediationQuery' => $remediation['query'],
        ];
    }

    /**
     * @return array{outcome:string, reason:string, capability:string, plan:string, status:string}
     */
    private function evaluate(string $capability): array
    {
        if ($this->snapshot instanceof MyronEntitlementSnapshot) {
            if (!MyronLicenseStatus::allowsRuntime($this->snapshot->status)) {
                return $this->evaluation('blocked', 'license_status_' . $this->snapshot->status, $capability);
            }
            if (!array_key_exists($capability, $this->snapshot->capabilities)) {
                return $this->evaluation('warn', 'unknown_capability', $capability);
            }
            if ($this->snapshot->capabilities[$capability] !== true) {
                return $this->evaluation('blocked', 'capability_not_entitled', $capability);
            }

            return $this->evaluation('allowed', 'entitled', $capability);
        }

        if ($this->gate instanceof MyronEntitlementGate) {
            try {
                return $this->gate->evaluate($capability);
            } catch (Throwable) {
                return $this->evaluation('blocked', 'entitlement_evaluation_failed', $capability);
            }
        }

        return $this->evaluation('blocked', 'entitlement_gate_unavailable', $capability);
    }

    /**
     * @return array{outcome:string, reason:string, capability:string, plan:string, status:string}
     */
    private function evaluation(string $outcome, string $reason, string $capability): array
    {
        return [
            'outcome' => $outcome,
            'reason' => $reason,
            'capability' => $capability,
            'plan' => $this->snapshot?->plan ?? '',
            'status' => $this->snapshot?->status ?? '',
        ];
    }
}
