import DryRunGraphModelMigrationPlanRequest from "../../../../src/domain/migrations/DryRunGraphModelMigrationPlanRequest.js";
import DryRunGraphModelMigrationPlanner from "../../../../src/domain/migrations/DryRunGraphModelMigrationPlanner.js";
import GenesisEquivalenceComparisonBasis from "../../../../src/domain/migrations/GenesisEquivalenceComparisonBasis.js";
import GenesisEquivalenceGate from "../../../../src/domain/migrations/GenesisEquivalenceGate.js";
import GenesisEquivalenceReading from "../../../../src/domain/migrations/GenesisEquivalenceReading.js";
import GraphModelMigrationFinalizationRequest from "../../../../src/domain/migrations/GraphModelMigrationFinalizationRequest.js";
import GraphModelMigrationFinalizationSafety from "../../../../src/domain/migrations/GraphModelMigrationFinalizationSafety.js";
import GraphModelMigrationOperationLowerer from "../../../../src/domain/migrations/GraphModelMigrationOperationLowerer.js";
import { finalizeGraphModelMigration } from "./GraphModelMigrationFinalizer.js";
import { reviewedGraphModelMigrationFinalizationSafetyResult } from "./GraphModelMigrationFinalizationReview.js";
import { writeGraphModelMigrationScratchHistory } from "./GraphModelMigrationScratchWriter.js";
import { runMigrationGit } from "./GitMigrationCommandRunner.js";
/** Result of the wired v18 graph-model migration command flow. */
export class GraphModelMigrationCommandResult {
    dryRunPlan;
    loweringResult;
    scratchWriteResult;
    gateResult;
    finalizationResult;
    constructor(dryRunPlan, loweringResult, scratchWriteResult, gateResult, finalizationResult) {
        this.dryRunPlan = dryRunPlan;
        this.loweringResult = loweringResult;
        this.scratchWriteResult = scratchWriteResult;
        this.gateResult = gateResult;
        this.finalizationResult = finalizationResult;
        Object.freeze(this);
    }
}
export class GraphModelMigrationCommandError extends Error {
    constructor(message) {
        super(message);
        this.name = 'GraphModelMigrationCommandError';
    }
}
/** Runs dry-run planning, lowering, scratch writing, equivalence, and optional finalization. */
export async function runGraphModelMigrationCommand(options) {
    const repositoryPath = requireNonEmptyString(options.repositoryPath, 'repositoryPath');
    const scratchRefName = requireNonEmptyString(options.scratchRefName, 'scratchRefName');
    const dryRunRequest = requireDryRunRequest(options.dryRunRequest);
    const dryRunPlan = new DryRunGraphModelMigrationPlanner().plan(dryRunRequest);
    const loweringResult = new GraphModelMigrationOperationLowerer().lower(dryRunPlan);
    if (loweringResult.hasFatalErrors() || loweringResult.patchPlan === null) {
        return new GraphModelMigrationCommandResult(dryRunPlan, loweringResult, null, null, null);
    }
    const scratchWriteResult = await writeGraphModelMigrationScratchHistory({
        repositoryPath,
        scratchRefName,
        patchPlan: loweringResult.patchPlan,
    });
    if (scratchWriteResult.hasFatalErrors()) {
        return new GraphModelMigrationCommandResult(dryRunPlan, loweringResult, scratchWriteResult, null, null);
    }
    const readings = await resolveReadings(options, scratchWriteResult);
    const gateResult = new GenesisEquivalenceGate().evaluate(requireBasis(options.equivalenceBasis), readings.legacyReading, readings.scratchReading);
    if (options.finalization === null) {
        return new GraphModelMigrationCommandResult(dryRunPlan, loweringResult, scratchWriteResult, gateResult, null);
    }
    const finalizationResult = await runFinalization({
        repositoryPath,
        scratchWriteResult,
        gateResult,
        finalization: options.finalization,
    });
    return new GraphModelMigrationCommandResult(dryRunPlan, loweringResult, scratchWriteResult, gateResult, finalizationResult);
}
async function resolveReadings(options, scratchWriteResult) {
    if (options.readingProviders !== null) {
        return Object.freeze({
            legacyReading: requireReading(await options.readingProviders.legacyReading(), 'legacyReading'),
            scratchReading: requireReading(await options.readingProviders.scratchReading(scratchWriteResult), 'scratchReading'),
        });
    }
    return Object.freeze({
        legacyReading: requireReading(options.legacyReading, 'legacyReading'),
        scratchReading: requireReading(options.scratchReading, 'scratchReading'),
    });
}
async function runFinalization(options) {
    const observedLiveHead = await gitTextOrNull(options.repositoryPath, [
        'show-ref',
        '--verify',
        '--hash',
        options.finalization.liveRefName,
    ]);
    const request = new GraphModelMigrationFinalizationRequest({
        liveRefName: options.finalization.liveRefName,
        expectedLiveHead: options.finalization.expectedLiveHead,
        observedLiveHead,
        scratchRef: options.scratchWriteResult.scratchRef,
        scratchHead: options.scratchWriteResult.scratchHead,
        archiveRefName: options.finalization.archiveRefName,
        confirmation: options.finalization.confirmation,
        gateResult: options.gateResult,
        runtimeConformance: await runtimeConformanceFromProvider(options.finalization.runtimeConformance, options.scratchWriteResult),
    });
    const safetyResult = reviewedGraphModelMigrationFinalizationSafetyResult(new GraphModelMigrationFinalizationSafety().evaluate(request), options.finalization.reviewedRequest ?? null);
    return await finalizeGraphModelMigration({
        repositoryPath: options.repositoryPath,
        safetyResult,
    });
}
function runtimeConformanceFromProvider(provider, scratchWriteResult) {
    if (provider === null) {
        return Promise.resolve(null);
    }
    return provider(scratchWriteResult);
}
function requireDryRunRequest(request) {
    if (!(request instanceof DryRunGraphModelMigrationPlanRequest)) {
        throw new GraphModelMigrationCommandError('dryRunRequest must be a DryRunGraphModelMigrationPlanRequest');
    }
    return request;
}
function requireBasis(basis) {
    if (!(basis instanceof GenesisEquivalenceComparisonBasis)) {
        throw new GraphModelMigrationCommandError('equivalenceBasis must be a GenesisEquivalenceComparisonBasis');
    }
    return basis;
}
function requireReading(reading, label) {
    if (!(reading instanceof GenesisEquivalenceReading)) {
        throw new GraphModelMigrationCommandError(`${label} must be a GenesisEquivalenceReading`);
    }
    return reading;
}
function requireNonEmptyString(value, name) {
    if (typeof value !== 'string' || value.length === 0) {
        throw new GraphModelMigrationCommandError(`${name} must be a non-empty string`);
    }
    return value;
}
async function gitTextOrNull(cwd, args) {
    const result = await runMigrationGit(cwd, args, null);
    if (!result.ok()) {
        return null;
    }
    return result.stdout.trim();
}
