import { addressabilityRecoveryHint } from './terminal/resolve.js';
import { injectTargetFromReplyRail } from './session/inject.js';
/**
 * Match free-text answer against question options.
 * Exact (case-insensitive) > startsWith > includes. Returns 0-based index or -1.
 */
export function matchOptionIndex(answer, options) {
    if (!options?.length)
        return -1;
    const needle = answer.trim().toLowerCase();
    if (!needle)
        return -1;
    const labels = options.map((o) => (o.label ?? '').trim());
    const exact = labels.findIndex((l) => l.toLowerCase() === needle);
    if (exact >= 0)
        return exact;
    const starts = labels.findIndex((l) => l.toLowerCase().startsWith(needle));
    if (starts >= 0)
        return starts;
    const includes = labels.findIndex((l) => l.toLowerCase().includes(needle));
    return includes;
}
/**
 * Build the keystroke payload that closes an AskUserQuestion TUI.
 * Numbered options are selected by digit (1-based) + Enter. Free text with an
 * "Other" option selects Other then types; without Other, types the answer.
 */
export function keystrokesForAnswer(answer, options) {
    const idx = matchOptionIndex(answer, options);
    if (idx >= 0) {
        // AskUserQuestion / plan select-lists are 1-indexed digits.
        return { payload: `${idx + 1}`, matched: 'option' };
    }
    if (options?.length) {
        const otherIdx = options.findIndex((o) => /^other$/i.test((o.label ?? '').trim()));
        if (otherIdx >= 0) {
            // Select Other, then type the free text on the next field.
            return { payload: `${otherIdx + 1}\n${answer}`, matched: 'other' };
        }
    }
    return { payload: answer, matched: 'free-text' };
}
/** True when the session is waiting on user input (parked on a question/plan). */
export function isParkedOnInput(session) {
    if (!session)
        return false;
    if (session.status === 'input_required')
        return true;
    if (session.activity === 'waiting_input')
        return true;
    if (session.awaitingReason === 'question' || session.awaitingReason === 'plan_review' || session.awaitingReason === 'permission') {
        return true;
    }
    return false;
}
/** True when an open feed block still needs an answer. */
export function isOpenQuestionBlock(block) {
    if (!block)
        return false;
    if (block.answer || block.parkedAt || block.continuedAt || block.defaultedAt)
        return false;
    return (block.questions?.length ?? 0) > 0;
}
function injectTargetForSession(session) {
    if (session.provenance?.reply) {
        const fromRail = injectTargetFromReplyRail(session.provenance.reply);
        if (fromRail)
            return fromRail;
    }
    // Only the agents-pty sidecar is addressable by session id. Generic headless
    // runs have no PTY server entry — those go through resume, not inject.
    if (session.sessionId && session.host === 'pty') {
        return { backend: 'pty', id: session.sessionId };
    }
    return null;
}
/**
 * Pick the delivery mechanism for one answer.
 *
 * Precedence:
 *   1. Parked on open question + injectable rail (tmux/iterm/pty) → keystroke
 *   2. Parked on open question + headless (no rail) → resume with answer
 *   3. Parked on open question + no rail + interactive → refuse (don't mailbox-drop)
 *   4. Otherwise → mailbox (running agent between tool calls)
 */
export function resolveAnswerRoute(input) {
    const { answer, block, session } = input;
    const openQ = isOpenQuestionBlock(block);
    const parked = isParkedOnInput(session);
    // Options from the first question on the block (or the session's structured question).
    const options = block?.questions?.[0]?.options ??
        session?.question?.options?.map((o) => ({ label: o.label }));
    if (openQ && parked && session) {
        const inject = injectTargetForSession(session);
        const { payload, matched } = keystrokesForAnswer(answer, options);
        if (inject) {
            const kind = inject.backend === 'tmux' ? 'tmux'
                : inject.backend === 'iterm' ? 'iterm'
                    : inject.backend === 'pty' ? 'pty'
                        : 'pty';
            return {
                kind,
                reason: `Parked on open question — drive ${inject.backend} selection (${matched}).`,
                payload,
                inject,
            };
        }
        // Headless / no rail: re-enter via resume.
        if (session.context === 'headless' || session.context === 'teams' || !session.tty) {
            const sid = session.sessionId ?? input.mailboxId;
            if (!sid) {
                return {
                    kind: 'refuse',
                    reason: 'Parked headless agent has no session id to resume.',
                };
            }
            return {
                kind: 'resume',
                reason: 'Parked headless agent — resume with the answer as the next user turn.',
                payload: answer,
                resume: { sessionId: sid, agent: session.kind },
            };
        }
        return {
            kind: 'refuse',
            reason: 'Agent is parked on a question but has no addressable terminal (no tmux/iterm/pty rail). ' +
                addressabilityRecoveryHint(session),
        };
    }
    // Open block but agent is still looping between tool calls — mailbox is correct.
    // No block — free-form mid-flight steer — mailbox.
    return {
        kind: 'mailbox',
        reason: openQ
            ? 'Open block on a running agent — deliver via mailbox at next tool call.'
            : 'No open question — deliver via mailbox at next tool call.',
        payload: answer,
    };
}
/** Build argv for a headless resume that continues with the answer text. */
export function resumeArgv(route) {
    if (route.kind !== 'resume' || !route.resume) {
        throw new Error('resumeArgv requires a resume route');
    }
    const { agent, sessionId } = route.resume;
    const text = route.payload ?? '';
    // `agents run <agent> --resume <id> -- <answer>` — the trailing prompt is the
    // next user turn after native resume (claude/codex) or /continue replay.
    return ['run', agent, '--resume', sessionId, '--', text];
}
