import { basename, join } from 'path';
import { existsSync, readdirSync, readFileSync } from 'fs';
import { normalizeComposerTitle, titlesAlign } from './composer-title-index.js';
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
export function normalizeAgentTitle(title) {
    return title
        .trim()
        .replace(/\s+/g, ' ')
        .replace(/\d+\s*(?:s|m|h|d|w)\b/gi, '')
        .replace(/\d+[smhdw]$/i, '')
        .trim()
        .toLowerCase();
}
/** Score how well a Cursor sidebar title matches a JSONL agent row. */
export function agentTitleMatchScore(sidebarTitle, jsonlLabel) {
    const a = normalizeAgentTitle(sidebarTitle);
    const b = normalizeAgentTitle(jsonlLabel);
    if (!a || !b)
        return 0;
    if (a === b)
        return 100;
    if (a.length >= 10 && b.length >= 10 && (a.includes(b) || b.includes(a)))
        return 85;
    let score = 0;
    const wordsA = a.split(/\s+/).filter((w) => w.length > 3);
    const wordsB = new Set(b.split(/\s+/).filter((w) => w.length > 3));
    for (const w of wordsA) {
        if (wordsB.has(w))
            score += 2;
    }
    // Russian / iOS sidebar titles vs first user message (different wording, same topic).
    const stems = [
        [/\bios\b/i, 5],
        [/приложен/i, 5],
        [/полноцен/i, 4],
        [/testflight/i, 4],
        [/\bgit\b/i, 4],
        [/репоз/i, 3],
        [/развер/i, 3],
        [/деплой/i, 3],
        [/проект/i, 2],
    ];
    for (const [re, bonus] of stems) {
        if (re.test(a) && re.test(b))
            score += bonus;
    }
    if (/\bgit\b/.test(a) && (/\bgit\b/.test(b) || b.includes('гит') || b.includes('репо')))
        score += 4;
    if (/\b(repository|repo|deployment|deploy|project)\b/.test(a)) {
        if (b.includes('репо') || b.includes('repo') || b.includes('разверни') || b.includes('deploy'))
            score += 3;
        if (b.includes('project') || b.includes('проект'))
            score += 2;
    }
    return score;
}
export function findAgentById(jsonl, agentId) {
    for (const repo of jsonl.repos) {
        const hit = repo.agents.find((a) => a.id === agentId);
        if (hit)
            return hit;
    }
    return undefined;
}
/** Best JSONL agent for a Cursor sidebar row (generated title vs first user message). */
export function matchJsonlAgentForSidebarTitle(rowTitle, jsonl, composerIdByTitle) {
    const fromDomUuid = composerIdByTitle?.[normalizeAgentTitle(rowTitle)];
    if (fromDomUuid) {
        const hit = findAgentById(jsonl, fromDomUuid);
        if (hit)
            return hit;
    }
    let best;
    for (const repo of jsonl.repos) {
        for (const agent of repo.agents) {
            let score = agentTitleMatchScore(rowTitle, agent.title);
            if (agent.preview) {
                score = Math.max(score, agentTitleMatchScore(rowTitle, agent.preview));
            }
            if (score >= 4 && (!best || score > best.score)) {
                best = { agent, score };
            }
        }
    }
    return best?.agent;
}
/** Editor auxiliary tabs expose truncated `data-resource-name` — match folder prefix. */
function findJsonlAgentIdByFolderPrefix(projectsDir, prefix) {
    const p = prefix.toLowerCase().replace(/[^0-9a-f-]/g, '');
    if (p.length < 32)
        return null;
    if (!existsSync(projectsDir))
        return null;
    for (const project of readdirSync(projectsDir, { withFileTypes: true })) {
        if (!project.isDirectory())
            continue;
        const base = join(projectsDir, project.name, 'agent-transcripts');
        if (!existsSync(base))
            continue;
        const hit = walkFolderPrefix(base, p, 0);
        if (hit)
            return hit;
    }
    return null;
}
function walkFolderPrefix(dir, prefix, depth) {
    if (depth > 5 || !existsSync(dir))
        return null;
    for (const entry of readdirSync(dir, { withFileTypes: true })) {
        if (!entry.isDirectory())
            continue;
        const name = entry.name.toLowerCase();
        if (name.startsWith(prefix) || prefix.startsWith(name)) {
            const jsonl = join(dir, entry.name, `${entry.name}.jsonl`);
            if (existsSync(jsonl))
                return entry.name;
        }
        const nested = walkFolderPrefix(join(dir, entry.name), prefix, depth + 1);
        if (nested)
            return nested;
    }
    return null;
}
export function resolveComposerUuidFromPartial(partialId, opts) {
    if (UUID_RE.test(partialId))
        return partialId;
    const p = partialId.toLowerCase();
    if (p.length < 32 || !/^[0-9a-f-]/.test(p))
        return undefined;
    for (const uuid of Object.values(opts?.composerIdByTitle ?? {})) {
        if (uuid.toLowerCase().startsWith(p))
            return uuid;
    }
    for (const repo of opts?.agentsIndex?.repos ?? []) {
        for (const agent of repo.agents) {
            if (agent.id.toLowerCase().startsWith(p))
                return agent.id;
        }
    }
    return undefined;
}
function findJsonlByAgentId(projectsDir, agentId) {
    if (!existsSync(projectsDir))
        return null;
    for (const project of readdirSync(projectsDir, { withFileTypes: true })) {
        if (!project.isDirectory())
            continue;
        const base = join(projectsDir, project.name, 'agent-transcripts');
        const direct = join(base, agentId, `${agentId}.jsonl`);
        if (existsSync(direct))
            return direct;
        const found = walkForAgent(base, agentId);
        if (found)
            return found;
    }
    return null;
}
function walkForAgent(dir, agentId, depth = 0) {
    if (depth > 5 || !existsSync(dir))
        return null;
    for (const entry of readdirSync(dir, { withFileTypes: true })) {
        const full = join(dir, entry.name);
        if (entry.isDirectory()) {
            const r = walkForAgent(full, agentId, depth + 1);
            if (r)
                return r;
        }
        else if (entry.name === `${agentId}.jsonl`) {
            return full;
        }
    }
    return null;
}
function peekFirstUserMessage(filePath) {
    try {
        const buf = readFileSync(filePath, 'utf-8').slice(0, 8192);
        for (const line of buf.split('\n')) {
            if (!line.trim())
                continue;
            const row = JSON.parse(line);
            if (row.role !== 'user')
                continue;
            const text = row.message?.content?.find((c) => c.type === 'text')?.text ?? '';
            const cleaned = text.replace(/<user_query>\s*/gi, '').replace(/<\/user_query>/gi, '').trim();
            if (cleaned)
                return cleaned.slice(0, 200);
        }
    }
    catch {
        /* ignore */
    }
    return undefined;
}
function collectJsonlAgents(projectsDir) {
    const out = [];
    if (!existsSync(projectsDir))
        return out;
    function walk(dir, depth) {
        if (depth > 4)
            return;
        for (const entry of readdirSync(dir, { withFileTypes: true })) {
            const full = join(dir, entry.name);
            if (entry.isDirectory()) {
                walk(full, depth + 1);
                continue;
            }
            if (!entry.name.endsWith('.jsonl') || full.includes('/subagents/'))
                continue;
            const id = basename(entry.name, '.jsonl');
            const preview = peekFirstUserMessage(full);
            out.push({
                id,
                title: preview?.slice(0, 80) || `Agent ${id.slice(0, 8)}`,
                preview,
            });
        }
    }
    for (const project of readdirSync(projectsDir, { withFileTypes: true })) {
        if (!project.isDirectory())
            continue;
        const transcriptsDir = join(projectsDir, project.name, 'agent-transcripts');
        if (!existsSync(transcriptsDir))
            continue;
        walk(transcriptsDir, 0);
    }
    return out;
}
export function isSyntheticAgentId(agentId) {
    return (!agentId ||
        /^tab-\d+$/.test(agentId) ||
        /^sidebar-\d+$/.test(agentId) ||
        agentId.startsWith('title:'));
}
/** Map app/relay agentId (+ optional sidebar title) to an on-disk JSONL id. */
export function resolveJsonlAgentId(projectsDir, agentId, opts) {
    if (findJsonlByAgentId(projectsDir, agentId))
        return agentId;
    const fromPartial = resolveComposerUuidFromPartial(agentId, {
        composerIdByTitle: opts?.composerIdByTitle,
    }) ?? findJsonlAgentIdByFolderPrefix(projectsDir, agentId);
    if (fromPartial && findJsonlByAgentId(projectsDir, fromPartial))
        return fromPartial;
    const title = opts?.title?.trim();
    const map = opts?.composerIdByTitle;
    if (title && map) {
        const mapped = map[normalizeComposerTitle(title)] ?? map[normalizeAgentTitle(title)];
        if (mapped && findJsonlByAgentId(projectsDir, mapped))
            return mapped;
    }
    const activeComposerId = opts?.activeComposerId?.trim();
    if (isSyntheticAgentId(agentId) &&
        activeComposerId &&
        UUID_RE.test(activeComposerId) &&
        findJsonlByAgentId(projectsDir, activeComposerId)) {
        const tabT = opts?.activeTabTitle?.trim();
        if (title && tabT && !titlesAlign(title, tabT)) {
            /* wrong Cursor tab open — do not bind sidebar route to foreign composer */
        }
        else if (!title || !tabT || titlesAlign(title, tabT)) {
            return activeComposerId;
        }
    }
    if (title) {
        let best;
        for (const row of collectJsonlAgents(projectsDir)) {
            let score = agentTitleMatchScore(title, row.title);
            if (row.preview)
                score = Math.max(score, agentTitleMatchScore(title, row.preview));
            if (score >= 4 && (!best || score > best.score))
                best = { id: row.id, score };
        }
        if (best)
            return best.id;
    }
    if (isSyntheticAgentId(agentId) && !title)
        return agentId;
    if (UUID_RE.test(agentId) && !findJsonlByAgentId(projectsDir, agentId) && title) {
        const fromTitle = resolveJsonlAgentId(projectsDir, 'sidebar-0', { title, composerIdByTitle: map });
        if (fromTitle !== 'sidebar-0' && findJsonlByAgentId(projectsDir, fromTitle))
            return fromTitle;
    }
    return agentId;
}
export function resolveJsonlFilePath(projectsDir, agentId, opts) {
    const resolved = resolveJsonlAgentId(projectsDir, agentId, opts);
    return findJsonlByAgentId(projectsDir, resolved);
}
