<?php

namespace App\Filament\Resources;

use App\Contracts\Hardening\WriteGateInterface;
use App\Exceptions\Hardening\ProviderAccessHardeningRequired;
use App\Filament\Resources\RestoreRunResource\Pages;
use App\Jobs\BulkRestoreRunDeleteJob;
use App\Jobs\BulkRestoreRunForceDeleteJob;
use App\Jobs\BulkRestoreRunRestoreJob;
use App\Jobs\ExecuteRestoreRunJob;
use App\Models\BackupItem;
use App\Models\BackupSet;
use App\Models\EntraGroup;
use App\Models\RestoreRun;
use App\Models\Tenant;
use App\Models\User;
use App\Rules\SkipOrUuidRule;
use App\Services\Auth\CapabilityResolver;
use App\Services\Directory\EntraGroupLabelResolver;
use App\Services\Intune\AuditLogger;
use App\Services\Intune\RestoreDiffGenerator;
use App\Services\Intune\RestoreRiskChecker;
use App\Services\Intune\RestoreService;
use App\Services\OperationRunService;
use App\Services\Operations\BulkSelectionIdentity;
use App\Support\Auth\Capabilities;
use App\Support\Badges\BadgeDomain;
use App\Support\Badges\BadgeRenderer;
use App\Support\Filament\FilterOptionCatalog;
use App\Support\Filament\FilterPresets;
use App\Support\OperationRunLinks;
use App\Support\OpsUx\OperationUxPresenter;
use App\Support\OpsUx\OpsUxBrowserEvents;
use App\Support\Rbac\UiEnforcement;
use App\Support\RestoreRunIdempotency;
use App\Support\RestoreRunStatus;
use BackedEnum;
use Filament\Actions;
use Filament\Actions\ActionGroup;
use Filament\Actions\BulkAction;
use Filament\Actions\BulkActionGroup;
use Filament\Facades\Filament;
use Filament\Forms;
use Filament\Infolists;
use Filament\Notifications\Notification;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Schemas\Components\Wizard\Step;
use Filament\Schemas\Schema;
use Filament\Tables;
use Filament\Tables\Contracts\HasTable;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use UnitEnum;

class RestoreRunResource extends Resource
{
    protected static ?string $model = RestoreRun::class;

    protected static ?string $tenantOwnershipRelationshipName = 'tenant';

    public static function shouldRegisterNavigation(): bool
    {
        if (Filament::getCurrentPanel()?->getId() === 'admin') {
            return false;
        }

        return parent::shouldRegisterNavigation();
    }

    protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-arrow-path-rounded-square';

    protected static string|UnitEnum|null $navigationGroup = 'Backups & Restore';

    public static function canCreate(): bool
    {
        $tenant = Tenant::current();
        $user = auth()->user();

        if (! $tenant instanceof Tenant || ! $user instanceof User) {
            return false;
        }

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

        return $resolver->isMember($user, $tenant)
            && $resolver->can($user, $tenant, Capabilities::TENANT_MANAGE);
    }

    public static function form(Schema $schema): Schema
    {
        return $schema
            ->schema([
                Forms\Components\Select::make('backup_set_id')
                    ->label('Backup set')
                    ->options(function () {
                        $tenantId = Tenant::currentOrFail()->getKey();

                        return BackupSet::query()
                            ->when($tenantId, fn ($query) => $query->where('tenant_id', $tenantId))
                            ->orderByDesc('created_at')
                            ->get()
                            ->mapWithKeys(function (BackupSet $set) {
                                $label = sprintf(
                                    '%s • %s items • %s',
                                    $set->name,
                                    $set->item_count ?? 0,
                                    optional($set->created_at)->format('Y-m-d H:i')
                                );

                                return [$set->id => $label];
                            });
                    })
                    ->reactive()
                    ->afterStateUpdated(function (Set $set): void {
                        $set('scope_mode', 'all');
                        $set('backup_item_ids', null);
                        $set('group_mapping', []);
                        $set('is_dry_run', true);
                    })
                    ->required(),
                Forms\Components\CheckboxList::make('backup_item_ids')
                    ->label('Items to restore (optional)')
                    ->options(fn (Get $get) => static::restoreItemOptionData($get('backup_set_id'))['options'])
                    ->descriptions(fn (Get $get) => static::restoreItemOptionData($get('backup_set_id'))['descriptions'])
                    ->columns(1)
                    ->searchable()
                    ->bulkToggleable()
                    ->reactive()
                    ->afterStateUpdated(fn (Set $set) => $set('group_mapping', []))
                    ->helperText('Search by name, type, or ID. Preview-only types stay in dry-run; leave empty to include all items. Include foundations (scope tags, assignment filters) with policies to re-map IDs.'),
                Section::make('Group mapping')
                    ->description('Some source groups do not exist in the target tenant. Map them or choose Skip.')
                    ->schema(function (Get $get): array {
                        $backupSetId = $get('backup_set_id');
                        $selectedItemIds = $get('backup_item_ids');
                        $tenant = Tenant::current();

                        if (! $tenant || ! $backupSetId) {
                            return [];
                        }

                        $unresolved = static::unresolvedGroups(
                            backupSetId: $backupSetId,
                            selectedItemIds: is_array($selectedItemIds) ? $selectedItemIds : null,
                            tenant: $tenant
                        );

                        $groupCacheQuery = EntraGroup::query()->where('tenant_id', $tenant->getKey());
                        $hasCachedGroups = $groupCacheQuery->exists();

                        $stalenessDays = (int) config('directory_groups.staleness_days', 30);
                        $cutoff = now('UTC')->subDays(max(1, $stalenessDays));
                        $latestSeen = $groupCacheQuery->max('last_seen_at');
                        $isStale = $hasCachedGroups && (! $latestSeen || $latestSeen < $cutoff);

                        $cacheNotice = match (true) {
                            ! $hasCachedGroups => 'No cached groups found. Run "Sync Groups" first.',
                            $isStale => "Cached groups may be stale (>${stalenessDays} days). Consider running \"Sync Groups\".",
                            default => null,
                        };

                        return array_map(function (array $group) use ($cacheNotice): Forms\Components\TextInput {
                            $groupId = $group['id'];
                            $label = $group['label'];

                            return Forms\Components\TextInput::make("group_mapping.{$groupId}")
                                ->label($label)
                                ->placeholder('SKIP or target group Object ID (GUID)')
                                ->rules([new SkipOrUuidRule])
                                ->required()
                                ->suffixAction(
                                    Actions\Action::make('select_from_directory_cache_'.str_replace('-', '_', $groupId))
                                        ->icon('heroicon-o-magnifying-glass')
                                        ->iconButton()
                                        ->tooltip('Select from Directory cache')
                                        ->modalHeading('Select from Directory cache')
                                        ->modalWidth('5xl')
                                        ->modalSubmitAction(false)
                                        ->modalCancelActionLabel('Close')
                                        ->modalContent(fn () => view('filament.modals.entra-group-cache-picker', [
                                            'sourceGroupId' => $groupId,
                                        ]))
                                )
                                ->helperText(fn (): string => ($cacheNotice ? ($cacheNotice.' ') : '').'Paste the target Entra ID group Object ID (GUID). Labels use cached directory groups only (no live Graph lookups). Use SKIP to omit the assignment.')
                                ->hintAction(
                                    Actions\Action::make('open_directory_groups_'.str_replace('-', '_', $groupId))
                                        ->label('Sync Groups')
                                        ->icon('heroicon-o-arrow-path')
                                        ->color('warning')
                                        ->url(fn (): string => EntraGroupResource::getUrl('index', tenant: Tenant::current()))
                                        ->visible(fn (): bool => $cacheNotice !== null)
                                );
                        }, $unresolved);
                    })
                    ->visible(function (Get $get): bool {
                        $backupSetId = $get('backup_set_id');
                        $selectedItemIds = $get('backup_item_ids');
                        $tenant = Tenant::current();

                        if (! $tenant || ! $backupSetId) {
                            return false;
                        }

                        return static::unresolvedGroups(
                            backupSetId: $backupSetId,
                            selectedItemIds: is_array($selectedItemIds) ? $selectedItemIds : null,
                            tenant: $tenant
                        ) !== [];
                    }),
                Forms\Components\Toggle::make('is_dry_run')
                    ->label('Preview only (dry-run)')
                    ->default(true),
            ]);
    }

    public static function makeCreateAction(): Actions\CreateAction
    {
        $action = Actions\CreateAction::make()
            ->label('New restore run');

        UiEnforcement::forAction($action)
            ->requireCapability(Capabilities::TENANT_MANAGE)
            ->apply();

        return $action;
    }

    public static function getEloquentQuery(): Builder
    {
        $tenantId = Tenant::current()?->getKey();

        return parent::getEloquentQuery()
            ->with('backupSet')
            ->when(
                $tenantId !== null,
                fn (Builder $query): Builder => $query->where('tenant_id', (int) $tenantId),
            )
            ->when(
                $tenantId === null,
                fn (Builder $query): Builder => $query->whereRaw('1 = 0'),
            );
    }

    /**
     * @return array<int, Step>
     */
    public static function getWizardSteps(): array
    {
        return [
            Step::make('Select Backup Set')
                ->description('What are we restoring from?')
                ->schema([
                    Forms\Components\Select::make('backup_set_id')
                        ->label('Backup set')
                        ->options(function () {
                            $tenantId = Tenant::currentOrFail()->getKey();

                            return BackupSet::query()
                                ->when($tenantId, fn ($query) => $query->where('tenant_id', $tenantId))
                                ->orderByDesc('created_at')
                                ->get()
                                ->mapWithKeys(function (BackupSet $set) {
                                    $label = sprintf(
                                        '%s • %s items • %s',
                                        $set->name,
                                        $set->item_count ?? 0,
                                        optional($set->created_at)->format('Y-m-d H:i')
                                    );

                                    return [$set->id => $label];
                                });
                        })
                        ->reactive()
                        ->afterStateUpdated(function (Set $set, Get $get): void {
                            $set('scope_mode', 'all');
                            $set('backup_item_ids', null);
                            $set('group_mapping', static::groupMappingPlaceholders(
                                backupSetId: $get('backup_set_id'),
                                scopeMode: 'all',
                                selectedItemIds: null,
                                tenant: Tenant::current(),
                            ));
                            $set('is_dry_run', true);
                            $set('acknowledged_impact', false);
                            $set('tenant_confirm', null);
                            $set('check_summary', null);
                            $set('check_results', []);
                            $set('checks_ran_at', null);
                            $set('preview_summary', null);
                            $set('preview_diffs', []);
                            $set('preview_ran_at', null);
                        })
                        ->required(),
                ]),
            Step::make('Define Restore Scope')
                ->description('What exactly should be restored?')
                ->schema([
                    Forms\Components\Radio::make('scope_mode')
                        ->label('Scope')
                        ->options([
                            'all' => 'All items (default)',
                            'selected' => 'Selected items only',
                        ])
                        ->default('all')
                        ->reactive()
                        ->afterStateUpdated(function (Set $set, Get $get, $state): void {
                            $backupSetId = $get('backup_set_id');
                            $tenant = Tenant::current();
                            $set('is_dry_run', true);
                            $set('acknowledged_impact', false);
                            $set('tenant_confirm', null);
                            $set('check_summary', null);
                            $set('check_results', []);
                            $set('checks_ran_at', null);
                            $set('preview_summary', null);
                            $set('preview_diffs', []);
                            $set('preview_ran_at', null);

                            if ($state === 'all') {
                                $set('backup_item_ids', null);
                                $set('group_mapping', static::groupMappingPlaceholders(
                                    backupSetId: $backupSetId,
                                    scopeMode: 'all',
                                    selectedItemIds: null,
                                    tenant: $tenant,
                                ));

                                return;
                            }

                            $set('group_mapping', []);
                            $set('backup_item_ids', []);
                        })
                        ->required(),
                    Forms\Components\Select::make('backup_item_ids')
                        ->label('Items to restore')
                        ->multiple()
                        ->searchable()
                        ->searchValues()
                        ->searchDebounce(400)
                        ->optionsLimit(300)
                        ->options(fn (Get $get) => static::restoreItemGroupedOptions($get('backup_set_id')))
                        ->reactive()
                        ->afterStateUpdated(function (Set $set, Get $get): void {
                            $backupSetId = $get('backup_set_id');
                            $selectedItemIds = $get('backup_item_ids');
                            $selectedItemIds = is_array($selectedItemIds) ? $selectedItemIds : null;
                            $tenant = Tenant::current();

                            $set('group_mapping', static::groupMappingPlaceholders(
                                backupSetId: $backupSetId,
                                scopeMode: 'selected',
                                selectedItemIds: $selectedItemIds,
                                tenant: $tenant,
                            ));
                            $set('is_dry_run', true);
                            $set('acknowledged_impact', false);
                            $set('tenant_confirm', null);
                            $set('check_summary', null);
                            $set('check_results', []);
                            $set('checks_ran_at', null);
                            $set('preview_summary', null);
                            $set('preview_diffs', []);
                            $set('preview_ran_at', null);
                        })
                        ->visible(fn (Get $get): bool => $get('scope_mode') === 'selected')
                        ->required(fn (Get $get): bool => $get('scope_mode') === 'selected')
                        ->hintActions([
                            Actions\Action::make('select_all_backup_items')
                                ->label('Select all')
                                ->icon('heroicon-o-check')
                                ->color('gray')
                                ->visible(fn (Get $get): bool => filled($get('backup_set_id')) && $get('scope_mode') === 'selected')
                                ->action(function (Get $get, Set $set): void {
                                    $groupedOptions = static::restoreItemGroupedOptions($get('backup_set_id'));

                                    $allItemIds = [];

                                    foreach ($groupedOptions as $options) {
                                        $allItemIds = array_merge($allItemIds, array_keys($options));
                                    }

                                    $set('backup_item_ids', array_values($allItemIds), shouldCallUpdatedHooks: true);
                                }),
                            Actions\Action::make('clear_backup_items')
                                ->label('Clear')
                                ->icon('heroicon-o-x-mark')
                                ->color('gray')
                                ->visible(fn (Get $get): bool => $get('scope_mode') === 'selected')
                                ->action(fn (Set $set) => $set('backup_item_ids', [], shouldCallUpdatedHooks: true)),
                        ])
                        ->helperText('Search by name or ID. Include foundations (scope tags, assignment filters) with policies to re-map IDs. Options are grouped by category, type, and platform.'),
                    Section::make('Group mapping')
                        ->description('Some source groups do not exist in the target tenant. Map them or choose Skip.')
                        ->schema(function (Get $get): array {
                            $backupSetId = $get('backup_set_id');
                            $scopeMode = $get('scope_mode') ?? 'all';
                            $selectedItemIds = $get('backup_item_ids');
                            $tenant = Tenant::current();

                            if (! $tenant || ! $backupSetId) {
                                return [];
                            }

                            $selectedItemIds = is_array($selectedItemIds) ? $selectedItemIds : null;

                            if ($scopeMode === 'selected' && ($selectedItemIds === null || $selectedItemIds === [])) {
                                return [];
                            }

                            $unresolved = static::unresolvedGroups(
                                backupSetId: $backupSetId,
                                selectedItemIds: $scopeMode === 'selected' ? $selectedItemIds : null,
                                tenant: $tenant
                            );

                            $groupCacheQuery = EntraGroup::query()->where('tenant_id', $tenant->getKey());
                            $hasCachedGroups = $groupCacheQuery->exists();

                            $stalenessDays = (int) config('directory_groups.staleness_days', 30);
                            $cutoff = now('UTC')->subDays(max(1, $stalenessDays));
                            $latestSeen = $groupCacheQuery->max('last_seen_at');
                            $isStale = $hasCachedGroups && (! $latestSeen || $latestSeen < $cutoff);

                            $cacheNotice = match (true) {
                                ! $hasCachedGroups => 'No cached groups found. Run "Sync Groups" first.',
                                $isStale => "Cached groups may be stale (>${stalenessDays} days). Consider running \"Sync Groups\".",
                                default => null,
                            };

                            return array_map(function (array $group) use ($cacheNotice): Forms\Components\TextInput {
                                $groupId = $group['id'];
                                $label = $group['label'];

                                return Forms\Components\TextInput::make("group_mapping.{$groupId}")
                                    ->label($label)
                                    ->placeholder('SKIP or target group Object ID (GUID)')
                                    ->rules([new SkipOrUuidRule])
                                    ->reactive()
                                    ->afterStateUpdated(function (Set $set): void {
                                        $set('check_summary', null);
                                        $set('check_results', []);
                                        $set('checks_ran_at', null);
                                        $set('preview_summary', null);
                                        $set('preview_diffs', []);
                                        $set('preview_ran_at', null);
                                    })
                                    ->required()
                                    ->suffixAction(
                                        Actions\Action::make('select_from_directory_cache_'.str_replace('-', '_', $groupId))
                                            ->icon('heroicon-o-magnifying-glass')
                                            ->iconButton()
                                            ->tooltip('Select from Directory cache')
                                            ->modalHeading('Select from Directory cache')
                                            ->modalWidth('5xl')
                                            ->modalSubmitAction(false)
                                            ->modalCancelActionLabel('Close')
                                            ->modalContent(fn () => view('filament.modals.entra-group-cache-picker', [
                                                'sourceGroupId' => $groupId,
                                            ]))
                                    )
                                    ->helperText(fn (): string => ($cacheNotice ? ($cacheNotice.' ') : '').'Paste the target Entra ID group Object ID (GUID). Labels use cached directory groups only (no live Graph lookups). Use SKIP to omit the assignment.')
                                    ->hintAction(
                                        Actions\Action::make('open_directory_groups_'.str_replace('-', '_', $groupId))
                                            ->label('Sync Groups')
                                            ->icon('heroicon-o-arrow-path')
                                            ->color('warning')
                                            ->url(fn (): string => EntraGroupResource::getUrl('index', tenant: Tenant::current()))
                                            ->visible(fn (): bool => $cacheNotice !== null)
                                    );
                            }, $unresolved);
                        })
                        ->visible(function (Get $get): bool {
                            $backupSetId = $get('backup_set_id');
                            $scopeMode = $get('scope_mode') ?? 'all';
                            $selectedItemIds = $get('backup_item_ids');
                            $tenant = Tenant::current();

                            if (! $tenant || ! $backupSetId) {
                                return false;
                            }

                            $selectedItemIds = is_array($selectedItemIds) ? $selectedItemIds : null;

                            if ($scopeMode === 'selected' && ($selectedItemIds === null || $selectedItemIds === [])) {
                                return false;
                            }

                            return static::unresolvedGroups(
                                backupSetId: $backupSetId,
                                selectedItemIds: $scopeMode === 'selected' ? $selectedItemIds : null,
                                tenant: $tenant
                            ) !== [];
                        }),
                ]),
            Step::make('Safety & Conflict Checks')
                ->description('Is this dangerous?')
                ->schema([
                    Forms\Components\Hidden::make('check_summary')
                        ->default(null),
                    Forms\Components\Hidden::make('checks_ran_at')
                        ->default(null),
                    Forms\Components\ViewField::make('check_results')
                        ->label('Checks')
                        ->default([])
                        ->view('filament.forms.components.restore-run-checks')
                        ->viewData(fn (Get $get): array => [
                            'summary' => $get('check_summary'),
                            'ranAt' => $get('checks_ran_at'),
                        ])
                        ->hintActions([
                            Actions\Action::make('run_restore_checks')
                                ->label('Run checks')
                                ->icon('heroicon-o-shield-check')
                                ->color('gray')
                                ->visible(fn (Get $get): bool => filled($get('backup_set_id')))
                                ->action(function (Get $get, Set $set): void {
                                    $tenant = Tenant::current();

                                    if (! $tenant) {
                                        return;
                                    }

                                    $backupSetId = $get('backup_set_id');

                                    if (! $backupSetId) {
                                        return;
                                    }

                                    $backupSet = BackupSet::find($backupSetId);

                                    if (! $backupSet || $backupSet->tenant_id !== $tenant->id) {
                                        Notification::make()
                                            ->title('Unable to run checks')
                                            ->body('Backup set is not available for the active tenant.')
                                            ->danger()
                                            ->send();

                                        return;
                                    }

                                    $scopeMode = $get('scope_mode') ?? 'all';
                                    $selectedItemIds = ($scopeMode === 'selected')
                                        ? ($get('backup_item_ids') ?? null)
                                        : null;

                                    $selectedItemIds = is_array($selectedItemIds) ? $selectedItemIds : null;

                                    $groupMapping = static::normalizeGroupMapping($get('group_mapping'));

                                    $checker = app(RestoreRiskChecker::class);
                                    $outcome = $checker->check(
                                        tenant: $tenant,
                                        backupSet: $backupSet,
                                        selectedItemIds: $selectedItemIds,
                                        groupMapping: $groupMapping,
                                    );

                                    $set('check_summary', $outcome['summary'] ?? [], shouldCallUpdatedHooks: true);
                                    $set('check_results', $outcome['results'] ?? [], shouldCallUpdatedHooks: true);
                                    $set('checks_ran_at', now()->toIso8601String(), shouldCallUpdatedHooks: true);

                                    $summary = $outcome['summary'] ?? [];
                                    $blockers = (int) ($summary['blocking'] ?? 0);
                                    $warnings = (int) ($summary['warning'] ?? 0);

                                    if ($blockers > 0) {
                                        $set('is_dry_run', true, shouldCallUpdatedHooks: true);
                                    }

                                    Notification::make()
                                        ->title('Safety checks completed')
                                        ->body("Blocking: {$blockers} • Warnings: {$warnings}")
                                        ->status($blockers > 0 ? 'danger' : ($warnings > 0 ? 'warning' : 'success'))
                                        ->send();
                                }),
                            Actions\Action::make('clear_restore_checks')
                                ->label('Clear')
                                ->icon('heroicon-o-x-mark')
                                ->color('gray')
                                ->visible(fn (Get $get): bool => filled($get('check_results')) || filled($get('check_summary')))
                                ->action(function (Set $set): void {
                                    $set('is_dry_run', true, shouldCallUpdatedHooks: true);
                                    $set('acknowledged_impact', false, shouldCallUpdatedHooks: true);
                                    $set('tenant_confirm', null, shouldCallUpdatedHooks: true);
                                    $set('check_summary', null, shouldCallUpdatedHooks: true);
                                    $set('check_results', [], shouldCallUpdatedHooks: true);
                                    $set('checks_ran_at', null, shouldCallUpdatedHooks: true);
                                }),
                        ])
                        ->helperText('Run checks after defining scope and mapping missing groups.'),
                ]),
            Step::make('Preview')
                ->description('Dry-run preview')
                ->schema([
                    Forms\Components\Hidden::make('preview_summary')
                        ->default(null),
                    Forms\Components\Hidden::make('preview_ran_at')
                        ->default(null)
                        ->required(),
                    Forms\Components\ViewField::make('preview_diffs')
                        ->label('Preview')
                        ->default([])
                        ->view('filament.forms.components.restore-run-preview')
                        ->viewData(fn (Get $get): array => [
                            'summary' => $get('preview_summary'),
                            'ranAt' => $get('preview_ran_at'),
                        ])
                        ->hintActions([
                            Actions\Action::make('run_restore_preview')
                                ->label('Generate preview')
                                ->icon('heroicon-o-eye')
                                ->color('gray')
                                ->visible(fn (Get $get): bool => filled($get('backup_set_id')))
                                ->action(function (Get $get, Set $set): void {
                                    $tenant = Tenant::current();

                                    if (! $tenant) {
                                        return;
                                    }

                                    $backupSetId = $get('backup_set_id');

                                    if (! $backupSetId) {
                                        return;
                                    }

                                    $backupSet = BackupSet::find($backupSetId);

                                    if (! $backupSet || $backupSet->tenant_id !== $tenant->id) {
                                        Notification::make()
                                            ->title('Unable to generate preview')
                                            ->body('Backup set is not available for the active tenant.')
                                            ->danger()
                                            ->send();

                                        return;
                                    }

                                    $scopeMode = $get('scope_mode') ?? 'all';
                                    $selectedItemIds = ($scopeMode === 'selected')
                                        ? ($get('backup_item_ids') ?? null)
                                        : null;

                                    $selectedItemIds = is_array($selectedItemIds) ? $selectedItemIds : null;

                                    $generator = app(RestoreDiffGenerator::class);
                                    $outcome = $generator->generate(
                                        tenant: $tenant,
                                        backupSet: $backupSet,
                                        selectedItemIds: $selectedItemIds,
                                    );

                                    $summary = $outcome['summary'] ?? [];
                                    $diffs = $outcome['diffs'] ?? [];

                                    $set('preview_summary', $summary, shouldCallUpdatedHooks: true);
                                    $set('preview_diffs', $diffs, shouldCallUpdatedHooks: true);
                                    $set('preview_ran_at', $summary['generated_at'] ?? now()->toIso8601String(), shouldCallUpdatedHooks: true);

                                    $policiesChanged = (int) ($summary['policies_changed'] ?? 0);
                                    $policiesTotal = (int) ($summary['policies_total'] ?? 0);

                                    Notification::make()
                                        ->title('Preview generated')
                                        ->body("Policies: {$policiesChanged}/{$policiesTotal} changed")
                                        ->status($policiesChanged > 0 ? 'warning' : 'success')
                                        ->send();
                                }),
                            Actions\Action::make('clear_restore_preview')
                                ->label('Clear')
                                ->icon('heroicon-o-x-mark')
                                ->color('gray')
                                ->visible(fn (Get $get): bool => filled($get('preview_diffs')) || filled($get('preview_summary')))
                                ->action(function (Set $set): void {
                                    $set('is_dry_run', true, shouldCallUpdatedHooks: true);
                                    $set('acknowledged_impact', false, shouldCallUpdatedHooks: true);
                                    $set('tenant_confirm', null, shouldCallUpdatedHooks: true);
                                    $set('preview_summary', null, shouldCallUpdatedHooks: true);
                                    $set('preview_diffs', [], shouldCallUpdatedHooks: true);
                                    $set('preview_ran_at', null, shouldCallUpdatedHooks: true);
                                }),
                        ])
                        ->helperText('Generate a normalized diff preview before creating the dry-run restore.'),
                ]),
            Step::make('Confirm & Execute')
                ->description('Point of no return')
                ->schema([
                    Forms\Components\Placeholder::make('confirm_environment')
                        ->label('Environment')
                        ->content(fn (): string => app()->environment('production') ? 'prod' : 'test'),
                    Forms\Components\Placeholder::make('confirm_tenant_label')
                        ->label('Tenant hard-confirm label')
                        ->content(function (): string {
                            $tenant = Tenant::current();

                            if (! $tenant) {
                                return '';
                            }

                            $tenantIdentifier = $tenant->tenant_id ?? $tenant->external_id;

                            return (string) ($tenant->name ?? $tenantIdentifier ?? $tenant->getKey());
                        }),
                    Forms\Components\Toggle::make('is_dry_run')
                        ->label('Preview only (dry-run)')
                        ->default(true)
                        ->reactive()
                        ->disabled(function (Get $get): bool {
                            if (! filled($get('checks_ran_at'))) {
                                return true;
                            }

                            $summary = $get('check_summary');

                            if (! is_array($summary)) {
                                return false;
                            }

                            return (int) ($summary['blocking'] ?? 0) > 0;
                        })
                        ->helperText('Turn OFF to queue a real execution. Execution requires checks + preview + confirmations.'),
                    Forms\Components\Checkbox::make('acknowledged_impact')
                        ->label('I reviewed the impact (checks + preview)')
                        ->accepted()
                        ->visible(fn (Get $get): bool => $get('is_dry_run') === false),
                    Forms\Components\TextInput::make('tenant_confirm')
                        ->label('Type the tenant label to confirm execution')
                        ->required(fn (Get $get): bool => $get('is_dry_run') === false)
                        ->visible(fn (Get $get): bool => $get('is_dry_run') === false)
                        ->in(function (): array {
                            $tenant = Tenant::current();

                            if (! $tenant) {
                                return [];
                            }

                            $tenantIdentifier = $tenant->tenant_id ?? $tenant->external_id;

                            return [(string) ($tenant->name ?? $tenantIdentifier ?? $tenant->getKey())];
                        })
                        ->validationMessages([
                            'in' => 'Tenant hard-confirm does not match.',
                        ])
                        ->helperText(function (): string {
                            $tenant = Tenant::current();

                            if (! $tenant) {
                                return '';
                            }

                            $tenantIdentifier = $tenant->tenant_id ?? $tenant->external_id;
                            $expected = (string) ($tenant->name ?? $tenantIdentifier ?? $tenant->getKey());

                            return "Type: {$expected}";
                        }),
                ]),
        ];
    }

    public static function table(Table $table): Table
    {
        return $table
            ->persistFiltersInSession()
            ->persistSearchInSession()
            ->persistSortInSession()
            ->columns([
                Tables\Columns\TextColumn::make('backupSet.name')
                    ->label('Backup set')
                    ->searchable()
                    ->sortable(),
                Tables\Columns\IconColumn::make('is_dry_run')->label('Dry-run')->boolean(),
                Tables\Columns\TextColumn::make('status')
                    ->badge()
                    ->formatStateUsing(BadgeRenderer::label(BadgeDomain::RestoreRunStatus))
                    ->color(BadgeRenderer::color(BadgeDomain::RestoreRunStatus))
                    ->icon(BadgeRenderer::icon(BadgeDomain::RestoreRunStatus))
                    ->iconColor(BadgeRenderer::iconColor(BadgeDomain::RestoreRunStatus))
                    ->sortable(),
                Tables\Columns\TextColumn::make('summary_total')
                    ->label('Total')
                    ->state(fn (RestoreRun $record): int => (int) (($record->metadata ?? [])['total'] ?? 0)),
                Tables\Columns\TextColumn::make('summary_succeeded')
                    ->label('Succeeded')
                    ->state(fn (RestoreRun $record): int => (int) (($record->metadata ?? [])['succeeded'] ?? 0)),
                Tables\Columns\TextColumn::make('summary_failed')
                    ->label('Failed')
                    ->state(fn (RestoreRun $record): int => (int) (($record->metadata ?? [])['failed'] ?? 0)),
                Tables\Columns\TextColumn::make('started_at')->dateTime()->since()->sortable(),
                Tables\Columns\TextColumn::make('completed_at')->dateTime()->since()->sortable(),
                Tables\Columns\TextColumn::make('requested_by')->label('Requested by')->searchable()->sortable(),
            ])
            ->filters([
                Tables\Filters\SelectFilter::make('status')
                    ->options(FilterOptionCatalog::restoreRunStatuses())
                    ->searchable(),
                Tables\Filters\SelectFilter::make('outcome')
                    ->options(FilterOptionCatalog::restoreRunOutcomes())
                    ->query(function (\Illuminate\Database\Eloquent\Builder $query, array $data): \Illuminate\Database\Eloquent\Builder {
                        $value = $data['value'] ?? null;

                        return match ((string) $value) {
                            'succeeded' => $query->whereIn('status', [
                                \App\Support\RestoreRunStatus::Previewed->value,
                                \App\Support\RestoreRunStatus::Completed->value,
                            ]),
                            'partial' => $query->whereIn('status', [
                                \App\Support\RestoreRunStatus::Partial->value,
                                \App\Support\RestoreRunStatus::CompletedWithErrors->value,
                            ]),
                            'failed' => $query->whereIn('status', [
                                \App\Support\RestoreRunStatus::Failed->value,
                                \App\Support\RestoreRunStatus::Cancelled->value,
                                \App\Support\RestoreRunStatus::Aborted->value,
                            ]),
                            default => $query,
                        };
                    }),
                FilterPresets::dateRange('started_at', 'Started', 'started_at'),
                FilterPresets::archived(),
            ])
            ->actions([
                Actions\ViewAction::make(),
                ActionGroup::make([
                    static::rerunActionWithGate(),
                    UiEnforcement::forTableAction(
                        Actions\Action::make('restore')
                            ->label('Restore')
                            ->color('success')
                            ->icon('heroicon-o-arrow-uturn-left')
                            ->requiresConfirmation()
                            ->visible(fn (RestoreRun $record): bool => $record->trashed())
                            ->action(function (RestoreRun $record, \App\Services\Intune\AuditLogger $auditLogger) {
                                $record->restore();

                                if ($record->tenant) {
                                    $auditLogger->log(
                                        tenant: $record->tenant,
                                        action: 'restore_run.restored',
                                        resourceType: 'restore_run',
                                        resourceId: (string) $record->id,
                                        status: 'success',
                                        context: ['metadata' => ['backup_set_id' => $record->backup_set_id]]
                                    );
                                }

                                Notification::make()
                                    ->title('Restore run restored')
                                    ->success()
                                    ->send();
                            }),
                        fn () => Tenant::current(),
                    )
                        ->requireCapability(Capabilities::TENANT_MANAGE)
                        ->preserveVisibility()
                        ->apply(),
                    UiEnforcement::forTableAction(
                        Actions\Action::make('archive')
                            ->label('Archive')
                            ->color('danger')
                            ->icon('heroicon-o-archive-box-x-mark')
                            ->requiresConfirmation()
                            ->visible(fn (RestoreRun $record): bool => ! $record->trashed())
                            ->action(function (RestoreRun $record, \App\Services\Intune\AuditLogger $auditLogger) {
                                if (! $record->isDeletable()) {
                                    Notification::make()
                                        ->title('Restore run cannot be archived')
                                        ->body("Not deletable (status: {$record->status})")
                                        ->warning()
                                        ->send();

                                    return;
                                }

                                $record->delete();

                                if ($record->tenant) {
                                    $auditLogger->log(
                                        tenant: $record->tenant,
                                        action: 'restore_run.deleted',
                                        resourceType: 'restore_run',
                                        resourceId: (string) $record->id,
                                        status: 'success',
                                        context: ['metadata' => ['backup_set_id' => $record->backup_set_id]]
                                    );
                                }

                                Notification::make()
                                    ->title('Restore run archived')
                                    ->success()
                                    ->send();
                            }),
                        fn () => Tenant::current(),
                    )
                        ->requireCapability(Capabilities::TENANT_MANAGE)
                        ->preserveVisibility()
                        ->apply(),
                    UiEnforcement::forTableAction(
                        Actions\Action::make('forceDelete')
                            ->label('Force delete')
                            ->color('danger')
                            ->icon('heroicon-o-trash')
                            ->requiresConfirmation()
                            ->visible(fn (RestoreRun $record): bool => $record->trashed())
                            ->action(function (RestoreRun $record, \App\Services\Intune\AuditLogger $auditLogger) {
                                if ($record->tenant) {
                                    $auditLogger->log(
                                        tenant: $record->tenant,
                                        action: 'restore_run.force_deleted',
                                        resourceType: 'restore_run',
                                        resourceId: (string) $record->id,
                                        status: 'success',
                                        context: ['metadata' => ['backup_set_id' => $record->backup_set_id]]
                                    );
                                }

                                $record->forceDelete();

                                Notification::make()
                                    ->title('Restore run permanently deleted')
                                    ->success()
                                    ->send();
                            }),
                        fn () => Tenant::current(),
                    )
                        ->requireCapability(Capabilities::TENANT_DELETE)
                        ->preserveVisibility()
                        ->apply(),
                ])->icon('heroicon-o-ellipsis-vertical'),
            ])
            ->bulkActions([
                BulkActionGroup::make([
                    UiEnforcement::forBulkAction(
                        BulkAction::make('bulk_delete')
                            ->label('Archive Restore Runs')
                            ->icon('heroicon-o-trash')
                            ->color('danger')
                            ->requiresConfirmation()
                            ->hidden(function (HasTable $livewire): bool {
                                $trashedFilterState = $livewire->getTableFilterState(TrashedFilter::class) ?? [];
                                $value = $trashedFilterState['value'] ?? null;

                                $isOnlyTrashed = in_array($value, [0, '0', false], true);

                                return $isOnlyTrashed;
                            })
                            ->modalDescription('This archives restore runs (soft delete). Already archived runs will be skipped.')
                            ->form(function (Collection $records) {
                                if ($records->count() >= 20) {
                                    return [
                                        Forms\Components\TextInput::make('confirmation')
                                            ->label('Type DELETE to confirm')
                                            ->in(['DELETE'])
                                            ->validationMessages([
                                                'in' => 'Please type DELETE to confirm.',
                                            ]),
                                    ];
                                }

                                return [];
                            })
                            ->action(function (Collection $records) {
                                $tenant = Tenant::current();
                                $user = auth()->user();
                                $count = $records->count();
                                $ids = $records->pluck('id')->toArray();

                                if (! $tenant instanceof Tenant) {
                                    return;
                                }

                                $initiator = $user instanceof User ? $user : null;

                                /** @var BulkSelectionIdentity $selection */
                                $selection = app(BulkSelectionIdentity::class);
                                $selectionIdentity = $selection->fromIds($ids);

                                /** @var OperationRunService $runs */
                                $runs = app(OperationRunService::class);

                                $opRun = $runs->enqueueBulkOperation(
                                    tenant: $tenant,
                                    type: 'restore_run.delete',
                                    targetScope: [
                                        'entra_tenant_id' => (string) ($tenant->tenant_id ?? $tenant->external_id),
                                    ],
                                    selectionIdentity: $selectionIdentity,
                                    dispatcher: function ($operationRun) use ($tenant, $initiator, $ids): void {
                                        BulkRestoreRunDeleteJob::dispatch(
                                            tenantId: (int) $tenant->getKey(),
                                            userId: (int) ($initiator?->getKey() ?? 0),
                                            restoreRunIds: $ids,
                                            operationRun: $operationRun,
                                        );
                                    },
                                    initiator: $initiator,
                                    extraContext: [
                                        'restore_run_count' => $count,
                                    ],
                                    emitQueuedNotification: false,
                                );

                                OperationUxPresenter::queuedToast('restore_run.delete')
                                    ->actions([
                                        Actions\Action::make('view_run')
                                            ->label('View run')
                                            ->url(OperationRunLinks::view($opRun, $tenant)),
                                    ])
                                    ->send();
                            })
                            ->deselectRecordsAfterCompletion(),
                    )
                        ->requireCapability(Capabilities::TENANT_MANAGE)
                        ->apply(),

                    UiEnforcement::forBulkAction(
                        BulkAction::make('bulk_restore')
                            ->label('Restore Restore Runs')
                            ->icon('heroicon-o-arrow-uturn-left')
                            ->color('success')
                            ->requiresConfirmation()
                            ->hidden(function (HasTable $livewire): bool {
                                $trashedFilterState = $livewire->getTableFilterState(TrashedFilter::class) ?? [];
                                $value = $trashedFilterState['value'] ?? null;

                                $isOnlyTrashed = in_array($value, [0, '0', false], true);

                                return ! $isOnlyTrashed;
                            })
                            ->modalHeading(fn (Collection $records) => "Restore {$records->count()} restore runs?")
                            ->modalDescription('Archived runs will be restored back to the active list. Active runs will be skipped.')
                            ->action(function (Collection $records) {
                                $tenant = Tenant::current();
                                $user = auth()->user();
                                $count = $records->count();
                                $ids = $records->pluck('id')->toArray();

                                if (! $tenant instanceof Tenant) {
                                    return;
                                }

                                $initiator = $user instanceof User ? $user : null;

                                /** @var BulkSelectionIdentity $selection */
                                $selection = app(BulkSelectionIdentity::class);
                                $selectionIdentity = $selection->fromIds($ids);

                                /** @var OperationRunService $runs */
                                $runs = app(OperationRunService::class);

                                $opRun = $runs->enqueueBulkOperation(
                                    tenant: $tenant,
                                    type: 'restore_run.restore',
                                    targetScope: [
                                        'entra_tenant_id' => (string) ($tenant->tenant_id ?? $tenant->external_id),
                                    ],
                                    selectionIdentity: $selectionIdentity,
                                    dispatcher: function ($operationRun) use ($count, $tenant, $initiator, $ids): void {
                                        if ($count >= 20) {
                                            BulkRestoreRunRestoreJob::dispatch(
                                                tenantId: (int) $tenant->getKey(),
                                                userId: (int) ($initiator?->getKey() ?? 0),
                                                restoreRunIds: $ids,
                                                operationRun: $operationRun,
                                            );

                                            return;
                                        }

                                        BulkRestoreRunRestoreJob::dispatchSync(
                                            tenantId: (int) $tenant->getKey(),
                                            userId: (int) ($initiator?->getKey() ?? 0),
                                            restoreRunIds: $ids,
                                            operationRun: $operationRun,
                                        );
                                    },
                                    initiator: $initiator,
                                    extraContext: [
                                        'restore_run_count' => $count,
                                    ],
                                    emitQueuedNotification: false,
                                );

                                OperationUxPresenter::queuedToast('restore_run.restore')
                                    ->actions([
                                        Actions\Action::make('view_run')
                                            ->label('View run')
                                            ->url(OperationRunLinks::view($opRun, $tenant)),
                                    ])
                                    ->send();
                            })
                            ->deselectRecordsAfterCompletion(),
                    )
                        ->requireCapability(Capabilities::TENANT_MANAGE)
                        ->apply(),

                    UiEnforcement::forBulkAction(
                        BulkAction::make('bulk_force_delete')
                            ->label('Force Delete Restore Runs')
                            ->icon('heroicon-o-trash')
                            ->color('danger')
                            ->requiresConfirmation()
                            ->hidden(function (HasTable $livewire): bool {
                                $trashedFilterState = $livewire->getTableFilterState(TrashedFilter::class) ?? [];
                                $value = $trashedFilterState['value'] ?? null;

                                $isOnlyTrashed = in_array($value, [0, '0', false], true);

                                return ! $isOnlyTrashed;
                            })
                            ->modalHeading(fn (Collection $records) => "Force delete {$records->count()} restore runs?")
                            ->modalDescription('This is permanent. Only archived restore runs will be permanently deleted; active runs will be skipped.')
                            ->form([
                                Forms\Components\TextInput::make('confirmation')
                                    ->label('Type DELETE to confirm')
                                    ->required()
                                    ->in(['DELETE'])
                                    ->validationMessages([
                                        'in' => 'Please type DELETE to confirm.',
                                    ]),
                            ])
                            ->action(function (Collection $records) {
                                $tenant = Tenant::current();
                                $user = auth()->user();
                                $count = $records->count();
                                $ids = $records->pluck('id')->toArray();

                                if (! $tenant instanceof Tenant) {
                                    return;
                                }

                                $initiator = $user instanceof User ? $user : null;

                                /** @var BulkSelectionIdentity $selection */
                                $selection = app(BulkSelectionIdentity::class);
                                $selectionIdentity = $selection->fromIds($ids);

                                /** @var OperationRunService $runs */
                                $runs = app(OperationRunService::class);

                                $opRun = $runs->enqueueBulkOperation(
                                    tenant: $tenant,
                                    type: 'restore_run.force_delete',
                                    targetScope: [
                                        'entra_tenant_id' => (string) ($tenant->tenant_id ?? $tenant->external_id),
                                    ],
                                    selectionIdentity: $selectionIdentity,
                                    dispatcher: function ($operationRun) use ($count, $tenant, $initiator, $ids): void {
                                        if ($count >= 20) {
                                            BulkRestoreRunForceDeleteJob::dispatch(
                                                tenantId: (int) $tenant->getKey(),
                                                userId: (int) ($initiator?->getKey() ?? 0),
                                                restoreRunIds: $ids,
                                                operationRun: $operationRun,
                                            );

                                            return;
                                        }

                                        BulkRestoreRunForceDeleteJob::dispatchSync(
                                            tenantId: (int) $tenant->getKey(),
                                            userId: (int) ($initiator?->getKey() ?? 0),
                                            restoreRunIds: $ids,
                                            operationRun: $operationRun,
                                        );
                                    },
                                    initiator: $initiator,
                                    extraContext: [
                                        'restore_run_count' => $count,
                                    ],
                                    emitQueuedNotification: false,
                                );

                                OperationUxPresenter::queuedToast('restore_run.force_delete')
                                    ->actions([
                                        Actions\Action::make('view_run')
                                            ->label('View run')
                                            ->url(OperationRunLinks::view($opRun, $tenant)),
                                    ])
                                    ->send();
                            })
                            ->deselectRecordsAfterCompletion(),
                    )
                        ->requireCapability(Capabilities::TENANT_DELETE)
                        ->apply(),
                ]),
            ])
            ->emptyStateHeading('No restore runs')
            ->emptyStateDescription('Start a restoration from a backup set.')
            ->emptyStateIcon('heroicon-o-arrow-path-rounded-square')
            ->emptyStateActions([
                static::makeCreateAction(),
            ]);
    }

    public static function infolist(Schema $schema): Schema
    {
        return $schema
            ->schema([
                Infolists\Components\TextEntry::make('backupSet.name')->label('Backup set'),
                Infolists\Components\TextEntry::make('status')
                    ->badge()
                    ->formatStateUsing(BadgeRenderer::label(BadgeDomain::RestoreRunStatus))
                    ->color(BadgeRenderer::color(BadgeDomain::RestoreRunStatus))
                    ->icon(BadgeRenderer::icon(BadgeDomain::RestoreRunStatus))
                    ->iconColor(BadgeRenderer::iconColor(BadgeDomain::RestoreRunStatus)),
                Infolists\Components\TextEntry::make('counts')
                    ->label('Counts')
                    ->state(function (RestoreRun $record): string {
                        $meta = $record->metadata ?? [];
                        $total = (int) ($meta['total'] ?? 0);
                        $succeeded = (int) ($meta['succeeded'] ?? 0);
                        $failed = (int) ($meta['failed'] ?? 0);

                        return sprintf('Total: %d • Succeeded: %d • Failed: %d', $total, $succeeded, $failed);
                    }),
                Infolists\Components\TextEntry::make('is_dry_run')
                    ->label('Dry-run')
                    ->formatStateUsing(fn ($state) => $state ? 'Yes' : 'No')
                    ->badge(),
                Infolists\Components\TextEntry::make('requested_by'),
                Infolists\Components\TextEntry::make('started_at')->dateTime(),
                Infolists\Components\TextEntry::make('completed_at')->dateTime(),
                Infolists\Components\ViewEntry::make('preview')
                    ->label('Preview')
                    ->view('filament.infolists.entries.restore-preview')
                    ->state(fn ($record) => $record->preview ?? []),
                Infolists\Components\ViewEntry::make('results')
                    ->label('Results')
                    ->view('filament.infolists.entries.restore-results')
                    ->state(fn ($record) => $record->results ?? []),
            ]);
    }

    public static function getPages(): array
    {
        return [
            'index' => Pages\ListRestoreRuns::route('/'),
            'create' => Pages\CreateRestoreRun::route('/create'),
            'view' => Pages\ViewRestoreRun::route('/{record}'),
        ];
    }

    /**
     * @return array{label:?string,category:?string,restore:?string,risk:?string}|array<string,mixed>
     */
    private static function typeMeta(?string $type): array
    {
        if ($type === null) {
            return [];
        }

        $types = array_merge(
            config('tenantpilot.supported_policy_types', []),
            config('tenantpilot.foundation_types', [])
        );

        return collect($types)
            ->firstWhere('type', $type) ?? [];
    }

    /**
     * @return array{options: array<int, string>, descriptions: array<int, string>}
     */
    private static function restoreItemOptionData(?int $backupSetId): array
    {
        $tenant = Tenant::current();

        if (! $tenant || ! $backupSetId) {
            return [
                'options' => [],
                'descriptions' => [],
            ];
        }

        $cacheKey = sprintf('restore_run_item_options:%s:%s', $tenant->getKey(), $backupSetId);

        return Cache::store('array')->rememberForever($cacheKey, function () use ($backupSetId, $tenant): array {
            $items = BackupItem::query()
                ->where('backup_set_id', $backupSetId)
                ->whereHas('backupSet', fn ($query) => $query->where('tenant_id', $tenant->getKey()))
                ->where(function ($query) {
                    $query->whereNull('policy_id')
                        ->orWhereDoesntHave('policy')
                        ->orWhereHas('policy', fn ($policyQuery) => $policyQuery->whereNull('ignored_at'));
                })
                ->with(['policy:id,display_name', 'policyVersion:id,version_number,captured_at'])
                ->get()
                ->sortBy(function (BackupItem $item) {
                    $meta = static::typeMeta($item->policy_type);
                    $category = $meta['category'] ?? 'Policies';
                    $categoryKey = $category === 'Foundations' ? 'zz-'.$category : $category;
                    $name = strtolower($item->resolvedDisplayName());

                    return strtolower($categoryKey.'-'.$name);
                });

            $options = [];
            $descriptions = [];

            foreach ($items as $item) {
                $meta = static::typeMeta($item->policy_type);
                $typeLabel = $meta['label'] ?? $item->policy_type;
                $category = $meta['category'] ?? 'Policies';
                $restore = $meta['restore'] ?? 'enabled';
                $platform = $item->platform ?? $meta['platform'] ?? null;
                $displayName = $item->resolvedDisplayName();
                $identifier = $item->policy_identifier ?? null;
                $versionNumber = $item->policyVersion?->version_number;

                $options[$item->id] = $displayName;

                $parts = array_filter([
                    $category,
                    $typeLabel,
                    $platform,
                    "restore: {$restore}",
                    $versionNumber ? "version: {$versionNumber}" : null,
                    $item->hasAssignments() ? "assignments: {$item->assignment_count}" : null,
                    $identifier ? 'id: '.Str::limit($identifier, 24, '...') : null,
                ]);

                $descriptions[$item->id] = implode(' • ', $parts);
            }

            return [
                'options' => $options,
                'descriptions' => $descriptions,
            ];
        });
    }

    /**
     * @return array<string, array<int|string, string>>
     */
    private static function restoreItemGroupedOptions(?int $backupSetId): array
    {
        $tenant = Tenant::current();

        if (! $tenant || ! $backupSetId) {
            return [];
        }

        $items = BackupItem::query()
            ->where('backup_set_id', $backupSetId)
            ->whereHas('backupSet', fn ($query) => $query->where('tenant_id', $tenant->getKey()))
            ->where(function ($query) {
                $query->whereNull('policy_id')
                    ->orWhereDoesntHave('policy')
                    ->orWhereHas('policy', fn ($policyQuery) => $policyQuery->whereNull('ignored_at'));
            })
            ->with(['policy:id,display_name'])
            ->get()
            ->sortBy(function (BackupItem $item) {
                $meta = static::typeMeta($item->policy_type);
                $category = $meta['category'] ?? 'Policies';
                $categoryKey = $category === 'Foundations' ? 'zz-'.$category : $category;
                $typeLabel = $meta['label'] ?? $item->policy_type;
                $platform = $item->platform ?? $meta['platform'] ?? null;
                $name = strtolower($item->resolvedDisplayName());

                return strtolower($categoryKey.'-'.$typeLabel.'-'.$platform.'-'.$name);
            });

        $groups = [];

        foreach ($items as $item) {
            $meta = static::typeMeta($item->policy_type);
            $typeLabel = $meta['label'] ?? $item->policy_type;
            $category = $meta['category'] ?? 'Policies';
            $platform = $item->platform ?? $meta['platform'] ?? 'all';
            $restoreMode = $meta['restore'] ?? 'enabled';

            $groupLabel = implode(' • ', array_filter([
                $category,
                $typeLabel,
                $platform,
                $restoreMode === 'preview-only' ? 'preview-only' : null,
            ]));

            $groups[$groupLabel] ??= [];
            $groups[$groupLabel][$item->id] = $item->resolvedDisplayName();
        }

        return $groups;
    }

    public static function createRestoreRun(array $data): RestoreRun
    {
        $tenant = Tenant::current();
        $user = auth()->user();

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

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

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

        if (! $resolver->can($user, $tenant, Capabilities::TENANT_MANAGE)) {
            abort(403);
        }

        try {
            app(WriteGateInterface::class)->evaluate($tenant, 'restore.execute');
        } catch (ProviderAccessHardeningRequired $e) {
            app(\App\Services\Intune\AuditLogger::class)->log(
                tenant: $tenant,
                action: 'intune_rbac.write_blocked',
                status: 'blocked',
                actorId: (int) $user->getKey(),
                actorEmail: $user->email,
                actorName: $user->name,
                resourceType: 'restore_run',
                context: [
                    'metadata' => [
                        'operation_type' => 'restore.execute',
                        'reason_code' => $e->reasonCode,
                        'backup_set_id' => $data['backup_set_id'] ?? null,
                    ],
                ],
            );

            Notification::make()
                ->title('Write operation blocked')
                ->body($e->reasonMessage)
                ->danger()
                ->send();

            throw ValidationException::withMessages([
                'backup_set_id' => $e->reasonMessage,
            ]);
        }

        /** @var BackupSet $backupSet */
        $backupSet = BackupSet::findOrFail($data['backup_set_id']);

        if ($backupSet->tenant_id !== $tenant->id) {
            abort(403, 'Backup set does not belong to the active tenant.');
        }

        /** @var RestoreService $service */
        $service = app(RestoreService::class);

        $scopeMode = $data['scope_mode'] ?? 'all';
        $selectedItemIds = ($scopeMode === 'selected') ? ($data['backup_item_ids'] ?? null) : null;
        $selectedItemIds = is_array($selectedItemIds) ? $selectedItemIds : null;

        $actorEmail = auth()->user()?->email;
        $actorName = auth()->user()?->name;
        $isDryRun = (bool) ($data['is_dry_run'] ?? true);
        $groupMapping = static::normalizeGroupMapping($data['group_mapping'] ?? null);

        $checkSummary = $data['check_summary'] ?? null;
        $checkResults = $data['check_results'] ?? null;
        $checksRanAt = $data['checks_ran_at'] ?? null;
        $previewSummary = $data['preview_summary'] ?? null;
        $previewDiffs = $data['preview_diffs'] ?? null;
        $previewRanAt = $data['preview_ran_at'] ?? null;

        $tenantIdentifier = $tenant->tenant_id ?? $tenant->external_id;
        $highlanderLabel = (string) ($tenant->name ?? $tenantIdentifier ?? $tenant->getKey());

        if (! $isDryRun) {
            if (! is_array($checkSummary) || ! filled($checksRanAt)) {
                throw ValidationException::withMessages([
                    'check_summary' => 'Run safety checks before executing.',
                ]);
            }

            $blocking = (int) ($checkSummary['blocking'] ?? 0);
            $hasBlockers = (bool) ($checkSummary['has_blockers'] ?? ($blocking > 0));

            if ($blocking > 0 || $hasBlockers) {
                throw ValidationException::withMessages([
                    'check_summary' => 'Blocking checks must be resolved before executing.',
                ]);
            }

            if (! filled($previewRanAt)) {
                throw ValidationException::withMessages([
                    'preview_ran_at' => 'Generate preview before executing.',
                ]);
            }

            if (! (bool) ($data['acknowledged_impact'] ?? false)) {
                throw ValidationException::withMessages([
                    'acknowledged_impact' => 'Please acknowledge that you reviewed the impact.',
                ]);
            }

            $tenantConfirm = $data['tenant_confirm'] ?? null;

            if (! is_string($tenantConfirm) || $tenantConfirm !== $highlanderLabel) {
                throw ValidationException::withMessages([
                    'tenant_confirm' => 'Tenant hard-confirm does not match.',
                ]);
            }
        }

        if ($isDryRun) {
            $restoreRun = $service->execute(
                tenant: $tenant,
                backupSet: $backupSet,
                selectedItemIds: $selectedItemIds,
                dryRun: true,
                actorEmail: $actorEmail,
                actorName: $actorName,
                groupMapping: $groupMapping,
            );

            $metadata = $restoreRun->metadata ?? [];

            if (is_array($checkSummary)) {
                $metadata['check_summary'] = $checkSummary;
            }

            if (is_array($checkResults)) {
                $metadata['check_results'] = $checkResults;
            }

            if (is_string($checksRanAt) && $checksRanAt !== '') {
                $metadata['checks_ran_at'] = $checksRanAt;
            }

            if (is_array($previewSummary)) {
                $metadata['preview_summary'] = $previewSummary;
            }

            if (is_array($previewDiffs)) {
                $metadata['preview_diffs'] = $previewDiffs;
            }

            if (is_string($previewRanAt) && $previewRanAt !== '') {
                $metadata['preview_ran_at'] = $previewRanAt;
            }

            $restoreRun->update(['metadata' => $metadata]);

            return $restoreRun->refresh();
        }

        $preview = $service->preview($tenant, $backupSet, $selectedItemIds);

        $metadata = [
            'scope_mode' => $selectedItemIds === null ? 'all' : 'selected',
            'environment' => app()->environment('production') ? 'prod' : 'test',
            'highlander_label' => $highlanderLabel,
            'confirmed_at' => now()->toIso8601String(),
            'confirmed_by' => $actorEmail,
            'confirmed_by_name' => $actorName,
        ];

        if (is_array($checkSummary)) {
            $metadata['check_summary'] = $checkSummary;
        }

        if (is_array($checkResults)) {
            $metadata['check_results'] = $checkResults;
        }

        if (is_string($checksRanAt) && $checksRanAt !== '') {
            $metadata['checks_ran_at'] = $checksRanAt;
        }

        if (is_array($previewSummary)) {
            $metadata['preview_summary'] = $previewSummary;
        }

        if (is_array($previewDiffs)) {
            $metadata['preview_diffs'] = $previewDiffs;
        }

        if (is_string($previewRanAt) && $previewRanAt !== '') {
            $metadata['preview_ran_at'] = $previewRanAt;
        }

        $idempotencyKey = RestoreRunIdempotency::restoreExecuteKey(
            tenantId: (int) $tenant->getKey(),
            backupSetId: (int) $backupSet->getKey(),
            selectedItemIds: $selectedItemIds,
            groupMapping: $groupMapping,
        );

        $existing = RestoreRunIdempotency::findActiveRestoreRun((int) $tenant->getKey(), $idempotencyKey);

        if ($existing) {
            $existingOpRunId = (int) ($existing->operation_run_id ?? 0);
            $existingOpRun = $existingOpRunId > 0
                ? \App\Models\OperationRun::query()->find($existingOpRunId)
                : null;

            $toast = OperationUxPresenter::alreadyQueuedToast('restore.execute')
                ->body('Reusing the active restore run.');

            if ($existingOpRun) {
                $toast->actions([
                    Actions\Action::make('view_run')
                        ->label('View run')
                        ->url(OperationRunLinks::view($existingOpRun, $tenant)),
                ]);
            }

            $toast->send();

            return $existing;
        }

        try {
            $restoreRun = RestoreRun::create([
                'tenant_id' => $tenant->id,
                'backup_set_id' => $backupSet->id,
                'requested_by' => $actorEmail,
                'is_dry_run' => false,
                'status' => RestoreRunStatus::Queued->value,
                'idempotency_key' => $idempotencyKey,
                'requested_items' => $selectedItemIds,
                'preview' => $preview,
                'metadata' => $metadata,
                'group_mapping' => $groupMapping !== [] ? $groupMapping : null,
            ]);
        } catch (QueryException $exception) {
            $existing = RestoreRunIdempotency::findActiveRestoreRun((int) $tenant->getKey(), $idempotencyKey);

            if ($existing) {
                $existingOpRunId = (int) ($existing->operation_run_id ?? 0);
                $existingOpRun = $existingOpRunId > 0
                    ? \App\Models\OperationRun::query()->find($existingOpRunId)
                    : null;

                $toast = OperationUxPresenter::alreadyQueuedToast('restore.execute')
                    ->body('Reusing the active restore run.');

                if ($existingOpRun) {
                    $toast->actions([
                        Actions\Action::make('view_run')
                            ->label('View run')
                            ->url(OperationRunLinks::view($existingOpRun, $tenant)),
                    ]);
                }

                $toast->send();

                return $existing;
            }

            throw $exception;
        }

        app(AuditLogger::class)->log(
            tenant: $tenant,
            action: 'restore.queued',
            context: [
                'metadata' => [
                    'restore_run_id' => $restoreRun->id,
                    'backup_set_id' => $backupSet->id,
                ],
            ],
            actorEmail: $actorEmail,
            actorName: $actorName,
            resourceType: 'restore_run',
            resourceId: (string) $restoreRun->id,
            status: 'success',
        );

        /** @var OperationRunService $runs */
        $runs = app(OperationRunService::class);
        $initiator = auth()->user();
        $initiator = $initiator instanceof \App\Models\User ? $initiator : null;

        $opRun = $runs->ensureRun(
            tenant: $tenant,
            type: 'restore.execute',
            inputs: [
                'restore_run_id' => (int) $restoreRun->getKey(),
                'backup_set_id' => (int) $backupSet->getKey(),
                'is_dry_run' => (bool) ($restoreRun->is_dry_run ?? false),
            ],
            initiator: $initiator,
        );

        if ((int) ($restoreRun->operation_run_id ?? 0) !== (int) $opRun->getKey()) {
            $restoreRun->update(['operation_run_id' => $opRun->getKey()]);
        }

        ExecuteRestoreRunJob::dispatch($restoreRun->id, $actorEmail, $actorName, $opRun);

        OperationUxPresenter::queuedToast('restore.execute')
            ->actions([
                Actions\Action::make('view_run')
                    ->label('View run')
                    ->url(OperationRunLinks::view($opRun, $tenant)),
            ])
            ->send();

        return $restoreRun->refresh();
    }

    /**
     * @param  array<int>|null  $selectedItemIds
     * @return array<int, array{id:string,label:string}>
     */
    private static function unresolvedGroups(?int $backupSetId, ?array $selectedItemIds, Tenant $tenant): array
    {
        if (! $backupSetId) {
            return [];
        }

        $query = BackupItem::query()->where('backup_set_id', $backupSetId);

        if ($selectedItemIds !== null) {
            $query->whereIn('id', $selectedItemIds);
        }

        $items = $query->get(['assignments']);
        $assignments = [];
        $sourceNames = [];

        foreach ($items as $item) {
            if (! is_array($item->assignments) || $item->assignments === []) {
                continue;
            }

            foreach ($item->assignments as $assignment) {
                if (! is_array($assignment)) {
                    continue;
                }

                $target = $assignment['target'] ?? [];
                $odataType = $target['@odata.type'] ?? '';

                if (! in_array($odataType, [
                    '#microsoft.graph.groupAssignmentTarget',
                    '#microsoft.graph.exclusionGroupAssignmentTarget',
                ], true)) {
                    continue;
                }

                $groupId = $target['groupId'] ?? null;

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

                $assignments[] = $groupId;
                $displayName = $target['group_display_name'] ?? null;

                if (is_string($displayName) && $displayName !== '') {
                    $sourceNames[$groupId] = $displayName;
                }
            }
        }

        $groupIds = array_values(array_unique($assignments));

        if ($groupIds === []) {
            return [];
        }

        $resolver = app(EntraGroupLabelResolver::class);
        $cached = $resolver->lookupMany($tenant, $groupIds);

        return array_map(function (string $groupId) use ($sourceNames, $cached): array {
            $cachedName = $cached[strtolower($groupId)] ?? null;
            $fallbackName = $cachedName ?? ($sourceNames[$groupId] ?? null);

            return [
                'id' => $groupId,
                'label' => EntraGroupLabelResolver::formatLabel($fallbackName, $groupId),
            ];
        }, $groupIds);
    }

    /**
     * @param  array<int>|null  $selectedItemIds
     * @return array<string, string|null>
     */
    private static function groupMappingPlaceholders(?int $backupSetId, string $scopeMode, ?array $selectedItemIds, ?Tenant $tenant): array
    {
        if (! $tenant || ! $backupSetId) {
            return [];
        }

        if ($scopeMode === 'selected' && ($selectedItemIds === null || $selectedItemIds === [])) {
            return [];
        }

        $unresolved = static::unresolvedGroups(
            backupSetId: $backupSetId,
            selectedItemIds: $scopeMode === 'selected' ? $selectedItemIds : null,
            tenant: $tenant,
        );

        $placeholders = [];

        foreach ($unresolved as $group) {
            $groupId = $group['id'] ?? null;

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

            $placeholders[$groupId] = null;
        }

        return $placeholders;
    }

    /**
     * @return array<string, string>
     */
    private static function normalizeGroupMapping(mixed $mapping): array
    {
        if ($mapping instanceof \Illuminate\Contracts\Support\Arrayable) {
            $mapping = $mapping->toArray();
        }

        if ($mapping instanceof \stdClass) {
            $mapping = (array) $mapping;
        }

        if (! is_array($mapping)) {
            return [];
        }

        $result = [];

        if (array_key_exists('group_mapping', $mapping)) {
            $nested = $mapping['group_mapping'];

            if ($nested instanceof \Illuminate\Contracts\Support\Arrayable) {
                $nested = $nested->toArray();
            }

            if ($nested instanceof \stdClass) {
                $nested = (array) $nested;
            }

            if (is_array($nested)) {
                $mapping = $nested;
            }
        }

        foreach ($mapping as $key => $value) {
            if (! is_string($key) || $key === '') {
                continue;
            }

            $sourceGroupId = str_starts_with($key, 'group_mapping.')
                ? substr($key, strlen('group_mapping.'))
                : $key;

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

            if ($value instanceof BackedEnum) {
                $value = $value->value;
            }

            if (is_array($value) || $value instanceof \stdClass) {
                $value = (array) $value;
                $value = $value['value'] ?? $value['id'] ?? null;
            }

            if (is_string($value)) {
                $value = trim($value);
                $result[$sourceGroupId] = $value !== '' ? $value : null;

                continue;
            }

            $result[$sourceGroupId] = null;
        }

        return array_filter($result, static fn (?string $value): bool => is_string($value) && $value !== '');
    }

    /**
     * Build the rerun table action with UiEnforcement + write gate disabled state.
     *
     * UiEnforcement::apply() overrides ->disabled() and ->tooltip(), so the gate
     * check must compose on top of the enforcement action AFTER apply(). This method
     * extracts the rerun action into its own builder to keep the table definition clean.
     */
    private static function rerunActionWithGate(): Actions\Action|BulkAction
    {
        /** @var Actions\Action $action */
        $action = UiEnforcement::forTableAction(
            Actions\Action::make('rerun')
                ->label('Rerun')
                ->icon('heroicon-o-arrow-path')
                ->color('primary')
                ->requiresConfirmation()
                ->visible(function (RestoreRun $record): bool {
                    $backupSet = $record->backupSet;

                    return ! $record->trashed()
                        && $record->isDeletable()
                        && $backupSet !== null
                        && ! $backupSet->trashed();
                })
                ->action(function (
                    RestoreRun $record,
                    RestoreService $restoreService,
                    \App\Services\Intune\AuditLogger $auditLogger,
                    HasTable $livewire
                ) {
                    $tenant = $record->tenant;
                    $backupSet = $record->backupSet;

                    if ($record->trashed() || ! $tenant || ! $backupSet || $backupSet->trashed()) {
                        Notification::make()
                            ->title('Restore run cannot be rerun')
                            ->body('Restore run or backup set is archived or unavailable.')
                            ->warning()
                            ->send();

                        return;
                    }

                    try {
                        app(WriteGateInterface::class)->evaluate($tenant, 'restore.rerun');
                    } catch (ProviderAccessHardeningRequired $e) {
                        app(\App\Services\Intune\AuditLogger::class)->log(
                            tenant: $tenant,
                            action: 'intune_rbac.write_blocked',
                            status: 'blocked',
                            actorEmail: auth()->user()?->email,
                            actorName: auth()->user()?->name,
                            resourceType: 'restore_run',
                            resourceId: (string) $record->getKey(),
                            context: [
                                'metadata' => [
                                    'operation_type' => 'restore.rerun',
                                    'reason_code' => $e->reasonCode,
                                    'backup_set_id' => $backupSet?->getKey(),
                                    'original_restore_run_id' => $record->getKey(),
                                ],
                            ],
                        );

                        Notification::make()
                            ->title('Write operation blocked')
                            ->body($e->reasonMessage)
                            ->danger()
                            ->send();

                        return;
                    }

                    if (! (bool) $record->is_dry_run) {
                        $selectedItemIds = is_array($record->requested_items) ? $record->requested_items : null;
                        $groupMapping = is_array($record->group_mapping) ? $record->group_mapping : [];
                        $actorEmail = auth()->user()?->email;
                        $actorName = auth()->user()?->name;
                        $tenantIdentifier = $tenant->tenant_id ?? $tenant->external_id;
                        $highlanderLabel = (string) ($tenant->name ?? $tenantIdentifier ?? $tenant->getKey());

                        $preview = $restoreService->preview($tenant, $backupSet, $selectedItemIds);
                        $metadata = [
                            'scope_mode' => $selectedItemIds === null ? 'all' : 'selected',
                            'environment' => app()->environment('production') ? 'prod' : 'test',
                            'highlander_label' => $highlanderLabel,
                            'confirmed_at' => now()->toIso8601String(),
                            'confirmed_by' => $actorEmail,
                            'confirmed_by_name' => $actorName,
                            'rerun_of_restore_run_id' => $record->id,
                        ];

                        $idempotencyKey = RestoreRunIdempotency::restoreExecuteKey(
                            tenantId: (int) $tenant->getKey(),
                            backupSetId: (int) $backupSet->getKey(),
                            selectedItemIds: $selectedItemIds,
                            groupMapping: $groupMapping,
                        );

                        $existing = RestoreRunIdempotency::findActiveRestoreRun((int) $tenant->getKey(), $idempotencyKey);

                        if ($existing) {
                            $existingOpRunId = (int) ($existing->operation_run_id ?? 0);
                            $existingOpRun = $existingOpRunId > 0
                                ? \App\Models\OperationRun::query()->find($existingOpRunId)
                                : null;

                            OpsUxBrowserEvents::dispatchRunEnqueued($livewire);

                            $toast = OperationUxPresenter::alreadyQueuedToast('restore.execute')
                                ->body('Reusing the active restore run.');

                            if ($existingOpRun) {
                                $toast->actions([
                                    Action::make('view_run')
                                        ->label('View run')
                                        ->url(OperationRunLinks::view($existingOpRun, $tenant)),
                                ]);
                            }

                            $toast->send();

                            return;
                        }

                        try {
                            $newRun = RestoreRun::create([
                                'tenant_id' => $tenant->id,
                                'backup_set_id' => $backupSet->id,
                                'requested_by' => $actorEmail,
                                'is_dry_run' => false,
                                'status' => RestoreRunStatus::Queued->value,
                                'idempotency_key' => $idempotencyKey,
                                'requested_items' => $selectedItemIds,
                                'preview' => $preview,
                                'metadata' => $metadata,
                                'group_mapping' => $groupMapping !== [] ? $groupMapping : null,
                            ]);
                        } catch (QueryException $exception) {
                            $existing = RestoreRunIdempotency::findActiveRestoreRun((int) $tenant->getKey(), $idempotencyKey);

                            if ($existing) {
                                $existingOpRunId = (int) ($existing->operation_run_id ?? 0);
                                $existingOpRun = $existingOpRunId > 0
                                    ? \App\Models\OperationRun::query()->find($existingOpRunId)
                                    : null;

                                OpsUxBrowserEvents::dispatchRunEnqueued($livewire);

                                $toast = OperationUxPresenter::alreadyQueuedToast('restore.execute')
                                    ->body('Reusing the active restore run.');

                                if ($existingOpRun) {
                                    $toast->actions([
                                        Action::make('view_run')
                                            ->label('View run')
                                            ->url(OperationRunLinks::view($existingOpRun, $tenant)),
                                    ]);
                                }

                                $toast->send();

                                return;
                            }

                            throw $exception;
                        }

                        $auditLogger->log(
                            tenant: $tenant,
                            action: 'restore.queued',
                            context: [
                                'metadata' => [
                                    'restore_run_id' => $newRun->id,
                                    'backup_set_id' => $backupSet->id,
                                    'rerun_of_restore_run_id' => $record->id,
                                ],
                            ],
                            actorEmail: $actorEmail,
                            actorName: $actorName,
                            resourceType: 'restore_run',
                            resourceId: (string) $newRun->id,
                            status: 'success',
                        );

                        /** @var OperationRunService $runs */
                        $runs = app(OperationRunService::class);
                        $initiator = auth()->user();
                        $initiator = $initiator instanceof \App\Models\User ? $initiator : null;

                        $opRun = $runs->ensureRun(
                            tenant: $tenant,
                            type: 'restore.execute',
                            inputs: [
                                'restore_run_id' => (int) $newRun->getKey(),
                                'backup_set_id' => (int) $backupSet->getKey(),
                                'is_dry_run' => (bool) ($newRun->is_dry_run ?? false),
                            ],
                            initiator: $initiator,
                        );

                        if ((int) ($newRun->operation_run_id ?? 0) !== (int) $opRun->getKey()) {
                            $newRun->update(['operation_run_id' => $opRun->getKey()]);
                        }

                        ExecuteRestoreRunJob::dispatch($newRun->id, $actorEmail, $actorName, $opRun);

                        $auditLogger->log(
                            tenant: $tenant,
                            action: 'restore_run.rerun',
                            resourceType: 'restore_run',
                            resourceId: (string) $newRun->id,
                            status: 'success',
                            context: [
                                'metadata' => [
                                    'original_restore_run_id' => $record->id,
                                    'backup_set_id' => $backupSet->id,
                                ],
                            ],
                            actorEmail: $actorEmail,
                            actorName: $actorName,
                        );

                        OpsUxBrowserEvents::dispatchRunEnqueued($livewire);
                        OperationUxPresenter::queuedToast('restore.execute')
                            ->actions([
                                Action::make('view_run')
                                    ->label('View run')
                                    ->url(OperationRunLinks::view($opRun, $tenant)),
                            ])
                            ->send();

                        return;
                    }

                    try {
                        $newRun = $restoreService->execute(
                            tenant: $tenant,
                            backupSet: $backupSet,
                            selectedItemIds: $record->requested_items ?? null,
                            dryRun: (bool) $record->is_dry_run,
                            actorEmail: auth()->user()?->email,
                            actorName: auth()->user()?->name,
                            groupMapping: $record->group_mapping ?? []
                        );
                    } catch (\Throwable $throwable) {
                        Notification::make()
                            ->title('Restore run failed to start')
                            ->body($throwable->getMessage())
                            ->danger()
                            ->send();

                        return;
                    }

                    $auditLogger->log(
                        tenant: $tenant,
                        action: 'restore_run.rerun',
                        resourceType: 'restore_run',
                        resourceId: (string) $newRun->id,
                        status: 'success',
                        context: [
                            'metadata' => [
                                'original_restore_run_id' => $record->id,
                                'backup_set_id' => $backupSet->id,
                            ],
                        ]
                    );

                    OpsUxBrowserEvents::dispatchRunEnqueued($livewire);
                    OperationUxPresenter::queuedToast('restore.execute')
                        ->send();
                }),
            fn () => Tenant::current(),
        )
            ->requireCapability(Capabilities::TENANT_MANAGE)
            ->preserveVisibility()
            ->apply();

        // Compose write gate disabled/tooltip on top of UiEnforcement's RBAC check.
        // UiEnforcement::apply() sets its own ->disabled() / ->tooltip();
        // we override here to merge both concerns.
        $action->disabled(function (?Model $record = null): bool {
            $user = auth()->user();

            if (! $user instanceof User) {
                return true;
            }

            $tenant = $record instanceof RestoreRun ? $record->tenant : Tenant::current();

            if (! $tenant instanceof Tenant) {
                return true;
            }

            // Check RBAC capability first (mirrors UiEnforcement logic)
            $resolver = app(CapabilityResolver::class);

            if (! $resolver->can($user, $tenant, Capabilities::TENANT_MANAGE)) {
                return true;
            }

            // Then check write gate
            return app(WriteGateInterface::class)->wouldBlock($tenant);
        });

        $action->tooltip(function (?Model $record = null): ?string {
            $user = auth()->user();

            if (! $user instanceof User) {
                return \App\Support\Auth\UiTooltips::insufficientPermission();
            }

            $tenant = $record instanceof RestoreRun ? $record->tenant : Tenant::current();

            if (! $tenant instanceof Tenant) {
                return 'Tenant unavailable';
            }

            // Check RBAC capability first
            $resolver = app(CapabilityResolver::class);

            if (! $resolver->can($user, $tenant, Capabilities::TENANT_MANAGE)) {
                return \App\Support\Auth\UiTooltips::insufficientPermission();
            }

            // Then check write gate
            try {
                app(WriteGateInterface::class)->evaluate($tenant, 'restore.rerun');
            } catch (ProviderAccessHardeningRequired $e) {
                return $e->reasonMessage;
            }

            return null;
        });

        return $action;
    }
}
