import { VirtualRouterError, VirtualRouterErrorCode } from './types.js';
import { ROUTING_INSTRUCTION_MARKER_PATTERN } from './routing-instructions/types.js';
import { parseRoutingInstructions } from './routing-instructions/parse.js';
import { createVirtualRouterEngineProxy } from './engine-selection/native-virtual-router-engine-proxy.js';
import { cleanRoutingInstructionMarkersWithNative, parseRoutingInstructionKindsWithNative } from './engine-selection/native-virtual-router-routing-instructions-semantics.js';
import { getLatestUserTextFromResponsesContext, hasLatestUserRoutingInstructionMarker, hasRoutingInstructionMarkerInResponsesContext } from './engine-legacy/helpers.js';
import { extractMessageText, getLatestUserMessage } from './message-utils.js';
import { ProviderRegistry } from './provider-registry.js';
import { resolveStopMessageScope } from './engine/routing-state/store.js';
import { loadRoutingInstructionStateSync } from './sticky-session-store.js';
import { mergeStopMessageFromPersisted } from './stop-message-state-sync.js';
export class VirtualRouterEngine {
    nativeProxy;
    registry;
    routingInstructionStateStore;
    constructor(deps) {
        this.nativeProxy = createVirtualRouterEngineProxy();
        this.registry = new ProviderRegistry();
        this.routingInstructionStateStore = new Map();
        if (deps) {
            this.nativeProxy.updateDeps(deps);
        }
    }
    get antigravitySessionAliasStore() {
        return this.nativeProxy.antigravitySessionAliasStore;
    }
    get routingInstructionState() {
        return this.routingInstructionStateStore;
    }
    get providerRegistry() {
        return this.registry;
    }
    initialize(config) {
        this.nativeProxy.initialize(JSON.stringify(config));
        this.registry.load(config.providers ?? {});
        this.routingInstructionStateStore.clear();
    }
    updateDeps(deps) {
        this.nativeProxy.updateDeps(deps);
    }
    updateVirtualRouterConfig(config) {
        this.nativeProxy.updateVirtualRouterConfig(JSON.stringify(config));
        this.registry.load(config.providers ?? {});
    }
    markProviderCooldown(providerKey, cooldownMs) {
        this.nativeProxy.markProviderCooldown(providerKey, cooldownMs);
    }
    clearProviderCooldown(providerKey) {
        this.nativeProxy.clearProviderCooldown(providerKey);
    }
    route(request, metadata) {
        const parseLog = buildRoutingInstructionParseLog(request, metadata);
        const nativeMetadata = injectRuntimeNowMs(metadata);
        let raw;
        try {
            raw = this.nativeProxy.route(JSON.stringify(request), JSON.stringify(nativeMetadata));
        }
        catch (error) {
            throw normalizeNativeVirtualRouterError(error);
        }
        if (typeof raw !== 'string') {
            throw normalizeNativeVirtualRouterError(raw);
        }
        if (raw.startsWith('Error:') || raw.startsWith(VIRTUAL_ROUTER_ERROR_PREFIX)) {
            throw normalizeNativeVirtualRouterError(raw);
        }
        const parsed = JSON.parse(raw);
        emitRoutingInstructionParseLog(parseLog);
        // Keep legacy observable behavior for callers/tests that inspect the request object
        // after route(): instruction markers are stripped from forwarded payload structures.
        cleanRoutingInstructionMarkersInPlace(request);
        const stopScope = parseLog?.stopScope || resolveStopMessageScope(metadata);
        const stopState = stopScope ? this.getStopMessageState(metadata) : null;
        const forceStopStatusLabel = Boolean(parseLog?.stopMessageTypes.length ||
            parseLog?.scopedTypes.some((type) => type === 'stopMessageSet' || type === 'stopMessageMode' || type === 'stopMessageClear'));
        emitVirtualRouterHitLog(parsed, {
            requestId: metadata.requestId,
            stopScope,
            stopState,
            forceStopStatusLabel
        });
        return parsed;
    }
    getStopMessageState(metadata) {
        const raw = this.nativeProxy.getStopMessageState(JSON.stringify(metadata));
        const snapshot = JSON.parse(raw);
        const scope = resolveStopMessageScope(metadata);
        return mergeStopMessageSnapshotWithPersisted(snapshot, scope);
    }
    getPreCommandState(metadata) {
        const raw = this.nativeProxy.getPreCommandState(JSON.stringify(metadata));
        return JSON.parse(raw);
    }
    handleProviderFailure(event) {
        this.nativeProxy.handleProviderFailure(JSON.stringify(event));
    }
    handleProviderError(event) {
        this.nativeProxy.handleProviderError(JSON.stringify(event));
    }
    handleProviderSuccess(event) {
        this.nativeProxy.handleProviderSuccess(JSON.stringify(event));
    }
    getStatus() {
        return JSON.parse(this.nativeProxy.getStatus());
    }
}
const VIRTUAL_ROUTER_ERROR_PREFIX = 'VIRTUAL_ROUTER_ERROR:';
function normalizeNativeVirtualRouterError(error) {
    if (error instanceof VirtualRouterError) {
        return error;
    }
    const message = extractNativeErrorMessage(error);
    const parsed = parseVirtualRouterErrorMessage(message);
    if (parsed) {
        return new VirtualRouterError(parsed.message, parsed.code);
    }
    if (isVirtualRouterErrorLike(error)) {
        return new VirtualRouterError(typeof error.message === 'string' && error.message.trim() ? error.message : 'Virtual router error', error.code);
    }
    return error instanceof Error ? error : new Error(message || 'Virtual router error');
}
function mergeStopMessageSnapshotWithPersisted(snapshot, scope) {
    if (!scope) {
        return snapshot;
    }
    let persisted = null;
    try {
        persisted = loadRoutingInstructionStateSync(scope);
    }
    catch {
        return snapshot;
    }
    if (!persisted) {
        return snapshot;
    }
    const persistedText = typeof persisted.stopMessageText === 'string' ? persisted.stopMessageText.trim() : '';
    if (!snapshot && !persistedText) {
        return snapshot;
    }
    const existing = {
        stopMessageSource: snapshot?.stopMessageSource,
        stopMessageText: snapshot?.stopMessageText,
        stopMessageMaxRepeats: snapshot?.stopMessageMaxRepeats,
        stopMessageUsed: snapshot?.stopMessageUsed,
        stopMessageUpdatedAt: snapshot?.stopMessageUpdatedAt,
        stopMessageLastUsedAt: snapshot?.stopMessageLastUsedAt,
        stopMessageStageMode: snapshot?.stopMessageStageMode,
        stopMessageAiMode: snapshot?.stopMessageAiMode,
        stopMessageAiSeedPrompt: snapshot?.stopMessageAiSeedPrompt,
        stopMessageAiHistory: snapshot?.stopMessageAiHistory
    };
    const merged = mergeStopMessageFromPersisted(existing, persisted);
    const base = snapshot ?? {
        stopMessageMaxRepeats: typeof merged.stopMessageMaxRepeats === 'number' && Number.isFinite(merged.stopMessageMaxRepeats)
            ? merged.stopMessageMaxRepeats
            : 0
    };
    const mergedMaxRepeats = typeof merged.stopMessageMaxRepeats === 'number' && Number.isFinite(merged.stopMessageMaxRepeats)
        ? merged.stopMessageMaxRepeats
        : base.stopMessageMaxRepeats;
    return {
        ...base,
        stopMessageSource: merged.stopMessageSource,
        stopMessageText: merged.stopMessageText,
        stopMessageMaxRepeats: mergedMaxRepeats,
        stopMessageUsed: merged.stopMessageUsed,
        stopMessageUpdatedAt: merged.stopMessageUpdatedAt,
        stopMessageLastUsedAt: merged.stopMessageLastUsedAt,
        stopMessageStageMode: merged.stopMessageStageMode,
        stopMessageAiMode: merged.stopMessageAiMode,
        stopMessageAiSeedPrompt: merged.stopMessageAiSeedPrompt,
        stopMessageAiHistory: merged.stopMessageAiHistory
    };
}
function extractNativeErrorMessage(error) {
    if (typeof error === 'string') {
        return error;
    }
    if (error instanceof Error) {
        return error.message;
    }
    return '';
}
function parseVirtualRouterErrorMessage(message) {
    if (!message) {
        return null;
    }
    const normalized = message.startsWith('Error:') ? message.replace(/^Error:\s*/, '') : message;
    if (!normalized.startsWith(VIRTUAL_ROUTER_ERROR_PREFIX)) {
        return null;
    }
    const remainder = normalized.slice(VIRTUAL_ROUTER_ERROR_PREFIX.length);
    const idx = remainder.indexOf(':');
    if (idx <= 0) {
        return null;
    }
    const code = remainder.slice(0, idx);
    const detail = remainder.slice(idx + 1).trim();
    if (!isVirtualRouterErrorCode(code)) {
        return null;
    }
    return { code, message: detail || 'Virtual router error' };
}
function isVirtualRouterErrorCode(value) {
    return Object.values(VirtualRouterErrorCode).includes(value);
}
function isVirtualRouterErrorLike(error) {
    if (!error || typeof error !== 'object') {
        return false;
    }
    const record = error;
    return typeof record.code === 'string' && isVirtualRouterErrorCode(record.code);
}
function injectRuntimeNowMs(metadata) {
    const nowMs = Date.now();
    const rt = metadata.__rt;
    if (rt && typeof rt === 'object' && !Array.isArray(rt)) {
        return { ...metadata, __rt: { ...rt, nowMs } };
    }
    return { ...metadata, __rt: { nowMs } };
}
function buildRoutingInstructionParseLog(request, metadata) {
    const messages = Array.isArray(request.messages)
        ? (request.messages ?? [])
        : [];
    if (!messages.length) {
        return null;
    }
    const latest = getLatestUserMessage(messages);
    const latestText = latest ? extractMessageText(latest).trim() : '';
    const parsedKinds = parseRoutingInstructionKindsWithNative(request);
    const stopMessageTypes = parsedKinds.filter((type) => type === 'stopMessageSet' || type === 'stopMessageMode' || type === 'stopMessageClear');
    const scopedTypes = parsedKinds.filter((type) => type === 'stopMessageSet' ||
        type === 'stopMessageMode' ||
        type === 'stopMessageClear' ||
        type === 'preCommandSet' ||
        type === 'preCommandClear');
    const markerDetected = messages.some((message) => {
        if (!message || typeof message !== 'object') {
            return false;
        }
        const record = message;
        return record.role === 'user' && typeof record.content === 'string' && /<\*\*[\s\S]*?\*\*>/.test(record.content);
    });
    const hasStopKeyword = /stopmessage/i.test(latestText);
    if (!hasStopKeyword && stopMessageTypes.length === 0 && scopedTypes.length === 0) {
        return null;
    }
    return {
        requestId: metadata.requestId || 'n/a',
        markerDetected,
        preview: latestText.replace(/\s+/g, ' ').slice(0, 120),
        stopMessageTypes,
        scopedTypes,
        stopScope: resolveStopMessageScope(metadata)
    };
}
function emitRoutingInstructionParseLog(log) {
    if (!log) {
        return;
    }
    const reset = '\x1b[0m';
    const tagColor = '\x1b[38;5;39m';
    const scopeColor = '\x1b[38;5;220m';
    console.log(`${tagColor}[virtual-router][stop_message_parse]${reset} requestId=${log.requestId} marker=${log.markerDetected ? 'detected' : 'missing'} parsed=${log.stopMessageTypes.join(',') || 'none'} preview=${log.preview}`);
    if (log.scopedTypes.length > 0) {
        if (log.stopScope) {
            console.log(`${scopeColor}[virtual-router][stop_scope]${reset} requestId=${log.requestId} stage=apply scope=${log.stopScope} instructions=${log.scopedTypes.join(',')}`);
        }
        else {
            console.log(`${scopeColor}[virtual-router][stop_scope]${reset} requestId=${log.requestId} stage=drop reason=missing_tmux_scope instructions=${log.scopedTypes.join(',')}`);
        }
    }
}
function emitVirtualRouterHitLog(result, options) {
    const reset = '\x1b[0m';
    const prefixColor = '\x1b[38;5;208m';
    const timeColor = '\x1b[90m';
    const stopColor = '\x1b[38;5;214m';
    const routeColorMap = {
        multimodal: '\x1b[38;5;45m',
        tools: '\x1b[38;5;214m',
        thinking: '\x1b[34m',
        coding: '\x1b[35m',
        longcontext: '\x1b[38;5;141m',
        web_search: '\x1b[32m',
        search: '\x1b[38;5;34m',
        vision: '\x1b[38;5;207m',
        background: '\x1b[90m'
    };
    const now = new Date();
    const timestamp = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
    const routeLabel = result.decision.poolId
        ? `${result.decision.routeName}/${result.decision.poolId}`
        : result.decision.routeName;
    const routeColor = routeColorMap[result.decision.routeName] ?? '\x1b[36m';
    const providerKey = result.decision.providerKey || result.target.providerKey;
    const modelSuffix = result.target.modelId ? `.${result.target.modelId}` : '';
    const reason = result.decision.reasoning ? ` reason=${result.decision.reasoning}` : '';
    const stopStatusLabel = formatStopMessageStatusLabel(options?.stopState ?? null, options?.stopScope, Boolean(options?.forceStopStatusLabel));
    const requestId = typeof options?.requestId === 'string' ? options.requestId : '';
    const requestLabel = requestId && !requestId.includes('unknown') ? ` req=${requestId}` : '';
    console.log(`${prefixColor}[virtual-router-hit]${reset} ${timeColor}${timestamp}${reset}${requestLabel} ${routeColor}${routeLabel} -> ${providerKey}${modelSuffix}${reason}${reset}${stopStatusLabel ? ` ${stopColor}${stopStatusLabel}${reset}` : ''}`);
}
function formatStopMessageStatusLabel(snapshot, scope, forceShow) {
    const scopeLabel = scope && scope.trim() ? scope.trim() : 'none';
    if (!snapshot) {
        if (!forceShow) {
            return '';
        }
        return `[stopMessage:scope=${scopeLabel} active=no state=cleared]`;
    }
    const text = typeof snapshot.stopMessageText === 'string' ? snapshot.stopMessageText.trim() : '';
    const safeText = text ? (text.length > 24 ? `${text.slice(0, 21)}...` : text) : '(mode-only)';
    const mode = (snapshot.stopMessageStageMode || 'unset').toString().toLowerCase();
    const maxRepeats = typeof snapshot.stopMessageMaxRepeats === 'number' && Number.isFinite(snapshot.stopMessageMaxRepeats)
        ? Math.max(0, Math.floor(snapshot.stopMessageMaxRepeats))
        : 0;
    const used = typeof snapshot.stopMessageUsed === 'number' && Number.isFinite(snapshot.stopMessageUsed)
        ? Math.max(0, Math.floor(snapshot.stopMessageUsed))
        : 0;
    const remaining = maxRepeats > 0 ? Math.max(0, maxRepeats - used) : -1;
    const active = mode !== 'off' && Boolean(text) && maxRepeats > 0;
    const rounds = maxRepeats > 0 ? `${used}/${maxRepeats}` : `${used}/-`;
    const left = remaining >= 0 ? String(remaining) : 'n/a';
    return `[stopMessage:scope=${scopeLabel} text="${safeText}" mode=${mode} round=${rounds} left=${left} active=${active ? 'yes' : 'no'}]`;
}
function cleanRoutingInstructionMarkersInPlace(request) {
    const cleaned = cleanRoutingInstructionMarkersWithNative(request);
    if (Array.isArray(cleaned.messages)) {
        request.messages = cleaned.messages;
    }
    const cleanedSemantics = cleaned.semantics;
    if (cleanedSemantics && typeof cleanedSemantics === 'object' && !Array.isArray(cleanedSemantics)) {
        const cleanedResponses = cleanedSemantics.responses;
        if (cleanedResponses && typeof cleanedResponses === 'object' && !Array.isArray(cleanedResponses)) {
            const cleanedContext = cleanedResponses.context;
            if (cleanedContext !== undefined) {
                const semantics = request.semantics && typeof request.semantics === 'object' && !Array.isArray(request.semantics)
                    ? request.semantics
                    : {};
                const responses = semantics.responses && typeof semantics.responses === 'object' && !Array.isArray(semantics.responses)
                    ? semantics.responses
                    : {};
                responses.context = cleanedContext;
                semantics.responses = responses;
                request.semantics = semantics;
            }
        }
    }
}
function collectClientScopedInstructions(request) {
    const messages = Array.isArray(request.messages)
        ? request.messages
        : [];
    const responsesContext = request.semantics && typeof request.semantics === 'object'
        ? request.semantics.responses?.context
        : undefined;
    const responsesHasMarker = hasRoutingInstructionMarkerInResponsesContext(responsesContext);
    const latestUserHasMarker = hasLatestUserRoutingInstructionMarker(messages) || responsesHasMarker;
    if (!latestUserHasMarker && !messages.some((message) => {
        if (!message || typeof message !== 'object') {
            return false;
        }
        const record = message;
        return record.role === 'user' && typeof record.content === 'string' && ROUTING_INSTRUCTION_MARKER_PATTERN.test(record.content);
    })) {
        return { instructions: [], latestUserHasMarker };
    }
    let instructions = parseRoutingInstructions(messages);
    if (instructions.length === 0 && responsesHasMarker) {
        const responsesLatestUserText = getLatestUserTextFromResponsesContext(responsesContext);
        if (responsesLatestUserText) {
            instructions = parseRoutingInstructions([{ role: 'user', content: responsesLatestUserText }]);
        }
    }
    return { instructions, latestUserHasMarker };
}
function isClientScopedInstruction(instruction) {
    return (instruction.type === 'stopMessageSet' ||
        instruction.type === 'stopMessageMode' ||
        instruction.type === 'stopMessageClear' ||
        instruction.type === 'preCommandSet' ||
        instruction.type === 'preCommandClear' ||
        instruction.type === 'clear');
}
