/**
 * WizardRouter — declarative flow pipelines + overlay stack.
 *
 * Two layers:
 *   Flow cursor    — linear pipeline of screens, advanced with next()
 *   Overlay stack  — interrupts (outage, auth-expired, etc.) that push/pop
 *
 * The visible screen is: top of overlay stack if non-empty, otherwise the flow cursor.
 *
 * Adding a flow screen = append to a pipeline array.
 * Adding an overlay = call pushOverlay() from anywhere.
 * No switch statements, no hardcoded transitions in business logic.
 */
import { OutroKind } from './session-constants.js';
import { FLOWS, Screen, Flow } from './flows.js';
// Re-export so existing imports from './router.js' keep working
export { Screen, Flow };
// ── Screen name taxonomy ──────────────────────────────────────────────
/** Screens that interrupt flows as overlays */
export var Overlay;
(function (Overlay) {
    Overlay["Outage"] = "outage";
    Overlay["Snake"] = "snake";
    Overlay["Mcp"] = "mcp-overlay";
    Overlay["Slack"] = "slack-overlay";
    Overlay["Logout"] = "logout-overlay";
    Overlay["Login"] = "login-overlay";
})(Overlay || (Overlay = {}));
// ── Router ────────────────────────────────────────────────────────────
export class WizardRouter {
    flow;
    flowName;
    overlays = [];
    constructor(flowName = Flow.Wizard) {
        this.flowName = flowName;
        this.flow = FLOWS[flowName];
    }
    /**
     * Resolve which screen should be active based on session state.
     * Walks the flow pipeline, skipping hidden entries and completed entries,
     * returns the first incomplete screen.
     */
    resolve(session) {
        if (this.overlays.length > 0) {
            return this.overlays[this.overlays.length - 1];
        }
        // If the user cancelled before the run completed, jump directly to Outro.
        // Success/Error are handled by the normal flow (MCP + Slack still need to show).
        if (session.outroData?.kind === OutroKind.Cancel) {
            return Screen.Outro;
        }
        for (const entry of this.flow) {
            if (entry.show && !entry.show(session))
                continue;
            if (entry.isComplete && entry.isComplete(session))
                continue;
            return entry.screen;
        }
        // All entries complete — show the last screen (outro)
        return this.flow[this.flow.length - 1].screen;
    }
    /** The screen that should be rendered right now. */
    get activeScreen() {
        // Overlays take priority — resolve() handles this too,
        // but activeScreen is called before session is available in some paths
        if (this.overlays.length > 0) {
            return this.overlays[this.overlays.length - 1];
        }
        return this.flow[0].screen;
    }
    /** The name of the active flow. */
    get activeFlow() {
        return this.flowName;
    }
    /** Whether an overlay is currently active. */
    get hasOverlay() {
        return this.overlays.length > 0;
    }
    /**
     * Push an overlay that interrupts the current flow.
     * The flow resumes when the overlay is dismissed via popOverlay().
     */
    pushOverlay(overlay) {
        this.overlays.push(overlay);
    }
    /**
     * Dismiss the topmost overlay. The flow screen underneath resumes.
     */
    popOverlay() {
        this.overlays.pop();
    }
    /**
     * Find the index of the entry that resolve() would currently land on.
     * Returns flow.length when every entry is complete (i.e. on the trailing
     * fallback screen).
     */
    activeIndex(session) {
        for (let i = 0; i < this.flow.length; i++) {
            const entry = this.flow[i];
            if (entry.show && !entry.show(session))
                continue;
            if (entry.isComplete && entry.isComplete(session))
                continue;
            return i;
        }
        return this.flow.length;
    }
    /**
     * Whether the user can go back from the current screen.
     *
     * Walks the flow backwards from the active entry through entries that
     * the user has already completed (regardless of whether they're still
     * "shown" — RegionSelect is a good example: once a region is picked,
     * `show` returns false but the step still happened and is revertible).
     *
     * Returns true if it finds a previously-completed entry with a `revert`
     * defined. Returns false the moment it hits a completed entry without a
     * revert — those act as a wall (e.g. Run, which would be destructive
     * to undo).
     *
     * Overlays disable back-nav so users dismiss the overlay first.
     */
    canGoBack(session) {
        if (this.overlays.length > 0)
            return false;
        const activeIdx = this.activeIndex(session);
        for (let i = activeIdx - 1; i >= 0; i--) {
            const entry = this.flow[i];
            // Walk through entries that have happened. An entry "happened" if
            // its isComplete predicate is true; show=false alone doesn't disqualify
            // (a step can be hidden because it already concluded).
            if (!entry.isComplete || !entry.isComplete(session))
                continue;
            // Completed entry: either it can be reverted, or it's a wall.
            return Boolean(entry.revert);
        }
        return false;
    }
    /**
     * Step back one decision. Calls the most recent revertible entry's
     * `revert` callback so the router naturally re-resolves to that screen
     * on the next render. Returns true if a revert fired.
     *
     * Reverts that return `false` are treated as no-ops (nothing meaningful
     * to undo at that step) and the walk continues further back.
     */
    goBack(session, store) {
        if (this.overlays.length > 0)
            return false;
        const activeIdx = this.activeIndex(session);
        for (let i = activeIdx - 1; i >= 0; i--) {
            const entry = this.flow[i];
            if (!entry.isComplete || !entry.isComplete(session))
                continue;
            if (!entry.revert)
                return false; // wall — no back past this entry
            const reverted = entry.revert(store);
            if (reverted === false)
                continue; // no-op revert, keep walking
            this._lastDirection = 'pop';
            return true;
        }
        return false;
    }
    /**
     * Direction hint for screen transitions.
     */
    _lastDirection = null;
    get lastNavDirection() {
        return this._lastDirection;
    }
    /** @internal — called by store wrapper to track direction */
    _setDirection(dir) {
        this._lastDirection = dir;
    }
}
