/**
 * wrap-shim-generator.ts
 *
 * For each DetectedAgent from the scanner (slice 1), generates a sibling
 * `<file>.governed.<ext>` shim file that:
 *   - Imports the original module
 *   - Wraps the LLM client using the appropriate Connexum governed equivalent
 *   - Re-exports the original module's public API with the wrapped client
 *   - Includes a stable agentId header for idempotent re-runs
 *
 * Pure function: generateWrapShim() returns string content only (no disk writes).
 * writeWrapShims() is the disk-writing orchestrator called by the CLI.
 *
 * F-NEW-PRODUCT-AGENT-DIR-WRAPPER slice 2
 * @connexum/ai-governance
 */
import * as fs from 'fs';
import * as path from 'path';
// ---------------------------------------------------------------------------
// Template key derivation
// ---------------------------------------------------------------------------
/**
 * Maps (language, provider, framework) to a template key.
 * Returns null when no template exists for the combo (caller skips + logs).
 */
export function templateKey(language, provider, framework) {
    const lang = language === 'javascript' ? 'typescript' : language;
    // Framework-first routing
    if (framework === 'langchain') {
        const p = provider ?? 'openai';
        if (lang === 'python') {
            if (p === 'anthropic')
                return 'python-langchain-anthropic';
            return 'python-langchain-openai'; // default: openai backend
        }
        // TypeScript LangChain uses openai shim (GovernedOpenAI wraps the underlying client)
        if (lang === 'typescript')
            return 'typescript-openai';
    }
    if (lang === 'python') {
        switch (provider) {
            case 'openai': return 'python-openai';
            case 'anthropic': return 'python-anthropic';
            case 'google': return 'python-google';
            case 'bedrock': return 'python-bedrock';
            case 'ollama': return 'python-ollama';
            case 'huggingface': return 'python-huggingface';
            default: return null;
        }
    }
    if (lang === 'typescript') {
        switch (provider) {
            case 'openai': return 'typescript-openai';
            case 'anthropic': return 'typescript-anthropic';
            case 'google': return 'typescript-google';
            default: return null;
        }
    }
    return null;
}
// ---------------------------------------------------------------------------
// Template loading
// ---------------------------------------------------------------------------
const TEMPLATES_DIR = path.join(__dirname, 'shim-templates');
/** Cache loaded template text to avoid repeated disk reads. */
const _templateCache = new Map();
function loadTemplate(key) {
    if (_templateCache.has(key))
        return _templateCache.get(key);
    // Template files named: <key>.template.{py,ts}.txt
    const suffix = key.startsWith('python') ? 'py' : 'ts';
    const templateFile = path.join(TEMPLATES_DIR, `${key}.template.${suffix}.txt`);
    const content = fs.readFileSync(templateFile, 'utf-8');
    _templateCache.set(key, content);
    return content;
}
// ---------------------------------------------------------------------------
// Interpolation
// ---------------------------------------------------------------------------
/**
 * Replace {{PLACEHOLDER}} tokens in a template string.
 */
function interpolate(template, vars) {
    return template.replace(/\{\{([A-Z_]+)\}\}/g, (_, key) => {
        if (key in vars)
            return vars[key];
        return `{{${key}}}`; // leave unknown placeholders intact
    });
}
function packsToLiteral(packs, lang) {
    if (packs.length === 0) {
        return lang === 'python' ? '[]' : '[]';
    }
    const items = packs.map(p => `"${p}"`).join(', ');
    return `[${items}]`;
}
/**
 * Pure function. Returns the shim file content for a single DetectedAgent.
 * Does not write to disk.
 *
 * @param agent   The DetectedAgent from the scanner.
 * @param agentDir Absolute path to the scanned agent directory.
 * @returns       The generated shim file content as a string.
 * @throws        Error if the template file cannot be read.
 */
export function generateWrapShim(agent, agentDir) {
    const key = templateKey(agent.language, agent.provider, agent.framework);
    if (!key) {
        throw new Error(`No shim template for (${agent.language}, provider=${agent.provider}, framework=${agent.framework})`);
    }
    const template = loadTemplate(key);
    // Derive module name from filePath (basename without extension)
    const basename = path.basename(agent.filePath);
    const ext = path.extname(basename);
    const moduleName = basename.slice(0, -ext.length);
    const sourceFile = basename;
    const packsLiteral = packsToLiteral(agent.packs, agent.language);
    return interpolate(template, {
        AGENT_ID: agent.agentId,
        MODULE_NAME: moduleName,
        SOURCE_FILE: sourceFile,
        PACKS: packsLiteral,
    });
}
/**
 * Derive the output shim path for an agent.
 * `agents/bot.py` => `agents/bot.governed.py`
 * `agents/bot.ts`  => `agents/bot.governed.ts`
 */
export function shimPathFor(agent, agentDir) {
    const absSource = path.join(agentDir, agent.filePath);
    const ext = path.extname(absSource);
    const withoutExt = absSource.slice(0, -ext.length);
    return `${withoutExt}.governed${ext}`;
}
/** Marker string present in all auto-generated shims. Used for don't-clobber check. */
export const SHIM_MARKER = '# auto-generated by @connexum/ai-governance';
export const SHIM_MARKER_TS = '// auto-generated by @connexum/ai-governance';
function hasShimMarker(content) {
    return (content.includes(SHIM_MARKER) ||
        content.includes(SHIM_MARKER_TS));
}
/**
 * Write shim files for all detected agents into the agent directory.
 * - Skips files that already exist AND were previously auto-generated (idempotent).
 * - Skips files that already exist but were manually edited (don't-clobber).
 * - Logs a warning for providers without a template (no crash).
 *
 * @param agents    Array from scanAgentDirectory().
 * @param agentDir  Absolute path to the agent directory.
 * @param log       Optional log function (defaults to process.stdout.write).
 * @returns         Array of ShimResult, one per agent.
 */
export function writeWrapShims(agents, agentDir, log = (msg) => process.stdout.write(msg + '\n')) {
    const results = [];
    for (const agent of agents) {
        const outPath = shimPathFor(agent, agentDir);
        const key = templateKey(agent.language, agent.provider, agent.framework);
        // No template for this (provider, language) combo
        if (!key) {
            const msg = `  [SKIP] ${agent.filePath}: no shim template for ` +
                `(${agent.language}, provider=${agent.provider ?? 'null'}, ` +
                `framework=${agent.framework ?? 'null'}) — deferred to follow-up`;
            log(msg);
            results.push({
                shimPath: outPath,
                content: '',
                status: 'skipped-no-template',
                message: msg,
            });
            continue;
        }
        // File already exists — check if it's ours or customer-edited
        if (fs.existsSync(outPath)) {
            const existing = fs.readFileSync(outPath, 'utf-8');
            if (!hasShimMarker(existing)) {
                // Customer-edited: do not clobber
                const msg = `  [WARN] ${path.basename(outPath)} already exists and was manually edited — skipping. ` +
                    `Delete the file to regenerate.`;
                log(msg);
                results.push({
                    shimPath: outPath,
                    content: '',
                    status: 'skipped-exists',
                    message: msg,
                });
                continue;
            }
            // It's our own generated file — idempotent re-run: regenerate it silently
        }
        // Generate and write
        const content = generateWrapShim(agent, agentDir);
        fs.writeFileSync(outPath, content, { encoding: 'utf-8', mode: 0o644 });
        const msg = `  [OK]   ${agent.filePath} => ${path.relative(agentDir, outPath)}`;
        log(msg);
        results.push({
            shimPath: outPath,
            content,
            status: 'generated',
            message: msg,
        });
    }
    return results;
}
//# sourceMappingURL=wrap-shim-generator.js.map