<?php

namespace App\Services;

use App\Filament\Resources\Concerns\ChecksPermissionEscalation;
use App\Models\Central\Tenant;
use App\Models\Concerns\HasPermissions;
use App\Models\MenuItem;
use App\Models\User;
use Illuminate\Support\Facades\Auth;

/**
 * Builds the section → module → actions permission matrix from the (global) menu
 * definition. Shared by the tenant role/user editors and the landlord's per-tenant
 * entitlement editor so all three render the identical {@see PermissionMatrix}.
 */
class PermissionCatalog
{
    /**
     * @param  array<int, string>|null  $limitTo  Permission names to keep. Null =
     *                                            the whole catalog. Modules and
     *                                            sections left with no actions
     *                                            drop out entirely.
     * @return array<int, array{section:string, icon:string|null, modules:array<int, array{key:string, label:string, icon:string|null, actions:array<int,string>}>}>
     */
    public static function matrix(?array $limitTo = null): array
    {
        $allowed = $limitTo === null ? null : array_flip($limitTo);

        return MenuItem::with('children')
            ->whereNull('parent_id')
            ->where('is_active', true)
            ->orderBy('order')
            ->get()
            ->map(function (MenuItem $section) use ($allowed) {
                $modules = $section->isGroup() ? $section->children : collect([$section]);

                return [
                    'section' => $section->name,
                    'icon' => $section->icon,
                    'modules' => $modules
                        ->filter(fn (MenuItem $m) => $m->module_key !== null)
                        ->map(fn (MenuItem $m) => [
                            'key' => $m->module_key,
                            'label' => $m->name,
                            'icon' => $m->icon,
                            'actions' => collect($m->actions ?? [])
                                ->filter(fn (string $a): bool => $allowed === null || isset($allowed["{$m->module_key}.{$a}"]))
                                ->values()
                                ->all(),
                        ])
                        ->filter(fn (array $m): bool => count($m['actions']) > 0)
                        ->values()
                        ->all(),
                ];
            })
            ->filter(fn ($s) => count($s['modules']) > 0)
            ->values()
            ->all();
    }

    /**
     * The matrix as it should be *rendered* for the acting user: capped by the
     * tenant's entitlement ceiling.
     *
     * A permission the landlord has not entitled the tenant to is refused by
     * {@see HasPermissions::hasPermissionTo()} for everyone
     * in that tenant — full admins included — so offering it in a role or user
     * editor advertises access that can never take effect. No entitlement rows
     * at all means no ceiling has been configured (the back-compat default), and
     * the whole catalog is shown.
     *
     * Deliberately NOT capped by what the acting administrator personally holds:
     * they must still be able to SEE and revoke a permission they cannot grant.
     * That direction is guarded at save time instead
     * ({@see ChecksPermissionEscalation}).
     *
     * @param  int|null  $tenantId  Explicit tenant to cap against. Pass this from
     *                              any editor that isn't rendering under the acted-on
     *                              tenant's own `web` guard — e.g. Central's per-tenant
     *                              user editor, where `Auth::user()` is a `CentralAdmin`
     *                              with no `tenant_id` of its own. Defaults to the
     *                              current user's tenant when omitted.
     * @return array<int, array{section:string, icon:string|null, modules:array<int, array{key:string, label:string, icon:string|null, actions:array<int,string>}>}>
     */
    public static function entitledMatrix(?int $tenantId = null): array
    {
        $tenantId ??= Auth::user()?->tenant_id;
        $ceiling = $tenantId ? Tenant::entitledPermissionNamesFor($tenantId) : [];

        return self::matrix($ceiling === [] ? null : $ceiling);
    }

    /**
     * The permissions an actor may grant in a tenant role/user editor, capped by
     * the tenant's entitlement ceiling. Null = unrestricted (full admin in a
     * tenant with no ceiling configured, or single-tenant).
     *
     * @return array<int, string>|null
     */
    public static function grantableFor(?User $actor): ?array
    {
        if (! $actor) {
            return [];
        }

        $ceiling = $actor->tenant_id
            ? Tenant::entitledPermissionNamesFor((int) $actor->tenant_id)
            : [];
        $ceiling = $ceiling === [] ? null : $ceiling; // [] means "no ceiling set"

        if ($actor->isFullAdmin()) {
            return $ceiling;
        }

        $actorPermissions = $actor->cachedPermissionNames()->all();

        return $ceiling === null
            ? array_values($actorPermissions)
            : array_values(array_intersect($actorPermissions, $ceiling));
    }

    /**
     * Every permission name (module.action), labelled "Section / Module — Action" for
     * display in a role/user permission-matrix checkbox list (RoleResource, UserResource).
     *
     * @return array<string, string>
     */
    public static function labelledOptions(): array
    {
        $options = [];
        foreach (self::matrix() as $section) {
            foreach ($section['modules'] as $module) {
                foreach ($module['actions'] as $action) {
                    $options["{$module['key']}.{$action}"] = "{$section['section']} / {$module['label']} — {$action}";
                }
            }
        }

        return $options;
    }

    /**
     * Every permission name (module.action) the platform knows about.
     *
     * @return array<int, string>
     */
    public static function allPermissionNames(): array
    {
        $names = [];
        foreach (self::matrix() as $section) {
            foreach ($section['modules'] as $module) {
                foreach ($module['actions'] as $action) {
                    $names[] = "{$module['key']}.{$action}";
                }
            }
        }

        return $names;
    }
}
