import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
import { basename } from "node:path";
import { scanTimestampedLines } from "./timestamp-scanner.js";
const TIMESTAMP_PREFIX = /^\[(\d{2})\/(\d{2})\/(\d{4}|\d{2}),\s+(\d{1,2}):(\d{2})(?::(\d{2}))?\]\s*(.*)$/;
export function whatsappTextNormaliser(input) {
    const { filePath, accountId, timezone } = input;
    const opts = (input.opts ?? {});
    if (!accountId || !accountId.trim()) {
        throw new Error("whatsapp-text: accountId is required.");
    }
    if (!timezone || !timezone.trim()) {
        throw new Error("whatsapp-text: timezone is required (e.g. 'Europe/London').");
    }
    const rawBytes = readFileSync(filePath);
    const archiveSha256 = createHash("sha256").update(rawBytes).digest("hex");
    const text = decodeAndNormalise(rawBytes);
    if (text.length === 0) {
        throw new Error(`whatsapp-text: file is empty — not a _chat.txt. file=${filePath}`);
    }
    const ordering = resolveOrdering(opts.dateFormat, text);
    const matchPrefix = makeWhatsappPrefixMatcher(ordering);
    const { parsedLines } = scanTimestampedLines({
        text,
        matchPrefix,
        formatIso: (parts) => isoWithOffset(parts.year, parts.month, parts.day, parts.hour, parts.minute, parts.second, timezone),
    });
    // `parsed` is the matched-prefix count (= parsedLines.length).
    // `parseErrors` is structurally zero — the scanner cannot fail on body
    // shape post-Task-897. `systemSkipped` / `mediaSkipped` are zero too:
    // skip detection lived in the pre-897 colon-parser path that's gone.
    // The bin emits `[conversation-archive] parsed lines=N` and routes a
    // zero-parsed file to the document-ingest chunker rather than failing.
    return {
        parsedLines,
        archiveSha256,
        archiveSourceFile: basename(filePath),
        counters: {
            parsed: parsedLines.length,
            systemSkipped: 0,
            mediaSkipped: 0,
            parseErrors: 0,
        },
    };
}
function decodeAndNormalise(bytes) {
    let text;
    try {
        text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
    }
    catch (err) {
        throw new Error(`whatsapp-text: UTF-8 decode failed — ${err instanceof Error ? err.message : String(err)}. The file is not valid UTF-8; re-export from WhatsApp.`);
    }
    if (text.charCodeAt(0) === 0xfeff)
        text = text.slice(1);
    text = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
    // Strip leading bidi marks (U+200E LRM, U+200F RLM) only at line-start —
    // some WhatsApp builds prefix the timestamp header. Body-internal bidi
    // marks are preserved (semantic).
    const leadingBidiMatches = text.match(/(?:^|\n)[‎‏]+/g) || [];
    let lrmStripped = 0;
    let rlmStripped = 0;
    for (const m of leadingBidiMatches) {
        for (const ch of m) {
            if (ch === "‎")
                lrmStripped++;
            else if (ch === "‏")
                rlmStripped++;
        }
    }
    if (leadingBidiMatches.length > 0) {
        text = text.replace(/(^|\n)[‎‏]+/g, "$1");
        process.stderr.write(`[conversation-archive] source=whatsapp decoded normalised lrm-stripped=${lrmStripped} rlm-stripped=${rlmStripped}\n`);
    }
    return text;
}
function makeWhatsappPrefixMatcher(ordering) {
    return (line) => {
        const m = line.match(TIMESTAMP_PREFIX);
        if (!m)
            return null;
        const a = parseInt(m[1], 10);
        const b = parseInt(m[2], 10);
        const yearRaw = m[3];
        const hour = parseInt(m[4], 10);
        const minute = parseInt(m[5], 10);
        const second = m[6] !== undefined ? parseInt(m[6], 10) : 0;
        const remainder = m[7] ?? "";
        const day = ordering === "MMDD" ? b : a;
        const month = ordering === "MMDD" ? a : b;
        if (month < 1 || month > 12 || day < 1 || day > 31)
            return null;
        if (hour > 23 || minute > 59 || second > 59)
            return null;
        const year = yearRaw.length === 2 ? 2000 + parseInt(yearRaw, 10) : parseInt(yearRaw, 10);
        return { year, month, day, hour, minute, second, remainder };
    };
}
function resolveOrdering(explicit, text) {
    if (explicit === "MM/DD/YY" || explicit === "MM/DD/YYYY")
        return "MMDD";
    if (explicit === "DD/MM/YY" || explicit === "DD/MM/YYYY")
        return "DDMM";
    // Probe the file: first valid match wins. DD/MM precedence is consistent
    // with WhatsApp's UK/EU default; explicit MM/DD is an opt-in.
    const ddmm = makeWhatsappPrefixMatcher("DDMM");
    const mmdd = makeWhatsappPrefixMatcher("MMDD");
    for (const line of text.split("\n")) {
        if (ddmm(line))
            return "DDMM";
        if (mmdd(line))
            return "MMDD";
    }
    return "DDMM";
}
function isoWithOffset(year, month, day, hour, minute, second, timezone) {
    const guessUtcMs = Date.UTC(year, month - 1, day, hour, minute, second);
    let offMin = offsetMinutesAt(new Date(guessUtcMs), timezone);
    const refinedUtcMs = guessUtcMs - offMin * 60_000;
    offMin = offsetMinutesAt(new Date(refinedUtcMs), timezone);
    const sign = offMin >= 0 ? "+" : "-";
    const absOff = Math.abs(offMin);
    const offHH = String(Math.floor(absOff / 60)).padStart(2, "0");
    const offMM = String(absOff % 60).padStart(2, "0");
    const Y = String(year).padStart(4, "0");
    const M = String(month).padStart(2, "0");
    const D = String(day).padStart(2, "0");
    const H = String(hour).padStart(2, "0");
    const Mi = String(minute).padStart(2, "0");
    const S = String(second).padStart(2, "0");
    return `${Y}-${M}-${D}T${H}:${Mi}:${S}${sign}${offHH}:${offMM}`;
}
function offsetMinutesAt(date, timezone) {
    const formatter = new Intl.DateTimeFormat("en-US", {
        timeZone: timezone,
        timeZoneName: "longOffset",
    });
    const parts = formatter.formatToParts(date);
    const tzPart = parts.find((p) => p.type === "timeZoneName");
    if (!tzPart) {
        throw new Error(`whatsapp-text: unable to read offset for timezone "${timezone}".`);
    }
    const value = tzPart.value;
    if (value === "GMT" || value === "UTC")
        return 0;
    const m = value.match(/^(?:GMT|UTC)([+-])(\d{1,2}):?(\d{2})?$/);
    if (!m) {
        throw new Error(`whatsapp-text: cannot parse timezone offset "${value}" for IANA zone "${timezone}".`);
    }
    const sign = m[1] === "+" ? 1 : -1;
    const hh = parseInt(m[2], 10);
    const mm = m[3] ? parseInt(m[3], 10) : 0;
    return sign * (hh * 60 + mm);
}
//# sourceMappingURL=whatsapp-text.js.map