import { getKeybindings } from "@earendil-works/pi-tui";
import { buildQuestionnaire } from "./build-questionnaire.js";
import { ROW_INTENT_META } from "./row-intent.js";
import { routeKey } from "./key-router.js";
import { computeFocusedOptionHasPreview } from "./selectors/derivations.js";
import { reduce } from "./state-reducer.js";
// Module-level constant; reused for cursor-end mutations after setValue rehydration.
// Ctrl-E → tui.editor.cursorLineEnd (public path; pi-tui keybindings.js:25-28).
const CURSOR_END = "\x05";
function initialState() {
    return {
        currentTab: 0,
        optionIndex: 0,
        inputMode: false,
        notesVisible: false,
        chatFocused: false,
        answers: new Map(),
        multiSelectChecked: new Set(),
        notesByTab: new Map(),
        focusedOptionHasPreview: false,
        submitChoiceIndex: 0,
        notesDraft: "",
    };
}
/**
 * Slim runtime: owns the canonical state cell, the input-buffer cell, the
 * two-pass `notesVisible` dispatch loop, and the effect runner. State
 * transitions go through the pure `reduce` reducer; UI fan-out goes through
 * the `QuestionnairePropsAdapter` produced by `buildQuestionnaire`.
 */
export class QuestionnaireSession {
    constructor(config) {
        this.state = initialState();
        this.tui = config.tui;
        this.done = config.done;
        this.questions = config.params.questions;
        this.isMulti = this.questions.length > 1;
        this.itemsByTab = config.itemsByTab;
        // Seed from the focused option at start; the reducer keeps it in sync via withFocusedOptionHasPreview.
        this.state = { ...this.state, focusedOptionHasPreview: computeFocusedOptionHasPreview(this.questions, 0, 0) };
        const built = buildQuestionnaire({
            tui: this.tui,
            theme: config.theme,
            questions: this.questions,
            itemsByTab: this.itemsByTab,
            isMulti: this.isMulti,
            initialState: this.state,
            getCurrentTab: () => this.state.currentTab,
        });
        this.notesInput = built.notesInput;
        this.inlineInput = built.inlineInput;
        this.viewAdapter = built.adapter;
        this.component = {
            render: built.render,
            invalidate: built.invalidate,
            handleInput: (data) => this.dispatch(data),
        };
        this.viewAdapter.apply(this.state);
    }
    dispatch(data) {
        const action = routeKey(data, this.state, this.runtime());
        if (action.kind === "ignore") {
            this.handleIgnoreInline(data);
            return;
        }
        this.commit(action);
    }
    commit(action) {
        const result = reduce(this.state, action, this.applyContext());
        this.state = result.state;
        for (const effect of result.effects)
            this.runEffect(effect);
        this.state = this.mirrorNotesDraft(this.state);
        this.viewAdapter.apply(this.state);
    }
    mirrorNotesDraft(s) {
        const draft = this.notesInput.getValue();
        return s.notesDraft === draft ? s : { ...s, notesDraft: draft };
    }
    runEffect(effect) {
        switch (effect.kind) {
            case "set_input_buffer":
                this.inlineInput.setValue(effect.value);
                this.inlineInput.handleInput(CURSOR_END);
                return;
            case "clear_input_buffer":
                this.inlineInput.setValue("");
                return;
            case "set_notes_value":
                this.notesInput.setValue(effect.value);
                return;
            case "set_notes_focused":
                this.notesInput.focused = effect.focused;
                return;
            case "forward_notes_keystroke":
                this.notesInput.handleInput(effect.data);
                return;
            case "done":
                this.done(effect.result);
                return;
        }
    }
    /**
     * Per-keystroke `ignore` fast path: delegates to the headless `inlineInput`
     * Input so bracketed-paste accumulator (`input.js:33-63`) and Kitty CSI-u
     * decode (`input.js:155-163`) take effect. Cursor is NOT force-reset here —
     * doing so would corrupt split-chunk pastes (a `\x05` byte mid-paste lands
     * verbatim in `pasteBuffer` and survives `handlePaste`'s narrow strip).
     * Cursor advances naturally via `insertCharacter` on typing/paste; cursor-
     * movement keys (Left/Right/Home/End/word-jumps) are now functional, with
     * the always-end visual cursor marker drawn independently by
     * `WrappingSelect.renderInlineInputRow`. `viewAdapter.apply` is called
     * directly without a reducer round-trip — preserves the D3 fast-path
     * latency profile from Phase 11.
     */
    handleIgnoreInline(data) {
        if (!this.state.inputMode)
            return;
        this.inlineInput.handleInput(data);
        this.viewAdapter.apply(this.state);
    }
    runtime() {
        return {
            keybindings: getKeybindings(),
            inputBuffer: this.inlineInput.getValue(),
            questions: this.questions,
            isMulti: this.isMulti,
            currentItem: this.currentItem(),
            items: this.itemsByTab[this.state.currentTab] ?? [],
        };
    }
    applyContext() {
        return {
            questions: this.questions,
            itemsByTab: this.itemsByTab,
        };
    }
    currentItem() {
        if (this.state.chatFocused)
            return { kind: "chat", label: ROW_INTENT_META.chat.label };
        const arr = this.itemsByTab[this.state.currentTab] ?? [];
        if (this.state.optionIndex < arr.length)
            return arr[this.state.optionIndex];
        return { kind: "chat", label: ROW_INTENT_META.chat.label };
    }
}
//# sourceMappingURL=questionnaire-session.js.map