import { describe, it, expect } from 'vitest';
import { OrchestrateSetupUseCase } from '../../../../../modules/setup-wizard/usecases/orchestrateSetup.usecase.js';
import { succeeded, blocked } from '../../../../../modules/setup-wizard/entities/stepOutcome/stepOutcome.js';
import { AwaitingInputClosedError, NonInteractiveInputError, } from '../../../../../modules/setup-wizard/entities/promptInputError/promptInputError.js';
import { SetupStateFactory } from '../../../../../tests/factories/setupState.factory.js';
import { StubDependencyProbeGateway } from '../../../../../tests/stubs/setup-wizard/dependencyProbe.stub.js';
import { StubClaudeAuthGateway } from '../../../../../tests/stubs/setup-wizard/claudeAuth.stub.js';
import { StubDaemonServiceGateway } from '../../../../../tests/stubs/setup-wizard/daemonService.stub.js';
import { StubDaemonHealthProbeGateway } from '../../../../../tests/stubs/setup-wizard/daemonHealthProbe.stub.js';
import { StubEnvFileGateway } from '../../../../../tests/stubs/setup-wizard/envFile.stub.js';
import { StubGitRemoteGateway } from '../../../../../tests/stubs/setup-wizard/gitRemote.stub.js';
import { StubProjectConfigGateway } from '../../../../../tests/stubs/setup-wizard/projectConfig.stub.js';
import { StubSkillTemplateGateway } from '../../../../../tests/stubs/setup-wizard/skillTemplate.stub.js';
import { StubServerConfigGateway } from '../../../../../tests/stubs/setup-wizard/serverConfig.stub.js';
import { StubValidationGateway } from '../../../../../tests/stubs/setup-wizard/validation.stub.js';
import { StubAiFallbackGateway } from '../../../../../tests/stubs/setup-wizard/aiFallback.stub.js';
import { StubPromptGateway } from '../../../../../tests/stubs/setup-wizard/prompt.stub.js';
class FakeStep {
    id;
    title;
    detectResult;
    executeResult;
    detectCalls = 0;
    executeCalls = 0;
    constructor(id, title, detectResult, executeResult) {
        this.id = id;
        this.title = title;
        this.detectResult = detectResult;
        this.executeResult = executeResult;
    }
    async detect() {
        this.detectCalls++;
        return this.detectResult;
    }
    async execute() {
        this.executeCalls++;
        return this.executeResult;
    }
}
class RecordingEmitter {
    started = [];
    completed = [];
    warnings = [];
    resumeBanners = [];
    doneSummaries = [];
    emitStepStarted(stepId) {
        this.started.push(stepId);
    }
    emitStepCompleted(stepId, outcome) {
        this.completed.push({ id: stepId, status: outcome.status });
    }
    emitAwaitingInput() { }
    emitInstructions() { }
    emitWarning(message) {
        this.warnings.push(message);
    }
    emitResumeBanner(stepId, position, total) {
        this.resumeBanners.push({ stepId, position, total });
    }
    emitDone(summary) {
        this.doneSummaries.push(summary);
    }
}
class RecordingStateGateway {
    loadResult;
    saved = [];
    constructor(loadResult) {
        this.loadResult = loadResult;
    }
    load() {
        return this.loadResult;
    }
    save(state) {
        this.saved.push(state);
    }
    reset() { }
}
function buildContext(emitter, stateGateway, yes = false) {
    return {
        state: null,
        currentStepId: null,
        project: { localPath: '/tmp/p', platform: 'github', preset: 'backend', language: 'en', remoteUrl: null },
        flags: { path: '/tmp/p', json: false, force: false, ai: false, yes, showSecrets: false },
        gateways: {
            setupState: stateGateway,
            dependencyProbe: new StubDependencyProbeGateway(),
            claudeAuth: new StubClaudeAuthGateway(),
            daemonService: new StubDaemonServiceGateway(),
            daemonHealthProbe: new StubDaemonHealthProbeGateway(),
            envFile: new StubEnvFileGateway(),
            gitRemote: new StubGitRemoteGateway({ projectPath: '/tmp/p' }),
            projectConfig: new StubProjectConfigGateway(),
            skillTemplate: new StubSkillTemplateGateway(),
            serverConfig: new StubServerConfigGateway(),
            validation: new StubValidationGateway(),
            aiFallback: new StubAiFallbackGateway(),
            prompt: new StubPromptGateway(),
        },
        emitter,
        now: () => new Date('2026-05-28T10:00:00.000Z'),
    };
}
describe('OrchestrateSetupUseCase', () => {
    const orchestrator = new OrchestrateSetupUseCase();
    it('runs every step and returns exit code 0 when all succeed', async () => {
        const emitter = new RecordingEmitter();
        const stateGateway = new RecordingStateGateway({ state: null, corrupted: false });
        const steps = [
            new FakeStep('dependencies', 'Deps', null, succeeded()),
            new FakeStep('claude-login', 'Login', null, succeeded()),
            new FakeStep('daemon', 'Daemon', null, succeeded()),
        ];
        const result = await orchestrator.execute({ context: buildContext(emitter, stateGateway), steps });
        expect(result.exitCode).toBe(0);
        expect(result.resumedFromStepId).toBeNull();
        expect(result.stateWasCorrupted).toBe(false);
        expect(steps.every((step) => step.executeCalls === 1)).toBe(true);
        expect(emitter.started).toHaveLength(3);
        expect(emitter.doneSummaries).toHaveLength(1);
    });
    it('saves state after every step', async () => {
        const emitter = new RecordingEmitter();
        const stateGateway = new RecordingStateGateway({ state: null, corrupted: false });
        const steps = [
            new FakeStep('dependencies', 'Deps', null, succeeded()),
            new FakeStep('claude-login', 'Login', null, succeeded()),
        ];
        await orchestrator.execute({ context: buildContext(emitter, stateGateway), steps });
        expect(stateGateway.saved).toHaveLength(2);
        expect(stateGateway.saved[1].steps['claude-login']?.status).toBe('succeeded');
    });
    it('skips already-succeeded steps without executing them (idempotence)', async () => {
        const emitter = new RecordingEmitter();
        const loaded = SetupStateFactory.create({
            steps: { dependencies: succeeded(), 'claude-login': succeeded() },
        });
        const stateGateway = new RecordingStateGateway({ state: loaded, corrupted: false });
        const steps = [
            new FakeStep('dependencies', 'Deps', succeeded(), succeeded()),
            new FakeStep('claude-login', 'Login', succeeded(), succeeded()),
        ];
        const result = await orchestrator.execute({ context: buildContext(emitter, stateGateway), steps });
        expect(result.exitCode).toBe(0);
        expect(steps.every((step) => step.executeCalls === 0)).toBe(true);
        expect(emitter.started).toHaveLength(0);
    });
    it('resumes from the first incomplete step and emits a resume banner', async () => {
        const emitter = new RecordingEmitter();
        const loaded = SetupStateFactory.create({ steps: { dependencies: succeeded() } });
        const stateGateway = new RecordingStateGateway({ state: loaded, corrupted: false });
        const steps = [
            new FakeStep('dependencies', 'Deps', succeeded(), succeeded()),
            new FakeStep('claude-login', 'Login', null, succeeded()),
            new FakeStep('daemon', 'Daemon', null, succeeded()),
        ];
        const result = await orchestrator.execute({ context: buildContext(emitter, stateGateway), steps });
        expect(result.resumedFromStepId).toBe('claude-login');
        expect(emitter.resumeBanners).toEqual([{ stepId: 'claude-login', position: 2, total: 3 }]);
        expect(steps[1].executeCalls).toBe(1);
    });
    it('returns exit code 2 and stops on a blocking step in non-interactive mode', async () => {
        const emitter = new RecordingEmitter();
        const stateGateway = new RecordingStateGateway({ state: null, corrupted: false });
        const steps = [
            new FakeStep('dependencies', 'Deps', null, succeeded()),
            new FakeStep('claude-login', 'Login', null, blocked('blocked', 'fix it')),
            new FakeStep('daemon', 'Daemon', null, succeeded()),
        ];
        const result = await orchestrator.execute({ context: buildContext(emitter, stateGateway, true), steps });
        expect(result.exitCode).toBe(2);
        expect(steps[2].executeCalls).toBe(0);
        expect(emitter.doneSummaries).toHaveLength(1);
    });
    it('returns exit code 1 on a blocking step in interactive mode', async () => {
        const emitter = new RecordingEmitter();
        const stateGateway = new RecordingStateGateway({ state: null, corrupted: false });
        const steps = [
            new FakeStep('dependencies', 'Deps', null, blocked('blocked', 'fix it')),
            new FakeStep('claude-login', 'Login', null, succeeded()),
        ];
        const result = await orchestrator.execute({ context: buildContext(emitter, stateGateway, false), steps });
        expect(result.exitCode).toBe(1);
        expect(steps[1].executeCalls).toBe(0);
    });
    it('maps an AwaitingInputClosedError thrown by a step to a blocked outcome', async () => {
        const emitter = new RecordingEmitter();
        const stateGateway = new RecordingStateGateway({ state: null, corrupted: false });
        const throwingStep = {
            id: 'add-project',
            title: 'Add project',
            async detect() {
                return null;
            },
            async execute() {
                throw new AwaitingInputClosedError();
            },
        };
        const result = await orchestrator.execute({ context: buildContext(emitter, stateGateway), steps: [throwingStep] });
        expect(result.exitCode).toBe(1);
        const outcome = result.finalState.steps['add-project'];
        expect(outcome?.status).toBe('blocked');
        expect(outcome?.message).toBe('Aucune réponse reçue, le setup est interrompu');
    });
    it('maps a NonInteractiveInputError thrown by a step to a blocked outcome', async () => {
        const emitter = new RecordingEmitter();
        const stateGateway = new RecordingStateGateway({ state: null, corrupted: false });
        const throwingStep = {
            id: 'add-project',
            title: 'Add project',
            async detect() {
                return null;
            },
            async execute() {
                throw new NonInteractiveInputError();
            },
        };
        const result = await orchestrator.execute({ context: buildContext(emitter, stateGateway, true), steps: [throwingStep] });
        expect(result.exitCode).toBe(2);
        const outcome = result.finalState.steps['add-project'];
        expect(outcome?.status).toBe('blocked');
        expect(outcome?.message).toBe('Mode non-interactif : aucune entrée disponible pour cette étape');
    });
    it('exposes the current step id on the context while a step executes', async () => {
        const emitter = new RecordingEmitter();
        const stateGateway = new RecordingStateGateway({ state: null, corrupted: false });
        let seen = null;
        const observingStep = {
            id: 'daemon',
            title: 'Daemon',
            async detect() {
                return null;
            },
            async execute(context) {
                seen = context.currentStepId;
                return succeeded();
            },
        };
        await orchestrator.execute({ context: buildContext(emitter, stateGateway), steps: [observingStep] });
        expect(seen).toBe('daemon');
    });
    it('warns and starts fresh when the loaded state is corrupted', async () => {
        const emitter = new RecordingEmitter();
        const stateGateway = new RecordingStateGateway({ state: null, corrupted: true });
        const steps = [new FakeStep('dependencies', 'Deps', null, succeeded())];
        const result = await orchestrator.execute({ context: buildContext(emitter, stateGateway), steps });
        expect(result.stateWasCorrupted).toBe(true);
        expect(emitter.warnings.length).toBeGreaterThan(0);
        expect(result.exitCode).toBe(0);
    });
});
//# sourceMappingURL=orchestrateSetup.usecase.test.js.map