"use strict";
/**
 * Agent Prompts — storage + CLAUDE.md insert/remove helpers.
 *
 * Two scopes:
 *   - Global: ~/.ccweb/agent-prompts[-<username>].json (per-user isolation,
 *     same pattern as global-shortcuts-<user>.json)
 *   - Project: {projectFolder}/.ccweb/agent-prompts.json
 *
 * CLAUDE.md operations are always targeted at a specific project folder and
 * are decoupled from prompt storage — the frontend passes the raw prompt text
 * to /toggle, so insertion/removal doesn't care whether the text originated
 * from a global or project prompt.
 *
 * Line-ending normalisation: file content is normalised to LF on read, and
 * written back with LF. This avoids \r\n-vs-\n mismatches silently breaking
 * the "exact text match" contract when a file has been edited on Windows.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.instructionsFilePath = instructionsFilePath;
exports.instructionsFilename = instructionsFilename;
exports.readGlobalPrompts = readGlobalPrompts;
exports.writeGlobalPrompts = writeGlobalPrompts;
exports.readProjectPrompts = readProjectPrompts;
exports.writeProjectPrompts = writeProjectPrompts;
exports.readClaudeMd = readClaudeMd;
exports.writeClaudeMd = writeClaudeMd;
exports.isInserted = isInserted;
exports.insertIntoClaudeMd = insertIntoClaudeMd;
exports.removeFromClaudeMd = removeFromClaudeMd;
exports.annotateInserted = annotateInserted;
exports.validatePromptInput = validatePromptInput;
exports.claudeMdPath = claudeMdPath;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const config_1 = require("./config");
const logger_1 = require("./logger");
const adapters_1 = require("./adapters");
const log = (0, logger_1.modLogger)('prompt');
// ── Project instructions file (was: CLAUDE.md only) ─────────────────────────
// Each adapter reports its own instruction filename:
//   claude  → CLAUDE.md
//   codex   → AGENTS.md
//   gemini/opencode/qwen → AGENTS.md (industry convention)
//   terminal → null (no instruction semantic)
// Callers pass project.cliTool; we derive the path via the adapter.
function instructionsFilename(cliTool) {
    const tool = cliTool ?? 'claude';
    return (0, adapters_1.getAdapter)(tool).getProjectInstructionsFilename();
}
function instructionsFilePath(folderPath, cliTool) {
    const name = instructionsFilename(cliTool);
    return name ? path.join(folderPath, name) : null;
}
const CLAUDE_MD = 'CLAUDE.md';
const PROJECT_PROMPTS_FILE = 'agent-prompts.json';
// ── Global prompts (per user) ────────────────────────────────────────────────
function globalPromptsFile(username) {
    if (!username || (0, config_1.isAdminUser)(username)) {
        return path.join(config_1.DATA_DIR, 'agent-prompts.json');
    }
    const safe = username.replace(/[^a-zA-Z0-9_-]/g, '_');
    return path.join(config_1.DATA_DIR, `agent-prompts-${safe}.json`);
}
function readGlobalPrompts(username) {
    const file = globalPromptsFile(username);
    if (!fs.existsSync(file))
        return [];
    try {
        const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
        return Array.isArray(parsed) ? parsed : [];
    }
    catch (err) {
        log.warn({ err, file }, 'agent-prompts json parse failed');
        return [];
    }
}
function writeGlobalPrompts(list, username) {
    const file = globalPromptsFile(username);
    (0, config_1.atomicWriteSync)(file, JSON.stringify(list, null, 2));
}
// ── Project prompts ──────────────────────────────────────────────────────────
function projectPromptsFile(folderPath) {
    return path.join((0, config_1.ccwebDir)(folderPath), PROJECT_PROMPTS_FILE);
}
function readProjectPrompts(folderPath) {
    const file = projectPromptsFile(folderPath);
    if (!fs.existsSync(file))
        return [];
    try {
        const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
        return Array.isArray(parsed) ? parsed : [];
    }
    catch (err) {
        log.warn({ err, file }, 'agent-prompts json parse failed');
        return [];
    }
}
function writeProjectPrompts(folderPath, list) {
    const dir = (0, config_1.ccwebDir)(folderPath);
    fs.mkdirSync(dir, { recursive: true });
    (0, config_1.atomicWriteSync)(path.join(dir, PROJECT_PROMPTS_FILE), JSON.stringify(list, null, 2));
}
// ── Project instructions file operations ────────────────────────────────────
// Historical function names are kept ('readClaudeMd' etc) for backwards compat
// across the codebase. The underlying file is now adapter-determined: CLAUDE.md
// for Claude, AGENTS.md for Codex and the other Agent-SDK-convention tools.
// Callers pass project.cliTool; omitting it defaults to 'claude' (preserves
// pre-refactor behavior for any call site that hasn't been updated yet).
function claudeMdPath(folderPath, cliTool) {
    // Null cliTool (terminal) → fall back to CLAUDE.md so existing tests and
    // call sites with no cliTool plumbing don't change behavior. Real-world
    // callers for terminal projects are already guarded by adapter semantics.
    const name = instructionsFilename(cliTool) ?? CLAUDE_MD;
    return path.join(folderPath, name);
}
/** Read the project instructions file, normalising CRLF → LF. Returns empty
 *  string if the file doesn't exist (treated as "no content yet"). */
function readClaudeMd(folderPath, cliTool) {
    const file = claudeMdPath(folderPath, cliTool);
    if (!fs.existsSync(file))
        return '';
    const raw = fs.readFileSync(file, 'utf-8');
    return raw.replace(/\r\n/g, '\n');
}
function writeClaudeMd(folderPath, content, cliTool) {
    const file = claudeMdPath(folderPath, cliTool);
    (0, config_1.atomicWriteSync)(file, content);
}
/** Exact substring check (after LF normalisation). */
function isInserted(claudeMd, command) {
    return claudeMd.includes(command);
}
/**
 * Append the command to the instructions file separated by a blank line.
 * Idempotent: if the exact text already appears anywhere in the file, no
 * changes are made.
 *
 * Returns { changed: true } if the file was written, { changed: false } if
 * the text was already present.
 */
function insertIntoClaudeMd(folderPath, command, cliTool) {
    const current = readClaudeMd(folderPath, cliTool);
    if (current.includes(command))
        return { changed: false };
    // Ensure a blank-line separator between prior content and the new block.
    let next;
    if (current.length === 0) {
        next = command + '\n';
    }
    else {
        const needsTrailingNewline = !current.endsWith('\n');
        const leader = needsTrailingNewline ? '\n\n' : '\n';
        next = current + leader + command + '\n';
    }
    writeClaudeMd(folderPath, next, cliTool);
    return { changed: true };
}
/**
 * Remove the command from CLAUDE.md via exact text match. Attempts two
 * patterns in order:
 *   1. '\n\n' + command + '\n'   — the exact form we insert
 *   2. command                    — plain match anywhere (covers manual-insert
 *                                    variants)
 * If neither matches, returns { changed: false, reason: 'not-found' } so the
 * caller can surface a "please delete manually" prompt to the user.
 *
 * Returns { changed: false, reason: 'not-present' } if the text simply isn't
 * in the file (e.g. already removed / never inserted) — distinguishable from
 * 'not-found' by callers that want to stay silent in that case.
 */
function removeFromClaudeMd(folderPath, command, cliTool) {
    const current = readClaudeMd(folderPath, cliTool);
    if (current.length === 0 || !current.includes(command)) {
        return { changed: false, reason: 'not-present' };
    }
    // Try the inserted pattern first.
    const anchored = '\n\n' + command + '\n';
    if (current.includes(anchored)) {
        const next = current.replace(anchored, '');
        writeClaudeMd(folderPath, next, cliTool);
        return { changed: true, removed: 1 };
    }
    // Also try the single-newline form, in case the user edited surrounding
    // whitespace or the block was at the start of the file.
    const half = '\n\n' + command;
    if (current.endsWith(half)) {
        const next = current.slice(0, -half.length) + '\n';
        writeClaudeMd(folderPath, next, cliTool);
        return { changed: true, removed: 1 };
    }
    // Exact substring fallback — may leave orphaned blank lines, which the
    // user accepted as the cost of "exact text match" semantics.
    if (current.includes(command)) {
        const next = current.replace(command, '');
        writeClaudeMd(folderPath, next, cliTool);
        return { changed: true, removed: 1 };
    }
    return { changed: false, reason: 'not-found' };
}
// ── Shape helpers ────────────────────────────────────────────────────────────
/** Attach `inserted: boolean` to each prompt based on a claudeMd snapshot. */
function annotateInserted(prompts, claudeMd) {
    return prompts.map((p) => ({ ...p, inserted: claudeMd.includes(p.command) }));
}
/** Validate label + command payload; returns null on success or an error message. */
function validatePromptInput(body) {
    const label = typeof body.label === 'string' ? body.label.trim() : '';
    const command = typeof body.command === 'string' ? body.command : '';
    if (!label)
        return 'label is required';
    if (label.length > 100)
        return 'label must be ≤100 characters';
    if (!command.trim())
        return 'command is required';
    if (command.length > 8000)
        return 'command must be ≤8000 characters';
    return { label, command };
}
// Suppress unused-import warning when `os` is only conditionally referenced in
// future extensions (kept imported for parity with other helper modules).
void os;
//# sourceMappingURL=agent-prompts.js.map