import { LONG_STRING_THRESHOLD } from "./types.js";
// Color palette per the design.
const KEY_FG = "bright-white";
const STRING_FG = "bright-green";
const NUMBER_FG = "bright-cyan";
const BOOLEAN_FG = "bright-magenta";
const NULL_FG = "gray";
const GLYPH_FG = "gray";
const PREVIEW_FG = "gray";
const INDENT = "  ";
export function renderJson(node, opts) {
    return renderNode(node, 0, opts, undefined, true);
}
// Render a JsonNode at the given depth. `keyLabel` is the JSON
// object-key (already JSON-quoted with the colon, e.g. `"usage": `)
// when this node is being rendered as an object value. `isLast`
// controls trailing-comma suppression.
function renderNode(node, depth, opts, keyLabel, isLast) {
    const indent = INDENT.repeat(depth);
    const trailing = isLast ? "" : ",";
    switch (node.kind) {
        case "primitive":
            return [
                primitiveLine(node, indent, keyLabel, trailing, opts),
            ];
        case "longString":
            return renderLongString(node, indent, depth, keyLabel, trailing, opts);
        case "object":
            return renderObject(node, indent, depth, keyLabel, trailing, opts);
        case "array":
            return renderArray(node, indent, depth, keyLabel, trailing, opts);
    }
}
function primitiveLine(node, indent, keyLabel, trailing, opts) {
    const segments = [
        cursor(node.path, opts.cursorPath),
        { text: indent },
    ];
    if (keyLabel !== undefined) {
        segments.push({ text: keyLabel, fg: KEY_FG });
    }
    segments.push({ text: node.raw, fg: primitiveColor(node.valueType) });
    if (trailing)
        segments.push({ text: trailing });
    return { ownerPath: node.path, segments };
}
function primitiveColor(vt) {
    switch (vt) {
        case "string":
            return STRING_FG;
        case "number":
            return NUMBER_FG;
        case "boolean":
            return BOOLEAN_FG;
        case "null":
            return NULL_FG;
    }
}
function renderLongString(node, indent, depth, keyLabel, trailing, opts) {
    const isOpen = opts.open.has(node.path);
    const glyph = isOpen ? "▼" : "▶";
    if (!isOpen) {
        const preview = node.raw.split("\n")[0].slice(0, LONG_STRING_THRESHOLD - 20);
        const segments = [
            cursor(node.path, opts.cursorPath),
            { text: indent },
            { text: `${glyph} `, fg: GLYPH_FG },
        ];
        if (keyLabel !== undefined) {
            segments.push({ text: keyLabel, fg: KEY_FG });
        }
        segments.push({ text: `"${preview}…"`, fg: STRING_FG }, { text: ` (${node.raw.length} chars)`, fg: PREVIEW_FG });
        if (trailing)
            segments.push({ text: trailing });
        return [{ ownerPath: node.path, segments }];
    }
    // Open: the first line shows the glyph and key (if any) plus an
    // opening quote; subsequent lines indent the body; the final line
    // closes the quote.
    const childIndent = INDENT.repeat(depth + 1);
    const lines = [];
    const headerSegs = [
        cursor(node.path, opts.cursorPath),
        { text: indent },
        { text: `${glyph} `, fg: GLYPH_FG },
    ];
    if (keyLabel !== undefined)
        headerSegs.push({ text: keyLabel, fg: KEY_FG });
    headerSegs.push({ text: '"', fg: STRING_FG });
    lines.push({ ownerPath: node.path, segments: headerSegs });
    for (const ln of node.raw.split("\n")) {
        lines.push({
            ownerPath: node.path,
            segments: [
                { text: "  " },
                { text: childIndent },
                { text: ln, fg: STRING_FG },
            ],
        });
    }
    const closeSegs = [
        { text: "  " },
        { text: indent },
        { text: '"', fg: STRING_FG },
    ];
    if (trailing)
        closeSegs.push({ text: trailing });
    lines.push({ ownerPath: node.path, segments: closeSegs });
    return lines;
}
function renderObject(node, indent, depth, keyLabel, trailing, opts) {
    const isOpen = opts.open.has(node.path);
    if (!isOpen) {
        return [
            collapsedContainer(node.path, indent, keyLabel, "{", `${node.entries.length} keys`, "}", trailing, opts),
        ];
    }
    const lines = [];
    lines.push(openLine(node.path, indent, keyLabel, "{", opts));
    node.entries.forEach((entry, i) => {
        const childKey = `${JSON.stringify(entry.key)}: `;
        const childLines = renderNode(entry.child, depth + 1, opts, childKey, i === node.entries.length - 1);
        lines.push(...childLines);
    });
    lines.push(closeLine(node.path, indent, "}", trailing));
    return lines;
}
function renderArray(node, indent, depth, keyLabel, trailing, opts) {
    const isOpen = opts.open.has(node.path);
    if (!isOpen) {
        return [
            collapsedContainer(node.path, indent, keyLabel, "[", `${node.items.length} items`, "]", trailing, opts),
        ];
    }
    const lines = [];
    lines.push(openLine(node.path, indent, keyLabel, "[", opts));
    node.items.forEach((item, i) => {
        const childLines = renderNode(item, depth + 1, opts, undefined, i === node.items.length - 1);
        lines.push(...childLines);
    });
    lines.push(closeLine(node.path, indent, "]", trailing));
    return lines;
}
function collapsedContainer(path, indent, keyLabel, openChar, countLabel, closeChar, trailing, opts) {
    const segments = [
        cursor(path, opts.cursorPath),
        { text: indent },
        { text: "▶ ", fg: GLYPH_FG },
    ];
    if (keyLabel !== undefined)
        segments.push({ text: keyLabel, fg: KEY_FG });
    segments.push({ text: `${openChar} ${countLabel} ${closeChar}` });
    if (trailing)
        segments.push({ text: trailing });
    return { ownerPath: path, segments };
}
function openLine(path, indent, keyLabel, openChar, opts) {
    const segments = [
        cursor(path, opts.cursorPath),
        { text: indent },
        { text: "▼ ", fg: GLYPH_FG },
    ];
    if (keyLabel !== undefined)
        segments.push({ text: keyLabel, fg: KEY_FG });
    segments.push({ text: openChar });
    return { ownerPath: path, segments };
}
function closeLine(path, indent, closeChar, trailing) {
    const segments = [
        { text: "  " },
        { text: indent },
        { text: closeChar },
    ];
    if (trailing)
        segments.push({ text: trailing });
    return { ownerPath: path, segments };
}
function cursor(path, cursorPath) {
    return { text: cursorPath === path ? "> " : "  " };
}
// Convert a line's segments to plain text — useful for tests and the
// outer composition layer when it just needs the text content.
export function lineToText(line) {
    return line.segments.map((s) => s.text).join("");
}
// Compute the default-open set per the design: top-level container
// open; one level of nested containers open; small containers (≤ 3
// items) open. Returns paths only.
export function defaultOpenSet(root) {
    const out = new Set();
    if (isContainer(root))
        out.add(root.path);
    // One level deep.
    for (const child of containerChildren(root)) {
        if (isContainer(child))
            out.add(child.path);
        // Plus small grandchildren.
        for (const grand of containerChildren(child)) {
            if (isContainer(grand) && isSmall(grand))
                out.add(grand.path);
        }
    }
    return out;
}
function isContainer(node) {
    return node.kind === "object" || node.kind === "array";
}
function isSmall(node) {
    if (node.kind === "object")
        return node.entries.length <= 3;
    if (node.kind === "array")
        return node.items.length <= 3;
    return false;
}
function containerChildren(node) {
    if (node.kind === "object")
        return node.entries.map((e) => e.child);
    if (node.kind === "array")
        return node.items;
    return [];
}
