import { applyRoutingInstructions, cleanMessagesFromRoutingInstructions, parseRoutingInstructions } from '../routing-instructions.js';
import { buildMetadataInstructions } from '../engine/routing-state/metadata.js';
import { getRoutingInstructionState, persistRoutingInstructionState, resolveStopMessageScope } from '../engine/routing-state/store.js';
import { ensureStopMessageModeMaxRepeats } from '../routing-stop-message-state-codec.js';
import { extractMessageText } from '../message-utils.js';
import { cleanResponsesContextFromRoutingInstructions, getLatestUserTextFromResponsesContext, hasLatestUserRoutingInstructionMarker, hasRoutingInstructionMarker, hasRoutingInstructionMarkerInResponsesContext, isServerToolFollowupRequest, isStopScopeTraceEnabled, normalizeStopMessageAiMode, normalizeStopMessageStageMode, stripClientInjectScopedFields, stripStopMessageFields } from './helpers.js';
import { enforceAllowlistIntersection } from './route-state-allowlist.js';
export function buildRoutingState(engine, request, metadata) {
    const stickyKey = engine.resolveStickyKey(metadata);
    const sessionScope = engine.resolveSessionScope(metadata);
    const stopMessageScope = resolveStopMessageScope(metadata);
    // Route sticky state remains session/request scoped for routing behavior,
    // but stopMessage state is tmux/clockd scoped and merged separately below.
    const stateKey = sessionScope || stickyKey || 'default';
    const baseState = getRoutingInstructionState(stateKey, engine.routingInstructionState, engine.routingStateStore);
    let routingState = stripStopMessageFields(baseState);
    const metadataInstructions = buildMetadataInstructions(metadata);
    if (metadataInstructions.length > 0) {
        routingState = applyRoutingInstructions(metadataInstructions, routingState);
    }
    const disableStickyRoutes = metadata &&
        typeof metadata === 'object' &&
        metadata.disableStickyRoutes === true;
    if (disableStickyRoutes && (routingState.stickyTarget || routingState.preferTarget)) {
        routingState = {
            ...routingState,
            stickyTarget: undefined,
            preferTarget: undefined
        };
    }
    if (stopMessageScope) {
        const sessionState = getRoutingInstructionState(stopMessageScope, engine.routingInstructionState, engine.routingStateStore);
        if (ensureStopMessageModeMaxRepeats(sessionState)) {
            engine.routingInstructionState.set(stopMessageScope, sessionState);
            persistRoutingInstructionState(stopMessageScope, sessionState, engine.routingStateStore);
        }
        if (typeof sessionState.stopMessageText === 'string' ||
            typeof sessionState.stopMessageMaxRepeats === 'number' ||
            typeof sessionState.stopMessageStageMode === 'string' ||
            typeof sessionState.stopMessageAiMode === 'string' ||
            typeof sessionState.stopMessageAiSeedPrompt === 'string' ||
            Array.isArray(sessionState.stopMessageAiHistory)) {
            routingState = {
                ...routingState,
                stopMessageText: sessionState.stopMessageText,
                stopMessageMaxRepeats: sessionState.stopMessageMaxRepeats,
                stopMessageUsed: sessionState.stopMessageUsed,
                stopMessageUpdatedAt: sessionState.stopMessageUpdatedAt,
                stopMessageLastUsedAt: sessionState.stopMessageLastUsedAt,
                stopMessageStageMode: sessionState.stopMessageStageMode,
                stopMessageAiMode: sessionState.stopMessageAiMode,
                stopMessageAiSeedPrompt: sessionState.stopMessageAiSeedPrompt,
                stopMessageAiHistory: sessionState.stopMessageAiHistory
            };
        }
        if (typeof sessionState.preCommandScriptPath === 'string' && sessionState.preCommandScriptPath.trim()) {
            routingState = {
                ...routingState,
                preCommandSource: sessionState.preCommandSource,
                preCommandScriptPath: sessionState.preCommandScriptPath,
                preCommandUpdatedAt: sessionState.preCommandUpdatedAt
            };
        }
    }
    let parsedInstructions = parseRoutingInstructions(request.messages);
    const responsesContext = request.semantics && typeof request.semantics === 'object'
        ? request.semantics.responses?.context
        : undefined;
    const responsesLatestUserText = getLatestUserTextFromResponsesContext(responsesContext);
    const responsesHasMarker = hasRoutingInstructionMarkerInResponsesContext(responsesContext);
    if (!parsedInstructions.length && responsesHasMarker) {
        parsedInstructions = parseRoutingInstructions([
            { role: 'user', content: responsesLatestUserText }
        ]);
    }
    const markerDetected = hasRoutingInstructionMarker(request.messages) || responsesHasMarker;
    const latestMessage = request.messages.length > 0 ? request.messages[request.messages.length - 1] : undefined;
    const latestUserText = latestMessage && latestMessage.role === 'user' ? extractMessageText(latestMessage).trim() : '';
    const stopMessageLogText = responsesHasMarker ? responsesLatestUserText : latestUserText;
    const containsStopMessage = Boolean(stopMessageLogText && /stopmessage/i.test(stopMessageLogText));
    const stopMessageTypes = parsedInstructions
        .filter((entry) => entry.type === 'stopMessageSet' ||
        entry.type === 'stopMessageMode' ||
        entry.type === 'stopMessageClear')
        .map((entry) => entry.type);
    if (containsStopMessage || stopMessageTypes.length > 0) {
        const preview = stopMessageLogText.replace(/\s+/g, ' ').slice(0, 120);
        engine.debug?.log?.(`[virtual-router][stop_message_parse] requestId=${metadata.requestId || 'n/a'} marker=${markerDetected ? 'detected' : 'missing'} parsed=${stopMessageTypes.join(',') || 'none'} preview=${preview}`);
    }
    if (markerDetected && isStopScopeTraceEnabled()) {
        const parsedTypes = parsedInstructions.map((entry) => entry.type);
        engine.debug?.log?.(`[virtual-router][instruction_parse] requestId=${metadata.requestId || 'n/a'} marker=detected parsed=${parsedTypes.join(',') || 'none'}`);
    }
    else if (isStopScopeTraceEnabled()) {
        if (stopMessageLogText && /stopmessage/i.test(stopMessageLogText)) {
            const preview = stopMessageLogText.replace(/\s+/g, ' ').slice(0, 120);
            engine.debug?.log?.(`[virtual-router][instruction_parse] requestId=${metadata.requestId || 'n/a'} marker=missing contains_stopmessage=yes preview=${preview}`);
        }
    }
    const serverToolFollowup = isServerToolFollowupRequest(metadata);
    const latestUserHasMarker = hasLatestUserRoutingInstructionMarker(request.messages) || responsesHasMarker;
    let instructions = parsedInstructions;
    if (serverToolFollowup && instructions.length > 0) {
        instructions = instructions.filter((entry) => entry.type !== 'stopMessageSet' &&
            entry.type !== 'stopMessageMode' &&
            entry.type !== 'stopMessageClear' &&
            entry.type !== 'preCommandSet' &&
            entry.type !== 'preCommandClear');
    }
    if (stopMessageScope && parsedInstructions.length > 0) {
        const sessionState = getRoutingInstructionState(stopMessageScope, engine.routingInstructionState, engine.routingStateStore);
        const hasStaleStopMessageInstruction = !latestUserHasMarker &&
            parsedInstructions.some((entry) => entry.type === 'stopMessageSet' || entry.type === 'stopMessageMode');
        if (hasStaleStopMessageInstruction) {
            const hasActiveStopState = typeof sessionState.stopMessageText === 'string' ||
                typeof sessionState.stopMessageMaxRepeats === 'number' ||
                typeof sessionState.stopMessageStageMode === 'string' ||
                typeof sessionState.stopMessageAiMode === 'string' ||
                typeof sessionState.stopMessageAiSeedPrompt === 'string' ||
                Array.isArray(sessionState.stopMessageAiHistory);
            const hasStopLifecycleStamp = (typeof sessionState.stopMessageUpdatedAt === 'number' &&
                Number.isFinite(sessionState.stopMessageUpdatedAt)) ||
                (typeof sessionState.stopMessageLastUsedAt === 'number' &&
                    Number.isFinite(sessionState.stopMessageLastUsedAt));
            if (hasActiveStopState || hasStopLifecycleStamp) {
                instructions = instructions.filter((entry) => entry.type !== 'stopMessageSet' && entry.type !== 'stopMessageMode');
            }
        }
    }
    // stopMessage/precommand require explicit client inject scope (tmux).
    // When scope is missing, drop only these instructions and keep request passthrough.
    if (instructions.length > 0 && !stopMessageScope) {
        const blockedInstructionTypes = instructions
            .filter((entry) => entry.type === 'stopMessageSet' ||
            entry.type === 'stopMessageMode' ||
            entry.type === 'stopMessageClear' ||
            entry.type === 'preCommandSet' ||
            entry.type === 'preCommandClear')
            .map((entry) => entry.type);
        if (blockedInstructionTypes.length > 0 && isStopScopeTraceEnabled()) {
            engine.debug?.log?.(`[virtual-router][stop_scope] requestId=${metadata.requestId || 'n/a'} stage=drop reason=missing_tmux_scope instructions=${blockedInstructionTypes.join(',')}`);
        }
        instructions = instructions.filter((entry) => entry.type !== 'stopMessageSet' &&
            entry.type !== 'stopMessageMode' &&
            entry.type !== 'stopMessageClear' &&
            entry.type !== 'preCommandSet' &&
            entry.type !== 'preCommandClear');
    }
    if (hasRoutingInstructionMarker(request.messages) || responsesHasMarker) {
        request.messages = cleanMessagesFromRoutingInstructions(request.messages);
        cleanResponsesContextFromRoutingInstructions(responsesContext);
    }
    let appliedRoutingState = routingState;
    if (instructions.length > 0) {
        appliedRoutingState = applyRoutingInstructions(instructions, routingState);
        const persistedBaseState = stopMessageScope
            ? stripClientInjectScopedFields(appliedRoutingState)
            : appliedRoutingState;
        routingState = persistedBaseState;
        engine.routingInstructionState.set(stateKey, persistedBaseState);
        persistRoutingInstructionState(stateKey, persistedBaseState, engine.routingStateStore);
        // Persist stopMessage under tmux/clockd scope so client injection and trigger matching
        // use the same scope and never fall back to generic session keys.
        if (stopMessageScope) {
            const hasStopMessageSet = instructions.some((entry) => entry.type === 'stopMessageSet');
            const hasStopMessageMode = instructions.some((entry) => entry.type === 'stopMessageMode');
            const hasGlobalClear = instructions.some((entry) => entry.type === 'clear');
            const hasStopMessageClear = hasGlobalClear || instructions.some((entry) => entry.type === 'stopMessageClear');
            if (hasStopMessageSet || hasStopMessageMode || hasStopMessageClear) {
                const activeInstructionTypes = instructions
                    .filter((entry) => entry.type === 'stopMessageSet' ||
                    entry.type === 'stopMessageMode' ||
                    entry.type === 'stopMessageClear')
                    .map((entry) => entry.type);
                if (isStopScopeTraceEnabled()) {
                    engine.debug?.log?.(`[virtual-router][stop_scope] requestId=${metadata.requestId || 'n/a'} stage=apply scope=${stopMessageScope} instructions=${activeInstructionTypes.join(',') || 'none'}`);
                }
                const sessionState = getRoutingInstructionState(stopMessageScope, engine.routingInstructionState, engine.routingStateStore);
                let nextSessionState = {
                    ...sessionState
                };
                let shouldPersistSessionState = false;
                const hasStopMessageStateChanged = () => {
                    return (nextSessionState.stopMessageText !== sessionState.stopMessageText ||
                        nextSessionState.stopMessageMaxRepeats !== sessionState.stopMessageMaxRepeats ||
                        nextSessionState.stopMessageUsed !== sessionState.stopMessageUsed ||
                        nextSessionState.stopMessageUpdatedAt !== sessionState.stopMessageUpdatedAt ||
                        nextSessionState.stopMessageLastUsedAt !== sessionState.stopMessageLastUsedAt ||
                        nextSessionState.stopMessageSource !== sessionState.stopMessageSource ||
                        nextSessionState.stopMessageStageMode !== sessionState.stopMessageStageMode ||
                        nextSessionState.stopMessageAiMode !== sessionState.stopMessageAiMode ||
                        nextSessionState.stopMessageAiSeedPrompt !== sessionState.stopMessageAiSeedPrompt ||
                        JSON.stringify(nextSessionState.stopMessageAiHistory || []) !==
                            JSON.stringify(sessionState.stopMessageAiHistory || []));
                };
                if (hasStopMessageClear) {
                    if (hasGlobalClear) {
                        // <**clear**> is a hard reset: clear all session-scoped routing state
                        // and let persistRoutingInstructionState delete persistence markers.
                        nextSessionState = {
                            forcedTarget: undefined,
                            stickyTarget: undefined,
                            preferTarget: undefined,
                            allowedProviders: new Set(),
                            disabledProviders: new Set(),
                            disabledKeys: new Map(),
                            disabledModels: new Map(),
                            stopMessageSource: undefined,
                            stopMessageText: undefined,
                            stopMessageMaxRepeats: undefined,
                            stopMessageUsed: undefined,
                            stopMessageUpdatedAt: undefined,
                            stopMessageLastUsedAt: undefined,
                            stopMessageStageMode: undefined,
                            stopMessageAiMode: undefined,
                            stopMessageAiSeedPrompt: undefined,
                            stopMessageAiHistory: undefined,
                            preCommandSource: undefined,
                            preCommandScriptPath: undefined,
                            preCommandUpdatedAt: undefined
                        };
                        shouldPersistSessionState = true;
                    }
                    else {
                        nextSessionState.stopMessageText = undefined;
                        nextSessionState.stopMessageMaxRepeats = undefined;
                        nextSessionState.stopMessageUsed = undefined;
                        nextSessionState.stopMessageUpdatedAt = undefined;
                        nextSessionState.stopMessageLastUsedAt = undefined;
                        nextSessionState.stopMessageSource = undefined;
                        nextSessionState.stopMessageStageMode = undefined;
                        nextSessionState.stopMessageAiMode = undefined;
                        nextSessionState.stopMessageAiSeedPrompt = undefined;
                        nextSessionState.stopMessageAiHistory = undefined;
                        shouldPersistSessionState = true;
                    }
                }
                else if (hasStopMessageSet || hasStopMessageMode) {
                    nextSessionState.stopMessageText =
                        typeof appliedRoutingState.stopMessageText === 'string' &&
                            appliedRoutingState.stopMessageText.trim()
                            ? appliedRoutingState.stopMessageText
                            : undefined;
                    nextSessionState.stopMessageMaxRepeats =
                        typeof appliedRoutingState.stopMessageMaxRepeats === 'number' &&
                            Number.isFinite(appliedRoutingState.stopMessageMaxRepeats)
                            ? Math.floor(appliedRoutingState.stopMessageMaxRepeats)
                            : undefined;
                    nextSessionState.stopMessageUsed =
                        typeof appliedRoutingState.stopMessageUsed === 'number' &&
                            Number.isFinite(appliedRoutingState.stopMessageUsed)
                            ? Math.max(0, Math.floor(appliedRoutingState.stopMessageUsed))
                            : undefined;
                    nextSessionState.stopMessageUpdatedAt =
                        typeof appliedRoutingState.stopMessageUpdatedAt === 'number' &&
                            Number.isFinite(appliedRoutingState.stopMessageUpdatedAt)
                            ? appliedRoutingState.stopMessageUpdatedAt
                            : undefined;
                    nextSessionState.stopMessageLastUsedAt =
                        typeof appliedRoutingState.stopMessageLastUsedAt === 'number' &&
                            Number.isFinite(appliedRoutingState.stopMessageLastUsedAt)
                            ? appliedRoutingState.stopMessageLastUsedAt
                            : undefined;
                    nextSessionState.stopMessageSource =
                        typeof appliedRoutingState.stopMessageSource === 'string' &&
                            appliedRoutingState.stopMessageSource.trim()
                            ? appliedRoutingState.stopMessageSource.trim()
                            : undefined;
                    nextSessionState.stopMessageStageMode = normalizeStopMessageStageMode(appliedRoutingState.stopMessageStageMode);
                    nextSessionState.stopMessageAiMode = normalizeStopMessageAiMode(appliedRoutingState.stopMessageAiMode);
                    nextSessionState.stopMessageAiSeedPrompt =
                        typeof appliedRoutingState.stopMessageAiSeedPrompt === 'string' &&
                            appliedRoutingState.stopMessageAiSeedPrompt.trim()
                            ? appliedRoutingState.stopMessageAiSeedPrompt.trim()
                            : undefined;
                    nextSessionState.stopMessageAiHistory = Array.isArray(appliedRoutingState.stopMessageAiHistory)
                        ? appliedRoutingState.stopMessageAiHistory
                        : undefined;
                    shouldPersistSessionState = hasStopMessageStateChanged();
                }
                if (shouldPersistSessionState) {
                    engine.routingInstructionState.set(stopMessageScope, nextSessionState);
                    persistRoutingInstructionState(stopMessageScope, nextSessionState, engine.routingStateStore);
                }
                else {
                    nextSessionState = sessionState;
                }
                // 日志展示使用 session scope 的 stopMessage 状态，避免每次解析重复刷新时间/次数。
                if (typeof nextSessionState.stopMessageText === 'string' ||
                    typeof nextSessionState.stopMessageMaxRepeats === 'number' ||
                    typeof nextSessionState.stopMessageStageMode === 'string' ||
                    typeof nextSessionState.stopMessageAiMode === 'string' ||
                    typeof nextSessionState.stopMessageAiSeedPrompt === 'string' ||
                    Array.isArray(nextSessionState.stopMessageAiHistory)) {
                    routingState.stopMessageText = nextSessionState.stopMessageText;
                    routingState.stopMessageMaxRepeats = nextSessionState.stopMessageMaxRepeats;
                    routingState.stopMessageUsed = nextSessionState.stopMessageUsed;
                    routingState.stopMessageUpdatedAt = nextSessionState.stopMessageUpdatedAt;
                    routingState.stopMessageLastUsedAt = nextSessionState.stopMessageLastUsedAt;
                    routingState.stopMessageStageMode = nextSessionState.stopMessageStageMode;
                    routingState.stopMessageAiMode = nextSessionState.stopMessageAiMode;
                    routingState.stopMessageAiSeedPrompt = nextSessionState.stopMessageAiSeedPrompt;
                    routingState.stopMessageAiHistory = nextSessionState.stopMessageAiHistory;
                }
            }
        }
    }
    if (instructions.length > 0 && stopMessageScope) {
        const hasPreCommandSet = instructions.some((entry) => entry.type === 'preCommandSet');
        const hasPreCommandClear = instructions.some((entry) => entry.type === 'preCommandClear');
        if (hasPreCommandSet || hasPreCommandClear) {
            const sessionState = getRoutingInstructionState(stopMessageScope, engine.routingInstructionState, engine.routingStateStore);
            const nextSessionState = {
                ...sessionState
            };
            let changed = false;
            if (hasPreCommandClear) {
                changed =
                    typeof sessionState.preCommandScriptPath === 'string' ||
                        typeof sessionState.preCommandSource === 'string' ||
                        typeof sessionState.preCommandUpdatedAt === 'number';
                nextSessionState.preCommandScriptPath = undefined;
                nextSessionState.preCommandSource = undefined;
                nextSessionState.preCommandUpdatedAt = Date.now();
            }
            if (hasPreCommandSet) {
                const scriptPath = typeof appliedRoutingState.preCommandScriptPath === 'string'
                    ? appliedRoutingState.preCommandScriptPath.trim()
                    : '';
                if (scriptPath) {
                    if (sessionState.preCommandScriptPath !== scriptPath) {
                        changed = true;
                    }
                    nextSessionState.preCommandScriptPath = scriptPath;
                    nextSessionState.preCommandSource = 'explicit';
                    nextSessionState.preCommandUpdatedAt =
                        typeof appliedRoutingState.preCommandUpdatedAt === 'number' &&
                            Number.isFinite(appliedRoutingState.preCommandUpdatedAt)
                            ? appliedRoutingState.preCommandUpdatedAt
                            : Date.now();
                }
            }
            if (changed) {
                engine.routingInstructionState.set(stopMessageScope, nextSessionState);
                persistRoutingInstructionState(stopMessageScope, nextSessionState, engine.routingStateStore);
                routingState.preCommandScriptPath = nextSessionState.preCommandScriptPath;
                routingState.preCommandSource = nextSessionState.preCommandSource;
                routingState.preCommandUpdatedAt = nextSessionState.preCommandUpdatedAt;
            }
        }
    }
    if (instructions.length === 0 && stopMessageScope) {
        const sessionState = getRoutingInstructionState(stopMessageScope, engine.routingInstructionState, engine.routingStateStore);
        if (typeof sessionState.stopMessageText === 'string' ||
            typeof sessionState.stopMessageMaxRepeats === 'number' ||
            typeof sessionState.stopMessageStageMode === 'string' ||
            typeof sessionState.stopMessageAiMode === 'string' ||
            typeof sessionState.stopMessageAiSeedPrompt === 'string' ||
            Array.isArray(sessionState.stopMessageAiHistory)) {
            routingState.stopMessageText = sessionState.stopMessageText;
            routingState.stopMessageMaxRepeats = sessionState.stopMessageMaxRepeats;
            routingState.stopMessageUsed = sessionState.stopMessageUsed;
            routingState.stopMessageUpdatedAt = sessionState.stopMessageUpdatedAt;
            routingState.stopMessageLastUsedAt = sessionState.stopMessageLastUsedAt;
            routingState.stopMessageStageMode = sessionState.stopMessageStageMode;
            routingState.stopMessageAiMode = sessionState.stopMessageAiMode;
            routingState.stopMessageAiSeedPrompt = sessionState.stopMessageAiSeedPrompt;
            routingState.stopMessageAiHistory = sessionState.stopMessageAiHistory;
        }
        if (typeof sessionState.preCommandScriptPath === 'string' && sessionState.preCommandScriptPath.trim()) {
            routingState.preCommandScriptPath = sessionState.preCommandScriptPath;
            routingState.preCommandSource = sessionState.preCommandSource;
            routingState.preCommandUpdatedAt = sessionState.preCommandUpdatedAt;
        }
    }
    // Guardrail: if a session is restricted to providers that do not exist in any routing pools,
    // we must not hard-fail the request loop. Auto-clear the allowlist and fall back to normal routing.
    routingState = enforceAllowlistIntersection(engine, routingState, stateKey);
    return {
        routingState,
        stateKey,
        stopMessageScope,
        metadataInstructions,
        instructions
    };
}
