/**
 * Splices the schema-driven kitchen-sink block (returned by the compiler
 * in `CompilerOutput.syncedConfig`) into the user's quickback.config.ts.
 *
 * The block is owned by Quickback and replaced wholesale on every compile,
 * so users *discover* options here but *enable* them by copying lines
 * above the start sentinel — Quickback then drops them from the next
 * regenerated block (the server-side renderer omits keys the user has
 * already set).
 *
 * Sentinels — must appear on lines of their own:
 *   // [quickback:options-start]
 *   // [quickback:options-end]
 *
 * If the sentinels are absent, the block is inserted just before the
 * final `})` closing `defineConfig(...)`. If `defineConfig(...)` can't
 * be located, the file is left untouched and a warning is returned.
 */
import fs from "fs/promises";
const START_SENTINEL = "// [quickback:options-start]";
const END_SENTINEL = "// [quickback:options-end]";
export async function syncConfigFile(filePath, synced) {
    let original;
    try {
        original = await fs.readFile(filePath, "utf-8");
    }
    catch {
        return {
            changed: false,
            inserted: false,
            skippedReason: `Config file not found: ${filePath}`,
            omittedKeys: synced.omittedKeys,
        };
    }
    const next = applySync(original, synced.block);
    if (next.skippedReason) {
        return {
            changed: false,
            inserted: false,
            skippedReason: next.skippedReason,
            omittedKeys: synced.omittedKeys,
        };
    }
    if (next.text === original) {
        return {
            changed: false,
            inserted: next.inserted,
            omittedKeys: synced.omittedKeys,
        };
    }
    await fs.writeFile(filePath, next.text, "utf-8");
    return {
        changed: true,
        inserted: next.inserted,
        omittedKeys: synced.omittedKeys,
    };
}
/**
 * Pure string transform — exported for unit tests.
 */
export function applySync(original, blockText) {
    const startIdx = findSentinelLine(original, START_SENTINEL);
    const endIdx = findSentinelLine(original, END_SENTINEL);
    if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
        return {
            text: replaceRange(original, startIdx, endIdx, blockText),
            inserted: false,
        };
    }
    if (startIdx !== -1 || endIdx !== -1) {
        return {
            text: original,
            inserted: false,
            skippedReason: "Found one sentinel comment but not the other; refusing to overwrite. Restore both " +
                "[quickback:options-start] and [quickback:options-end] markers, or delete both to let " +
                "Quickback re-insert them.",
        };
    }
    // No sentinels: insert before the closing `})` of defineConfig(...).
    const insertion = findDefineConfigInsertionPoint(original);
    if (insertion === null) {
        return {
            text: original,
            inserted: false,
            skippedReason: "Could not locate `defineConfig(` closing brace; skipping kitchen-sink sync. " +
                "Add `// [quickback:options-start]` and `// [quickback:options-end]` markers " +
                "manually if you want auto-discovery of new options.",
        };
    }
    const before = original.slice(0, insertion.index);
    const after = original.slice(insertion.index);
    // Ensure exactly one blank line between user content and the block.
    const sep = before.endsWith("\n\n") ? "" : before.endsWith("\n") ? "\n" : "\n\n";
    return {
        text: `${before}${sep}${blockText}\n${after}`,
        inserted: true,
    };
}
/**
 * Returns the line index of the first line whose trimmed content starts
 * with `sentinel`, or -1 if not found.
 *
 * Trimmed start match (not strict equality) tolerates trailing comments
 * / whitespace the user may have added — e.g.
 * `// [quickback:options-start]   <- regenerated each compile`.
 */
function findSentinelLine(text, sentinel) {
    const lines = text.split("\n");
    for (let i = 0; i < lines.length; i++) {
        if (lines[i].trim().startsWith(sentinel))
            return i;
    }
    return -1;
}
function replaceRange(text, startLine, endLine, blockText) {
    const lines = text.split("\n");
    const before = lines.slice(0, startLine);
    const after = lines.slice(endLine + 1);
    return [...before, ...blockText.split("\n"), ...after].join("\n");
}
/**
 * Find the position to insert the block when no sentinels are present:
 * the line containing `})` (or `});`) that closes `defineConfig(`.
 *
 * Heuristic: locate the first `defineConfig(` and walk forward, tracking
 * paren depth. When depth returns to zero we've found the matching `)`,
 * and the block goes on the line of that `)`.
 *
 * Skips parens inside string literals and line/block comments.
 */
function findDefineConfigInsertionPoint(text) {
    const startMatch = text.match(/defineConfig\s*\(/);
    if (!startMatch || startMatch.index === undefined)
        return null;
    let i = startMatch.index + startMatch[0].length;
    let depth = 1;
    let inString = false;
    let inLineComment = false;
    let inBlockComment = false;
    while (i < text.length && depth > 0) {
        const ch = text[i];
        const next = text[i + 1];
        if (inLineComment) {
            if (ch === "\n")
                inLineComment = false;
            i++;
            continue;
        }
        if (inBlockComment) {
            if (ch === "*" && next === "/") {
                inBlockComment = false;
                i += 2;
                continue;
            }
            i++;
            continue;
        }
        if (inString) {
            if (ch === "\\") {
                i += 2;
                continue;
            }
            if (ch === inString)
                inString = false;
            i++;
            continue;
        }
        if (ch === "/" && next === "/") {
            inLineComment = true;
            i += 2;
            continue;
        }
        if (ch === "/" && next === "*") {
            inBlockComment = true;
            i += 2;
            continue;
        }
        if (ch === "'" || ch === '"' || ch === "`") {
            inString = ch;
            i++;
            continue;
        }
        if (ch === "(")
            depth++;
        else if (ch === ")") {
            depth--;
            if (depth === 0) {
                // We're sitting on the closing `)` of defineConfig(...).
                // Walk back to the start of this line so the block is
                // inserted on its own lines just above the `)`.
                let lineStart = i;
                while (lineStart > 0 && text[lineStart - 1] !== "\n")
                    lineStart--;
                return { index: lineStart };
            }
        }
        i++;
    }
    return null;
}
export const _internal = {
    START_SENTINEL,
    END_SENTINEL,
    findSentinelLine,
    findDefineConfigInsertionPoint,
};
//# sourceMappingURL=config-sync.js.map