/**
 * NERF Prompt Loader
 *
 * Reads .md prompt templates from the prompts/ directory and substitutes
 * {{variable}} placeholders with provided values.
 *
 * Templates live at prompts/ relative to this module's directory.
 * They use {{variableName}} syntax for substitution.
 *
 * All templates are eagerly loaded into cache at module init via warmCache().
 * This prevents a running session from being invalidated when another `nerf`
 * launch overwrites ~/.nerf/agent/ with newer templates via initResources().
 * Without eager caching, the in-memory extension code (which knows variable
 * set A) can read a newer template from disk (which expects variable set B),
 * causing a "template declares {{X}} but no value was provided" crash
 * mid-session — especially for late-loading templates like complete-milestone
 * that aren't read until the end of a long auto-mode run.
 */
import { readFileSync, readdirSync, existsSync } from "node:fs";
import { NerfError, NERF_PARSE_ERROR } from "./errors.js";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { homedir } from "node:os";
/**
 * Resolve the NERF extension directory.
 *
 * `import.meta.url` resolves to whichever copy of this module is executing.
 * On Windows (npm global install via MSYS2 / Git Bash) this can resolve to
 * the npm-global `AppData/Roaming/npm/…` path, which does NOT contain the
 * prompts/ and templates/ subtrees that initResources() copies to
 * `~/.nerf/agent/extensions/nerf-engine/`. Detect the mismatch and fall back to
 * the user-local agent directory.
 */
function resolveExtensionDir() {
    const moduleDir = dirname(fileURLToPath(import.meta.url));
    if (existsSync(join(moduleDir, "prompts")))
        return moduleDir;
    // Fallback: user-local agent directory
    const nerfHome = process.env.NERF_HOME || join(homedir(), ".nerf");
    const agentNerfDir = join(nerfHome, "agent", "extensions", "nerf");
    if (existsSync(join(agentNerfDir, "prompts")))
        return agentNerfDir;
    // Last resort: return the module dir (warmCache will silently handle the miss)
    return moduleDir;
}
const __extensionDir = resolveExtensionDir();
const promptsDir = join(__extensionDir, "prompts");
const templatesDir = join(__extensionDir, "templates");
// Cache all templates eagerly at module load — a running session uses the
// template versions that were on disk at startup, immune to later overwrites.
const templateCache = new Map();
/**
 * Eagerly read all .md files from prompts/ and templates/ into cache.
 * Called once at module init so that every template is snapshot before
 * a concurrent initResources() can overwrite files on disk.
 */
function warmCache() {
    try {
        for (const file of readdirSync(promptsDir)) {
            if (!file.endsWith(".md"))
                continue;
            const name = file.slice(0, -3);
            if (!templateCache.has(name)) {
                templateCache.set(name, readFileSync(join(promptsDir, file), "utf-8"));
            }
        }
    }
    catch {
        // prompts/ may not exist in test environments — lazy loading still works.
        // Emit a diagnostic when running outside tests so wrong-path bugs are visible.
        if (!process.env.VITEST && !process.env.NODE_TEST) {
            process.stderr.write(`[nerf:prompt-loader] warmCache: prompts dir not found: ${promptsDir}\n`);
        }
    }
    try {
        for (const file of readdirSync(templatesDir)) {
            if (!file.endsWith(".md"))
                continue;
            const cacheKey = `tpl:${file.slice(0, -3)}`;
            if (!templateCache.has(cacheKey)) {
                templateCache.set(cacheKey, readFileSync(join(templatesDir, file), "utf-8"));
            }
        }
    }
    catch {
        // templates/ may not exist in test environments — lazy loading still works.
        if (!process.env.VITEST && !process.env.NODE_TEST) {
            process.stderr.write(`[nerf:prompt-loader] warmCache: templates dir not found: ${templatesDir}\n`);
        }
    }
}
// Snapshot all templates at module load time
warmCache();
/**
 * Load a prompt template and substitute variables.
 *
 * @param name - Template filename without .md extension (e.g. "execute-task")
 * @param vars - Key-value pairs to substitute for {{key}} placeholders
 */
export function loadPrompt(name, vars = {}) {
    let content = templateCache.get(name);
    if (content === undefined) {
        const path = join(promptsDir, `${name}.md`);
        content = readFileSync(path, "utf-8");
        templateCache.set(name, content);
    }
    const effectiveVars = {
        skillActivation: "If a `NERF Skill Preferences` block is present in system context, use it and the `<available_skills>` catalog in your system prompt to decide which skills to load and follow for this unit, without relaxing required verification or artifact rules.",
        ...vars,
    };
    // Check BEFORE substitution: find all {{varName}} placeholders the template
    // declares and verify every one has a value in vars. Checking after substitution
    // would also flag {{...}} patterns injected by inlined content (e.g. template
    // files embedded in {{inlinedContext}}), producing false positives.
    const declared = content.match(/\{\{[a-zA-Z][a-zA-Z0-9_]*\}\}/g);
    if (declared) {
        const missing = [...new Set(declared)]
            .map(m => m.slice(2, -2))
            .filter(key => !(key in effectiveVars));
        if (missing.length > 0) {
            throw new NerfError(NERF_PARSE_ERROR, `loadPrompt("${name}"): template declares {{${missing.join("}}, {{")}}}} but no value was provided. ` +
                `This usually means the extension code in memory is older than the template on disk. ` +
                `Restart pi to reload the extension.`);
        }
    }
    for (const [key, value] of Object.entries(effectiveVars)) {
        content = content.replaceAll(`{{${key}}}`, value);
    }
    return content.trim();
}
/**
 * Load a raw template file from the templates/ directory.
 * Cached with a `tpl:` prefix to avoid collisions with prompt cache keys.
 */
export function loadTemplate(name) {
    const cacheKey = `tpl:${name}`;
    let content = templateCache.get(cacheKey);
    if (content === undefined) {
        const path = join(templatesDir, `${name}.md`);
        content = readFileSync(path, "utf-8");
        templateCache.set(cacheKey, content);
    }
    return content.trim();
}
/**
 * Load a template and wrap it with a labeled footer for inlining into prompts.
 * The template body is emitted first so that any YAML frontmatter (---) remains
 * at the first non-whitespace line of the template content.
 */
export function inlineTemplate(name, label) {
    const content = loadTemplate(name);
    return `${content}\n\n### Output Template: ${label}\nSource: \`templates/${name}.md\``;
}
