import { customMessageExcludesContext, drainAgentMessageQueue, normalizeInterruptAbortMessage } from "./agent-session-types.js";
export async function _queueSteer(text, images) {
    this._steeringMessages.push(text);
    this._emitQueueUpdate();
    const content = [{ type: "text", text }];
    if (images) {
        content.push(...images);
    }
    this._queueAgentMessage({
        role: "user",
        content,
        timestamp: Date.now(),
    }, "steer");
}
/**
 * Internal: Queue a follow-up message (already expanded, no extension command check).
 */
export async function _queueFollowUp(text, images) {
    this._followUpMessages.push(text);
    this._emitQueueUpdate();
    const content = [{ type: "text", text }];
    if (images) {
        content.push(...images);
    }
    this._queueAgentMessage({
        role: "user",
        content,
        timestamp: Date.now(),
    }, "followUp");
}
/**
 * Throw an error if the text is an extension command.
 */
export function _throwIfExtensionCommand(text) {
    const spaceIndex = text.indexOf(" ");
    const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
    const command = this._extensionRunner.getCommand(commandName);
    if (command) {
        throw new Error(`Extension command "/${commandName}" cannot be queued. Use prompt() or execute the command when not streaming.`);
    }
}
/**
 * Send a custom message to the session. Creates a CustomMessageEntry.
 *
 * Handles five cases:
 * - Streaming + interrupt trigger: aborts the active run and starts an immediate custom-message turn
 * - Streaming + explicit display-only context exclusion: appends to state/session, no turn and no queue
 * - Streaming otherwise: queues message, processed when loop pulls from queue
 * - Not streaming + triggerTurn: appends to state/session, starts new turn
 * - Not streaming + no trigger: appends to state/session, no turn
 *
 * @param message Custom message with customType, content, display, details
 * @param options.triggerTurn If true and not streaming, triggers a new LLM turn
 * @param options.deliverAs Delivery mode: "steer", "followUp", "nextTurn", or "interrupt"
 */
export async function sendCustomMessage(message, options) {
    const appMessage = {
        role: "custom",
        customType: message.customType,
        content: message.content ?? [],
        display: message.display,
        details: message.details,
        timestamp: Date.now(),
        ...(options?.excludeFromContext === true ? { excludeFromContext: true } : {}),
    };
    if (options?.deliverAs === "nextTurn") {
        this._pendingNextTurnMessages.push(appMessage);
    }
    else if (options?.deliverAs === "interrupt" && options.triggerTurn) {
        await this._enqueueInterruptCustomMessage(appMessage, options);
    }
    else if (this.isStreaming && options?.excludeFromContext === true && options.triggerTurn !== true && options.deliverAs === undefined) {
        this._appendCustomMessage(appMessage);
    }
    else if (this.isStreaming) {
        this._queueAgentMessage(appMessage, options?.deliverAs === "followUp" ? "followUp" : "steer");
    }
    else if (options?.triggerTurn) {
        await this._runAgentPrompt(appMessage);
    }
    else {
        this._appendCustomMessage(appMessage);
    }
}
export function _appendCustomMessage(message) {
    this.agent.state.messages.push(message);
    this.sessionManager.appendCustomMessageEntry(message.customType, message.content, message.display, message.details, customMessageExcludesContext(message));
    this._emit({ type: "message_start", message });
    this._emit({ type: "message_end", message });
}
export function _enqueueInterruptCustomMessage(message, options) {
    this._pendingInterruptDeliveries += 1;
    // Establish the hold synchronously when the interrupt is enqueued, not when
    // the serialized delivery callback later starts. Callers commonly fire and
    // forget sendCustomMessage(), then queue additional steer/follow-up messages
    // before the promise chain gets a microtask; those messages must be captured
    // in the active interrupt hold instead of pi-agent-core's live queues.
    this._ensureActiveInterruptQueueHold();
    const delivery = this._interruptDeliveryQueue.then(async () => {
        try {
            await this._sendInterruptCustomMessageNow(message, options);
        }
        finally {
            this._pendingInterruptDeliveries -= 1;
            if (this._pendingInterruptDeliveries === 0) {
                this._restoreAndClearActiveInterruptQueueHold();
            }
        }
    });
    this._interruptDeliveryQueue = delivery.catch(() => undefined);
    return delivery;
}
export async function _sendInterruptCustomMessageNow(message, options) {
    this.abortRetry();
    this._ensureActiveInterruptQueueHold();
    if (this.isStreaming) {
        const previousAbortMessage = this._activeInterruptAbortMessage;
        this._activeInterruptAbortMessage = normalizeInterruptAbortMessage(options?.interruptAbortMessage);
        try {
            this.agent.abort();
            await this.agent.waitForIdle();
            await this._agentEventQueue;
        }
        finally {
            this._activeInterruptAbortMessage = previousAbortMessage;
        }
    }
    await this.agent.prompt(message);
}
export function _ensureActiveInterruptQueueHold() {
    if (this._activeInterruptQueueHold !== undefined) {
        return this._activeInterruptQueueHold;
    }
    const drained = this._drainQueuedAgentMessages();
    this._activeInterruptQueueHold = {
        steering: [...drained.steering],
        followUp: [...drained.followUp],
    };
    return this._activeInterruptQueueHold;
}
export function _restoreAndClearActiveInterruptQueueHold() {
    const hold = this._activeInterruptQueueHold;
    if (hold === undefined) {
        return;
    }
    const currentCoreQueues = this._drainQueuedAgentMessages();
    this._restoreQueuedAgentMessages({
        steering: [...hold.steering, ...currentCoreQueues.steering],
        followUp: [...hold.followUp, ...currentCoreQueues.followUp],
    });
    this._activeInterruptQueueHold = undefined;
}
export function _queueAgentMessage(message, delivery) {
    const hold = this._activeInterruptQueueHold;
    if (hold !== undefined) {
        if (delivery === "followUp") {
            hold.followUp.push(message);
        }
        else {
            hold.steering.push(message);
        }
        return;
    }
    if (delivery === "followUp") {
        this.agent.followUp(message);
    }
    else {
        this.agent.steer(message);
    }
}
export function _drainQueuedAgentMessages() {
    // pi-agent-core exposes public clear methods but no public drain/restore pair.
    // Interrupts need to prevent the aborting run from consuming queued steer/follow-up
    // messages while still preserving those queues for a later turn.
    const agentWithQueues = this.agent;
    return {
        steering: drainAgentMessageQueue(agentWithQueues.steeringQueue),
        followUp: drainAgentMessageQueue(agentWithQueues.followUpQueue),
    };
}
export function _restoreQueuedAgentMessages(queues) {
    for (const message of queues.steering) {
        this.agent.steer(message);
    }
    for (const message of queues.followUp) {
        this.agent.followUp(message);
    }
}
/**
 * Send a user message to the agent. Always triggers a turn.
 * When the agent is streaming, use deliverAs to specify how to queue the message.
 *
 * @param content User message content (string or content array)
 * @param options.deliverAs Delivery mode when streaming: "steer" or "followUp"
 */
export function clearQueue() {
    const steering = [...this._steeringMessages];
    const followUp = [...this._followUpMessages];
    this._steeringMessages = [];
    this._followUpMessages = [];
    this.agent.clearAllQueues();
    if (this._activeInterruptQueueHold !== undefined) {
        this._activeInterruptQueueHold.steering.length = 0;
        this._activeInterruptQueueHold.followUp.length = 0;
    }
    this._emitQueueUpdate();
    return { steering, followUp };
}
/** Number of pending messages (includes both steering and follow-up) */
export function getSteeringMessages() {
    return this._steeringMessages;
}
/** Get pending follow-up messages (read-only) */
export function getFollowUpMessages() {
    return this._followUpMessages;
}
export async function abort() {
    this.abortRetry();
    this.agent.abort();
    await this.agent.waitForIdle();
}
// =========================================================================
// Model Management
// =========================================================================
export function setSteeringMode(mode) {
    this.agent.steeringMode = mode;
    this.settingsManager.setSteeringMode(mode);
}
/**
 * Set follow-up message mode.
 * Saves to settings.
 */
export function setFollowUpMode(mode) {
    this.agent.followUpMode = mode;
    this.settingsManager.setFollowUpMode(mode);
}
// =========================================================================
// Compaction
// =========================================================================
/**
 * Apply validated logical deletions and rebuild active agent context.
 * Retained transcript entries/content blocks stay verbatim.
 */
export const agentSessionMessageQueueMethods = {
    _queueSteer,
    _queueFollowUp,
    _throwIfExtensionCommand,
    sendCustomMessage,
    _appendCustomMessage,
    _enqueueInterruptCustomMessage,
    _sendInterruptCustomMessageNow,
    _ensureActiveInterruptQueueHold,
    _restoreAndClearActiveInterruptQueueHold,
    _queueAgentMessage,
    _drainQueuedAgentMessages,
    _restoreQueuedAgentMessages,
    clearQueue,
    getSteeringMessages,
    getFollowUpMessages,
    abort,
    setSteeringMode,
    setFollowUpMode,
};
//# sourceMappingURL=agent-session-message-queue.js.map