import { computeFileHash, formatHashlineHeader, formatNumberedLines, InMemorySnapshotStore } from "./hashline-engine/index.js";
import { isAbsolute, normalize as normalizePath, relative, sep } from "node:path";
function toPosixPath(filePath) {
    return filePath.split(sep).join("/");
}
export function hashlineDisplayPath(absolutePath, cwd) {
    const relativePath = relative(cwd, absolutePath);
    if (relativePath && !relativePath.startsWith("..") && !isAbsolute(relativePath))
        return toPosixPath(relativePath);
    return toPosixPath(absolutePath);
}
export function normalizeHashlineContent(content) {
    return content.replace(/^\uFEFF/, "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
}
export function computeHashlineTag(content) {
    return computeFileHash(normalizeHashlineContent(content));
}
export function createHashlineSnapshotStore() {
    const snapshots = new InMemorySnapshotStore();
    const headers = new Map();
    return {
        snapshots,
        record(absolutePath, cwd, content) {
            const normalizedPath = normalizePath(absolutePath);
            const normalized = normalizeHashlineContent(content);
            const displayPath = hashlineDisplayPath(normalizedPath, cwd);
            const tag = snapshots.record(normalizedPath, normalized);
            const snapshot = { absolutePath: normalizedPath, displayPath, tag, content: normalized };
            headers.set(`${displayPath}\0${tag}`, snapshot);
            return snapshot;
        },
        findByHeader(displayPath, tag) {
            return headers.get(`${displayPath}\0${tag.toUpperCase()}`);
        },
    };
}
export function recordHashlineSnapshot(absolutePath, cwd, content, store) {
    return store.record(absolutePath, cwd, content);
}
export function formatHashlineContent(snapshot, content = snapshot.content, startLine = 1) {
    return [formatHashlineHeader(snapshot.displayPath, snapshot.tag), formatNumberedLines(normalizeHashlineContent(content), startLine)].join("\n");
}
export function stripKnownHashlineCopiedContentWithMeta(content, _absolutePath, _cwd, store) {
    const normalized = normalizeHashlineContent(content);
    const lines = normalized.split("\n");
    const headerIndex = lines.findIndex((line, index) => /^\[[^\]\n]+#[0-9A-Fa-f]{4}\]$/.test(line) && lines.slice(0, index).every((prefix) => prefix.trim() === "" || /^#\s+.+\/?$/.test(prefix)));
    if (headerIndex < 0)
        return { content, stripped: false };
    const header = (lines[headerIndex] ?? "").match(/^\[([^\]\n]+)#([0-9A-Fa-f]{4})\]$/);
    if (!header)
        return { content, stripped: false };
    const snapshot = store.findByHeader(header[1] ?? "", header[2] ?? "");
    if (!snapshot)
        return { content, stripped: false };
    const body = lines.slice(headerIndex + 1);
    if (body.length === 0)
        return { content: snapshot.content, stripped: true };
    const stripped = [];
    const snapshotLines = snapshot.content.split("\n");
    let sawRow = false;
    // Trailing tool chrome a model is likely to copy along with the hashline
    // body: the read/search continuation footers, the write tool's own
    // `Successfully wrote N bytes to <path>` confirmation (and its stripped-
    // note), and `Resolved …` conflict footers. These never carry a line
    // number, so they mark the end of the numbered body — they must not abort
    // stripping the way an arbitrary non-row line would.
    const isToolFooter = (line) => line.trim() === ""
        || /^\[\d+ more lines in file\./.test(line)
        || /^\[Showing lines /.test(line)
        || /^Successfully wrote \d+ bytes to /.test(line)
        || /^Resolved \d+ conflicts?/.test(line)
        || /^Resolved conflict \d+/.test(line)
        || /^Note: stripped copied hashline/.test(line)
        || /^\[[^\]\n]+#[0-9A-Fa-f]{4}\]$/.test(line);
    let onlyFooter = true;
    for (const line of body) {
        if (isToolFooter(line)) {
            // A footer after the numbered body ends the body; a footer before any
            // row (leading blanks / a copied snapshot header) is just skipped.
            if (sawRow)
                break;
            continue;
        }
        onlyFooter = false;
        const match = line.match(/^[* ]?(\d+):(.*)$/s);
        if (!match)
            return { content, stripped: false };
        sawRow = true;
        const lineNumber = Number.parseInt(match[1] ?? "0", 10);
        const strippedLine = match[2] ?? "";
        if (snapshotLines[lineNumber - 1] !== strippedLine)
            return { content, stripped: false };
        stripped.push(strippedLine);
    }
    if (sawRow)
        return { content: stripped.join("\n"), stripped: true };
    // Header + only tool chrome (e.g. a copied write confirmation
    // `Successfully wrote N bytes to <path>`) names a known snapshot with no
    // numbered body to recover — resolve to the snapshot's stored content.
    if (onlyFooter)
        return { content: snapshot.content, stripped: true };
    return { content, stripped: false };
}
export function stripKnownHashlineCopiedContent(content, absolutePath, cwd, store) {
    return stripKnownHashlineCopiedContentWithMeta(content, absolutePath, cwd, store).content;
}
export function formatCompactHashlineEditResult(snapshot, diff, messages = []) {
    return [formatHashlineHeader(snapshot.displayPath, snapshot.tag), ...messages, diff.diff?.trim() || `First changed line: ${diff.firstChangedLine ?? 1}`].join("\n");
}
//# sourceMappingURL=hashline.js.map