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";
function readInputCursor(input) {
    const value = input.getValue();
    const raw = Reflect.get(input, "cursor");
    return typeof raw === "number" && Number.isFinite(raw)
        ? Math.max(0, Math.min(raw, value.length))
        : value.length;
}
function writeInputCursor(input, cursor) {
    const value = input.getValue();
    Reflect.set(input, "cursor", Math.max(0, Math.min(cursor, value.length)));
}
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: "",
        customDraftByTab: new Map(),
        customCaretByTab: new Map(),
    };
}
/**
 * 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) {
        this.state = this.mirrorInlineInputDraft(this.state);
        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 };
    }
    mirrorInlineInputDraft(s) {
        if (!s.inputMode)
            return s;
        const value = this.inlineInput.getValue();
        const caret = readInputCursor(this.inlineInput);
        if (s.customDraftByTab.get(s.currentTab) === value && s.customCaretByTab.get(s.currentTab) === caret) {
            return s;
        }
        const customDraftByTab = new Map(s.customDraftByTab);
        const customCaretByTab = new Map(s.customCaretByTab);
        customDraftByTab.set(s.currentTab, value);
        customCaretByTab.set(s.currentTab, caret);
        return { ...s, customDraftByTab, customCaretByTab };
    }
    runEffect(effect) {
        switch (effect.kind) {
            case "set_input_buffer":
                this.inlineInput.setValue(effect.value);
                writeInputCursor(this.inlineInput, effect.caret);
                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, and
     * the live buffer/caret are mirrored into canonical state so rendering can
     * draw the same thick cursor at the editing caret. `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.state = this.mirrorInlineInputDraft(this.state);
        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