/**
 * Expand sub-agent transcripts into OpenAI chat format records.
 *
 * Reads the pipeline output (restructured-*.jsonl) and transcript directory,
 * identifies records that used the Agent tool, matches them to sub-agent
 * transcript files by timestamp proximity, and produces:
 *
 *   - openai-chat-with-subagents.jsonl  (main + sub-agent records)
 *   - subagent-groups.json              (maps main UIDs → sub-agent UIDs)
 *   - subagent-summary.json             (statistics)
 *
 * Usage:
 *   visionclaw expand-subagents <data-dir>
 */
import fs from "node:fs";
import path from "node:path";
import readline from "node:readline";
import { randomUUID } from "node:crypto";
// ---------------------------------------------------------------------------
// Index sub-agent transcript files
// ---------------------------------------------------------------------------
function indexSubAgents(transcriptDir) {
    const subAgents = [];
    for (const entry of fs.readdirSync(transcriptDir, { withFileTypes: true })) {
        if (!entry.isDirectory() || entry.name.startsWith("."))
            continue;
        const subagentsDir = path.join(transcriptDir, entry.name, "subagents");
        if (!fs.existsSync(subagentsDir))
            continue;
        for (const file of fs.readdirSync(subagentsDir).filter((f) => f.endsWith(".jsonl"))) {
            const fullPath = path.join(subagentsDir, file);
            const content = fs.readFileSync(fullPath, "utf-8");
            const lines = content.split("\n").filter((l) => l.trim());
            if (lines.length === 0)
                continue;
            try {
                const first = JSON.parse(lines[0]);
                subAgents.push({
                    filename: file,
                    fullPath,
                    firstTimestamp: first.timestamp ?? "",
                    messageCount: lines.length,
                });
            }
            catch {
                // skip malformed files
            }
        }
    }
    return subAgents;
}
// ---------------------------------------------------------------------------
// Match Agent tool_call → sub-agent transcript by timestamp
// ---------------------------------------------------------------------------
function matchSubAgent(call, subAgents, usedFiles) {
    const callTime = new Date(call.timestamp).getTime();
    if (isNaN(callTime))
        return null;
    let best = null;
    let bestDist = Infinity;
    for (const sa of subAgents) {
        if (usedFiles.has(sa.fullPath))
            continue;
        const saStart = new Date(sa.firstTimestamp).getTime();
        if (isNaN(saStart))
            continue;
        // Sub-agent should start AFTER the tool_call (within -5s to +60s window)
        const diff = saStart - callTime;
        if (diff >= -5000 && diff <= 60000 && Math.abs(diff) < bestDist) {
            bestDist = Math.abs(diff);
            best = sa;
        }
    }
    return best;
}
// ---------------------------------------------------------------------------
// Convert a sub-agent transcript → ChatRecord
// ---------------------------------------------------------------------------
function convertSubAgentTranscript(transcriptPath, parentUid, subagentIndex, description) {
    const content = fs.readFileSync(transcriptPath, "utf-8");
    const lines = content.split("\n").filter((l) => l.trim());
    if (lines.length === 0)
        return null;
    const msgs = [];
    for (const line of lines) {
        try {
            const m = JSON.parse(line);
            if (m.type === "user" || m.type === "assistant")
                msgs.push(m);
        }
        catch {
            // skip
        }
    }
    if (msgs.length === 0)
        return null;
    const chatMessages = [];
    // System message
    chatMessages.push({
        role: "system",
        content: "You are a sub-agent launched to handle a specific task. Complete the task thoroughly and return your findings.",
    });
    for (const msg of msgs) {
        const contentBlocks = msg.message?.content;
        if (!contentBlocks)
            continue;
        if (msg.type === "user") {
            if (!Array.isArray(contentBlocks)) {
                chatMessages.push({ role: "user", content: contentBlocks });
                continue;
            }
            const blocks = contentBlocks;
            const toolResults = blocks.filter((b) => b.type === "tool_result");
            const textBlocks = blocks.filter((b) => b.type === "text");
            if (toolResults.length > 0) {
                for (const tr of toolResults) {
                    let resultContent = "";
                    if (typeof tr.content === "string") {
                        resultContent = tr.content;
                    }
                    else if (Array.isArray(tr.content)) {
                        resultContent = tr.content
                            .filter((b) => b.type === "text")
                            .map((b) => b.text ?? "")
                            .join("\n");
                    }
                    chatMessages.push({
                        role: "tool",
                        content: resultContent,
                        tool_call_id: tr.tool_use_id ?? `call_${randomUUID().slice(0, 8)}`,
                    });
                }
            }
            else if (textBlocks.length > 0) {
                chatMessages.push({
                    role: "user",
                    content: textBlocks.map((b) => b.text ?? "").join("\n"),
                });
            }
        }
        else if (msg.type === "assistant") {
            if (!Array.isArray(contentBlocks)) {
                chatMessages.push({ role: "assistant", content: contentBlocks });
                continue;
            }
            let thinkingBuf = "";
            let textBuf = "";
            const toolCalls = [];
            for (const block of contentBlocks) {
                if (block.type === "thinking") {
                    if (thinkingBuf)
                        thinkingBuf += "\n";
                    thinkingBuf += block.text ?? "";
                }
                else if (block.type === "text") {
                    if (textBuf)
                        textBuf += "\n";
                    textBuf += block.text ?? "";
                }
                else if (block.type === "tool_use") {
                    toolCalls.push({
                        id: block.id ?? `call_${randomUUID().slice(0, 8)}`,
                        type: "function",
                        function: {
                            name: block.name ?? "unknown",
                            arguments: block.input !== undefined
                                ? JSON.stringify(block.input)
                                : "",
                        },
                    });
                }
            }
            const assistantMsg = { role: "assistant", content: textBuf };
            if (thinkingBuf)
                assistantMsg.reasoning_content = thinkingBuf;
            if (toolCalls.length > 0)
                assistantMsg.tool_calls = toolCalls;
            chatMessages.push(assistantMsg);
        }
    }
    const lastMsg = chatMessages[chatMessages.length - 1];
    const success = lastMsg.role === "assistant" && !!lastMsg.content;
    return {
        uid: randomUUID(),
        agent_type: "subagent",
        subagent_index: subagentIndex,
        parent_uid: parentUid,
        prompt: description,
        success,
        messages: chatMessages,
    };
}
async function readJsonlRecords(filePath) {
    const results = [];
    if (!fs.existsSync(filePath))
        return results;
    const rl = readline.createInterface({
        input: fs.createReadStream(filePath),
        crlfDelay: Infinity,
    });
    let lineNum = 0;
    for await (const line of rl) {
        lineNum++;
        if (!line.trim())
            continue;
        try {
            results.push({ record: JSON.parse(line), lineNum });
        }
        catch {
            // skip
        }
    }
    return results;
}
async function readChatRecords(filePath) {
    const map = new Map();
    if (!fs.existsSync(filePath))
        return map;
    const rl = readline.createInterface({
        input: fs.createReadStream(filePath),
        crlfDelay: Infinity,
    });
    let lineNum = 0;
    for await (const line of rl) {
        lineNum++;
        if (!line.trim())
            continue;
        try {
            const rec = JSON.parse(line);
            // Check if it has Agent tool_calls
            const hasAgent = rec.messages.some((m) => m.role === "assistant" &&
                m.tool_calls?.some((tc) => tc.function.name === "Agent"));
            if (hasAgent)
                map.set(lineNum, rec);
        }
        catch {
            // skip
        }
    }
    return map;
}
// ---------------------------------------------------------------------------
// CLI entry
// ---------------------------------------------------------------------------
export async function runExpandSubagents(args) {
    const dataDir = args.find((a) => !a.startsWith("-"));
    if (!dataDir) {
        console.error("Usage: visionclaw expand-subagents <data-dir>\n" +
            "\n" +
            "  <data-dir>   Agent data directory (e.g. data-collector/james-smith/20260327T023519)\n" +
            "               Must contain output/ and transcripts/ directories.");
        process.exit(1);
    }
    if (!fs.existsSync(dataDir) || !fs.statSync(dataDir).isDirectory()) {
        console.error(`Not a directory: ${dataDir}`);
        process.exit(1);
    }
    const outputDir = path.join(dataDir, "output");
    const transcriptDir = path.join(dataDir, "transcripts");
    if (!fs.existsSync(outputDir)) {
        console.error(`No output/ directory found. Run the pipeline first.`);
        process.exit(1);
    }
    if (!fs.existsSync(transcriptDir)) {
        console.error(`No transcripts/ directory found.`);
        process.exit(1);
    }
    console.log("=== Sub-agent Expansion ===");
    console.log(`  Data dir:       ${dataDir}`);
    console.log(`  Output dir:     ${outputDir}`);
    console.log(`  Transcripts:    ${transcriptDir}`);
    // Index sub-agent files
    const subAgents = indexSubAgents(transcriptDir);
    console.log(`  Sub-agent transcripts: ${subAgents.length}`);
    if (subAgents.length === 0) {
        console.log("\nNo sub-agent transcripts found. Nothing to expand.");
        return;
    }
    // Find records with Agent tool_calls and match to sub-agent transcripts
    const categories = ["clean", "interrupted", "incomplete", "heartbeat"];
    const usedFiles = new Set();
    const matchedEntries = [];
    for (const cat of categories) {
        const restructuredPath = path.join(outputDir, `restructured-${cat}.jsonl`);
        const records = await readJsonlRecords(restructuredPath);
        for (const { record, lineNum } of records) {
            const agentCalls = [];
            for (const entry of record.trajectory) {
                if (entry.role === "tool_call" && entry.metadata?.name === "Agent") {
                    agentCalls.push({
                        tool_use_id: entry.metadata.tool_use_id ?? "",
                        description: entry.metadata.input?.description ?? "",
                        timestamp: entry.timestamp,
                    });
                }
            }
            if (agentCalls.length === 0)
                continue;
            const matched = [];
            for (const call of agentCalls) {
                const sa = matchSubAgent(call, subAgents, usedFiles);
                if (sa) {
                    usedFiles.add(sa.fullPath);
                    matched.push({ call, subagent: sa });
                }
            }
            matchedEntries.push({ category: cat, lineNum, agentCalls, matchedSubAgents: matched });
        }
    }
    const totalAgentCalls = matchedEntries.reduce((n, e) => n + e.agentCalls.length, 0);
    const totalMatched = matchedEntries.reduce((n, e) => n + e.matchedSubAgents.length, 0);
    console.log(`\n  Records with Agent calls: ${matchedEntries.length}`);
    console.log(`  Agent calls total:       ${totalAgentCalls}`);
    console.log(`  Matched to transcripts:  ${totalMatched}`);
    console.log(`  Unmatched:               ${totalAgentCalls - totalMatched}`);
    // Now read the openai-chat records and build the combined output
    const allOutput = [];
    const groups = [];
    for (const entry of matchedEntries) {
        const chatFile = path.join(outputDir, `openai-chat-${entry.category}.jsonl`);
        const chatRecords = await readChatRecords(chatFile);
        const mainRec = chatRecords.get(entry.lineNum);
        if (!mainRec)
            continue;
        const mainOutput = {
            ...mainRec,
            agent_type: "main",
            subagent_index: null,
            parent_uid: null,
        };
        if (!mainOutput.uid)
            mainOutput.uid = randomUUID();
        allOutput.push(mainOutput);
        const groupSubagents = [];
        for (let si = 0; si < entry.agentCalls.length; si++) {
            const call = entry.agentCalls[si];
            const match = entry.matchedSubAgents.find((m) => m.call.tool_use_id === call.tool_use_id);
            if (match) {
                const subRec = convertSubAgentTranscript(match.subagent.fullPath, mainOutput.uid, si, call.description);
                if (subRec) {
                    allOutput.push(subRec);
                    // Collect tools used
                    const toolsUsed = new Set();
                    for (const m of subRec.messages) {
                        if (m.tool_calls) {
                            for (const tc of m.tool_calls)
                                toolsUsed.add(tc.function.name);
                        }
                    }
                    groupSubagents.push({
                        uid: subRec.uid,
                        subagent_index: si,
                        prompt: call.description,
                        messages_count: subRec.messages.length,
                        transcript_file: match.subagent.filename,
                        tools_used: [...toolsUsed],
                    });
                }
            }
            else {
                groupSubagents.push({
                    uid: "",
                    subagent_index: si,
                    prompt: call.description,
                    messages_count: 0,
                    transcript_file: null,
                    tools_used: [],
                });
            }
        }
        groups.push({
            main_uid: mainOutput.uid,
            prompt: mainOutput.prompt,
            category: entry.category,
            main_messages_count: mainOutput.messages.length,
            subagents: groupSubagents,
        });
    }
    // Write output files
    const outFile = path.join(outputDir, "openai-chat-with-subagents.jsonl");
    const stream = fs.createWriteStream(outFile);
    for (const rec of allOutput) {
        stream.write(JSON.stringify(rec) + "\n");
    }
    stream.end();
    await new Promise((resolve) => {
        stream.on("close", resolve);
    });
    // Write groups
    const groupsFile = path.join(outputDir, "subagent-groups.json");
    fs.writeFileSync(groupsFile, JSON.stringify(groups, null, 2));
    // Write summary
    const mainCount = allOutput.filter((r) => r.agent_type === "main").length;
    const subCount = allOutput.filter((r) => r.agent_type === "subagent").length;
    const summaryData = {
        created: new Date().toISOString(),
        totalRecords: allOutput.length,
        mainAgentRecords: mainCount,
        subAgentRecords: subCount,
        totalSubagentCalls: totalAgentCalls,
        matchedSubagentTranscripts: totalMatched,
        unmatchedSubagentCalls: totalAgentCalls - totalMatched,
        groups: groups.length,
    };
    const summaryFile = path.join(outputDir, "subagent-summary.json");
    fs.writeFileSync(summaryFile, JSON.stringify(summaryData, null, 2));
    console.log(`\nWrote output files:`);
    console.log(`  ${outFile} (${allOutput.length} records: ${mainCount} main + ${subCount} subagent)`);
    console.log(`  ${groupsFile} (${groups.length} groups)`);
    console.log(`  ${summaryFile}`);
}
//# sourceMappingURL=subagent-expand.js.map