import { existsSync, readFileSync } from "node:fs";
const CELL_MARKER_RE = /^# %% \[(code|markdown|raw)\](?: cell:(\d+))?$/;
const ESCAPABLE_MARKER_RE = /^# %%+ \[(?:code|markdown|raw)\](?: cell:\d+)?$/;
const ESCAPED_MARKER_RE = /^# %%%+ \[(?:code|markdown|raw)\](?: cell:\d+)?$/;
export function isNotebookPath(absolutePath) {
    return /\.ipynb$/i.test(absolutePath);
}
function escapeMarkerLikeSourceLine(line) {
    if (ESCAPABLE_MARKER_RE.test(line))
        return `${line.slice(0, 2)}%${line.slice(2)}`;
    return line;
}
function unescapeMarkerLikeSourceLine(line) {
    if (ESCAPED_MARKER_RE.test(line))
        return `${line.slice(0, 2)}${line.slice(3)}`;
    return line;
}
export function cellSourceToString(source) {
    if (source === undefined)
        return "";
    if (typeof source === "string")
        return source;
    return source.join("");
}
export function splitNotebookSource(content) {
    return content.match(/[^\n]*\n|[^\n]+$/g) ?? [];
}
export function emptyNotebook() {
    return { cells: [], metadata: {}, nbformat: 4, nbformat_minor: 5 };
}
export function newNotebookCell(cellType, source) {
    const cell = { cell_type: cellType, metadata: {}, source: splitNotebookSource(source) };
    if (cellType === "code") {
        cell.execution_count = null;
        cell.outputs = [];
    }
    return cell;
}
export function notebookToEditableText(notebook) {
    const lines = [];
    for (const [index, cell] of notebook.cells.entries()) {
        const source = cellSourceToString(cell.source);
        lines.push(`# %% [${cell.cell_type}] cell:${index}`);
        if (source !== "")
            for (const line of source.split("\n"))
                lines.push(escapeMarkerLikeSourceLine(line));
    }
    return lines.join("\n");
}
function parseNotebookEditableText(text, displayPath) {
    const lines = text.split("\n");
    const cells = [];
    let current = null;
    let sawMarker = false;
    for (const line of lines) {
        const match = line.match(CELL_MARKER_RE);
        if (match) {
            sawMarker = true;
            const cellType = (match[1] ?? "raw");
            const cellIndex = match[2] !== undefined ? Number.parseInt(match[2], 10) : undefined;
            current = { cellType, cellIndex, source: "" };
            cells.push(current);
            continue;
        }
        if (!sawMarker) {
            throw new Error(`Invalid notebook editable representation for ${displayPath}: expected first line to be "# %% [code] cell:0", "# %% [markdown] cell:0", or "# %% [raw] cell:0".`);
        }
        const restored = unescapeMarkerLikeSourceLine(line);
        current.source = current.source === "" ? restored : `${current.source}\n${restored}`;
    }
    return cells;
}
export function applyNotebookEditableText(notebook, text, displayPath) {
    const parsed = parseNotebookEditableText(text, displayPath);
    const next = structuredClone(notebook);
    const used = new Set();
    next.cells = parsed.map((parsedCell) => {
        let cell;
        const cellIndex = parsedCell.cellIndex;
        if (cellIndex !== undefined && cellIndex >= 0 && cellIndex < notebook.cells.length && !used.has(cellIndex)) {
            used.add(cellIndex);
            cell = structuredClone(notebook.cells[cellIndex]);
        }
        else {
            cell = newNotebookCell(parsedCell.cellType, "");
        }
        cell.cell_type = parsedCell.cellType;
        cell.source = splitNotebookSource(parsedCell.source);
        if (parsedCell.cellType === "code") {
            if (cell.execution_count === undefined)
                cell.execution_count = null;
            if (cell.outputs === undefined)
                cell.outputs = [];
        }
        else {
            delete cell.execution_count;
            delete cell.outputs;
        }
        return cell;
    });
    return next;
}
export function readEditableNotebookText(absolutePath, displayPath) {
    const notebook = existsSync(absolutePath) ? parseNotebookSafe(readFileSync(absolutePath, "utf8"), displayPath) : emptyNotebook();
    return notebookToEditableText(notebook);
}
export function serializeEditedNotebookText(absolutePath, displayPath, text) {
    const notebook = existsSync(absolutePath) ? parseNotebookSafe(readFileSync(absolutePath, "utf8"), displayPath) : emptyNotebook();
    const next = applyNotebookEditableText(notebook, text, displayPath);
    return JSON.stringify(next, null, 1);
}
function parseNotebookSafe(raw, displayPath) {
    let parsed;
    try {
        parsed = JSON.parse(raw);
    }
    catch {
        throw new Error(`Invalid notebook JSON for ${displayPath}`);
    }
    if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
        throw new Error(`Invalid notebook JSON for ${displayPath}`);
    const doc = parsed;
    return { ...doc, cells: Array.isArray(doc.cells) ? doc.cells : [], metadata: (doc.metadata ?? {}), nbformat: doc.nbformat ?? 4, nbformat_minor: doc.nbformat_minor ?? 5 };
}
//# sourceMappingURL=notebook.js.map