/**
 * Unified data-processing pipeline.
 *
 * Takes a raw collected data directory (as produced by the data-collector)
 * and runs the data processing pipeline:
 *
 *   1. **Restructure** — segment, filter, match SDK transcripts, recover
 *      archived images, and output structured JSONL records
 *
 * Expected input directory layout:
 *   <data-dir>/
 *     logs/logs/*.jsonl                — VisionClaw log files
 *     transcripts/transcripts/         — SDK transcript session files + sidecar
 *     workspace/workspace/session.json — session metadata (session IDs)
 *
 * Output goes to <data-dir>/output/ (or a custom -o directory).
 */
import fs from "node:fs";
import path from "node:path";
import { restructure } from "./restructure.js";
import { discoverSessionIdsFromTranscripts } from "./transcript-discovery.js";
/**
 * Parse a --since value like "2026-03-23-22-10+08:00" into a Date.
 * Accepted format: YYYY-MM-DD-HH-mm followed by a timezone offset
 * (+HH:MM or -HH:MM) or Z for UTC.
 */
function parseSinceArg(value) {
    const m = /^(\d{4})-(\d{2})-(\d{2})-(\d{2})-(\d{2})(Z|[+-]\d{2}:\d{2})$/.exec(value);
    if (!m) {
        console.error(`Invalid --since format: "${value}" (expected YYYY-MM-DD-HH-mm+HH:MM, e.g. 2026-03-23-22-10+08:00)`);
        process.exit(1);
    }
    const [, yyyy, mm, dd, hh, min, tz] = m;
    const month = Number(mm);
    const day = Number(dd);
    const hour = Number(hh);
    const minute = Number(min);
    if (month < 1 || month > 12) {
        console.error(`Invalid --since month: ${mm} (must be 01-12)`);
        process.exit(1);
    }
    if (day < 1 || day > 31) {
        console.error(`Invalid --since day: ${dd} (must be 01-31)`);
        process.exit(1);
    }
    if (hour > 23) {
        console.error(`Invalid --since hour: ${hh} (must be 00-23)`);
        process.exit(1);
    }
    if (minute > 59) {
        console.error(`Invalid --since minute: ${min} (must be 00-59)`);
        process.exit(1);
    }
    if (tz !== "Z") {
        const tzH = Number(tz.slice(1, 3));
        const tzM = Number(tz.slice(4, 6));
        if (tzH > 14 || (tzH === 14 && tzM > 0) || tzM > 59) {
            console.error(`Invalid --since timezone offset: ${tz}`);
            process.exit(1);
        }
    }
    const iso = `${yyyy}-${mm}-${dd}T${hh}:${min}:00.000${tz}`;
    const d = new Date(iso);
    if (isNaN(d.getTime())) {
        console.error(`Invalid --since date: "${value}"`);
        process.exit(1);
    }
    // Verify the date wasn't silently rolled over (e.g. Feb 30 → Mar 2)
    const reparsed = new Date(`${yyyy}-${mm}-${dd}T00:00:00.000${tz}`);
    if (reparsed.getUTCDate() !== day || reparsed.getUTCMonth() + 1 !== month) {
        const localMs = d.getTime();
        const offsetMs = tz === "Z" ? 0 : (Number(tz.slice(0, 3)) * 60 + Number(tz.slice(4, 6)) * (tz.startsWith("-") ? -1 : 1)) * 60000;
        const local = new Date(localMs + offsetMs);
        if (local.getUTCDate() !== day || local.getUTCMonth() + 1 !== month) {
            console.error(`Invalid --since date: ${yyyy}-${mm}-${dd} does not exist`);
            process.exit(1);
        }
    }
    return d;
}
function parseProcessDataArgs(args) {
    let dataDir = "";
    let outputDir;
    let samples = false;
    let since;
    for (let i = 0; i < args.length; i++) {
        const arg = args[i];
        if ((arg === "-o" || arg === "--output-dir") && i + 1 < args.length) {
            outputDir = args[++i];
        }
        else if (arg === "--samples") {
            samples = true;
        }
        else if (arg === "--since" && i + 1 < args.length) {
            since = parseSinceArg(args[++i]);
        }
        else if (!arg.startsWith("-") && !dataDir) {
            dataDir = arg;
        }
    }
    if (!dataDir) {
        console.error("Usage: visionclaw process-data <data-dir> [-o output-dir] [--samples] [--since YYYY-MM-DD-HH-mm+HH:MM]");
        process.exit(1);
    }
    return { dataDir, outputDir, samples, since };
}
// ---------------------------------------------------------------------------
// Directory resolution
// ---------------------------------------------------------------------------
function resolveSubdir(base, ...candidates) {
    for (const c of candidates) {
        const full = path.join(base, c);
        if (fs.existsSync(full) && fs.statSync(full).isDirectory())
            return full;
    }
    return undefined;
}
function resolveFile(base, ...candidates) {
    for (const c of candidates) {
        const full = path.join(base, c);
        if (fs.existsSync(full) && fs.statSync(full).isFile())
            return full;
    }
    return undefined;
}
/**
 * Read session IDs and their mode mapping from workspace/session.json.
 * Supports single-session (sessionId / generalSessionId) and dual-session
 * (+ codingSessionId).
 */
function readSessionInfo(sessionJsonPath) {
    const modeMap = new Map();
    try {
        const raw = JSON.parse(fs.readFileSync(sessionJsonPath, "utf-8"));
        const addSession = (id, mode) => {
            if (typeof id !== "string" || id.length === 0)
                return;
            const normalizedMode = mode === "coding" ? "coding" : "general";
            modeMap.set(id, normalizedMode);
        };
        if (Array.isArray(raw.previousSessionIds)) {
            for (const entry of raw.previousSessionIds) {
                if (entry !== null && typeof entry === "object") {
                    const prev = entry;
                    addSession(prev.id, prev.mode);
                }
                else if (typeof entry === "string") {
                    addSession(entry, "general");
                }
            }
        }
        const generalId = typeof raw.generalSessionId === "string" && raw.generalSessionId.length > 0
            ? raw.generalSessionId
            : typeof raw.sessionId === "string" && raw.sessionId.length > 0
                ? raw.sessionId
                : undefined;
        addSession(generalId, "general");
        addSession(raw.codingSessionId, "coding");
    }
    catch {
        // return empty
    }
    const ids = [...modeMap.keys()];
    return { ids, modeMap };
}
// ---------------------------------------------------------------------------
// Copy workspace (excluding sensitive files)
// ---------------------------------------------------------------------------
const WORKSPACE_EXCLUDE = new Set(["config.json", "owner.json", "session.json"]);
function copyWorkspace(srcDir, dstDir) {
    fs.mkdirSync(dstDir, { recursive: true });
    let copied = 0;
    function walk(src, dst) {
        for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
            if (WORKSPACE_EXCLUDE.has(entry.name))
                continue;
            if (entry.name.endsWith(".tar.gz"))
                continue;
            const srcPath = path.join(src, entry.name);
            const dstPath = path.join(dst, entry.name);
            if (entry.isDirectory()) {
                fs.mkdirSync(dstPath, { recursive: true });
                walk(srcPath, dstPath);
            }
            else if (entry.isFile()) {
                fs.copyFileSync(srcPath, dstPath);
                copied++;
            }
        }
    }
    walk(srcDir, dstDir);
    return copied;
}
// ---------------------------------------------------------------------------
// Public entry point
// ---------------------------------------------------------------------------
export async function runProcessData(args) {
    const opts = parseProcessDataArgs(args);
    if (!fs.existsSync(opts.dataDir) || !fs.statSync(opts.dataDir).isDirectory()) {
        console.error(`Not a directory: ${opts.dataDir}`);
        process.exit(1);
    }
    // Resolve subdirectories
    const logsDir = resolveSubdir(opts.dataDir, "logs/logs", "logs");
    const transcriptDir = resolveSubdir(opts.dataDir, "transcripts/transcripts", "transcripts");
    const workspaceDir = resolveSubdir(opts.dataDir, "workspace/workspace", "workspace");
    const sessionJsonPath = resolveFile(opts.dataDir, "workspace/workspace/session.json", "workspace/session.json");
    const outputDir = opts.outputDir ?? path.join(opts.dataDir, "output");
    if (!logsDir) {
        console.error(`No logs directory found in ${opts.dataDir} (expected logs/logs/ or logs/)`);
        process.exit(1);
    }
    // Read session IDs to match transcript files (workspace + on-disk transcripts)
    const sessionInfo = sessionJsonPath ? readSessionInfo(sessionJsonPath) : { ids: [], modeMap: new Map() };
    if (transcriptDir) {
        const fromTranscripts = discoverSessionIdsFromTranscripts(transcriptDir);
        for (const id of fromTranscripts) {
            if (!sessionInfo.modeMap.has(id)) {
                sessionInfo.modeMap.set(id, "general");
            }
        }
        sessionInfo.ids = [...sessionInfo.modeMap.keys()];
    }
    console.log("=== VisionClaw Data Processing Pipeline ===");
    console.log(`  Data dir:       ${opts.dataDir}`);
    console.log(`  Logs dir:       ${logsDir}`);
    console.log(`  Transcript dir: ${transcriptDir ?? "(none)"}`);
    if (sessionInfo.ids.length > 0) {
        const labels = sessionInfo.ids.map((id) => `${id} (${sessionInfo.modeMap.get(id) ?? "?"})`);
        console.log(`  Sessions:       ${labels.join(", ")}`);
    }
    console.log(`  Output dir:     ${outputDir}`);
    if (opts.since) {
        console.log(`  Since:          ${opts.since.toISOString()}`);
    }
    // --- Stage 1: Copy workspace (excluding sensitive files) ---
    if (workspaceDir) {
        const wsDst = path.join(outputDir, "workspace");
        const count = copyWorkspace(workspaceDir, wsDst);
        console.log(`\nCopied workspace: ${count} files (excluding config/owner/session)`);
    }
    // --- Stage 2: Restructure ---
    console.log("\n--- Stage 2: Restructure ---");
    await restructure({
        logsDir,
        transcriptDir,
        outputDir,
        samples: opts.samples,
        sessionIds: sessionInfo.ids.length > 0 ? sessionInfo.ids : undefined,
        sessionModeMap: sessionInfo.modeMap.size > 0 ? sessionInfo.modeMap : undefined,
        since: opts.since,
    });
    console.log("\n=== Pipeline complete ===");
}
//# sourceMappingURL=pipeline.js.map