<?php

declare(strict_types=1);

use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\ActionSurfaceDiscoveredComponent;
use App\Support\Ui\ActionSurface\ActionSurfaceExemptions;
use App\Support\Ui\ActionSurface\ActionSurfaceProfileDefinition;
use App\Support\Ui\ActionSurface\ActionSurfaceSlotRequirement;
use App\Support\Ui\ActionSurface\ActionSurfaceValidator;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceComponentType;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance;
use App\Support\Ui\ActionSurface\Enums\ActionSurfacePanelScope;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceSlot;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceType;

final class ActionSurfaceValidatorCompleteStub
{
    public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
    {
        return ActionSurfaceDeclaration::forResource(ActionSurfaceProfile::CrudListAndView, ActionSurfaceType::CrudListFirstResource)
            ->satisfy(ActionSurfaceSlot::ListHeader)
            ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value)
            ->satisfy(ActionSurfaceSlot::ListRowMoreMenu)
            ->satisfy(ActionSurfaceSlot::ListBulkMoreGroup)
            ->satisfy(ActionSurfaceSlot::ListEmptyState)
            ->satisfy(ActionSurfaceSlot::DetailHeader);
    }
}

final class ActionSurfaceValidatorMissingSlotStub
{
    public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
    {
        return ActionSurfaceDeclaration::forResource(ActionSurfaceProfile::CrudListAndView, ActionSurfaceType::CrudListFirstResource)
            ->satisfy(ActionSurfaceSlot::ListHeader)
            ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value);
    }
}

final class ActionSurfaceValidatorRunLogNoExportStub
{
    public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
    {
        return ActionSurfaceDeclaration::forResource(ActionSurfaceProfile::RunLog, ActionSurfaceType::ReadOnlyRegistryReport)
            ->withDefaults(new \App\Support\Ui\ActionSurface\ActionSurfaceDefaults(
                moreGroupLabel: 'More',
                exportIsDefaultBulkActionForReadOnly: false,
            ))
            ->satisfy(ActionSurfaceSlot::ListHeader)
            ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value)
            ->satisfy(ActionSurfaceSlot::ListBulkMoreGroup)
            ->satisfy(ActionSurfaceSlot::ListEmptyState)
            ->satisfy(ActionSurfaceSlot::DetailHeader);
    }
}

final class ActionSurfaceValidatorExemptSlotWithoutReasonStub
{
    public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
    {
        return ActionSurfaceDeclaration::forResource(ActionSurfaceProfile::CrudListAndView, ActionSurfaceType::CrudListFirstResource)
            ->setSlot(ActionSurfaceSlot::ListHeader, ActionSurfaceSlotRequirement::exempt())
            ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value)
            ->satisfy(ActionSurfaceSlot::ListRowMoreMenu)
            ->satisfy(ActionSurfaceSlot::ListBulkMoreGroup)
            ->satisfy(ActionSurfaceSlot::ListEmptyState)
            ->satisfy(ActionSurfaceSlot::DetailHeader);
    }
}

final class ActionSurfaceValidatorMissingSurfaceTypeStub
{
    public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
    {
        return ActionSurfaceDeclaration::forResource(ActionSurfaceProfile::CrudListAndView, version: 2)
            ->satisfy(ActionSurfaceSlot::ListHeader)
            ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value)
            ->satisfy(ActionSurfaceSlot::ListRowMoreMenu)
            ->satisfy(ActionSurfaceSlot::ListBulkMoreGroup)
            ->satisfy(ActionSurfaceSlot::ListEmptyState)
            ->satisfy(ActionSurfaceSlot::DetailHeader);
    }
}

final class ActionSurfaceValidatorIncompatibleInspectAffordanceStub
{
    public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
    {
        return ActionSurfaceDeclaration::forResource(ActionSurfaceProfile::CrudListAndView, ActionSurfaceType::CrudListFirstResource)
            ->satisfy(ActionSurfaceSlot::ListHeader)
            ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ViewAction->value)
            ->satisfy(ActionSurfaceSlot::ListRowMoreMenu)
            ->satisfy(ActionSurfaceSlot::ListBulkMoreGroup)
            ->satisfy(ActionSurfaceSlot::ListEmptyState)
            ->satisfy(ActionSurfaceSlot::DetailHeader);
    }
}

final class ActionSurfaceValidatorPrimaryLinkColumnWithoutReasonStub
{
    public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
    {
        return ActionSurfaceDeclaration::forResource(ActionSurfaceProfile::ListOnlyReadOnly, ActionSurfaceType::ReadOnlyRegistryReport)
            ->satisfy(ActionSurfaceSlot::ListHeader)
            ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::PrimaryLinkColumn->value)
            ->satisfy(ActionSurfaceSlot::ListRowMoreMenu)
            ->satisfy(ActionSurfaceSlot::ListBulkMoreGroup)
            ->satisfy(ActionSurfaceSlot::ListEmptyState);
    }
}

final class ActionSurfaceValidatorNoDeclarationStub {}

function actionSurfaceComponent(string $className): ActionSurfaceDiscoveredComponent
{
    return new ActionSurfaceDiscoveredComponent(
        className: $className,
        componentType: ActionSurfaceComponentType::Resource,
        panelScopes: [ActionSurfacePanelScope::ManagedEnvironment],
    );
}

/**
 * @return array<int, string>
 */
function repositoryDiscoveredActionSurfaceClasses(): array
{
    return array_map(
        static fn (ActionSurfaceDiscoveredComponent $component): string => $component->className,
        ActionSurfaceValidator::withBaselineExemptions()->discoveredComponents(),
    );
}

/**
 * @param  array<int, \App\Support\Ui\ActionSurface\ActionSurfaceValidationIssue>  $issues
 */
function formatActionSurfaceIssues(array $issues): string
{
    return implode("\n", array_map(
        static fn ($issue): string => $issue->format(),
        $issues,
    ));
}

it('passes when all required slots are declared', function (): void {
    $validator = new ActionSurfaceValidator(
        profileDefinition: new ActionSurfaceProfileDefinition,
        exemptions: new ActionSurfaceExemptions([]),
    );

    $result = $validator->validateComponents([actionSurfaceComponent(ActionSurfaceValidatorCompleteStub::class)]);

    expect($result->hasIssues())->toBeFalse($result->formatForAssertion());
});

it('fails when a required slot is missing', function (): void {
    $validator = new ActionSurfaceValidator(
        profileDefinition: new ActionSurfaceProfileDefinition,
        exemptions: new ActionSurfaceExemptions([]),
    );

    $result = $validator->validateComponents([actionSurfaceComponent(ActionSurfaceValidatorMissingSlotStub::class)]);

    expect($result->hasIssues())->toBeTrue();
    expect($result->formatForAssertion())->toContain('Required slot is not declared');
});

it('fails missing declarations when no baseline exemption exists', function (): void {
    $validator = new ActionSurfaceValidator(
        profileDefinition: new ActionSurfaceProfileDefinition,
        exemptions: new ActionSurfaceExemptions([]),
    );

    $result = $validator->validateComponents([actionSurfaceComponent(ActionSurfaceValidatorNoDeclarationStub::class)]);

    expect($result->hasIssues())->toBeTrue();
    expect($result->formatForAssertion())->toContain('Missing action-surface declaration');
});

it('fails behavior-aware declarations when surface type is missing', function (): void {
    $validator = new ActionSurfaceValidator(
        profileDefinition: new ActionSurfaceProfileDefinition,
        exemptions: new ActionSurfaceExemptions([]),
    );

    $result = $validator->validateComponents([actionSurfaceComponent(ActionSurfaceValidatorMissingSurfaceTypeStub::class)]);

    expect($result->hasIssues())->toBeTrue();
    expect($result->formatForAssertion())->toContain('Behavior-aware declarations must define a surface type');
});

it('fails behavior-aware declarations when surface type and inspect affordance are incompatible', function (): void {
    $validator = new ActionSurfaceValidator(
        profileDefinition: new ActionSurfaceProfileDefinition,
        exemptions: new ActionSurfaceExemptions([]),
    );

    $result = $validator->validateComponents([actionSurfaceComponent(ActionSurfaceValidatorIncompatibleInspectAffordanceStub::class)]);

    expect($result->hasIssues())->toBeTrue();
    expect($result->formatForAssertion())->toContain('Inspect affordance "view_action" is incompatible with surface type "crud_list_first_resource"');
});

it('fails primary-link-column inspect affordances without an explicit reason', function (): void {
    $validator = new ActionSurfaceValidator(
        profileDefinition: new ActionSurfaceProfileDefinition,
        exemptions: new ActionSurfaceExemptions([]),
    );

    $result = $validator->validateComponents([actionSurfaceComponent(ActionSurfaceValidatorPrimaryLinkColumnWithoutReasonStub::class)]);

    expect($result->hasIssues())->toBeTrue();
    expect($result->formatForAssertion())->toContain('Primary link column inspect affordance requires a non-empty reason');
});

it('accepts missing declarations when explicit baseline exemption exists', function (): void {
    $validator = new ActionSurfaceValidator(
        profileDefinition: new ActionSurfaceProfileDefinition,
        exemptions: new ActionSurfaceExemptions([
            ActionSurfaceValidatorNoDeclarationStub::class => 'Retrofit intentionally deferred in validator unit test.',
        ]),
    );

    $result = $validator->validateComponents([actionSurfaceComponent(ActionSurfaceValidatorNoDeclarationStub::class)]);

    expect($result->hasIssues())->toBeFalse($result->formatForAssertion());
});

it('requires a bulk exemption reason when run-log export default is disabled', function (): void {
    $validator = new ActionSurfaceValidator(
        profileDefinition: new ActionSurfaceProfileDefinition,
        exemptions: new ActionSurfaceExemptions([]),
    );

    $result = $validator->validateComponents([actionSurfaceComponent(ActionSurfaceValidatorRunLogNoExportStub::class)]);

    expect($result->hasIssues())->toBeTrue();
    expect($result->formatForAssertion())->toContain('ReadOnly/RunLog profile disables Export default');
});

it('fails when slot exemption reason is missing', function (): void {
    $validator = new ActionSurfaceValidator(
        profileDefinition: new ActionSurfaceProfileDefinition,
        exemptions: new ActionSurfaceExemptions([]),
    );

    $result = $validator->validateComponents([actionSurfaceComponent(ActionSurfaceValidatorExemptSlotWithoutReasonStub::class)]);

    expect($result->hasIssues())->toBeTrue();
    expect($result->formatForAssertion())->toContain('Slot is marked exempt but exemption reason is missing or empty');
});

it('accepts the repository spec 192 inventory even when only inventory validation runs', function (): void {
    $validator = ActionSurfaceValidator::withBaselineExemptions();

    $result = $validator->validateComponents([]);

    expect($result->hasIssues())->toBeFalse($result->formatForAssertion());
});

it('accepts the repository spec 193 monitoring inventory even when only inventory validation runs', function (): void {
    $validator = ActionSurfaceValidator::withBaselineExemptions();

    $result = $validator->validateComponents([]);

    expect($result->hasIssues())->toBeFalse($result->formatForAssertion());
});

it('accepts the repository spec 195 residual inventory even when only inventory validation runs', function (): void {
    $validator = ActionSurfaceValidator::withBaselineExemptions();

    $result = $validator->validateComponents([]);

    expect($result->hasIssues())->toBeFalse($result->formatForAssertion());
});

it('fails when a residual system candidate is missing a spec 195 closure entry', function (): void {
    $inventory = ActionSurfaceExemptions::spec195ResidualSurfaceInventory();
    unset($inventory[\App\Filament\System\Pages\Dashboard::class]);

    $issues = ActionSurfaceValidator::validateSpec195ResidualInventoryFixture(
        inventory: $inventory,
        discoveredClasses: repositoryDiscoveredActionSurfaceClasses(),
        baselineExemptions: ActionSurfaceExemptions::baseline()->all(),
        residualCandidateClasses: [\App\Filament\System\Pages\Dashboard::class],
    );

    expect(formatActionSurfaceIssues($issues))
        ->toContain(\App\Filament\System\Pages\Dashboard::class)
        ->toContain('Residual action surface is missing a Spec 195 closure entry');
});

it('fails when a non-enrolled spec 195 residual surface has no reason category', function (): void {
    $inventory = ActionSurfaceExemptions::spec195ResidualSurfaceInventory();
    $inventory[\App\Filament\Pages\ChooseWorkspace::class]['reasonCategory'] = null;

    $issues = ActionSurfaceValidator::validateSpec195ResidualInventoryFixture(
        inventory: $inventory,
        discoveredClasses: repositoryDiscoveredActionSurfaceClasses(),
        baselineExemptions: ActionSurfaceExemptions::baseline()->all(),
    );

    expect(formatActionSurfaceIssues($issues))
        ->toContain(\App\Filament\Pages\ChooseWorkspace::class)
        ->toContain('reason category is invalid or missing');
});

it('fails when a spec 195 residual surface is missing structured evidence', function (): void {
    $inventory = ActionSurfaceExemptions::spec195ResidualSurfaceInventory();
    $inventory[\App\Filament\Pages\Workspaces\ManagedEnvironmentsLanding::class]['evidence'] = [];

    $issues = ActionSurfaceValidator::validateSpec195ResidualInventoryFixture(
        inventory: $inventory,
        discoveredClasses: repositoryDiscoveredActionSurfaceClasses(),
        baselineExemptions: ActionSurfaceExemptions::baseline()->all(),
    );

    expect(formatActionSurfaceIssues($issues))
        ->toContain(\App\Filament\Pages\Workspaces\ManagedEnvironmentsLanding::class)
        ->toContain('require at least one structured evidence descriptor');
});

it('fails when a retired spec 195 residual surface still remains baseline-exempt', function (): void {
    $inventory = ActionSurfaceExemptions::spec195ResidualSurfaceInventory();
    $baselineExemptions = ActionSurfaceExemptions::baseline()->all();
    $baselineExemptions[\App\Filament\Pages\BreakGlassRecovery::class] = 'Stale retired exemption.';

    $issues = ActionSurfaceValidator::validateSpec195ResidualInventoryFixture(
        inventory: $inventory,
        discoveredClasses: repositoryDiscoveredActionSurfaceClasses(),
        baselineExemptions: $baselineExemptions,
    );

    expect(formatActionSurfaceIssues($issues))
        ->toContain(\App\Filament\Pages\BreakGlassRecovery::class)
        ->toContain('must not remain baseline-exempt');
});
