import { extractMessageText, getLatestUserMessage } from '../message-utils.js';
import { parseStopMessageInstruction } from '../routing-stop-message-parser.js';
import { parsePreCommandInstruction } from '../routing-pre-command-parser.js';
import { stripCodeSegments } from './clean.js';
import { ROUTING_INSTRUCTION_MARKER_PATTERN } from './types.js';
export function parseRoutingInstructions(messages) {
    const instructions = [];
    // 只解析“当前最新一条消息”中的 marker，避免历史 user marker 在后续轮次被重复回放。
    const latestMessage = getLatestUserMessage(messages);
    if (!latestMessage) {
        return instructions;
    }
    const content = extractMessageText(latestMessage);
    if (!content) {
        return instructions;
    }
    const sanitized = stripCodeSegments(content);
    if (!sanitized || !ROUTING_INSTRUCTION_MARKER_PATTERN.test(sanitized)) {
        return instructions;
    }
    const regex = /<\*\*([\s\S]*?)\*\*>/g;
    let match;
    while ((match = regex.exec(sanitized)) !== null) {
        const instruction = match[1].trim();
        if (!instruction) {
            continue;
        }
        const segments = expandInstructionSegments(instruction);
        for (const segment of segments) {
            const parsed = parseSingleInstruction(segment);
            if (parsed) {
                instructions.push(parsed);
            }
        }
    }
    return normalizeStopMessageInstructionPrecedence(instructions);
}
/**
 * 解析并预处理路由指令，优先处理 clear 指令，确保新指令能够覆盖旧状态。
 * 返回清理后的指令列表，移除冗余的 stopMessageSet 指令。
 */
export function parseAndPreprocessRoutingInstructions(messages) {
    const rawInstructions = parseRoutingInstructions(messages);
    if (rawInstructions.length === 0) {
        return [];
    }
    // 检查是否有 clear 指令
    const hasClear = rawInstructions.some((inst) => inst.type === 'clear');
    if (!hasClear) {
        return rawInstructions;
    }
    // 如果有 clear 指令，clear 之后的指令才有效，clear 之前的指令被清除
    const clearIndex = rawInstructions.findIndex((inst) => inst.type === 'clear');
    const effectiveInstructions = rawInstructions.slice(clearIndex + 1);
    // 移除 clear 后冗余的 stopMessageSet 指令（如果与原指令相同）
    // 这里的逻辑会在 applyRoutingInstructions 中处理，
    // 所以我们只需要返回 clear 之后的指令即可
    return effectiveInstructions;
}
/**
 * 提取 clear 指令（如果存在）。用于在路由选择前优先执行清理操作。
 * @returns 是否存在 clear 指令
 */
export function extractClearInstruction(messages) {
    const instructions = parseRoutingInstructions(messages);
    return instructions.some((inst) => inst.type === 'clear');
}
/**
 * 提取 stopMessageClear 指令（如果存在）。
 * @returns 是否存在 stopMessageClear 指令
 */
export function extractStopMessageClearInstruction(messages) {
    const instructions = parseRoutingInstructions(messages);
    return instructions.some((inst) => inst.type === 'stopMessageClear');
}
function expandInstructionSegments(instruction) {
    const trimmed = instruction.trim();
    if (!trimmed) {
        return [];
    }
    const normalizedLeading = normalizeInstructionLeading(trimmed);
    // stopMessage 指令需要整体解析，不能按逗号拆分，否则类似
    // "<**stopMessage:\"继续\",3**>" 会被错误拆成 ["stopMessage:\"继续\"", "3"]。
    if (/^(?:"|')?stopMessage(?:"|')?\s*[:,]/i.test(normalizedLeading)) {
        return [normalizeStopMessageCommandPrefix(normalizedLeading)];
    }
    if (/^precommand(?:\s*:|$)/i.test(normalizedLeading)) {
        return [normalizedLeading];
    }
    const quotedStopMessage = normalizeQuotedStopMessageShorthand(normalizedLeading);
    if (quotedStopMessage) {
        return [normalizeStopMessageCommandPrefix(quotedStopMessage)];
    }
    const prefix = trimmed[0];
    if (prefix === '!' || prefix === '#' || prefix === '@') {
        const tokens = splitInstructionTargets(trimmed.substring(1));
        return tokens
            .map((token) => token.replace(/^[!#@]+/, '').trim())
            .filter((token) => token.length > 0)
            .map((token) => `${prefix}${token}`);
    }
    const splitTokens = splitInstructionTargets(trimmed);
    const recoveredStopMessage = recoverSplitStopMessageInstruction(splitTokens);
    if (recoveredStopMessage) {
        return [recoveredStopMessage];
    }
    return splitTokens;
}
function splitInstructionTargets(content) {
    return content
        .split(',')
        .map((segment) => segment.trim())
        .filter((segment) => segment.length > 0);
}
function normalizeInstructionLeading(content) {
    // Remove common zero-width prefixes that may be injected by client/editor copies.
    return content.replace(/^[\u200B-\u200D\u2060\uFEFF]+/, '').trimStart();
}
function normalizeStopMessageCommandPrefix(content) {
    const normalized = normalizeInstructionLeading(content);
    return normalized.replace(/^(?:"|')?stopMessage(?:"|')?\s*([:,])/i, 'stopMessage$1');
}
function normalizeSplitStopMessageHeadToken(token) {
    return normalizeInstructionLeading(token)
        .replace(/^["']+|["']+$/g, '')
        .trim();
}
function normalizeQuotedStopMessageShorthand(content) {
    const normalized = normalizeInstructionLeading(content);
    const quote = normalized[0];
    if (quote !== '"' && quote !== "'") {
        return null;
    }
    let escaped = false;
    for (let idx = 1; idx < normalized.length; idx += 1) {
        const ch = normalized[idx];
        if (escaped) {
            escaped = false;
            continue;
        }
        if (ch === '\\') {
            escaped = true;
            continue;
        }
        if (ch === quote) {
            return `stopMessage:${normalized}`;
        }
    }
    return null;
}
function recoverSplitStopMessageInstruction(tokens) {
    if (!Array.isArray(tokens) || tokens.length < 2) {
        return null;
    }
    const head = normalizeSplitStopMessageHeadToken(tokens[0]);
    if (!/^stopmessage$/i.test(head)) {
        return null;
    }
    const tail = tokens.slice(1).join(',').trim();
    if (!tail) {
        return null;
    }
    return `stopMessage:${tail}`;
}
function splitTargetAndProcessMode(rawTarget) {
    const trimmed = typeof rawTarget === 'string' ? rawTarget.trim() : '';
    if (!trimmed) {
        return { target: '' };
    }
    const separatorIndex = trimmed.lastIndexOf(':');
    if (separatorIndex <= 0 || separatorIndex === trimmed.length - 1) {
        return { target: trimmed };
    }
    const target = trimmed.slice(0, separatorIndex).trim();
    const modeToken = trimmed.slice(separatorIndex + 1).trim().toLowerCase();
    if (!target) {
        return { target: trimmed };
    }
    if (modeToken === 'passthrough') {
        return { target, processMode: 'passthrough' };
    }
    if (modeToken === 'chat') {
        return { target, processMode: 'chat' };
    }
    return { target };
}
function parseNamedTargetInstruction(instruction, prefix) {
    const re = new RegExp('^' + prefix + '\\s*:', 'i');
    if (!re.test(instruction)) {
        return null;
    }
    const body = instruction.slice(instruction.indexOf(':') + 1).trim();
    if (!body) {
        return null;
    }
    const { target, processMode } = splitTargetAndProcessMode(body);
    if (!target) {
        return null;
    }
    const parsed = parseTarget(target);
    if (!parsed) {
        return null;
    }
    const normalized = normalizeStickyOrForceTarget(parsed);
    return { type: prefix, ...normalized, ...(processMode ? { processMode } : {}) };
}
function parseSingleInstruction(instruction) {
    if (/^clear$/i.test(instruction)) {
        return { type: 'clear' };
    }
    const preCommandInstruction = parsePreCommandInstruction(instruction);
    if (preCommandInstruction) {
        return preCommandInstruction;
    }
    const stopMessageInstruction = parseStopMessageInstruction(instruction);
    if (stopMessageInstruction) {
        return stopMessageInstruction;
    }
    const stickyInstruction = parseNamedTargetInstruction(instruction, 'sticky');
    if (stickyInstruction) {
        return stickyInstruction;
    }
    const forceInstruction = parseNamedTargetInstruction(instruction, 'force');
    if (forceInstruction) {
        return forceInstruction;
    }
    const preferInstruction = parseNamedTargetInstruction(instruction, 'prefer');
    if (preferInstruction) {
        return preferInstruction;
    }
    if (instruction.startsWith('!')) {
        const rawTarget = instruction.substring(1).trim();
        const { target, processMode } = splitTargetAndProcessMode(rawTarget);
        if (!target) {
            return null;
        }
        const parsed = parseTarget(target);
        if (!parsed) {
            return null;
        }
        // 约定：
        // - "!providerA,providerB"：允许列表（whitelist），用于快速限制可用 provider 集合；
        // - "!provider.model" / "!provider[alias].model" / "!provider.2"：prefer 语义；
        //   model 可用时只命中该 model（忽略路由），不可用则自动清除并回退到正常路由命中。
        //
        // 这样可以在不破坏既有 "!glm,openai" 语义的前提下，引入基于模型的优先命中行为。
        if (!target.includes('.')) {
            if (parsed.provider) {
                return { type: 'allow', provider: parsed.provider, pathLength: parsed.pathLength };
            }
            return null;
        }
        const normalized = normalizeStickyOrForceTarget(parsed);
        return { type: 'prefer', ...normalized, ...(processMode ? { processMode } : {}) };
    }
    if (instruction.startsWith('#')) {
        const target = instruction.substring(1).trim();
        const parsed = parseTarget(target);
        if (parsed) {
            return { type: 'disable', ...parsed };
        }
    }
    else if (instruction.startsWith('@')) {
        const target = instruction.substring(1).trim();
        const parsed = parseTarget(target);
        if (parsed) {
            return { type: 'enable', ...parsed };
        }
    }
    else if (isValidProviderModel(instruction)) {
        const parsed = parseTarget(instruction);
        if (parsed) {
            const normalized = normalizeStickyOrForceTarget(parsed);
            return { type: 'force', ...normalized };
        }
    }
    else if (isValidIdentifier(instruction)) {
        // 仅 provider 标识（无 .）时，视为 provider 级白名单，等价于 "<**!provider**>"。
        // 这样可以用 "<**antigravity**>" 快速激活当前 routing 中所有 antigravity 相关池子，
        // 并保证路由仅命中该 provider 的所有模型/key。
        return {
            type: 'allow',
            provider: instruction,
            pathLength: 1
        };
    }
    return null;
}
function normalizeStopMessageInstructionPrecedence(instructions) {
    if (!Array.isArray(instructions) || instructions.length <= 1) {
        return instructions;
    }
    const isStopDirective = (inst) => inst.type === 'stopMessageSet' || inst.type === 'stopMessageMode' || inst.type === 'stopMessageClear';
    const hasGlobalClear = instructions.some((inst) => inst.type === 'clear');
    const hasStopClear = instructions.some((inst) => inst.type === 'stopMessageClear');
    if (hasGlobalClear) {
        const lastGlobalClearIndex = instructions.map((inst) => inst.type).lastIndexOf('clear');
        return lastGlobalClearIndex >= 0 ? [instructions[lastGlobalClearIndex]] : instructions;
    }
    if (hasStopClear) {
        const lastStopClearIndex = instructions.map((inst) => inst.type).lastIndexOf('stopMessageClear');
        return lastStopClearIndex >= 0 ? [instructions[lastStopClearIndex]] : instructions;
    }
    let lastStopIndex = -1;
    for (let idx = instructions.length - 1; idx >= 0; idx -= 1) {
        if (isStopDirective(instructions[idx])) {
            lastStopIndex = idx;
            break;
        }
    }
    if (lastStopIndex < 0) {
        return instructions;
    }
    return instructions.filter((inst, idx) => !isStopDirective(inst) || idx === lastStopIndex);
}
function parseTarget(target) {
    if (!target) {
        return null;
    }
    // Accept "provider[alias].model" (as printed in virtual-router-hit logs) to avoid users
    // needing to translate bracket notation back to dot notation manually.
    // With the alias disambiguated, allow dots in model ids (e.g. gpt-5.2) without ambiguity.
    const bracketMatch = target.match(/^([a-zA-Z0-9_-]+)\[([a-zA-Z0-9_-]*)\](?:\.(.+))?$/);
    if (bracketMatch) {
        const provider = bracketMatch[1];
        const keyAliasRaw = bracketMatch[2];
        const keyAlias = typeof keyAliasRaw === 'string' ? keyAliasRaw.trim() : '';
        const model = typeof bracketMatch[3] === 'string' ? bracketMatch[3].trim() : '';
        if (!provider || !isValidIdentifier(provider)) {
            return null;
        }
        // Allow omitting the alias: "provider[].model" means "provider.model across all aliases".
        // This also enables disambiguating model ids that contain dots, without requiring the user
        // to specify the alias.
        if (!keyAlias) {
            if (!model) {
                return { provider, pathLength: 1 };
            }
            if (!/^[a-zA-Z0-9_.-]+$/.test(model)) {
                return null;
            }
            return { provider, model, pathLength: 2 };
        }
        if (!isValidIdentifier(keyAlias)) {
            return null;
        }
        if (!model) {
            // Treat as explicit alias selection. Use pathLength=3 so engine resolves keyAlias directly.
            return { provider, keyAlias, pathLength: 3 };
        }
        // Model ids may contain dots (e.g. gpt-5.2); allow them here because alias is already explicit.
        if (!/^[a-zA-Z0-9_.-]+$/.test(model)) {
            return null;
        }
        return { provider, keyAlias, model, pathLength: 3 };
    }
    // Dot syntax: align with config parsing used by routing entries.
    // - "provider.modelId" -> modelId may contain dots; it always means model across all aliases.
    // - Key/alias selection must use bracket syntax: "provider[alias]" or "provider[alias].modelId".
    const firstDot = target.indexOf('.');
    if (firstDot < 0) {
        const provider = target.trim();
        if (!provider || !isValidIdentifier(provider)) {
            return null;
        }
        return { provider, pathLength: 1 };
    }
    const provider = target.slice(0, firstDot).trim();
    const remainder = target.slice(firstDot + 1).trim();
    if (!provider || !isValidIdentifier(provider) || !remainder) {
        return null;
    }
    // Support "provider.2" key-index notation (only when remainder is a plain integer).
    if (/^\d+$/.test(remainder)) {
        const keyIndex = Number.parseInt(remainder, 10);
        if (Number.isFinite(keyIndex) && keyIndex > 0) {
            return { provider, keyIndex, pathLength: 2 };
        }
    }
    // Treat everything after the first dot as the model id, allowing dots.
    if (!/^[a-zA-Z0-9_.-]+$/.test(remainder)) {
        return null;
    }
    return { provider, model: remainder, pathLength: 2 };
}
function normalizeStickyOrForceTarget(target) {
    if (target &&
        target.pathLength === 2 &&
        typeof target.model === 'string' &&
        typeof target.keyAlias === 'string' &&
        target.model === target.keyAlias) {
        const clone = { ...target };
        delete clone.keyAlias;
        return clone;
    }
    return target;
}
function isValidIdentifier(id) {
    return /^[a-zA-Z0-9_-]+$/.test(id);
}
function isValidProviderModel(providerModel) {
    const pattern = /^[a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)+$/;
    return pattern.test(providerModel);
}
