<?php

namespace App\Filament\Resources;

use App\Filament\Resources\FindingResource\Pages;
use App\Models\Finding;
use App\Models\InventoryItem;
use App\Models\PolicyVersion;
use App\Models\Tenant;
use App\Models\TenantMembership;
use App\Models\User;
use App\Services\Drift\DriftFindingDiffBuilder;
use App\Services\Findings\FindingWorkflowService;
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\Filament\TablePaginationProfiles;
use App\Support\Navigation\CanonicalNavigationContext;
use App\Support\Navigation\CrossResourceNavigationMatrix;
use App\Support\Navigation\RelatedContextEntry;
use App\Support\Navigation\RelatedNavigationResolver;
use App\Support\OperationRunLinks;
use App\Support\Rbac\UiEnforcement;
use App\Support\Rbac\UiTooltips;
use App\Support\RedactionIntegrity;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceSlot;
use BackedEnum;
use Filament\Actions;
use Filament\Actions\BulkAction;
use Filament\Actions\BulkActionGroup;
use Filament\Facades\Filament;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\TextEntry;
use Filament\Infolists\Components\ViewEntry;
use Filament\Notifications\Notification;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use InvalidArgumentException;
use Throwable;
use UnitEnum;

class FindingResource extends Resource
{
    protected static ?string $model = Finding::class;

    protected static ?string $tenantOwnershipRelationshipName = 'tenant';

    protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-exclamation-triangle';

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

    protected static ?string $navigationLabel = 'Findings';

    public static function canViewAny(): bool
    {
        $tenant = Tenant::current();

        $user = auth()->user();

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

        if (! $user->canAccessTenant($tenant)) {
            return false;
        }

        return $user->can(Capabilities::TENANT_FINDINGS_VIEW, $tenant);
    }

    public static function canView(Model $record): bool
    {
        $tenant = Tenant::current();

        $user = auth()->user();

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

        if (! $user->canAccessTenant($tenant)) {
            return false;
        }

        if (! $user->can(Capabilities::TENANT_FINDINGS_VIEW, $tenant)) {
            return false;
        }

        if ($record instanceof Finding) {
            return (int) $record->tenant_id === (int) $tenant->getKey();
        }

        return true;
    }

    public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
    {
        return ActionSurfaceDeclaration::forResource(ActionSurfaceProfile::CrudListAndView)
            ->satisfy(ActionSurfaceSlot::ListHeader, 'Header actions support filtered findings operations (legacy acknowledge-all-matching remains until bulk workflow migration).')
            ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value)
            ->satisfy(ActionSurfaceSlot::ListRowMoreMenu, 'Secondary row actions are grouped under "More".')
            ->satisfy(ActionSurfaceSlot::ListBulkMoreGroup, 'Bulk actions are grouped under "More".')
            ->exempt(ActionSurfaceSlot::ListEmptyState, 'Findings are generated by drift detection and intentionally have no create CTA.')
            ->satisfy(ActionSurfaceSlot::DetailHeader, 'View page exposes capability-gated workflow actions for finding lifecycle management.');
    }

    public static function form(Schema $schema): Schema
    {
        return $schema;
    }

    public static function infolist(Schema $schema): Schema
    {
        return $schema
            ->schema([
                Section::make('Finding')
                    ->schema([
                        TextEntry::make('finding_type')->badge()->label('Type'),
                        TextEntry::make('drift_surface_label')
                            ->label('Drift surface')
                            ->badge()
                            ->color('gray')
                            ->state(fn (Finding $record): ?string => static::driftSurfaceLabel($record))
                            ->visible(fn (Finding $record): bool => static::driftSurfaceLabel($record) !== null),
                        TextEntry::make('evidence_fidelity')
                            ->label('Fidelity')
                            ->badge()
                            ->formatStateUsing(fn (?string $state): string => is_string($state) && $state !== '' ? $state : 'meta')
                            ->color(fn (?string $state): string => match ((string) $state) {
                                'content' => 'success',
                                'meta' => 'gray',
                                default => 'gray',
                            }),
                        TextEntry::make('status')
                            ->badge()
                            ->formatStateUsing(BadgeRenderer::label(BadgeDomain::FindingStatus))
                            ->color(BadgeRenderer::color(BadgeDomain::FindingStatus))
                            ->icon(BadgeRenderer::icon(BadgeDomain::FindingStatus))
                            ->iconColor(BadgeRenderer::iconColor(BadgeDomain::FindingStatus)),
                        TextEntry::make('severity')
                            ->badge()
                            ->formatStateUsing(BadgeRenderer::label(BadgeDomain::FindingSeverity))
                            ->color(BadgeRenderer::color(BadgeDomain::FindingSeverity))
                            ->icon(BadgeRenderer::icon(BadgeDomain::FindingSeverity))
                            ->iconColor(BadgeRenderer::iconColor(BadgeDomain::FindingSeverity)),
                        TextEntry::make('fingerprint')->label('Fingerprint')->copyable(),
                        TextEntry::make('scope_key')->label('Scope')->copyable(),
                        TextEntry::make('subject_display_name')
                            ->label('Subject')
                            ->placeholder('—')
                            ->state(function (Finding $record): ?string {
                                $state = $record->subject_display_name;
                                if (is_string($state) && trim($state) !== '') {
                                    return $state;
                                }

                                $fallback = Arr::get($record->evidence_jsonb ?? [], 'display_name');
                                $fallback = is_string($fallback) ? trim($fallback) : null;

                                return $fallback !== '' ? $fallback : null;
                            }),
                        TextEntry::make('subject_type')
                            ->label('Subject type')
                            ->formatStateUsing(fn (mixed $state, Finding $record): string => static::subjectTypeLabel($record, $state)),
                        TextEntry::make('subject_external_id')->label('External ID')->copyable(),
                        TextEntry::make('baseline_operation_run_id')
                            ->label('Baseline run')
                            ->formatStateUsing(static fn (?int $state): string => $state ? '#'.$state : '—')
                            ->url(fn (Finding $record): ?string => $record->baseline_operation_run_id
                                ? OperationRunLinks::tenantlessView((int) $record->baseline_operation_run_id, static::findingRunNavigationContext($record))
                                : null)
                            ->openUrlInNewTab(),
                        TextEntry::make('current_operation_run_id')
                            ->label('Current run')
                            ->formatStateUsing(static fn (?int $state): string => $state ? '#'.$state : '—')
                            ->url(fn (Finding $record): ?string => $record->current_operation_run_id
                                ? OperationRunLinks::tenantlessView((int) $record->current_operation_run_id, static::findingRunNavigationContext($record))
                                : null)
                            ->openUrlInNewTab(),
                        TextEntry::make('acknowledged_at')->dateTime()->placeholder('—'),
                        TextEntry::make('acknowledged_by_user_id')->label('Acknowledged by')->placeholder('—'),
                        TextEntry::make('first_seen_at')->label('First seen')->dateTime()->placeholder('—'),
                        TextEntry::make('last_seen_at')->label('Last seen')->dateTime()->placeholder('—'),
                        TextEntry::make('times_seen')->label('Times seen')->placeholder('—'),
                        TextEntry::make('sla_days')->label('SLA days')->placeholder('—'),
                        TextEntry::make('due_at')->label('Due at')->dateTime()->placeholder('—'),
                        TextEntry::make('owner_user_id')
                            ->label('Owner')
                            ->formatStateUsing(fn (mixed $state, Finding $record): string => $record->ownerUser?->name ?? ($state ? 'User #'.$state : '—')),
                        TextEntry::make('assignee_user_id')
                            ->label('Assignee')
                            ->formatStateUsing(fn (mixed $state, Finding $record): string => $record->assigneeUser?->name ?? ($state ? 'User #'.$state : '—')),
                        TextEntry::make('triaged_at')->label('Triaged at')->dateTime()->placeholder('—'),
                        TextEntry::make('in_progress_at')->label('In progress at')->dateTime()->placeholder('—'),
                        TextEntry::make('reopened_at')->label('Reopened at')->dateTime()->placeholder('—'),
                        TextEntry::make('resolved_at')->label('Resolved at')->dateTime()->placeholder('—'),
                        TextEntry::make('resolved_reason')->label('Resolved reason')->placeholder('—'),
                        TextEntry::make('closed_at')->label('Closed at')->dateTime()->placeholder('—'),
                        TextEntry::make('closed_reason')->label('Closed/risk reason')->placeholder('—'),
                        TextEntry::make('closed_by_user_id')
                            ->label('Closed by')
                            ->formatStateUsing(fn (mixed $state, Finding $record): string => $record->closedByUser?->name ?? ($state ? 'User #'.$state : '—')),
                        TextEntry::make('created_at')->label('Created')->dateTime(),
                    ])
                    ->columns(2)
                    ->columnSpanFull(),

                Section::make('Evidence')
                    ->schema([
                        TextEntry::make('redaction_integrity_note')
                            ->label('Integrity note')
                            ->state(fn (Finding $record): ?string => static::redactionIntegrityNoteForRecord($record))
                            ->columnSpanFull()
                            ->visible(fn (Finding $record): bool => static::redactionIntegrityNoteForRecord($record) !== null),
                        TextEntry::make('baseline_evidence_fidelity')
                            ->label('Baseline fidelity')
                            ->badge()
                            ->state(fn (Finding $record): ?string => Arr::get($record->evidence_jsonb ?? [], 'baseline.provenance.fidelity'))
                            ->color(fn (?string $state): string => match ((string) $state) {
                                'content' => 'success',
                                'meta' => 'gray',
                                default => 'gray',
                            })
                            ->placeholder('—'),
                        TextEntry::make('baseline_evidence_source')
                            ->label('Baseline source')
                            ->state(fn (Finding $record): ?string => Arr::get($record->evidence_jsonb ?? [], 'baseline.provenance.source'))
                            ->placeholder('—'),
                        TextEntry::make('baseline_evidence_observed_at')
                            ->label('Baseline observed at')
                            ->state(fn (Finding $record): ?string => Arr::get($record->evidence_jsonb ?? [], 'baseline.provenance.observed_at'))
                            ->placeholder('—')
                            ->copyable(),

                        TextEntry::make('current_evidence_fidelity')
                            ->label('Current fidelity')
                            ->badge()
                            ->state(fn (Finding $record): ?string => Arr::get($record->evidence_jsonb ?? [], 'current.provenance.fidelity'))
                            ->color(fn (?string $state): string => match ((string) $state) {
                                'content' => 'success',
                                'meta' => 'gray',
                                default => 'gray',
                            })
                            ->placeholder('—'),
                        TextEntry::make('current_evidence_source')
                            ->label('Current source')
                            ->state(fn (Finding $record): ?string => Arr::get($record->evidence_jsonb ?? [], 'current.provenance.source'))
                            ->placeholder('—'),
                        TextEntry::make('current_evidence_observed_at')
                            ->label('Current observed at')
                            ->state(fn (Finding $record): ?string => Arr::get($record->evidence_jsonb ?? [], 'current.provenance.observed_at'))
                            ->placeholder('—')
                            ->copyable(),
                    ])
                    ->columns(3)
                    ->visible(function (Finding $record): bool {
                        $evidence = is_array($record->evidence_jsonb) ? $record->evidence_jsonb : [];

                        return Arr::has($evidence, 'baseline.provenance') || Arr::has($evidence, 'current.provenance');
                    })
                    ->columnSpanFull(),

                Section::make('Related context')
                    ->schema([
                        ViewEntry::make('related_context')
                            ->label('')
                            ->view('filament.infolists.entries.related-context')
                            ->state(fn (Finding $record): array => static::relatedContextEntries($record))
                            ->columnSpanFull(),
                    ])
                    ->columnSpanFull(),

                Section::make('Diff')
                    ->visible(fn (Finding $record): bool => $record->finding_type === Finding::FINDING_TYPE_DRIFT)
                    ->schema([
                        TextEntry::make('diff_unavailable')
                            ->label('')
                            ->state(fn (Finding $record): string => static::driftDiffUnavailableMessage($record))
                            ->visible(fn (Finding $record): bool => ! static::canRenderDriftDiff($record))
                            ->columnSpanFull(),
                        ViewEntry::make('rbac_role_definition_diff')
                            ->label('')
                            ->view('filament.infolists.entries.rbac-role-definition-diff')
                            ->state(fn (Finding $record): array => Arr::get($record->evidence_jsonb ?? [], 'rbac_role_definition', []))
                            ->visible(fn (Finding $record): bool => static::driftSummaryKind($record) === 'rbac_role_definition' && is_array(Arr::get($record->evidence_jsonb ?? [], 'rbac_role_definition')))
                            ->columnSpanFull(),
                        ViewEntry::make('settings_diff')
                            ->label('')
                            ->view('filament.infolists.entries.normalized-diff')
                            ->state(function (Finding $record): array {
                                $tenant = Tenant::current();
                                if (! $tenant) {
                                    return static::unavailableDiffState('No tenant context');
                                }

                                [$baselineVersion, $currentVersion] = static::resolveDriftDiffVersions($record, $tenant);

                                if (! static::hasRequiredDiffVersions($record, $baselineVersion, $currentVersion)) {
                                    return static::unavailableDiffState('Diff unavailable — referenced policy versions are missing.');
                                }

                                $diff = app(DriftFindingDiffBuilder::class)->buildSettingsDiff($baselineVersion, $currentVersion);

                                $addedCount = (int) Arr::get($diff, 'summary.added', 0);
                                $removedCount = (int) Arr::get($diff, 'summary.removed', 0);
                                $changedCount = (int) Arr::get($diff, 'summary.changed', 0);

                                if (($addedCount + $removedCount + $changedCount) === 0) {
                                    Arr::set(
                                        $diff,
                                        'summary.message',
                                        'No normalized changes were found. This drift finding may be based on fields that are intentionally excluded from normalization.'
                                    );
                                }

                                return $diff;
                            })
                            ->visible(fn (Finding $record): bool => static::canRenderDriftDiff($record) && Arr::get($record->evidence_jsonb ?? [], 'summary.kind') === 'policy_snapshot')
                            ->columnSpanFull(),

                        ViewEntry::make('scope_tags_diff')
                            ->label('')
                            ->view('filament.infolists.entries.scope-tags-diff')
                            ->state(function (Finding $record): array {
                                $tenant = Tenant::current();
                                if (! $tenant) {
                                    return static::unavailableDiffState('No tenant context');
                                }

                                [$baselineVersion, $currentVersion] = static::resolveDriftDiffVersions($record, $tenant);

                                if (! static::hasRequiredDiffVersions($record, $baselineVersion, $currentVersion)) {
                                    return static::unavailableDiffState('Diff unavailable — referenced policy versions are missing.');
                                }

                                return app(DriftFindingDiffBuilder::class)->buildScopeTagsDiff($baselineVersion, $currentVersion);
                            })
                            ->visible(fn (Finding $record): bool => static::canRenderDriftDiff($record) && Arr::get($record->evidence_jsonb ?? [], 'summary.kind') === 'policy_scope_tags')
                            ->columnSpanFull(),

                        ViewEntry::make('assignments_diff')
                            ->label('')
                            ->view('filament.infolists.entries.assignments-diff')
                            ->state(function (Finding $record): array {
                                $tenant = Tenant::current();
                                if (! $tenant) {
                                    return static::unavailableDiffState('No tenant context');
                                }

                                [$baselineVersion, $currentVersion] = static::resolveDriftDiffVersions($record, $tenant);

                                if (! static::hasRequiredDiffVersions($record, $baselineVersion, $currentVersion)) {
                                    return static::unavailableDiffState('Diff unavailable — referenced policy versions are missing.');
                                }

                                return app(DriftFindingDiffBuilder::class)->buildAssignmentsDiff($tenant, $baselineVersion, $currentVersion);
                            })
                            ->visible(fn (Finding $record): bool => static::canRenderDriftDiff($record) && Arr::get($record->evidence_jsonb ?? [], 'summary.kind') === 'policy_assignments')
                            ->columnSpanFull(),
                    ])
                    ->collapsed()
                    ->columnSpanFull(),

                Section::make('Evidence (Sanitized)')
                    ->schema([
                        ViewEntry::make('evidence_jsonb')
                            ->label('')
                            ->view('filament.infolists.entries.snapshot-json')
                            ->state(fn (Finding $record) => $record->evidence_jsonb ?? [])
                            ->columnSpanFull(),
                    ])
                    ->columnSpanFull(),
            ]);
    }

    private static function driftChangeType(Finding $record): string
    {
        $changeType = Arr::get($record->evidence_jsonb ?? [], 'change_type');

        return is_string($changeType) ? trim($changeType) : '';
    }

    private static function driftSummaryKind(Finding $record): string
    {
        $summaryKind = Arr::get($record->evidence_jsonb ?? [], 'summary.kind');

        return is_string($summaryKind) ? trim($summaryKind) : '';
    }

    private static function isRbacRoleDefinitionDrift(Finding $record): bool
    {
        return static::driftSummaryKind($record) === 'rbac_role_definition'
            || (string) Arr::get($record->evidence_jsonb ?? [], 'policy_type') === 'intuneRoleDefinition';
    }

    private static function driftSurfaceLabel(Finding $record): ?string
    {
        if (static::isRbacRoleDefinitionDrift($record)) {
            return __('findings.drift.rbac_role_definition');
        }

        return null;
    }

    private static function subjectTypeLabel(Finding $record, mixed $state): string
    {
        $policyType = Arr::get($record->evidence_jsonb ?? [], 'policy_type');

        if (is_string($policyType) && $policyType !== '') {
            $translated = __('findings.subject_types.'.$policyType);

            if ($translated !== 'findings.subject_types.'.$policyType) {
                return $translated;
            }
        }

        $value = is_string($state) ? trim($state) : '';

        return $value !== '' ? $value : '—';
    }

    private static function driftContextDescription(Finding $record): ?string
    {
        if (! static::isRbacRoleDefinitionDrift($record)) {
            return null;
        }

        $parts = [__('findings.drift.rbac_role_definition')];
        $diffKind = Arr::get($record->evidence_jsonb ?? [], 'rbac_role_definition.diff_kind');

        if (is_string($diffKind) && $diffKind !== '') {
            $parts[] = __('findings.rbac.'.$diffKind);
        }

        return implode(' | ', array_filter($parts, fn (?string $part): bool => is_string($part) && $part !== ''));
    }

    public static function findingSubheading(Finding $record): ?string
    {
        $parts = [];

        if (static::isRbacRoleDefinitionDrift($record)) {
            $parts[] = __('findings.rbac.detail_heading');
            $parts[] = __('findings.rbac.detail_subheading');
        }

        $integrity = static::redactionIntegrityNoteForRecord($record);

        if (is_string($integrity) && trim($integrity) !== '') {
            $parts[] = $integrity;
        }

        return $parts !== [] ? implode(' ', $parts) : null;
    }

    private static function hasBaselinePolicyVersionReference(Finding $record): bool
    {
        return is_numeric(Arr::get($record->evidence_jsonb ?? [], 'baseline.policy_version_id'));
    }

    private static function hasCurrentPolicyVersionReference(Finding $record): bool
    {
        return is_numeric(Arr::get($record->evidence_jsonb ?? [], 'current.policy_version_id'));
    }

    private static function canRenderDriftDiff(Finding $record): bool
    {
        if (static::driftSummaryKind($record) === 'rbac_role_definition') {
            return is_array(Arr::get($record->evidence_jsonb ?? [], 'rbac_role_definition'));
        }

        return match (static::driftChangeType($record)) {
            'missing_policy' => static::hasBaselinePolicyVersionReference($record),
            'unexpected_policy' => static::hasCurrentPolicyVersionReference($record),
            default => static::hasBaselinePolicyVersionReference($record) && static::hasCurrentPolicyVersionReference($record),
        };
    }

    public static function redactionIntegrityNoteForRecord(Finding $record): ?string
    {
        $evidence = is_array($record->evidence_jsonb) ? $record->evidence_jsonb : [];

        return RedactionIntegrity::noteForFindingEvidence($evidence);
    }

    private static function driftDiffUnavailableMessage(Finding $record): string
    {
        if (static::driftSummaryKind($record) === 'rbac_role_definition') {
            return 'RBAC evidence unavailable — normalized role definition evidence is missing.';
        }

        return match (static::driftChangeType($record)) {
            'missing_policy' => 'Diff unavailable — missing baseline policy version reference.',
            'unexpected_policy' => 'Diff unavailable — missing current policy version reference.',
            default => 'Diff unavailable — missing baseline/current policy version references.',
        };
    }

    /**
     * @return array{0: ?PolicyVersion, 1: ?PolicyVersion}
     */
    private static function resolveDriftDiffVersions(Finding $record, Tenant $tenant): array
    {
        $baselineId = Arr::get($record->evidence_jsonb ?? [], 'baseline.policy_version_id');
        $currentId = Arr::get($record->evidence_jsonb ?? [], 'current.policy_version_id');

        $baselineVersion = is_numeric($baselineId)
            ? PolicyVersion::query()->where('tenant_id', $tenant->getKey())->find((int) $baselineId)
            : null;

        $currentVersion = is_numeric($currentId)
            ? PolicyVersion::query()->where('tenant_id', $tenant->getKey())->find((int) $currentId)
            : null;

        return [$baselineVersion, $currentVersion];
    }

    private static function hasRequiredDiffVersions(
        Finding $record,
        ?PolicyVersion $baselineVersion,
        ?PolicyVersion $currentVersion,
    ): bool {
        return match (static::driftChangeType($record)) {
            'missing_policy' => $baselineVersion instanceof PolicyVersion,
            'unexpected_policy' => $currentVersion instanceof PolicyVersion,
            default => $baselineVersion instanceof PolicyVersion && $currentVersion instanceof PolicyVersion,
        };
    }

    /**
     * @return array{summary: array{message: string}, added: array<int, mixed>, removed: array<int, mixed>, changed: array<int, mixed>}
     */
    private static function unavailableDiffState(string $message): array
    {
        return [
            'summary' => ['message' => $message],
            'added' => [],
            'removed' => [],
            'changed' => [],
        ];
    }

    public static function table(Table $table): Table
    {
        return $table
            ->defaultSort('created_at', 'desc')
            ->paginated(TablePaginationProfiles::resource())
            ->persistFiltersInSession()
            ->persistSearchInSession()
            ->persistSortInSession()
            ->columns([
                Tables\Columns\TextColumn::make('finding_type')->badge()->label('Type'),
                Tables\Columns\TextColumn::make('status')
                    ->badge()
                    ->formatStateUsing(BadgeRenderer::label(BadgeDomain::FindingStatus))
                    ->color(BadgeRenderer::color(BadgeDomain::FindingStatus))
                    ->icon(BadgeRenderer::icon(BadgeDomain::FindingStatus))
                    ->iconColor(BadgeRenderer::iconColor(BadgeDomain::FindingStatus)),
                Tables\Columns\TextColumn::make('severity')
                    ->badge()
                    ->formatStateUsing(BadgeRenderer::label(BadgeDomain::FindingSeverity))
                    ->color(BadgeRenderer::color(BadgeDomain::FindingSeverity))
                    ->icon(BadgeRenderer::icon(BadgeDomain::FindingSeverity))
                    ->iconColor(BadgeRenderer::iconColor(BadgeDomain::FindingSeverity)),
                Tables\Columns\TextColumn::make('evidence_fidelity')
                    ->label('Fidelity')
                    ->badge()
                    ->formatStateUsing(fn (?string $state): string => is_string($state) && $state !== '' ? $state : 'meta')
                    ->color(fn (?string $state): string => match ((string) $state) {
                        'content' => 'success',
                        'meta' => 'gray',
                        default => 'gray',
                    })
                    ->sortable()
                    ->toggleable(isToggledHiddenByDefault: true),
                Tables\Columns\TextColumn::make('subject_display_name')
                    ->label('Subject')
                    ->placeholder('—')
                    ->searchable()
                    ->formatStateUsing(function (?string $state, Finding $record): ?string {
                        if (is_string($state) && trim($state) !== '') {
                            return $state;
                        }

                        $fallback = Arr::get($record->evidence_jsonb ?? [], 'display_name');
                        $fallback = is_string($fallback) ? trim($fallback) : null;

                        return $fallback !== '' ? $fallback : null;
                    })
                    ->description(fn (Finding $record): ?string => static::driftContextDescription($record)),
                Tables\Columns\TextColumn::make('subject_type')
                    ->label('Subject type')
                    ->searchable()
                    ->formatStateUsing(fn (mixed $state, Finding $record): string => static::subjectTypeLabel($record, $state))
                    ->toggleable(isToggledHiddenByDefault: true),
                Tables\Columns\TextColumn::make('due_at')
                    ->label('Due')
                    ->dateTime()
                    ->sortable()
                    ->placeholder('—'),
                Tables\Columns\TextColumn::make('assigneeUser.name')
                    ->label('Assignee')
                    ->placeholder('—'),
                Tables\Columns\TextColumn::make('subject_external_id')->label('External ID')->toggleable(isToggledHiddenByDefault: true),
                Tables\Columns\TextColumn::make('scope_key')->label('Scope')->toggleable(isToggledHiddenByDefault: true),
                Tables\Columns\TextColumn::make('created_at')->since()->label('Created'),
            ])
            ->filters([
                Tables\Filters\Filter::make('open')
                    ->label('Open')
                    ->default()
                    ->query(fn (Builder $query): Builder => $query->whereIn('status', Finding::openStatusesForQuery())),
                Tables\Filters\Filter::make('overdue')
                    ->label('Overdue')
                    ->query(fn (Builder $query): Builder => $query
                        ->whereIn('status', Finding::openStatusesForQuery())
                        ->whereNotNull('due_at')
                        ->where('due_at', '<', now())),
                Tables\Filters\Filter::make('high_severity')
                    ->label('High severity')
                    ->query(fn (Builder $query): Builder => $query->whereIn('severity', [
                        Finding::SEVERITY_HIGH,
                        Finding::SEVERITY_CRITICAL,
                    ])),
                Tables\Filters\Filter::make('my_assigned')
                    ->label('My assigned')
                    ->query(function (Builder $query): Builder {
                        $userId = auth()->id();

                        if (! is_numeric($userId)) {
                            return $query->whereRaw('1 = 0');
                        }

                        return $query->where('assignee_user_id', (int) $userId);
                    }),
                Tables\Filters\SelectFilter::make('status')
                    ->options(FilterOptionCatalog::findingStatuses())
                    ->label('Status'),
                Tables\Filters\SelectFilter::make('finding_type')
                    ->options([
                        Finding::FINDING_TYPE_DRIFT => 'Drift',
                        Finding::FINDING_TYPE_PERMISSION_POSTURE => 'Permission posture',
                        Finding::FINDING_TYPE_ENTRA_ADMIN_ROLES => 'Entra admin roles',
                    ])
                    ->label('Type'),
                Tables\Filters\SelectFilter::make('evidence_fidelity')
                    ->label('Fidelity')
                    ->options([
                        'content' => 'Content',
                        'meta' => 'Meta',
                    ]),
                Tables\Filters\Filter::make('scope_key')
                    ->form([
                        TextInput::make('scope_key')
                            ->label('Scope key')
                            ->placeholder('Inventory selection hash')
                            ->maxLength(255),
                    ])
                    ->query(function (Builder $query, array $data): Builder {
                        $scopeKey = $data['scope_key'] ?? null;

                        if (! is_string($scopeKey) || $scopeKey === '') {
                            return $query;
                        }

                        return $query->where('scope_key', $scopeKey);
                    }),
                Tables\Filters\Filter::make('run_ids')
                    ->label('Run IDs')
                    ->form([
                        TextInput::make('baseline_operation_run_id')
                            ->label('Baseline run id')
                            ->numeric(),
                        TextInput::make('current_operation_run_id')
                            ->label('Current run id')
                            ->numeric(),
                    ])
                    ->query(function (Builder $query, array $data): Builder {
                        $baselineRunId = $data['baseline_operation_run_id'] ?? null;
                        if (is_numeric($baselineRunId)) {
                            $query->where('baseline_operation_run_id', (int) $baselineRunId);
                        }

                        $currentRunId = $data['current_operation_run_id'] ?? null;
                        if (is_numeric($currentRunId)) {
                            $query->where('current_operation_run_id', (int) $currentRunId);
                        }

                        return $query;
                    }),
                FilterPresets::dateRange('created_at', 'Created', 'created_at'),
            ])
            ->recordUrl(static fn (Finding $record): ?string => static::canView($record)
                ? static::getUrl('view', ['record' => $record])
                : null)
            ->actions([
                static::primaryRelatedAction(),
                Actions\ActionGroup::make([
                    ...static::workflowActions(),
                ])
                    ->label('More')
                    ->icon('heroicon-o-ellipsis-vertical')
                    ->color('gray'),
            ])
            ->bulkActions([
                BulkActionGroup::make([
                    UiEnforcement::forBulkAction(
                        BulkAction::make('triage_selected')
                            ->label('Triage selected')
                            ->icon('heroicon-o-check')
                            ->color('gray')
                            ->requiresConfirmation()
                            ->action(function (Collection $records, FindingWorkflowService $workflow): void {
                                $tenant = Filament::getTenant();
                                $user = auth()->user();

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

                                $triagedCount = 0;
                                $skippedCount = 0;
                                $failedCount = 0;

                                foreach ($records as $record) {
                                    if (! $record instanceof Finding) {
                                        $skippedCount++;

                                        continue;
                                    }

                                    if ((int) $record->tenant_id !== (int) $tenant->getKey()) {
                                        $skippedCount++;

                                        continue;
                                    }

                                    if (! in_array((string) $record->status, [
                                        Finding::STATUS_NEW,
                                        Finding::STATUS_REOPENED,
                                        Finding::STATUS_ACKNOWLEDGED,
                                    ], true)) {
                                        $skippedCount++;

                                        continue;
                                    }

                                    try {
                                        $workflow->triage($record, $tenant, $user);
                                        $triagedCount++;
                                    } catch (Throwable) {
                                        $failedCount++;
                                    }
                                }

                                $body = "Triaged {$triagedCount} finding".($triagedCount === 1 ? '' : 's').'.';
                                if ($skippedCount > 0) {
                                    $body .= " Skipped {$skippedCount}.";
                                }
                                if ($failedCount > 0) {
                                    $body .= " Failed {$failedCount}.";
                                }

                                Notification::make()
                                    ->title('Bulk triage completed')
                                    ->body($body)
                                    ->status($failedCount > 0 ? 'warning' : 'success')
                                    ->send();
                            })
                            ->deselectRecordsAfterCompletion(),
                    )
                        ->requireCapability(Capabilities::TENANT_FINDINGS_TRIAGE)
                        ->tooltip(UiTooltips::INSUFFICIENT_PERMISSION)
                        ->apply(),

                    UiEnforcement::forBulkAction(
                        BulkAction::make('assign_selected')
                            ->label('Assign selected')
                            ->icon('heroicon-o-user-plus')
                            ->color('gray')
                            ->requiresConfirmation()
                            ->form([
                                Select::make('assignee_user_id')
                                    ->label('Assignee')
                                    ->placeholder('Unassigned')
                                    ->options(fn (): array => static::tenantMemberOptions())
                                    ->searchable(),
                                Select::make('owner_user_id')
                                    ->label('Owner')
                                    ->placeholder('Unassigned')
                                    ->options(fn (): array => static::tenantMemberOptions())
                                    ->searchable(),
                            ])
                            ->action(function (Collection $records, array $data, FindingWorkflowService $workflow): void {
                                $tenant = Filament::getTenant();
                                $user = auth()->user();

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

                                $assigneeUserId = is_numeric($data['assignee_user_id'] ?? null) ? (int) $data['assignee_user_id'] : null;
                                $ownerUserId = is_numeric($data['owner_user_id'] ?? null) ? (int) $data['owner_user_id'] : null;

                                $assignedCount = 0;
                                $skippedCount = 0;
                                $failedCount = 0;

                                foreach ($records as $record) {
                                    if (! $record instanceof Finding) {
                                        $skippedCount++;

                                        continue;
                                    }

                                    if ((int) $record->tenant_id !== (int) $tenant->getKey()) {
                                        $skippedCount++;

                                        continue;
                                    }

                                    if (! $record->hasOpenStatus()) {
                                        $skippedCount++;

                                        continue;
                                    }

                                    try {
                                        $workflow->assign($record, $tenant, $user, $assigneeUserId, $ownerUserId);
                                        $assignedCount++;
                                    } catch (Throwable) {
                                        $failedCount++;
                                    }
                                }

                                $body = "Updated {$assignedCount} finding".($assignedCount === 1 ? '' : 's').'.';
                                if ($skippedCount > 0) {
                                    $body .= " Skipped {$skippedCount}.";
                                }
                                if ($failedCount > 0) {
                                    $body .= " Failed {$failedCount}.";
                                }

                                Notification::make()
                                    ->title('Bulk assign completed')
                                    ->body($body)
                                    ->status($failedCount > 0 ? 'warning' : 'success')
                                    ->send();
                            })
                            ->deselectRecordsAfterCompletion(),
                    )
                        ->requireCapability(Capabilities::TENANT_FINDINGS_ASSIGN)
                        ->tooltip(UiTooltips::INSUFFICIENT_PERMISSION)
                        ->apply(),

                    UiEnforcement::forBulkAction(
                        BulkAction::make('resolve_selected')
                            ->label('Resolve selected')
                            ->icon('heroicon-o-check-badge')
                            ->color('success')
                            ->requiresConfirmation()
                            ->form([
                                Textarea::make('resolved_reason')
                                    ->label('Resolution reason')
                                    ->rows(3)
                                    ->required()
                                    ->maxLength(255),
                            ])
                            ->action(function (Collection $records, array $data, FindingWorkflowService $workflow): void {
                                $tenant = Filament::getTenant();
                                $user = auth()->user();

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

                                $reason = (string) ($data['resolved_reason'] ?? '');

                                $resolvedCount = 0;
                                $skippedCount = 0;
                                $failedCount = 0;

                                foreach ($records as $record) {
                                    if (! $record instanceof Finding) {
                                        $skippedCount++;

                                        continue;
                                    }

                                    if ((int) $record->tenant_id !== (int) $tenant->getKey()) {
                                        $skippedCount++;

                                        continue;
                                    }

                                    if (! $record->hasOpenStatus()) {
                                        $skippedCount++;

                                        continue;
                                    }

                                    try {
                                        $workflow->resolve($record, $tenant, $user, $reason);
                                        $resolvedCount++;
                                    } catch (Throwable) {
                                        $failedCount++;
                                    }
                                }

                                $body = "Resolved {$resolvedCount} finding".($resolvedCount === 1 ? '' : 's').'.';
                                if ($skippedCount > 0) {
                                    $body .= " Skipped {$skippedCount}.";
                                }
                                if ($failedCount > 0) {
                                    $body .= " Failed {$failedCount}.";
                                }

                                Notification::make()
                                    ->title('Bulk resolve completed')
                                    ->body($body)
                                    ->status($failedCount > 0 ? 'warning' : 'success')
                                    ->send();
                            })
                            ->deselectRecordsAfterCompletion(),
                    )
                        ->requireCapability(Capabilities::TENANT_FINDINGS_RESOLVE)
                        ->tooltip(UiTooltips::INSUFFICIENT_PERMISSION)
                        ->apply(),

                    UiEnforcement::forBulkAction(
                        BulkAction::make('close_selected')
                            ->label('Close selected')
                            ->icon('heroicon-o-x-circle')
                            ->color('danger')
                            ->requiresConfirmation()
                            ->form([
                                Textarea::make('closed_reason')
                                    ->label('Close reason')
                                    ->rows(3)
                                    ->required()
                                    ->maxLength(255),
                            ])
                            ->action(function (Collection $records, array $data, FindingWorkflowService $workflow): void {
                                $tenant = Filament::getTenant();
                                $user = auth()->user();

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

                                $reason = (string) ($data['closed_reason'] ?? '');

                                $closedCount = 0;
                                $skippedCount = 0;
                                $failedCount = 0;

                                foreach ($records as $record) {
                                    if (! $record instanceof Finding) {
                                        $skippedCount++;

                                        continue;
                                    }

                                    if ((int) $record->tenant_id !== (int) $tenant->getKey()) {
                                        $skippedCount++;

                                        continue;
                                    }

                                    if (! $record->hasOpenStatus()) {
                                        $skippedCount++;

                                        continue;
                                    }

                                    try {
                                        $workflow->close($record, $tenant, $user, $reason);
                                        $closedCount++;
                                    } catch (Throwable) {
                                        $failedCount++;
                                    }
                                }

                                $body = "Closed {$closedCount} finding".($closedCount === 1 ? '' : 's').'.';
                                if ($skippedCount > 0) {
                                    $body .= " Skipped {$skippedCount}.";
                                }
                                if ($failedCount > 0) {
                                    $body .= " Failed {$failedCount}.";
                                }

                                Notification::make()
                                    ->title('Bulk close completed')
                                    ->body($body)
                                    ->status($failedCount > 0 ? 'warning' : 'success')
                                    ->send();
                            })
                            ->deselectRecordsAfterCompletion(),
                    )
                        ->requireCapability(Capabilities::TENANT_FINDINGS_CLOSE)
                        ->tooltip(UiTooltips::INSUFFICIENT_PERMISSION)
                        ->apply(),

                    UiEnforcement::forBulkAction(
                        BulkAction::make('risk_accept_selected')
                            ->label('Risk accept selected')
                            ->icon('heroicon-o-shield-check')
                            ->color('warning')
                            ->requiresConfirmation()
                            ->form([
                                Textarea::make('closed_reason')
                                    ->label('Risk acceptance reason')
                                    ->rows(3)
                                    ->required()
                                    ->maxLength(255),
                            ])
                            ->action(function (Collection $records, array $data, FindingWorkflowService $workflow): void {
                                $tenant = Filament::getTenant();
                                $user = auth()->user();

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

                                $reason = (string) ($data['closed_reason'] ?? '');

                                $acceptedCount = 0;
                                $skippedCount = 0;
                                $failedCount = 0;

                                foreach ($records as $record) {
                                    if (! $record instanceof Finding) {
                                        $skippedCount++;

                                        continue;
                                    }

                                    if ((int) $record->tenant_id !== (int) $tenant->getKey()) {
                                        $skippedCount++;

                                        continue;
                                    }

                                    if (! $record->hasOpenStatus()) {
                                        $skippedCount++;

                                        continue;
                                    }

                                    try {
                                        $workflow->riskAccept($record, $tenant, $user, $reason);
                                        $acceptedCount++;
                                    } catch (Throwable) {
                                        $failedCount++;
                                    }
                                }

                                $body = "Risk accepted {$acceptedCount} finding".($acceptedCount === 1 ? '' : 's').'.';
                                if ($skippedCount > 0) {
                                    $body .= " Skipped {$skippedCount}.";
                                }
                                if ($failedCount > 0) {
                                    $body .= " Failed {$failedCount}.";
                                }

                                Notification::make()
                                    ->title('Bulk risk accept completed')
                                    ->body($body)
                                    ->status($failedCount > 0 ? 'warning' : 'success')
                                    ->send();
                            })
                            ->deselectRecordsAfterCompletion(),
                    )
                        ->requireCapability(Capabilities::TENANT_FINDINGS_RISK_ACCEPT)
                        ->tooltip(UiTooltips::INSUFFICIENT_PERMISSION)
                        ->apply(),
                ])->label('More'),
            ])
            ->emptyStateHeading('No findings match this view')
            ->emptyStateDescription('Adjust the current filters or wait for the next detection run to surface new findings.')
            ->emptyStateIcon('heroicon-o-exclamation-triangle');
    }

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

        return parent::getEloquentQuery()
            ->with(['assigneeUser', 'ownerUser', 'closedByUser'])
            ->addSelect([
                'subject_display_name' => InventoryItem::query()
                    ->select('display_name')
                    ->whereColumn('inventory_items.tenant_id', 'findings.tenant_id')
                    ->whereColumn('inventory_items.external_id', 'findings.subject_external_id')
                    ->limit(1),
            ])
            ->when($tenantId, fn (Builder $query) => $query->where('tenant_id', $tenantId));
    }

    /**
     * @return list<array{
     *     key: string,
     *     label: string,
     *     value: string,
     *     secondaryValue: ?string,
     *     targetUrl: ?string,
     *     targetKind: string,
     *     availability: string,
     *     unavailableReason: ?string,
     *     contextBadge: ?string,
     *     priority: int,
     *     actionLabel: string
     * }>
     */
    public static function relatedContextEntries(Finding $record): array
    {
        return app(RelatedNavigationResolver::class)
            ->detailEntries(CrossResourceNavigationMatrix::SOURCE_FINDING, $record);
    }

    private static function primaryRelatedAction(): Actions\Action
    {
        return Actions\Action::make('primary_drill_down')
            ->label(fn (Finding $record): string => static::primaryRelatedEntry($record)?->actionLabel ?? 'Open related record')
            ->url(fn (Finding $record): ?string => static::primaryRelatedEntry($record)?->targetUrl)
            ->hidden(fn (Finding $record): bool => ! (static::primaryRelatedEntry($record)?->isAvailable() ?? false))
            ->color('gray');
    }

    private static function primaryRelatedEntry(Finding $record): ?RelatedContextEntry
    {
        return app(RelatedNavigationResolver::class)
            ->primaryListAction(CrossResourceNavigationMatrix::SOURCE_FINDING, $record);
    }

    private static function findingRunNavigationContext(Finding $record): CanonicalNavigationContext
    {
        $tenant = $record->tenant;

        return new CanonicalNavigationContext(
            sourceSurface: 'finding.detail_section',
            canonicalRouteName: 'admin.operations.view',
            tenantId: $tenant?->getKey(),
            backLinkLabel: 'Back to finding',
            backLinkUrl: static::getUrl('view', ['record' => $record], tenant: $tenant),
            filterPayload: $tenant instanceof Tenant ? [
                'tableFilters' => [
                    'tenant_id' => ['value' => (string) $tenant->getKey()],
                ],
            ] : [],
        );
    }

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

    /**
     * @return array<int, Actions\Action>
     */
    public static function workflowActions(): array
    {
        return [
            static::triageAction(),
            static::startProgressAction(),
            static::assignAction(),
            static::resolveAction(),
            static::closeAction(),
            static::riskAcceptAction(),
            static::reopenAction(),
        ];
    }

    public static function triageAction(): Actions\Action
    {
        return UiEnforcement::forAction(
            Actions\Action::make('triage')
                ->label('Triage')
                ->icon('heroicon-o-check')
                ->color('gray')
                ->visible(fn (Finding $record): bool => in_array((string) $record->status, [
                    Finding::STATUS_NEW,
                    Finding::STATUS_REOPENED,
                    Finding::STATUS_ACKNOWLEDGED,
                ], true))
                ->action(function (Finding $record, FindingWorkflowService $workflow): void {
                    static::runWorkflowMutation(
                        record: $record,
                        successTitle: 'Finding triaged',
                        callback: fn (Finding $finding, Tenant $tenant, User $user): Finding => $workflow->triage($finding, $tenant, $user),
                    );
                })
        )
            ->preserveVisibility()
            ->requireCapability(Capabilities::TENANT_FINDINGS_TRIAGE)
            ->tooltip(UiTooltips::INSUFFICIENT_PERMISSION)
            ->apply();
    }

    public static function startProgressAction(): Actions\Action
    {
        return UiEnforcement::forAction(
            Actions\Action::make('start_progress')
                ->label('Start progress')
                ->icon('heroicon-o-play')
                ->color('info')
                ->visible(fn (Finding $record): bool => in_array((string) $record->status, [
                    Finding::STATUS_TRIAGED,
                    Finding::STATUS_ACKNOWLEDGED,
                ], true))
                ->action(function (Finding $record, FindingWorkflowService $workflow): void {
                    static::runWorkflowMutation(
                        record: $record,
                        successTitle: 'Finding moved to in progress',
                        callback: fn (Finding $finding, Tenant $tenant, User $user): Finding => $workflow->startProgress($finding, $tenant, $user),
                    );
                })
        )
            ->preserveVisibility()
            ->requireCapability(Capabilities::TENANT_FINDINGS_TRIAGE)
            ->tooltip(UiTooltips::INSUFFICIENT_PERMISSION)
            ->apply();
    }

    public static function assignAction(): Actions\Action
    {
        return UiEnforcement::forAction(
            Actions\Action::make('assign')
                ->label('Assign')
                ->icon('heroicon-o-user-plus')
                ->color('gray')
                ->visible(fn (Finding $record): bool => $record->hasOpenStatus())
                ->fillForm(fn (Finding $record): array => [
                    'assignee_user_id' => $record->assignee_user_id,
                    'owner_user_id' => $record->owner_user_id,
                ])
                ->form([
                    Select::make('assignee_user_id')
                        ->label('Assignee')
                        ->placeholder('Unassigned')
                        ->options(fn (): array => static::tenantMemberOptions())
                        ->searchable(),
                    Select::make('owner_user_id')
                        ->label('Owner')
                        ->placeholder('Unassigned')
                        ->options(fn (): array => static::tenantMemberOptions())
                        ->searchable(),
                ])
                ->action(function (Finding $record, array $data, FindingWorkflowService $workflow): void {
                    static::runWorkflowMutation(
                        record: $record,
                        successTitle: 'Finding assignment updated',
                        callback: fn (Finding $finding, Tenant $tenant, User $user): Finding => $workflow->assign(
                            $finding,
                            $tenant,
                            $user,
                            is_numeric($data['assignee_user_id'] ?? null) ? (int) $data['assignee_user_id'] : null,
                            is_numeric($data['owner_user_id'] ?? null) ? (int) $data['owner_user_id'] : null,
                        ),
                    );
                })
        )
            ->preserveVisibility()
            ->requireCapability(Capabilities::TENANT_FINDINGS_ASSIGN)
            ->tooltip(UiTooltips::INSUFFICIENT_PERMISSION)
            ->apply();
    }

    public static function resolveAction(): Actions\Action
    {
        return UiEnforcement::forAction(
            Actions\Action::make('resolve')
                ->label('Resolve')
                ->icon('heroicon-o-check-badge')
                ->color('success')
                ->visible(fn (Finding $record): bool => $record->hasOpenStatus())
                ->requiresConfirmation()
                ->form([
                    Textarea::make('resolved_reason')
                        ->label('Resolution reason')
                        ->rows(3)
                        ->required()
                        ->maxLength(255),
                ])
                ->action(function (Finding $record, array $data, FindingWorkflowService $workflow): void {
                    static::runWorkflowMutation(
                        record: $record,
                        successTitle: 'Finding resolved',
                        callback: fn (Finding $finding, Tenant $tenant, User $user): Finding => $workflow->resolve(
                            $finding,
                            $tenant,
                            $user,
                            (string) ($data['resolved_reason'] ?? ''),
                        ),
                    );
                })
        )
            ->preserveVisibility()
            ->requireCapability(Capabilities::TENANT_FINDINGS_RESOLVE)
            ->tooltip(UiTooltips::INSUFFICIENT_PERMISSION)
            ->apply();
    }

    public static function closeAction(): Actions\Action
    {
        return UiEnforcement::forAction(
            Actions\Action::make('close')
                ->label('Close')
                ->icon('heroicon-o-x-circle')
                ->color('danger')
                ->requiresConfirmation()
                ->form([
                    Textarea::make('closed_reason')
                        ->label('Close reason')
                        ->rows(3)
                        ->required()
                        ->maxLength(255),
                ])
                ->action(function (Finding $record, array $data, FindingWorkflowService $workflow): void {
                    static::runWorkflowMutation(
                        record: $record,
                        successTitle: 'Finding closed',
                        callback: fn (Finding $finding, Tenant $tenant, User $user): Finding => $workflow->close(
                            $finding,
                            $tenant,
                            $user,
                            (string) ($data['closed_reason'] ?? ''),
                        ),
                    );
                })
        )
            ->preserveVisibility()
            ->requireCapability(Capabilities::TENANT_FINDINGS_CLOSE)
            ->tooltip(UiTooltips::INSUFFICIENT_PERMISSION)
            ->apply();
    }

    public static function riskAcceptAction(): Actions\Action
    {
        return UiEnforcement::forAction(
            Actions\Action::make('risk_accept')
                ->label('Risk accept')
                ->icon('heroicon-o-shield-check')
                ->color('warning')
                ->requiresConfirmation()
                ->form([
                    Textarea::make('closed_reason')
                        ->label('Risk acceptance reason')
                        ->rows(3)
                        ->required()
                        ->maxLength(255),
                ])
                ->action(function (Finding $record, array $data, FindingWorkflowService $workflow): void {
                    static::runWorkflowMutation(
                        record: $record,
                        successTitle: 'Finding marked as risk accepted',
                        callback: fn (Finding $finding, Tenant $tenant, User $user): Finding => $workflow->riskAccept(
                            $finding,
                            $tenant,
                            $user,
                            (string) ($data['closed_reason'] ?? ''),
                        ),
                    );
                })
        )
            ->preserveVisibility()
            ->requireCapability(Capabilities::TENANT_FINDINGS_RISK_ACCEPT)
            ->tooltip(UiTooltips::INSUFFICIENT_PERMISSION)
            ->apply();
    }

    public static function reopenAction(): Actions\Action
    {
        return UiEnforcement::forAction(
            Actions\Action::make('reopen')
                ->label('Reopen')
                ->icon('heroicon-o-arrow-uturn-left')
                ->color('warning')
                ->requiresConfirmation()
                ->visible(fn (Finding $record): bool => Finding::isTerminalStatus((string) $record->status))
                ->action(function (Finding $record, FindingWorkflowService $workflow): void {
                    static::runWorkflowMutation(
                        record: $record,
                        successTitle: 'Finding reopened',
                        callback: fn (Finding $finding, Tenant $tenant, User $user): Finding => $workflow->reopen($finding, $tenant, $user),
                    );
                })
        )
            ->preserveVisibility()
            ->requireCapability(Capabilities::TENANT_FINDINGS_TRIAGE)
            ->tooltip(UiTooltips::INSUFFICIENT_PERMISSION)
            ->apply();
    }

    /**
     * @param  callable(Finding, Tenant, User): Finding  $callback
     */
    private static function runWorkflowMutation(Finding $record, string $successTitle, callable $callback): void
    {
        $tenant = Tenant::current();
        $user = auth()->user();

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

        if ((int) $record->tenant_id !== (int) $tenant->getKey()) {
            Notification::make()
                ->title('Finding belongs to a different tenant')
                ->danger()
                ->send();

            return;
        }

        try {
            $callback($record, $tenant, $user);
        } catch (InvalidArgumentException $e) {
            Notification::make()
                ->title('Workflow action failed')
                ->body($e->getMessage())
                ->danger()
                ->send();

            return;
        }

        Notification::make()
            ->title($successTitle)
            ->success()
            ->send();
    }

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

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

        return TenantMembership::query()
            ->where('tenant_id', (int) $tenant->getKey())
            ->join('users', 'users.id', '=', 'tenant_memberships.user_id')
            ->orderBy('users.name')
            ->pluck('users.name', 'users.id')
            ->mapWithKeys(fn (string $name, int|string $id): array => [(int) $id => $name])
            ->all();
    }
}
