import { VirtualRouterError, VirtualRouterErrorCode } from '../types.js';
import { readOptionalString } from './utils.js';
export function normalizeRouting(source) {
    const routing = {};
    for (const [routeName, entries] of Object.entries(source)) {
        if (!Array.isArray(entries) || !entries.length) {
            routing[routeName] = [];
            continue;
        }
        const allStrings = entries.every((entry) => typeof entry === 'string' || entry === null || entry === undefined);
        if (allStrings) {
            const targets = normalizeTargetList(entries);
            routing[routeName] = targets.length ? [buildLegacyRoutePool(routeName, targets)] : [];
            continue;
        }
        const normalized = [];
        const total = entries.length || 1;
        for (let index = 0; index < entries.length; index += 1) {
            const entry = entries[index];
            const pool = normalizeRoutePoolEntry(routeName, entry, index, total);
            if (pool && pool.targets.length) {
                normalized.push(pool);
            }
        }
        routing[routeName] = normalized;
    }
    return routing;
}
export function expandRoutingTable(routingSource, aliasIndex, modelIndex) {
    const routing = {};
    const targetKeys = new Set();
    for (const [routeName, pools] of Object.entries(routingSource)) {
        const expandedPools = [];
        for (const pool of pools) {
            const expandedTargets = [];
            let orderCounter = 0;
            for (const entry of pool.targets) {
                const parsed = parseRouteEntry(entry, aliasIndex);
                if (!parsed) {
                    continue;
                }
                if (!aliasIndex.has(parsed.providerId)) {
                    throw new VirtualRouterError(`Route "${routeName}" references unknown provider "${parsed.providerId}"`, VirtualRouterErrorCode.CONFIG_ERROR);
                }
                const modelInfo = modelIndex.get(parsed.providerId);
                if (modelInfo?.declared) {
                    if (!parsed.modelId) {
                        throw new VirtualRouterError(`Route "${routeName}" references empty model id for provider "${parsed.providerId}"`, VirtualRouterErrorCode.CONFIG_ERROR);
                    }
                    const knownModels = modelInfo.models ?? [];
                    if (!knownModels.length) {
                        throw new VirtualRouterError(`Route "${routeName}" references provider "${parsed.providerId}" but provider declares no models`, VirtualRouterErrorCode.CONFIG_ERROR);
                    }
                    if (!knownModels.includes(parsed.modelId)) {
                        throw new VirtualRouterError(`Route "${routeName}" references unknown model "${parsed.modelId}" for provider "${parsed.providerId}"`, VirtualRouterErrorCode.CONFIG_ERROR);
                    }
                }
                const aliases = parsed.keyAlias ? [parsed.keyAlias] : aliasIndex.get(parsed.providerId);
                if (!aliases.length) {
                    throw new VirtualRouterError(`Provider ${parsed.providerId} has no auth aliases but is referenced in routing`, VirtualRouterErrorCode.CONFIG_ERROR);
                }
                for (const alias of aliases) {
                    const runtimeKey = buildRuntimeKey(parsed.providerId, alias);
                    const targetKey = `${runtimeKey}.${parsed.modelId}`;
                    const existing = expandedTargets.find((candidate) => candidate.key === targetKey);
                    if (existing) {
                        if (parsed.priority > existing.priority) {
                            existing.priority = parsed.priority;
                        }
                        continue;
                    }
                    expandedTargets.push({ key: targetKey, priority: parsed.priority, order: orderCounter });
                    orderCounter += 1;
                    targetKeys.add(targetKey);
                }
            }
            if (expandedTargets.length) {
                const sortedTargets = pool.mode === 'priority'
                    ? [...expandedTargets]
                        .sort((a, b) => {
                        if (a.priority !== b.priority) {
                            return b.priority - a.priority;
                        }
                        return a.order - b.order;
                    })
                        .map((candidate) => candidate.key)
                    : expandedTargets.map((candidate) => candidate.key);
                expandedPools.push({
                    id: pool.id,
                    priority: pool.priority,
                    backup: pool.backup,
                    targets: sortedTargets,
                    ...(pool.mode ? { mode: pool.mode } : {}),
                    ...(pool.force ? { force: true } : {})
                });
            }
        }
        routing[routeName] = expandedPools;
    }
    return { routing, targetKeys };
}
function buildLegacyRoutePool(routeName, targets) {
    return {
        id: `${routeName}:pool0`,
        priority: targets.length,
        backup: false,
        targets
    };
}
function normalizeRoutePoolEntry(routeName, entry, index, total) {
    if (typeof entry === 'string') {
        const targets = normalizeTargetList(entry);
        return targets.length
            ? {
                id: `${routeName}:pool${index + 1}`,
                priority: total - index,
                backup: false,
                targets
            }
            : null;
    }
    if (!entry || typeof entry !== 'object') {
        return null;
    }
    const record = entry;
    const id = readOptionalString(record.id) ??
        readOptionalString(record.poolId) ??
        `${routeName}:pool${index + 1}`;
    const backup = record.backup === true ||
        record.isBackup === true ||
        (typeof record.type === 'string' && record.type.toLowerCase() === 'backup');
    const priority = normalizePriorityValue(record.priority, total - index);
    const targets = normalizeRouteTargets(record);
    const mode = normalizeRoutePoolMode(record.mode ?? record.strategy ?? record.routingMode);
    const force = record.force === true ||
        (typeof record.force === 'string' && record.force.trim().toLowerCase() === 'true');
    return targets.length
        ? {
            id,
            priority,
            backup,
            targets,
            ...(mode ? { mode } : {}),
            ...(force ? { force: true } : {})
        }
        : null;
}
function normalizeRoutePoolMode(value) {
    if (typeof value !== 'string') {
        return undefined;
    }
    const normalized = value.trim().toLowerCase();
    if (!normalized) {
        return undefined;
    }
    if (normalized === 'priority') {
        return 'priority';
    }
    if (normalized === 'round-robin' ||
        normalized === 'round_robin' ||
        normalized === 'roundrobin' ||
        normalized === 'rr') {
        return 'round-robin';
    }
    return undefined;
}
function normalizeRouteTargets(record) {
    const buckets = [record.targets, record.providers, record.pool, record.entries, record.items, record.routes];
    const normalized = [];
    for (const bucket of buckets) {
        for (const target of normalizeTargetList(bucket)) {
            if (!normalized.includes(target)) {
                normalized.push(target);
            }
        }
    }
    const singular = [record.target, record.provider];
    for (const candidate of singular) {
        for (const target of normalizeTargetList(candidate)) {
            if (!normalized.includes(target)) {
                normalized.push(target);
            }
        }
    }
    return normalized;
}
function normalizeTargetList(value) {
    if (Array.isArray(value)) {
        const normalized = [];
        for (const entry of value) {
            if (typeof entry === 'string') {
                const trimmed = entry.trim();
                if (trimmed && !normalized.includes(trimmed)) {
                    normalized.push(trimmed);
                }
            }
        }
        return normalized;
    }
    if (typeof value === 'string') {
        const trimmed = value.trim();
        return trimmed ? [trimmed] : [];
    }
    if (typeof value === 'number') {
        const str = String(value).trim();
        return str ? [str] : [];
    }
    return [];
}
function normalizePriorityValue(value, fallback) {
    if (typeof value === 'number' && Number.isFinite(value)) {
        return value;
    }
    if (typeof value === 'string') {
        const trimmed = value.trim();
        if (trimmed) {
            const parsed = Number(trimmed);
            if (Number.isFinite(parsed)) {
                return parsed;
            }
        }
    }
    return fallback;
}
function parseRouteEntry(entry, aliasIndex) {
    const value = typeof entry === 'string' ? entry.trim() : '';
    if (!value)
        return null;
    const firstDot = value.indexOf('.');
    if (firstDot <= 0 || firstDot === value.length - 1)
        return null;
    const providerId = value.slice(0, firstDot);
    const remainder = value.slice(firstDot + 1);
    const aliases = aliasIndex.get(providerId);
    if (aliases && aliases.length) {
        const secondDot = remainder.indexOf('.');
        if (secondDot > 0 && secondDot < remainder.length - 1) {
            const aliasCandidate = remainder.slice(0, secondDot);
            if (aliases.includes(aliasCandidate)) {
                const parsed = splitModelPriority(remainder.slice(secondDot + 1));
                return {
                    providerId,
                    keyAlias: aliasCandidate,
                    modelId: parsed.modelId,
                    priority: parsed.priority
                };
            }
        }
    }
    const parsed = splitModelPriority(remainder);
    return { providerId, modelId: parsed.modelId, priority: parsed.priority };
}
function splitModelPriority(raw) {
    const value = typeof raw === 'string' ? raw.trim() : '';
    if (!value) {
        return { modelId: value, priority: 100 };
    }
    const match = value.match(/^(.*):(\d+)$/);
    if (!match) {
        return { modelId: value, priority: 100 };
    }
    const modelId = (match[1] ?? '').trim();
    const priorityRaw = (match[2] ?? '').trim();
    const parsed = Number(priorityRaw);
    if (!modelId) {
        return { modelId: value, priority: 100 };
    }
    if (!Number.isFinite(parsed)) {
        return { modelId, priority: 100 };
    }
    return { modelId, priority: parsed };
}
export function parseTargetKey(targetKey) {
    const value = typeof targetKey === 'string' ? targetKey.trim() : '';
    if (!value)
        return null;
    const firstDot = value.indexOf('.');
    if (firstDot <= 0 || firstDot === value.length - 1)
        return null;
    const providerId = value.slice(0, firstDot);
    const remainder = value.slice(firstDot + 1);
    const secondDot = remainder.indexOf('.');
    if (secondDot <= 0 || secondDot === remainder.length - 1)
        return null;
    return {
        providerId,
        keyAlias: remainder.slice(0, secondDot),
        modelId: remainder.slice(secondDot + 1)
    };
}
export function buildRuntimeKey(providerId, keyAlias) {
    return `${providerId}.${keyAlias}`;
}
