import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
import { basename } from "node:path";
// ---------------------------------------------------------------------------
// x-dm — X (Twitter) Direct Message normaliser.
//
// Reads a single-conversation slice of an X archive's direct-messages export.
// The X archive ships DMs as a `.js` file with a JSONP-style assignment
// wrapper:
//
//   window.YTD.direct_messages.part0 = [
//     { dmConversation: { sessionId, messages: [ { messageCreate: …} ] } },
//     ...
//   ]
//
// Multi-conversation files are split by the x-import skill before invocation,
// one slice per sessionId — each slice is a per-conversation archive in
// the conversation-archive sense (one conversation-document `:KnowledgeDocument` per participant
// set). The skill writes each slice as either a wrapper-preserving `.js` shim
// containing a single-element array, or a wrapper-stripped JSON array; this
// normaliser accepts both shapes.
//
// Output bodies are `<senderId>: <text>` — senderId is the X numeric user id,
// which the x-import skill maps to a confirmed `:Person` / `:AdminUser`
// elementId before invoking the bash. The bash's `parser-miss` validation
// reads the senderId prefix and checks membership in the confirmed set.
// ---------------------------------------------------------------------------
const WRAPPER_PREFIX_RE = /^\s*window\.YTD\.direct_messages(?:_group)?\.part\d+\s*=\s*/;
export function xDmNormaliser(input) {
    const { filePath, accountId } = input;
    if (!accountId || !accountId.trim()) {
        throw new Error("x-dm: accountId is required.");
    }
    const rawBytes = readFileSync(filePath);
    const archiveSha256 = createHash("sha256").update(rawBytes).digest("hex");
    const text = decodeText(rawBytes, filePath);
    const stripped = text.replace(WRAPPER_PREFIX_RE, "");
    let parsed;
    try {
        parsed = JSON.parse(stripped);
    }
    catch (err) {
        throw new Error(`x-dm: JSON parse failed after wrapper strip — ${err instanceof Error ? err.message : String(err)}. The file must be either a wrapper-stripped JSON array or a window.YTD.direct_messages.part0 = […] shim. file=${filePath}`);
    }
    if (!Array.isArray(parsed)) {
        throw new Error(`x-dm: expected top-level array (post-wrapper-strip), got ${typeof parsed}. file=${filePath}`);
    }
    if (parsed.length !== 1) {
        throw new Error(`x-dm: expected exactly one dmConversation entry in slice, got ${parsed.length}. Multi-conversation archives must be split by the x-import skill before ingest. file=${filePath}`);
    }
    const entry = parsed[0];
    const convo = entry?.dmConversation;
    if (!convo || !Array.isArray(convo.messages)) {
        throw new Error(`x-dm: slice entry missing dmConversation.messages array. file=${filePath}`);
    }
    const messageCreates = [];
    for (const env of convo.messages) {
        const mc = env?.messageCreate;
        if (!mc)
            continue; // skip non-messageCreate envelopes (reactions, system joins)
        const senderId = mc.senderId;
        const createdAt = mc.createdAt;
        if (!senderId || !createdAt)
            continue;
        const body = typeof mc.text === "string" ? mc.text : "";
        messageCreates.push({ senderId, text: body, createdAt });
    }
    // X serialises createdAt as ISO-8601 (e.g. "2024-09-12T14:32:01.000Z").
    // Sort chronologically — X archives are usually in order, but the contract
    // doesn't guarantee it, so re-sorting is the deterministic primitive.
    messageCreates.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
    const parsedLines = messageCreates.map((m, idx) => ({
        dateSent: m.createdAt,
        body: `${m.senderId}: ${m.text}`,
        sequenceIndex: idx,
    }));
    return {
        parsedLines,
        archiveSha256,
        archiveSourceFile: basename(filePath),
        counters: {
            parsed: parsedLines.length,
            systemSkipped: convo.messages.length - parsedLines.length,
            mediaSkipped: 0,
            parseErrors: 0,
        },
    };
}
function decodeText(bytes, filePath) {
    let decoded;
    try {
        decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
    }
    catch (err) {
        throw new Error(`x-dm: UTF-8 decode failed — ${err instanceof Error ? err.message : String(err)}. file=${filePath}`);
    }
    if (decoded.charCodeAt(0) === 0xfeff)
        decoded = decoded.slice(1);
    return decoded;
}
//# sourceMappingURL=x-dm.js.map