<?php

declare(strict_types=1);

namespace App\Filament\Pages\Settings;

use App\Models\SupportAccessGrant;
use App\Models\User;
use App\Models\Workspace;
use App\Models\WorkspaceSetting;
use App\Services\Auth\SupportAccessGrantManager;
use App\Services\Auth\SupportAccessGrantResolver;
use App\Services\Auth\WorkspaceCapabilityResolver;
use App\Services\Entitlements\WorkspaceCommercialLifecycleResolver;
use App\Services\Entitlements\WorkspaceEntitlementResolver;
use App\Services\Entitlements\WorkspacePlanProfileCatalog;
use App\Services\Localization\LocaleResolver;
use App\Services\Settings\SettingsResolver;
use App\Services\Settings\SettingsWriter;
use App\Support\Ai\AiPolicyMode;
use App\Support\Ai\AiUseCaseCatalog;
use App\Support\Auth\Capabilities;
use App\Support\Auth\WorkspaceRole;
use App\Support\Settings\SettingDefinition;
use App\Support\Settings\SettingsRegistry;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceSlot;
use App\Support\Workspaces\WorkspaceContext;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Forms\Components\KeyValue;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use UnitEnum;

class WorkspaceSettings extends Page
{
    protected static bool $isDiscovered = false;

    protected static bool $shouldRegisterNavigation = false;

    protected static ?string $slug = 'settings/workspace';

    protected static ?string $title = 'Workspace settings';

    protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-cog-6-tooth';

    protected static string|UnitEnum|null $navigationGroup = 'Settings';

    protected static ?int $navigationSort = 20;

    /**
     * @var array<string, array{domain: string, key: string, type: 'int'|'json'|'string'|'bool'}>
     */
    private const SETTING_FIELDS = [
        'ai_policy_mode' => ['domain' => 'ai', 'key' => 'policy_mode', 'type' => 'string'],
        'localization_default_locale' => ['domain' => 'localization', 'key' => 'default_locale', 'type' => 'string'],
        'backup_retention_keep_last_default' => ['domain' => 'backup', 'key' => 'retention_keep_last_default', 'type' => 'int'],
        'backup_retention_min_floor' => ['domain' => 'backup', 'key' => 'retention_min_floor', 'type' => 'int'],
        'drift_severity_mapping' => ['domain' => 'drift', 'key' => 'severity_mapping', 'type' => 'json'],
        'baseline_severity_mapping' => ['domain' => 'baseline', 'key' => 'severity_mapping', 'type' => 'json'],
        'baseline_alert_min_severity' => ['domain' => 'baseline', 'key' => 'alert_min_severity', 'type' => 'string'],
        'baseline_auto_close_enabled' => ['domain' => 'baseline', 'key' => 'auto_close_enabled', 'type' => 'bool'],
        'findings_sla_days' => ['domain' => 'findings', 'key' => 'sla_days', 'type' => 'json'],
        'entitlements_plan_profile' => ['domain' => 'entitlements', 'key' => 'plan_profile', 'type' => 'string'],
        'entitlements_managed_tenant_limit_override_value' => ['domain' => 'entitlements', 'key' => 'managed_tenant_limit_override_value', 'type' => 'int'],
        'entitlements_managed_tenant_limit_override_reason' => ['domain' => 'entitlements', 'key' => 'managed_tenant_limit_override_reason', 'type' => 'string'],
        'entitlements_review_pack_generation_override_value' => ['domain' => 'entitlements', 'key' => 'review_pack_generation_override_value', 'type' => 'bool'],
        'entitlements_review_pack_generation_override_reason' => ['domain' => 'entitlements', 'key' => 'review_pack_generation_override_reason', 'type' => 'string'],
        'operations_operation_run_retention_days' => ['domain' => 'operations', 'key' => 'operation_run_retention_days', 'type' => 'int'],
        'operations_stuck_run_threshold_minutes' => ['domain' => 'operations', 'key' => 'stuck_run_threshold_minutes', 'type' => 'int'],
    ];

    /**
     * @var array<string, string>
     */
    private const ENTITLEMENT_OVERRIDE_REASON_FIELDS = [
        'entitlements_managed_tenant_limit_override_value' => 'entitlements_managed_tenant_limit_override_reason',
        'entitlements_review_pack_generation_override_value' => 'entitlements_review_pack_generation_override_reason',
    ];

    /**
     * Fields rendered as Filament KeyValue components (array state, not JSON string).
     *
     * @var array<int, string>
     */
    private const KEYVALUE_FIELDS = [
        'drift_severity_mapping',
    ];

    /**
     * Findings SLA days are decomposed into individual form fields per severity.
     *
     * @var array<string, string>
     */
    private const SLA_SUB_FIELDS = [
        'findings_sla_critical' => 'critical',
        'findings_sla_high' => 'high',
        'findings_sla_medium' => 'medium',
        'findings_sla_low' => 'low',
    ];

    /**
     * Baseline severity mapping is edited as explicit fields per change type.
     *
     * @var array<string, string>
     */
    private const BASELINE_MAPPING_SUB_FIELDS = [
        'baseline_severity_missing_policy' => 'missing_policy',
        'baseline_severity_different_version' => 'different_version',
        'baseline_severity_unexpected_policy' => 'unexpected_policy',
    ];

    public Workspace $workspace;

    /**
     * @var array<string, mixed>
     */
    public array $data = [];

    /**
     * @var array<string, mixed>
     */
    public array $workspaceOverrides = [];

    /**
     * @var array<string, array{source: string, value: mixed, system_default: mixed}>
     */
    public array $resolvedSettings = [];

    /**
     * @var array{
     *     plan_profile?: array{id: string, label: string, description: string, managed_tenant_limit_default: int, review_pack_generation_default: bool, is_default: bool},
     *     decisions?: array<string, array<string, mixed>>
     * }
     */
    public array $entitlementSummary = [];

    /**
     * @var array<string, mixed>
     */
    public array $commercialLifecycleSummary = [];

    /**
     * Per-domain "last modified" metadata: domain => {user_name, updated_at}.
     *
     * @var array<string, array{user_name: string, updated_at: Carbon}>
     */
    public array $domainLastModified = [];

    /**
     * @return array<Action>
     */
    protected function getHeaderActions(): array
    {
        return [
            Action::make('save')
                ->label(__('localization.workspace.save'))
                ->action(function (): void {
                    $this->save();
                })
                ->disabled(fn (): bool => ! $this->currentUserCanManage())
                ->tooltip(fn (): ?string => $this->currentUserCanManage()
                    ? null
                    : __('localization.workspace.no_manage_permission')),
        ];
    }

    public function getTitle(): string
    {
        return __('localization.workspace.title');
    }

    public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
    {
        return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::ListOnlyReadOnly)
            ->satisfy(ActionSurfaceSlot::ListHeader, 'Header action saves settings; each setting includes a confirmed reset action.')
            ->exempt(ActionSurfaceSlot::InspectAffordance, 'Workspace settings are edited as a singleton form without a record inspect action.')
            ->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'The page does not render table rows with secondary actions.')
            ->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'The page has no bulk actions because it manages a single settings scope.')
            ->exempt(ActionSurfaceSlot::ListEmptyState, 'The settings form is always rendered and has no list empty state.');
    }

    public function mount(): void
    {
        $user = auth()->user();

        if (! $user instanceof User) {
            abort(403);
        }

        $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request());

        if ($workspaceId === null) {
            $this->redirect('/admin/choose-workspace');

            return;
        }

        $workspace = Workspace::query()->whereKey($workspaceId)->first();

        if (! $workspace instanceof Workspace) {
            abort(404);
        }

        $this->workspace = $workspace;

        $this->authorizeWorkspaceView($user);

        $this->loadFormState();
    }

    public function content(Schema $schema): Schema
    {
        return $schema
            ->statePath('data')
            ->schema([
                Section::make(__('localization.workspace.section'))
                    ->description($this->sectionDescription('localization', __('localization.workspace.section_description')))
                    ->schema([
                        Select::make('localization_default_locale')
                            ->label(__('localization.workspace.default_locale_label'))
                            ->options(LocaleResolver::localeOptions())
                            ->placeholder(__('localization.workspace.default_locale_placeholder'))
                            ->native(false)
                            ->disabled(fn (): bool => ! $this->currentUserCanManage())
                            ->helperText(fn (): string => $this->localeDefaultHelperText())
                            ->hintAction($this->makeResetAction('localization_default_locale')),
                    ]),
                Section::make('Commercial posture')
                    ->description('Read-only subscription-backed or fallback-backed commercial posture for this workspace.')
                    ->columns(2)
                    ->schema([
                        Placeholder::make('commercial_posture_source')
                            ->label('Commercial source')
                            ->content(fn (): string => $this->commercialPostureSourceText()),
                        Placeholder::make('commercial_posture_state')
                            ->label('Commercial state')
                            ->content(fn (): string => $this->commercialPostureStateText()),
                        Placeholder::make('commercial_posture_timing')
                            ->label('Commercial timing')
                            ->content(fn (): string => $this->commercialPostureTimingText()),
                        Placeholder::make('commercial_posture_reason')
                            ->label('Explanation')
                            ->content(fn (): string => $this->commercialPostureReasonText())
                            ->columnSpanFull(),
                    ]),
                Section::make('Workspace lifecycle')
                    ->description('Read-only workspace closure posture. Closed workspaces keep history visible but block new tenant and workspace mutations.')
                    ->columns(2)
                    ->schema([
                        Placeholder::make('workspace_closure_posture')
                            ->label('Lifecycle')
                            ->content(fn (): string => $this->workspace->isClosed() ? 'Closed' : 'Open'),
                        Placeholder::make('workspace_closed_at')
                            ->label('Closed at')
                            ->content(fn (): string => $this->workspace->closed_at?->toDayDateTimeString() ?? 'Not closed'),
                        Placeholder::make('workspace_closure_reason')
                            ->label('Closure reason')
                            ->content(fn (): string => $this->workspace->closureReason() ?? 'Not closed')
                            ->columnSpanFull(),
                    ]),
                Section::make('Support access approval')
                    ->description('Review current support-access posture and decide pending workspace recovery requests.')
                    ->columns(2)
                    ->afterHeader(fn (): array => $this->supportAccessApprovalActions())
                    ->schema([
                        Placeholder::make('support_access_posture')
                            ->label('Current support access')
                            ->content(fn (): string => $this->supportAccessPostureText()),
                        Placeholder::make('support_access_pending_recovery')
                            ->label('Pending recovery requests')
                            ->content(fn (): string => $this->pendingSupportAccessRequestsText()),
                    ]),
                Section::make('Workspace entitlements')
                    ->description($this->sectionDescription('entitlements', 'Select a plan profile and optional first-slice overrides for onboarding activation and review pack generation.'))
                    ->columns(2)
                    ->schema([
                        Select::make('entitlements_plan_profile')
                            ->label('Plan profile')
                            ->options(app(WorkspacePlanProfileCatalog::class)->optionLabels())
                            ->placeholder(sprintf('Use default profile (%s)', app(WorkspacePlanProfileCatalog::class)->default()['label']))
                            ->native(false)
                            ->columnSpanFull()
                            ->disabled(fn (): bool => ! $this->currentUserCanManage())
                            ->helperText(fn (): string => $this->planProfileFieldHelperText()),
                        TextInput::make('entitlements_managed_tenant_limit_override_value')
                            ->label('Managed tenant activation limit override')
                            ->placeholder('Unset (uses plan profile default)')
                            ->suffix('tenants')
                            ->hint('0 or greater')
                            ->numeric()
                            ->integer()
                            ->minValue(0)
                            ->disabled(fn (): bool => ! $this->currentUserCanManage())
                            ->helperText(fn (): string => $this->managedTenantLimitHelperText())
                            ->hintAction($this->makeResetAction('entitlements_managed_tenant_limit_override_value')),
                        Textarea::make('entitlements_managed_tenant_limit_override_reason')
                            ->label('Managed tenant activation override reason')
                            ->rows(3)
                            ->maxLength(500)
                            ->disabled(fn (): bool => ! $this->currentUserCanManage())
                            ->helperText(fn (): string => $this->managedTenantLimitReasonHelperText()),
                        Select::make('entitlements_review_pack_generation_override_value')
                            ->label('Review pack generation override')
                            ->options(self::booleanOptions())
                            ->placeholder('Unset (uses plan profile default)')
                            ->native(false)
                            ->disabled(fn (): bool => ! $this->currentUserCanManage())
                            ->helperText(fn (): string => $this->reviewPackGenerationHelperText())
                            ->hintAction($this->makeResetAction('entitlements_review_pack_generation_override_value')),
                        Textarea::make('entitlements_review_pack_generation_override_reason')
                            ->label('Review pack generation override reason')
                            ->rows(3)
                            ->maxLength(500)
                            ->disabled(fn (): bool => ! $this->currentUserCanManage())
                            ->helperText(fn (): string => $this->reviewPackGenerationReasonHelperText()),
                    ]),
                Section::make('Workspace AI policy')
                    ->description($this->sectionDescription('ai', 'Control whether the workspace disables AI entirely or allows approved internal-only drafts on private-only infrastructure.'))
                    ->schema([
                        Select::make('ai_policy_mode')
                            ->label('AI posture')
                            ->options(AiPolicyMode::optionLabels())
                            ->placeholder('Unset (uses default)')
                            ->native(false)
                            ->disabled(fn (): bool => ! $this->currentUserCanManage())
                            ->helperText(fn (): string => $this->aiPolicyModeHelperText())
                            ->hintAction($this->makeResetAction('ai_policy_mode')),
                        Placeholder::make('ai_approved_use_cases')
                            ->label('Approved use cases')
                            ->content(fn (): string => $this->aiApprovedUseCasesText()),
                        Placeholder::make('ai_allowed_provider_classes')
                            ->label('Allowed provider classes')
                            ->content(fn (): string => $this->aiAllowedProviderClassesText()),
                        Placeholder::make('ai_blocked_data_classifications')
                            ->label('Blocked data classifications')
                            ->content(fn (): string => $this->aiBlockedDataClassificationsText()),
                    ]),
                Section::make('Backup settings')
                    ->description($this->sectionDescription('backup', 'Workspace defaults used when a schedule has no explicit value.'))
                    ->schema([
                        TextInput::make('backup_retention_keep_last_default')
                            ->label('Default retention keep-last')
                            ->placeholder('Unset (uses default)')
                            ->suffix('versions')
                            ->hint('1 – 365')
                            ->numeric()
                            ->integer()
                            ->minValue(1)
                            ->maxValue(365)
                            ->disabled(fn (): bool => ! $this->currentUserCanManage())
                            ->helperText(fn (): string => $this->helperTextFor('backup_retention_keep_last_default'))
                            ->hintAction($this->makeResetAction('backup_retention_keep_last_default')),
                        TextInput::make('backup_retention_min_floor')
                            ->label('Minimum retention floor')
                            ->placeholder('Unset (uses default)')
                            ->suffix('versions')
                            ->hint('1 – 365')
                            ->numeric()
                            ->integer()
                            ->minValue(1)
                            ->maxValue(365)
                            ->disabled(fn (): bool => ! $this->currentUserCanManage())
                            ->helperText(fn (): string => $this->helperTextFor('backup_retention_min_floor'))
                            ->hintAction($this->makeResetAction('backup_retention_min_floor')),
                    ]),
                Section::make('Drift settings')
                    ->description($this->sectionDescription('drift', 'Map finding types to severity levels. Allowed severities: critical, high, medium, low.'))
                    ->schema([
                        KeyValue::make('drift_severity_mapping')
                            ->label('Severity mapping')
                            ->keyLabel('Finding type')
                            ->valueLabel('Severity')
                            ->keyPlaceholder('e.g. drift')
                            ->valuePlaceholder('critical, high, medium, or low')
                            ->disabled(fn (): bool => ! $this->currentUserCanManage())
                            ->helperText(fn (): string => $this->helperTextFor('drift_severity_mapping'))
                            ->hintAction($this->makeResetAction('drift_severity_mapping')),
                    ]),
                Section::make('Baseline settings')
                    ->key('baseline_section')
                    ->description($this->sectionDescription('baseline', 'Tune baseline drift severity mapping, alert threshold, and stale-finding auto-close behavior.'))
                    ->columns(2)
                    ->afterHeader([
                        $this->makeResetAction('baseline_severity_mapping')->label('Reset mapping')->size('sm'),
                        $this->makeResetAction('baseline_alert_min_severity')->label('Reset threshold')->size('sm'),
                        $this->makeResetAction('baseline_auto_close_enabled')->label('Reset auto-close')->size('sm'),
                    ])
                    ->schema([
                        Select::make('baseline_severity_missing_policy')
                            ->label('Missing policy severity')
                            ->options(self::severityOptions())
                            ->placeholder('Unset (uses default)')
                            ->native(false)
                            ->disabled(fn (): bool => ! $this->currentUserCanManage())
                            ->helperText(fn (): string => $this->baselineSeverityFieldHelperText('missing_policy')),
                        Select::make('baseline_severity_different_version')
                            ->label('Different version severity')
                            ->options(self::severityOptions())
                            ->placeholder('Unset (uses default)')
                            ->native(false)
                            ->disabled(fn (): bool => ! $this->currentUserCanManage())
                            ->helperText(fn (): string => $this->baselineSeverityFieldHelperText('different_version')),
                        Select::make('baseline_severity_unexpected_policy')
                            ->label('Unexpected policy severity')
                            ->options(self::severityOptions())
                            ->placeholder('Unset (uses default)')
                            ->native(false)
                            ->disabled(fn (): bool => ! $this->currentUserCanManage())
                            ->helperText(fn (): string => $this->baselineSeverityFieldHelperText('unexpected_policy')),
                        Select::make('baseline_alert_min_severity')
                            ->label('Minimum alert severity')
                            ->options(self::severityOptions())
                            ->placeholder('Unset (uses default)')
                            ->native(false)
                            ->disabled(fn (): bool => ! $this->currentUserCanManage())
                            ->helperText(fn (): string => $this->helperTextFor('baseline_alert_min_severity')),
                        Select::make('baseline_auto_close_enabled')
                            ->label('Auto-close stale drift')
                            ->options(self::booleanOptions())
                            ->placeholder('Unset (uses default)')
                            ->native(false)
                            ->disabled(fn (): bool => ! $this->currentUserCanManage())
                            ->helperText(fn (): string => $this->helperTextFor('baseline_auto_close_enabled')),
                    ]),
                Section::make('Findings settings')
                    ->key('findings_section')
                    ->description($this->sectionDescription('findings', 'Configure workspace-wide SLA days by severity. Set one or more, or leave all empty to use the system default. Unset severities use their default.'))
                    ->columns(2)
                    ->afterHeader([
                        $this->makeResetAction('findings_sla_days')->label('Reset all SLA')->size('sm'),
                    ])
                    ->schema([
                        TextInput::make('findings_sla_critical')
                            ->label('Critical severity')
                            ->placeholder('Unset (uses default)')
                            ->suffix('days')
                            ->hint('1 – 3,650')
                            ->numeric()
                            ->integer()
                            ->minValue(1)
                            ->maxValue(3650)
                            ->disabled(fn (): bool => ! $this->currentUserCanManage())
                            ->helperText(fn (): string => $this->slaFieldHelperText('critical')),
                        TextInput::make('findings_sla_high')
                            ->label('High severity')
                            ->placeholder('Unset (uses default)')
                            ->suffix('days')
                            ->hint('1 – 3,650')
                            ->numeric()
                            ->integer()
                            ->minValue(1)
                            ->maxValue(3650)
                            ->disabled(fn (): bool => ! $this->currentUserCanManage())
                            ->helperText(fn (): string => $this->slaFieldHelperText('high')),
                        TextInput::make('findings_sla_medium')
                            ->label('Medium severity')
                            ->placeholder('Unset (uses default)')
                            ->suffix('days')
                            ->hint('1 – 3,650')
                            ->numeric()
                            ->integer()
                            ->minValue(1)
                            ->maxValue(3650)
                            ->disabled(fn (): bool => ! $this->currentUserCanManage())
                            ->helperText(fn (): string => $this->slaFieldHelperText('medium')),
                        TextInput::make('findings_sla_low')
                            ->label('Low severity')
                            ->placeholder('Unset (uses default)')
                            ->suffix('days')
                            ->hint('1 – 3,650')
                            ->numeric()
                            ->integer()
                            ->minValue(1)
                            ->maxValue(3650)
                            ->disabled(fn (): bool => ! $this->currentUserCanManage())
                            ->helperText(fn (): string => $this->slaFieldHelperText('low')),
                    ]),
                Section::make('Operations settings')
                    ->description($this->sectionDescription('operations', 'Workspace controls for operations retention and thresholds.'))
                    ->schema([
                        TextInput::make('operations_operation_run_retention_days')
                            ->label('Operation run retention')
                            ->placeholder('Unset (uses default)')
                            ->suffix('days')
                            ->hint('7 – 3,650')
                            ->numeric()
                            ->integer()
                            ->minValue(7)
                            ->maxValue(3650)
                            ->disabled(fn (): bool => ! $this->currentUserCanManage())
                            ->helperText(fn (): string => $this->helperTextFor('operations_operation_run_retention_days'))
                            ->hintAction($this->makeResetAction('operations_operation_run_retention_days')),
                        TextInput::make('operations_stuck_run_threshold_minutes')
                            ->label('Stuck run threshold')
                            ->placeholder('Unset (uses default)')
                            ->suffix('minutes')
                            ->hint('0 – 10,080')
                            ->numeric()
                            ->integer()
                            ->minValue(0)
                            ->maxValue(10080)
                            ->disabled(fn (): bool => ! $this->currentUserCanManage())
                            ->helperText(fn (): string => $this->helperTextFor('operations_stuck_run_threshold_minutes'))
                            ->hintAction($this->makeResetAction('operations_stuck_run_threshold_minutes')),
                    ]),
            ]);
    }

    public function save(): void
    {
        $user = auth()->user();

        if (! $user instanceof User) {
            abort(403);
        }

        $this->authorizeWorkspaceManage($user);

        $this->resetValidation();

        $this->composeBaselineSeveritySubFieldsIntoData();
        $this->composeSlaSubFieldsIntoData();

        [$normalizedValues, $validationErrors] = $this->normalizedInputValues();

        if ($validationErrors !== []) {
            throw ValidationException::withMessages($validationErrors);
        }

        $writer = app(SettingsWriter::class);
        $changedSettingsCount = 0;

        foreach (self::SETTING_FIELDS as $field => $setting) {
            $incomingValue = $normalizedValues[$field] ?? null;
            $currentOverride = $this->workspaceOverrideForField($field);

            if ($incomingValue === null) {
                if ($currentOverride === null) {
                    continue;
                }

                $writer->resetWorkspaceSetting(
                    actor: $user,
                    workspace: $this->workspace,
                    domain: $setting['domain'],
                    key: $setting['key'],
                );

                $changedSettingsCount++;

                continue;
            }

            if ($this->valuesEqual($incomingValue, $currentOverride)) {
                continue;
            }

            $writer->updateWorkspaceSetting(
                actor: $user,
                workspace: $this->workspace,
                domain: $setting['domain'],
                key: $setting['key'],
                value: $incomingValue,
            );

            $changedSettingsCount++;
        }

        $this->loadFormState();

        Notification::make()
            ->title($changedSettingsCount > 0 ? __('localization.notifications.workspace_settings_saved') : __('localization.notifications.workspace_settings_unchanged'))
            ->success()
            ->send();
    }

    public function resetSetting(string $field): void
    {
        $user = auth()->user();

        if (! $user instanceof User) {
            abort(403);
        }

        $this->authorizeWorkspaceManage($user);

        $setting = $this->settingForField($field);

        if ($this->workspaceOverrideForField($field) === null) {
            Notification::make()
                ->title(__('localization.notifications.setting_already_default'))
                ->success()
                ->send();

            return;
        }

        app(SettingsWriter::class)->resetWorkspaceSetting(
            actor: $user,
            workspace: $this->workspace,
            domain: $setting['domain'],
            key: $setting['key'],
        );

        $this->loadFormState();

        Notification::make()
            ->title(__('localization.notifications.workspace_setting_reset'))
            ->success()
            ->send();
    }

    private function resetEntitlementOverridePair(string $field): void
    {
        $user = auth()->user();

        if (! $user instanceof User) {
            abort(403);
        }

        $this->authorizeWorkspaceManage($user);

        if (! $this->hasEntitlementOverridePair($field)) {
            Notification::make()
                ->title('Entitlement already uses plan profile default')
                ->success()
                ->send();

            return;
        }

        $writer = app(SettingsWriter::class);
        $valueSetting = $this->settingForField($field);
        $reasonField = self::ENTITLEMENT_OVERRIDE_REASON_FIELDS[$field];
        $reasonSetting = $this->settingForField($reasonField);

        if ($this->workspaceOverrideForField($field) !== null) {
            $writer->resetWorkspaceSetting(
                actor: $user,
                workspace: $this->workspace,
                domain: $valueSetting['domain'],
                key: $valueSetting['key'],
            );
        }

        if ($this->workspaceOverrideForField($reasonField) !== null) {
            $writer->resetWorkspaceSetting(
                actor: $user,
                workspace: $this->workspace,
                domain: $reasonSetting['domain'],
                key: $reasonSetting['key'],
            );
        }

        $this->loadFormState();

        Notification::make()
            ->title('Workspace entitlement override reset')
            ->success()
            ->send();
    }

    private function loadFormState(): void
    {
        $resolver = app(SettingsResolver::class);

        $data = [];
        $workspaceOverrides = [];
        $resolvedSettings = [];

        foreach (self::SETTING_FIELDS as $field => $setting) {
            $resolved = $resolver->resolveDetailed(
                workspace: $this->workspace,
                domain: $setting['domain'],
                key: $setting['key'],
            );

            $workspaceValue = $resolved['workspace_value'];

            $workspaceOverrides[$field] = $workspaceValue;
            $resolvedSettings[$field] = [
                'source' => $resolved['source'],
                'value' => $resolved['value'],
                'system_default' => $resolved['system_default'],
            ];

            $data[$field] = $workspaceValue === null
                ? (in_array($field, self::KEYVALUE_FIELDS, true) ? [] : null)
                : $this->formatValueForInput($field, $workspaceValue);
        }

        $this->decomposeBaselineSeveritySubFields($data, $workspaceOverrides);
        $this->decomposeSlaSubFields($data, $workspaceOverrides, $resolvedSettings);

        $this->data = $data;
        $this->workspaceOverrides = $workspaceOverrides;
        $this->resolvedSettings = $resolvedSettings;
        $this->entitlementSummary = app(WorkspaceEntitlementResolver::class)->summary($this->workspace);
        $this->commercialLifecycleSummary = app(WorkspaceCommercialLifecycleResolver::class)->summary($this->workspace);

        $this->loadDomainLastModified();
    }

    /**
     * Load per-domain "last modified" metadata from workspace_settings.
     */
    private function loadDomainLastModified(): void
    {
        $domains = array_unique(array_column(self::SETTING_FIELDS, 'domain'));

        $records = WorkspaceSetting::query()
            ->where('workspace_id', (int) $this->workspace->getKey())
            ->whereIn('domain', $domains)
            ->whereNotNull('updated_by_user_id')
            ->with('updatedByUser:id,name')
            ->get();

        $domainInfo = [];

        foreach ($records as $record) {
            /** @var WorkspaceSetting $record */
            $domain = $record->domain;
            $updatedAt = $record->updated_at;

            if (! $updatedAt instanceof Carbon) {
                continue;
            }

            if (isset($domainInfo[$domain]) && $domainInfo[$domain]['updated_at']->gte($updatedAt)) {
                continue;
            }

            $user = $record->updatedByUser;

            $domainInfo[$domain] = [
                'user_name' => $user instanceof User ? $user->name : 'Unknown',
                'updated_at' => $updatedAt,
            ];
        }

        $this->domainLastModified = $domainInfo;
    }

    /**
     * @return array<Action>
     */
    private function supportAccessApprovalActions(): array
    {
        $grant = $this->currentPendingSupportAccessGrant();

        if (! $grant instanceof SupportAccessGrant) {
            return [];
        }

        return [
            Action::make('approve_support_access')
                ->label('Approve recovery access')
                ->color('success')
                ->requiresConfirmation()
                ->modalHeading('Approve recovery support access')
                ->modalDescription('This activates a time-limited workspace recovery support grant. Owner repair will still require active break-glass mode.')
                ->visible(fn (): bool => $this->currentUserCanApproveSupportAccess())
                ->action(function (): void {
                    $this->approveSupportAccess();
                }),
            Action::make('deny_support_access')
                ->label('Deny request')
                ->color('danger')
                ->requiresConfirmation()
                ->modalHeading('Deny recovery support access')
                ->modalDescription('This denies the pending recovery support request and keeps owner repair blocked for this workspace.')
                ->visible(fn (): bool => $this->currentUserCanApproveSupportAccess())
                ->action(function (): void {
                    $this->denySupportAccess();
                }),
        ];
    }

    public function approveSupportAccess(): void
    {
        $user = auth()->user();
        $grant = $this->currentPendingSupportAccessGrant();

        if (! $user instanceof User || ! $grant instanceof SupportAccessGrant) {
            abort(404);
        }

        app(SupportAccessGrantManager::class)->approve($grant, $user);
        $this->loadFormState();

        Notification::make()
            ->title('Recovery access approved')
            ->success()
            ->send();
    }

    public function denySupportAccess(): void
    {
        $user = auth()->user();
        $grant = $this->currentPendingSupportAccessGrant();

        if (! $user instanceof User || ! $grant instanceof SupportAccessGrant) {
            abort(404);
        }

        app(SupportAccessGrantManager::class)->deny($grant, $user);
        $this->loadFormState();

        Notification::make()
            ->title('Recovery access denied')
            ->success()
            ->send();
    }

    private function supportAccessPostureText(): string
    {
        $summary = app(SupportAccessGrantResolver::class)->summaryFor($this->workspace);

        if (($summary['grant_id'] ?? null) === null) {
            return 'No active or pending support access is recorded for this workspace.';
        }

        $parts = [
            (string) $summary['status_label'],
            (string) $summary['scope_label'],
            'requested by '.(string) $summary['requester_label'],
            'TTL '.$summary['requested_ttl_label'],
        ];

        if (is_string($summary['expires_label'] ?? null)) {
            $parts[] = 'expires '.$summary['expires_label'];
        }

        return implode(' · ', array_filter($parts));
    }

    private function pendingSupportAccessRequestsText(): string
    {
        $requests = app(SupportAccessGrantResolver::class)->pendingRecoveryRequestsFor($this->workspace);

        if ($requests->isEmpty()) {
            return 'No workspace recovery request is waiting for owner approval.';
        }

        return $requests
            ->map(fn (SupportAccessGrant $grant): string => sprintf(
                '#%d · %s · %s · %d minutes',
                (int) $grant->getKey(),
                $grant->requester?->name ?? $grant->requester?->email ?? 'Platform support',
                (string) $grant->reason,
                (int) $grant->ttl_minutes,
            ))
            ->implode("\n");
    }

    private function currentPendingSupportAccessGrant(): ?SupportAccessGrant
    {
        return app(SupportAccessGrantResolver::class)
            ->pendingRecoveryRequestsFor($this->workspace)
            ->first();
    }

    /**
     * Build a section description that appends "last modified" info when available.
     */
    private function sectionDescription(string $domain, string $baseDescription): string
    {
        $meta = $this->domainLastModified[$domain] ?? null;

        if (! is_array($meta)) {
            return $baseDescription;
        }

        /** @var Carbon $updatedAt */
        $updatedAt = $meta['updated_at'];

        return __('localization.workspace.last_modified_by', [
            'description' => $baseDescription,
            'user' => $meta['user_name'],
            'time' => $updatedAt->diffForHumans(),
        ]);
    }

    private function makeResetAction(string $field): Action
    {
        return Action::make('reset_'.$field)
            ->label(__('localization.workspace.reset'))
            ->color('danger')
            ->requiresConfirmation()
            ->action(function () use ($field): void {
                if ($this->isEntitlementOverrideValueField($field)) {
                    $this->resetEntitlementOverridePair($field);

                    return;
                }

                $this->resetSetting($field);
            })
            ->disabled(fn (): bool => ! $this->currentUserCanManage() || ! $this->canResetField($field))
            ->tooltip(function () use ($field): ?string {
                if (! $this->currentUserCanManage()) {
                    return __('localization.workspace.no_manage_permission');
                }

                if (! $this->canResetField($field)) {
                    if ($this->isEntitlementOverrideValueField($field)) {
                        return __('localization.workspace.no_workspace_override');
                    }

                    return __('localization.workspace.no_workspace_override');
                }

                return null;
            });
    }

    private function canResetField(string $field): bool
    {
        if ($this->isEntitlementOverrideValueField($field)) {
            return $this->hasEntitlementOverridePair($field);
        }

        return $this->hasWorkspaceOverride($field);
    }

    private function isEntitlementOverrideValueField(string $field): bool
    {
        return array_key_exists($field, self::ENTITLEMENT_OVERRIDE_REASON_FIELDS);
    }

    private function hasEntitlementOverridePair(string $field): bool
    {
        if (! $this->isEntitlementOverrideValueField($field)) {
            return false;
        }

        $reasonField = self::ENTITLEMENT_OVERRIDE_REASON_FIELDS[$field];

        return $this->workspaceOverrideForField($field) !== null
            || $this->workspaceOverrideForField($reasonField) !== null;
    }

    private function planProfileFieldHelperText(): string
    {
        $profile = $this->resolvedPlanProfile();
        $selectedProfile = $this->workspaceOverrideForField('entitlements_plan_profile');

        if (! is_string($selectedProfile) || $selectedProfile === '') {
            return sprintf('Default profile: %s. %s', $profile['label'], $profile['description']);
        }

        return sprintf('Effective profile: %s. %s', $profile['label'], $profile['description']);
    }

    private function managedTenantLimitHelperText(): string
    {
        $decision = $this->entitlementDecision(WorkspaceEntitlementResolver::KEY_MANAGED_TENANT_ACTIVATION_LIMIT);
        $effectiveValue = (int) ($decision['effective_value'] ?? 0);
        $currentUsage = (int) ($decision['current_usage'] ?? 0);
        $remainingCapacity = (int) ($decision['remaining_capacity'] ?? 0);

        $capacityText = $remainingCapacity < 0
            ? sprintf('Over limit by %d.', abs($remainingCapacity))
            : sprintf('%d remaining.', $remainingCapacity);

        return sprintf(
            'Effective limit: %d active managed tenants. Current usage: %d. %s Source: %s.',
            $effectiveValue,
            $currentUsage,
            $capacityText,
            $this->entitlementSourceLabel($decision),
        );
    }

    private function managedTenantLimitReasonHelperText(): string
    {
        return $this->entitlementReasonHelperText(
            valueField: 'entitlements_managed_tenant_limit_override_value',
            key: WorkspaceEntitlementResolver::KEY_MANAGED_TENANT_ACTIVATION_LIMIT,
        );
    }

    private function reviewPackGenerationHelperText(): string
    {
        $decision = $this->entitlementDecision(WorkspaceEntitlementResolver::KEY_REVIEW_PACK_GENERATION_ENABLED);

        return sprintf(
            'Effective state: %s. Source: %s.',
            (bool) ($decision['effective_value'] ?? false) ? 'enabled' : 'disabled',
            $this->entitlementSourceLabel($decision),
        );
    }

    private function reviewPackGenerationReasonHelperText(): string
    {
        return $this->entitlementReasonHelperText(
            valueField: 'entitlements_review_pack_generation_override_value',
            key: WorkspaceEntitlementResolver::KEY_REVIEW_PACK_GENERATION_ENABLED,
        );
    }

    private function aiPolicyModeHelperText(): string
    {
        $resolved = $this->resolvedSettings['ai_policy_mode'] ?? null;

        if (! is_array($resolved)) {
            return '';
        }

        $mode = AiPolicyMode::tryFrom((string) ($resolved['value'] ?? AiPolicyMode::Disabled->value))
            ?? AiPolicyMode::Disabled;

        $prefix = ! $this->hasWorkspaceOverride('ai_policy_mode')
            ? sprintf('Effective posture: %s. Source: %s.', $mode->label(), $this->sourceLabel((string) ($resolved['source'] ?? 'system_default')))
            : sprintf('Effective posture: %s.', $mode->label());

        return sprintf('%s %s', $prefix, $mode->summary());
    }

    private function aiApprovedUseCasesText(): string
    {
        return implode('; ', app(AiUseCaseCatalog::class)->labels()).'.';
    }

    private function aiAllowedProviderClassesText(): string
    {
        $labels = app(AiUseCaseCatalog::class)->allowedProviderClassLabelsForMode($this->effectiveAiPolicyMode());

        if ($labels === []) {
            return 'No provider classes are allowed while AI is disabled.';
        }

        return implode(', ', $labels).'.';
    }

    private function aiBlockedDataClassificationsText(): string
    {
        return implode(', ', app(AiUseCaseCatalog::class)->blockedDataClassificationLabels()).'.';
    }

    private function effectiveAiPolicyMode(): AiPolicyMode
    {
        $resolved = $this->resolvedSettings['ai_policy_mode'] ?? null;

        if (! is_array($resolved)) {
            return AiPolicyMode::Disabled;
        }

        return AiPolicyMode::tryFrom((string) ($resolved['value'] ?? AiPolicyMode::Disabled->value))
            ?? AiPolicyMode::Disabled;
    }

    private function entitlementReasonHelperText(string $valueField, string $key): string
    {
        $decision = $this->entitlementDecision($key);
        $rationale = is_string($decision['rationale'] ?? null) ? $decision['rationale'] : null;

        if ($this->workspaceOverrideForField($valueField) === null) {
            return 'Required when an explicit override value is set.';
        }

        if ($rationale === null || $rationale === '') {
            return 'Required when an explicit override value is set.';
        }

        return sprintf('Current rationale: %s', $rationale);
    }

    /**
     * @return array{id: string, label: string, description: string, managed_tenant_limit_default: int, review_pack_generation_default: bool, is_default: bool}
     */
    private function resolvedPlanProfile(): array
    {
        $profile = $this->entitlementSummary['plan_profile'] ?? null;

        if (is_array($profile)) {
            return $profile;
        }

        return app(WorkspacePlanProfileCatalog::class)->default();
    }

    /**
     * @return array<string, mixed>
     */
    private function entitlementDecision(string $key): array
    {
        $decision = $this->entitlementSummary['decisions'][$key] ?? null;

        return is_array($decision) ? $decision : [];
    }

    /**
     * @param  array<string, mixed>  $decision
     */
    private function entitlementSourceLabel(array $decision): string
    {
        if (($decision['source'] ?? null) === 'workspace_override') {
            return 'workspace override';
        }

        $planProfileLabel = $decision['plan_profile_label'] ?? null;

        if (is_string($planProfileLabel) && $planProfileLabel !== '') {
            return sprintf('%s plan profile', $planProfileLabel);
        }

        return 'plan profile default';
    }

    private function commercialPostureSourceText(): string
    {
        return ($this->commercialLifecycleSummary['fallback_status'] ?? true) ? 'fallback-backed' : 'subscription-backed';
    }

    private function commercialPostureStateText(): string
    {
        return (string) ($this->commercialLifecycleSummary['subscription_state_label']
            ?? $this->commercialLifecycleSummary['state_label']
            ?? 'Active paid');
    }

    private function commercialPostureTimingText(): string
    {
        $label = $this->commercialLifecycleSummary['subscription_key_date_label'] ?? null;
        $date = $this->commercialLifecycleSummary['subscription_key_date'] ?? null;

        if (is_string($label) && $label !== '' && $date instanceof Carbon) {
            return sprintf('%s: %s', $label, $date->toDayDateTimeString());
        }

        return 'No scheduled commercial date recorded.';
    }

    private function commercialPostureReasonText(): string
    {
        $reason = $this->commercialLifecycleSummary['rationale'] ?? null;

        if (is_string($reason) && $reason !== '') {
            return $reason;
        }

        return ($this->commercialLifecycleSummary['fallback_status'] ?? true)
            ? 'No current subscription record is stored. The workspace is using fallback lifecycle truth.'
            : 'No explicit commercial explanation recorded.';
    }

    private function helperTextFor(string $field): string
    {
        $resolved = $this->resolvedSettings[$field] ?? null;

        if (! is_array($resolved)) {
            return '';
        }

        $effectiveValue = $this->formatValueForDisplay($field, $resolved['value'] ?? null);

        if (! $this->hasWorkspaceOverride($field)) {
            return sprintf(
                'Unset. Effective value: %s (%s).',
                $effectiveValue,
                $this->sourceLabel((string) ($resolved['source'] ?? 'system_default')),
            );
        }

        return sprintf('Effective value: %s.', $effectiveValue);
    }

    private function localeDefaultHelperText(): string
    {
        $resolved = $this->resolvedSettings['localization_default_locale'] ?? null;

        if (! is_array($resolved)) {
            return '';
        }

        $effectiveLocale = LocaleResolver::normalize($resolved['value'] ?? null) ?? 'en';
        $localeLabel = LocaleResolver::localeOptions()[$effectiveLocale] ?? strtoupper($effectiveLocale);

        if (! $this->hasWorkspaceOverride('localization_default_locale')) {
            return __('localization.workspace.default_locale_helper_unset', [
                'locale' => $localeLabel,
                'source' => $this->sourceLabel((string) ($resolved['source'] ?? 'system_default')),
            ]);
        }

        return __('localization.workspace.default_locale_helper_set', [
            'locale' => $localeLabel,
        ]);
    }

    private function slaFieldHelperText(string $severity): string
    {
        $resolved = $this->resolvedSettings['findings_sla_days'] ?? null;

        if (! is_array($resolved)) {
            return '';
        }

        $effectiveValue = is_array($resolved['value'] ?? null)
            ? (int) ($resolved['value'][$severity] ?? 0)
            : 0;

        $systemDefault = is_array($resolved['system_default'] ?? null)
            ? (int) ($resolved['system_default'][$severity] ?? 0)
            : 0;

        if (! $this->hasWorkspaceOverride('findings_sla_days')) {
            return sprintf('Default: %d days.', $systemDefault);
        }

        return sprintf('Effective: %d days.', $effectiveValue);
    }

    private function baselineSeverityFieldHelperText(string $changeType): string
    {
        $resolved = $this->resolvedSettings['baseline_severity_mapping'] ?? null;

        if (! is_array($resolved)) {
            return '';
        }

        $effectiveValue = is_array($resolved['value'] ?? null)
            ? (string) ($resolved['value'][$changeType] ?? '')
            : '';

        $systemDefault = is_array($resolved['system_default'] ?? null)
            ? (string) ($resolved['system_default'][$changeType] ?? '')
            : '';

        if (! $this->hasWorkspaceOverride('baseline_severity_mapping')) {
            return sprintf('Default: %s.', $systemDefault);
        }

        if (
            is_array($this->workspaceOverrideForField('baseline_severity_mapping'))
            && array_key_exists($changeType, $this->workspaceOverrideForField('baseline_severity_mapping'))
        ) {
            return sprintf('Effective: %s.', $effectiveValue);
        }

        return sprintf('Unset. Effective value: %s (system default).', $effectiveValue);
    }

    /**
     * @return array{0: array<string, mixed>, 1: array<string, array<int, string>>}
     */
    private function normalizedInputValues(): array
    {
        $normalizedValues = [];
        $validationErrors = [];

        foreach (self::SETTING_FIELDS as $field => $_setting) {
            try {
                $normalizedValues[$field] = $this->normalizeFieldInput(
                    field: $field,
                    value: $this->data[$field] ?? null,
                );
            } catch (ValidationException $exception) {
                $messages = [];

                foreach ($exception->errors() as $errorMessages) {
                    foreach ((array) $errorMessages as $message) {
                        $messages[] = (string) $message;
                    }
                }

                if ($field === 'findings_sla_days') {
                    $severityToField = array_flip(self::SLA_SUB_FIELDS);

                    $targeted = false;

                    foreach ($messages as $message) {
                        if (preg_match('/include "(?<severity>critical|high|medium|low)"/i', $message, $matches) === 1) {
                            $severity = strtolower((string) $matches['severity']);
                            $subField = $severityToField[$severity] ?? null;

                            if (is_string($subField)) {
                                $validationErrors['data.'.$subField] ??= [];
                                $validationErrors['data.'.$subField][] = $message;
                                $targeted = true;
                            }
                        }
                    }

                    if (! $targeted) {
                        foreach (self::SLA_SUB_FIELDS as $subField => $_severity) {
                            $validationErrors['data.'.$subField] = $messages !== []
                                ? $messages
                                : ['Invalid value.'];
                        }
                    }

                    continue;
                }

                if ($field === 'baseline_severity_mapping') {
                    foreach (array_keys(self::BASELINE_MAPPING_SUB_FIELDS) as $subField) {
                        $validationErrors['data.'.$subField] = $messages !== []
                            ? $messages
                            : ['Invalid value.'];
                    }

                    continue;
                }

                $validationErrors['data.'.$field] = $messages !== []
                    ? $messages
                    : ['Invalid value.'];
            }
        }

        foreach (self::ENTITLEMENT_OVERRIDE_REASON_FIELDS as $valueField => $reasonField) {
            if (($normalizedValues[$valueField] ?? null) === null) {
                $normalizedValues[$reasonField] = null;

                continue;
            }

            if (($normalizedValues[$reasonField] ?? null) !== null) {
                continue;
            }

            $message = match ($valueField) {
                'entitlements_managed_tenant_limit_override_value' => 'Override reason is required when a managed tenant activation limit override is set.',
                'entitlements_review_pack_generation_override_value' => 'Override reason is required when a review pack generation override is set.',
                default => 'Override reason is required when an explicit override is set.',
            };

            $validationErrors['data.'.$reasonField] ??= [];
            $validationErrors['data.'.$reasonField][] = $message;
        }

        return [$normalizedValues, $validationErrors];
    }

    private function normalizeFieldInput(string $field, mixed $value): mixed
    {
        $setting = $this->settingForField($field);

        if ($value === null) {
            return null;
        }

        if (is_string($value) && trim($value) === '') {
            return null;
        }

        if (is_array($value) && $value === []) {
            return null;
        }

        if ($setting['type'] === 'json') {
            $value = $this->normalizeJsonInput($value);

            if (in_array($field, self::KEYVALUE_FIELDS, true)) {
                $value = $this->normalizeKeyValueInput($value);

                if ($value === []) {
                    return null;
                }
            }
        }

        $definition = $this->settingDefinition($field);

        $validator = Validator::make(
            data: ['value' => $value],
            rules: ['value' => $definition->rules],
        );

        if ($validator->fails()) {
            throw ValidationException::withMessages($validator->errors()->toArray());
        }

        return $definition->normalize($validator->validated()['value']);
    }

    /**
     * Normalize KeyValue component state.
     *
     * Filament's KeyValue UI keeps an empty row by default, which can submit as
     * ['' => ''] and would otherwise fail validation. We treat empty rows as unset.
     *
     * @param  array<mixed>  $value
     * @return array<string, mixed>
     */
    private function normalizeKeyValueInput(array $value): array
    {
        $normalized = [];

        foreach ($value as $key => $item) {
            if (is_array($item) && array_key_exists('key', $item)) {
                $rowKey = $item['key'];
                $rowValue = $item['value'] ?? null;

                if (! is_string($rowKey)) {
                    continue;
                }

                $trimmedKey = trim($rowKey);

                if ($trimmedKey === '') {
                    continue;
                }

                if (is_string($rowValue)) {
                    $trimmedValue = trim($rowValue);

                    if ($trimmedValue === '') {
                        continue;
                    }

                    $normalized[$trimmedKey] = $trimmedValue;

                    continue;
                }

                if ($rowValue === null) {
                    continue;
                }

                $normalized[$trimmedKey] = $rowValue;

                continue;
            }

            if (! is_string($key)) {
                continue;
            }

            $trimmedKey = trim($key);

            if ($trimmedKey === '') {
                continue;
            }

            if (is_string($item)) {
                $trimmedValue = trim($item);

                if ($trimmedValue === '') {
                    continue;
                }

                $normalized[$trimmedKey] = $trimmedValue;

                continue;
            }

            if ($item === null) {
                continue;
            }

            $normalized[$trimmedKey] = $item;
        }

        return $normalized;
    }

    private function normalizeJsonInput(mixed $value): array
    {
        if (is_array($value)) {
            return $value;
        }

        if (! is_string($value)) {
            throw ValidationException::withMessages([
                'value' => ['The value must be valid JSON.'],
            ]);
        }

        $decoded = json_decode($value, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            throw ValidationException::withMessages([
                'value' => ['The value must be valid JSON.'],
            ]);
        }

        if (! is_array($decoded)) {
            throw ValidationException::withMessages([
                'value' => ['The value must be a JSON object.'],
            ]);
        }

        return $decoded;
    }

    private function valuesEqual(mixed $left, mixed $right): bool
    {
        if ($left === null || $right === null) {
            return $left === $right;
        }

        if (is_array($left) && is_array($right)) {
            return $this->encodeCanonicalArray($left) === $this->encodeCanonicalArray($right);
        }

        if (is_numeric($left) && is_numeric($right)) {
            return (int) $left === (int) $right;
        }

        return $left === $right;
    }

    private function encodeCanonicalArray(array $value): string
    {
        $encoded = json_encode($this->sortNestedArray($value));

        return is_string($encoded) ? $encoded : '';
    }

    /**
     * @param  array<mixed>  $value
     * @return array<mixed>
     */
    private function sortNestedArray(array $value): array
    {
        foreach ($value as $key => $item) {
            if (! is_array($item)) {
                continue;
            }

            $value[$key] = $this->sortNestedArray($item);
        }

        ksort($value);

        return $value;
    }

    private function formatValueForInput(string $field, mixed $value): mixed
    {
        $setting = $this->settingForField($field);

        if ($setting['type'] === 'json') {
            if (! is_array($value)) {
                return null;
            }

            if (in_array($field, self::KEYVALUE_FIELDS, true)) {
                return $value;
            }

            $encoded = json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);

            return is_string($encoded) ? $encoded : null;
        }

        if ($setting['type'] === 'string') {
            return is_string($value) ? $value : null;
        }

        if ($setting['type'] === 'bool') {
            if (is_bool($value)) {
                return $value ? '1' : '0';
            }

            return is_numeric($value) ? (string) (int) $value : null;
        }

        return is_numeric($value) ? (int) $value : null;
    }

    private function formatValueForDisplay(string $field, mixed $value): string
    {
        $setting = $this->settingForField($field);

        if ($setting['type'] === 'json') {
            if (! is_array($value) || $value === []) {
                return '{}';
            }

            $encoded = json_encode($value, JSON_UNESCAPED_SLASHES);

            return is_string($encoded) ? $encoded : '{}';
        }

        if ($setting['type'] === 'string') {
            return is_string($value) && $value !== '' ? $value : 'null';
        }

        if ($setting['type'] === 'bool') {
            if (is_bool($value)) {
                return $value ? 'enabled' : 'disabled';
            }

            return 'null';
        }

        return is_numeric($value) ? (string) (int) $value : 'null';
    }

    private function sourceLabel(string $source): string
    {
        return match ($source) {
            'workspace_override' => __('localization.source.workspace_override'),
            'tenant_override' => 'tenant override',
            default => __('localization.source.system_default'),
        };
    }

    /**
     * @return array{domain: string, key: string, type: 'int'|'json'|'string'|'bool'}
     */
    private function settingForField(string $field): array
    {
        if (! isset(self::SETTING_FIELDS[$field])) {
            throw ValidationException::withMessages([
                'data' => [sprintf('Unknown settings field: %s', $field)],
            ]);
        }

        return self::SETTING_FIELDS[$field];
    }

    private function settingDefinition(string $field): SettingDefinition
    {
        $setting = $this->settingForField($field);

        return app(SettingsRegistry::class)->require($setting['domain'], $setting['key']);
    }

    private function hasWorkspaceOverride(string $field): bool
    {
        return $this->workspaceOverrideForField($field) !== null;
    }

    private function workspaceOverrideForField(string $field): mixed
    {
        return $this->workspaceOverrides[$field] ?? null;
    }

    /**
     * Decompose the baseline severity mapping JSON setting into explicit change-type sub-fields.
     *
     * @param  array<string, mixed>  $data
     * @param  array<string, mixed>  $workspaceOverrides
     */
    private function decomposeBaselineSeveritySubFields(array &$data, array &$workspaceOverrides): void
    {
        $mappingOverride = $workspaceOverrides['baseline_severity_mapping'] ?? null;

        foreach (self::BASELINE_MAPPING_SUB_FIELDS as $subField => $changeType) {
            $data[$subField] = is_array($mappingOverride) && isset($mappingOverride[$changeType])
                ? (string) $mappingOverride[$changeType]
                : null;
        }
    }

    /**
     * Decompose the findings_sla_days JSON setting into individual SLA sub-fields.
     *
     * @param  array<string, mixed>  $data
     * @param  array<string, mixed>  $workspaceOverrides
     * @param  array<string, array{source: string, value: mixed, system_default: mixed}>  $resolvedSettings
     */
    private function decomposeSlaSubFields(array &$data, array &$workspaceOverrides, array &$resolvedSettings): void
    {
        $slaOverride = $workspaceOverrides['findings_sla_days'] ?? null;
        $slaResolved = $resolvedSettings['findings_sla_days'] ?? null;

        foreach (self::SLA_SUB_FIELDS as $subField => $severity) {
            $data[$subField] = is_array($slaOverride) && isset($slaOverride[$severity])
                ? (int) $slaOverride[$severity]
                : null;
        }
    }

    /**
     * Re-compose baseline severity mapping sub-fields back into the baseline_severity_mapping data key before save.
     */
    private function composeBaselineSeveritySubFieldsIntoData(): void
    {
        $values = [];

        foreach (self::BASELINE_MAPPING_SUB_FIELDS as $subField => $changeType) {
            $value = $this->data[$subField] ?? null;

            if (! is_string($value) || trim($value) === '') {
                continue;
            }

            $values[$changeType] = strtolower(trim($value));
        }

        $this->data['baseline_severity_mapping'] = $values !== [] ? $values : null;
    }

    /**
     * Re-compose individual SLA sub-fields back into the findings_sla_days data key before save.
     */
    private function composeSlaSubFieldsIntoData(): void
    {
        $values = [];
        $hasAnyValue = false;

        foreach (self::SLA_SUB_FIELDS as $subField => $severity) {
            $val = $this->data[$subField] ?? null;

            if ($val !== null && (is_string($val) ? trim($val) !== '' : true)) {
                $values[$severity] = (int) $val;
                $hasAnyValue = true;
            }
        }

        $this->data['findings_sla_days'] = $hasAnyValue ? $values : null;
    }

    /**
     * @return array<string, string>
     */
    private static function severityOptions(): array
    {
        return [
            'low' => 'Low',
            'medium' => 'Medium',
            'high' => 'High',
            'critical' => 'Critical',
        ];
    }

    /**
     * @return array<string, string>
     */
    private static function booleanOptions(): array
    {
        return [
            '1' => 'Enabled',
            '0' => 'Disabled',
        ];
    }

    private function currentUserCanManage(): bool
    {
        $user = auth()->user();

        if (! $user instanceof User || ! $this->workspace instanceof Workspace) {
            return false;
        }

        /** @var WorkspaceCapabilityResolver $resolver */
        $resolver = app(WorkspaceCapabilityResolver::class);

        return $resolver->isMember($user, $this->workspace)
            && ! $this->workspace->isClosed()
            && $resolver->can($user, $this->workspace, Capabilities::WORKSPACE_SETTINGS_MANAGE);
    }

    private function currentUserCanApproveSupportAccess(): bool
    {
        $user = auth()->user();

        if (! $user instanceof User || ! $this->workspace instanceof Workspace) {
            return false;
        }

        /** @var WorkspaceCapabilityResolver $resolver */
        $resolver = app(WorkspaceCapabilityResolver::class);

        return $resolver->isMember($user, $this->workspace)
            && ! $this->workspace->isClosed()
            && $resolver->can($user, $this->workspace, Capabilities::WORKSPACE_SETTINGS_MANAGE)
            && $resolver->getRole($user, $this->workspace) === WorkspaceRole::Owner;
    }

    private function authorizeWorkspaceView(User $user): void
    {
        /** @var WorkspaceCapabilityResolver $resolver */
        $resolver = app(WorkspaceCapabilityResolver::class);

        if (! $resolver->isMember($user, $this->workspace)) {
            abort(404);
        }

        if (! $resolver->can($user, $this->workspace, Capabilities::WORKSPACE_SETTINGS_VIEW)) {
            abort(403);
        }
    }

    private function authorizeWorkspaceManage(User $user): void
    {
        /** @var WorkspaceCapabilityResolver $resolver */
        $resolver = app(WorkspaceCapabilityResolver::class);

        if (! $resolver->isMember($user, $this->workspace)) {
            abort(404);
        }

        if (! $resolver->can($user, $this->workspace, Capabilities::WORKSPACE_SETTINGS_MANAGE)) {
            abort(403);
        }

        if ($this->workspace->isClosed()) {
            throw ValidationException::withMessages([
                'workspace' => 'This workspace is closed. Reopen it before changing workspace settings.',
            ]);
        }
    }
}
