// Onboarding state management — graph-persisted workflow state.
// Purpose-built for Phase 0; migrates to a Workflow node with Task 13.
import { readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
const STEP_FIELDS = [
    "step1CompletedAt",
    "step2CompletedAt",
    "step3CompletedAt",
    "step4CompletedAt",
    "step5CompletedAt",
    "step6CompletedAt",
    "step7CompletedAt",
    "step8CompletedAt",
];
// neo4j-driver v5 returns Integer objects for integer properties, not native
// JS numbers. This converts them safely (values 0–8 are well within range).
function toJsNumber(val) {
    if (typeof val === "number")
        return val;
    if (val && typeof val === "object" && "toNumber" in val) {
        return val.toNumber();
    }
    return 0;
}
function recordToState(record) {
    return {
        currentStep: toJsNumber(record.get("currentStep")),
        step1CompletedAt: record.get("step1CompletedAt") ?? null,
        step2CompletedAt: record.get("step2CompletedAt") ?? null,
        step3CompletedAt: record.get("step3CompletedAt") ?? null,
        step4CompletedAt: record.get("step4CompletedAt") ?? null,
        step5CompletedAt: record.get("step5CompletedAt") ?? null,
        step6CompletedAt: record.get("step6CompletedAt") ?? null,
        step7CompletedAt: record.get("step7CompletedAt") ?? null,
        step8CompletedAt: record.get("step8CompletedAt") ?? null,
        createdAt: record.get("createdAt") ?? new Date().toISOString(),
        updatedAt: record.get("updatedAt") ?? new Date().toISOString(),
    };
}
function readMigrationStep(accountDir) {
    try {
        const raw = readFileSync(join(accountDir, "account.json"), "utf-8");
        const config = JSON.parse(raw);
        if (typeof config.onboardingStep === "number" && config.onboardingStep > 0) {
            return config.onboardingStep;
        }
    }
    catch {
        // File missing, unreadable, or malformed — no migration data
    }
    return 0;
}
function removeOnboardingStepFromFile(accountDir) {
    try {
        const configPath = join(accountDir, "account.json");
        const raw = readFileSync(configPath, "utf-8");
        const config = JSON.parse(raw);
        if ("onboardingStep" in config) {
            delete config.onboardingStep;
            writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
        }
    }
    catch {
        // Tolerate cleanup failure — graph is the authority now
    }
}
export async function getOnboardingState(sessionFactory, accountId, accountDir) {
    const session = sessionFactory();
    try {
        // Check for existing graph node
        const existing = await session.run(`MATCH (o:OnboardingState {accountId: $accountId})
       RETURN o.currentStep AS currentStep,
              o.step1CompletedAt AS step1CompletedAt,
              o.step2CompletedAt AS step2CompletedAt,
              o.step3CompletedAt AS step3CompletedAt,
              o.step4CompletedAt AS step4CompletedAt,
              o.step5CompletedAt AS step5CompletedAt,
              o.step6CompletedAt AS step6CompletedAt,
              o.step7CompletedAt AS step7CompletedAt,
              o.step8CompletedAt AS step8CompletedAt,
              o.createdAt AS createdAt,
              o.updatedAt AS updatedAt`, { accountId });
        if (existing.records.length > 0) {
            return recordToState(existing.records[0]);
        }
        // No graph node — check for migration from account.json
        const migratedStep = readMigrationStep(accountDir);
        const now = new Date().toISOString();
        // Build step completion timestamps for migrated steps.
        // account.json only stored a step counter, not per-step timestamps,
        // so migrated steps all get the current time — the original times
        // were never recorded.
        const stepProps = {};
        for (let i = 0; i < STEP_FIELDS.length; i++) {
            stepProps[STEP_FIELDS[i]] = i < migratedStep ? now : null;
        }
        // Create the node via MERGE (idempotent under concurrent access)
        const result = await session.run(`MERGE (o:OnboardingState {accountId: $accountId})
       ON CREATE SET
         o.currentStep = $currentStep,
         o.step1CompletedAt = $step1CompletedAt,
         o.step2CompletedAt = $step2CompletedAt,
         o.step3CompletedAt = $step3CompletedAt,
         o.step4CompletedAt = $step4CompletedAt,
         o.step5CompletedAt = $step5CompletedAt,
         o.step6CompletedAt = $step6CompletedAt,
         o.step7CompletedAt = $step7CompletedAt,
         o.step8CompletedAt = $step8CompletedAt,
         o.createdAt = $now,
         o.updatedAt = $now
       RETURN o.currentStep AS currentStep,
              o.step1CompletedAt AS step1CompletedAt,
              o.step2CompletedAt AS step2CompletedAt,
              o.step3CompletedAt AS step3CompletedAt,
              o.step4CompletedAt AS step4CompletedAt,
              o.step5CompletedAt AS step5CompletedAt,
              o.step6CompletedAt AS step6CompletedAt,
              o.step7CompletedAt AS step7CompletedAt,
              o.step8CompletedAt AS step8CompletedAt,
              o.createdAt AS createdAt,
              o.updatedAt AS updatedAt`, { accountId, currentStep: migratedStep, now, ...stepProps });
        // Clean up account.json if migration happened
        if (migratedStep > 0) {
            removeOnboardingStepFromFile(accountDir);
        }
        return recordToState(result.records[0]);
    }
    finally {
        await session.close();
    }
}
export async function completeOnboardingStep(sessionFactory, accountId, step) {
    if (!Number.isInteger(step) || step < 1 || step > 8) {
        throw new Error("Step must be between 1 and 8");
    }
    const session = sessionFactory();
    try {
        const stepField = STEP_FIELDS[step - 1];
        const now = new Date().toISOString();
        // Single query: MATCH + SET. If no node exists, MATCH returns zero
        // records and we detect that from the result — no separate check needed.
        const result = await session.run(`MATCH (o:OnboardingState {accountId: $accountId})
       SET o.${stepField} = CASE WHEN o.${stepField} IS NULL THEN $now ELSE o.${stepField} END,
           o.currentStep = CASE WHEN $step > o.currentStep THEN $step ELSE o.currentStep END,
           o.updatedAt = $now
       RETURN o.currentStep AS currentStep,
              o.step1CompletedAt AS step1CompletedAt,
              o.step2CompletedAt AS step2CompletedAt,
              o.step3CompletedAt AS step3CompletedAt,
              o.step4CompletedAt AS step4CompletedAt,
              o.step5CompletedAt AS step5CompletedAt,
              o.step6CompletedAt AS step6CompletedAt,
              o.step7CompletedAt AS step7CompletedAt,
              o.step8CompletedAt AS step8CompletedAt,
              o.createdAt AS createdAt,
              o.updatedAt AS updatedAt`, { accountId, step, now });
        if (result.records.length === 0) {
            throw new Error("No onboarding state found for this account. Call onboarding-get first.");
        }
        return recordToState(result.records[0]);
    }
    finally {
        await session.close();
    }
}
//# sourceMappingURL=onboarding.js.map