// src/engine/skill-adapter/index.ts — SPEC-682: Adapt raw skills with project context
// Replaces placeholders and rewrites commands based on ProjectKnowledge.
import { mkdir, readFile, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { readSkillRegistry, writeSkillRegistryEntry, } from '../../storage/skill-registry-storage.js';
import { PLACEHOLDER_MAP } from './placeholder-map.js';
import { rewritePackageManagerCommands, rewriteTestRunnerCommands } from './command-rewriter.js';
export { PLACEHOLDER_MAP } from './placeholder-map.js';
export { rewritePackageManagerCommands, rewriteTestRunnerCommands } from './command-rewriter.js';
// ---------------------------------------------------------------------------
// Pure adaptation logic
// ---------------------------------------------------------------------------
/**
 * Apply project-specific substitutions to raw skill content.
 * Pure function — no file I/O.
 *
 * Steps:
 * 1. Replace known placeholders (PLACEHOLDER_MAP)
 * 2. Track unknown placeholders (warn, leave as-is)
 * 3. Rewrite package manager commands (npm → pnpm, etc.)
 * 4. Rewrite test runner commands (jest → vitest, etc.)
 */
export function adaptSkillContent(rawContent, knowledge) {
    if (!rawContent) {
        return { adapted: '', replaced: [], unknown: [] };
    }
    const replaced = [];
    const unknown = [];
    // Step 1 & 2: Replace known placeholders, collect unknown ones
    let adapted = rawContent;
    // First pass: replace all known placeholders
    for (const def of PLACEHOLDER_MAP) {
        const occurrences = adapted.includes(def.placeholder);
        if (!occurrences) {
            continue;
        }
        let value = null;
        // derive function takes priority over knowledgePath
        if (def.derive) {
            value = def.derive(knowledge);
        }
        else if (def.knowledgePath !== null) {
            const rawVal = knowledge[def.knowledgePath];
            // Only use scalar string values; arrays/objects are not meaningful for substitution
            value =
                typeof rawVal === 'string' && rawVal !== ''
                    ? rawVal
                    : typeof rawVal === 'number'
                        ? String(rawVal)
                        : null;
        }
        if (value === null) {
            // Knowledge field missing — warn and skip (leave placeholder as-is)
            console.warn(`[skill-adapter] Knowledge field for placeholder "${def.placeholder}" is null/undefined — leaving as-is`);
        }
        else {
            // Replace all occurrences
            adapted = adapted.split(def.placeholder).join(value);
            replaced.push(def.placeholder);
        }
    }
    // Second pass: detect unknown placeholders {word_word} not in PLACEHOLDER_MAP
    const knownPlaceholders = new Set(PLACEHOLDER_MAP.map((d) => d.placeholder));
    const unknownMatches = adapted.match(/\{[a-z][a-z0-9_]*\}/g) ?? [];
    for (const match of unknownMatches) {
        if (!knownPlaceholders.has(match) && !unknown.includes(match)) {
            unknown.push(match);
            console.warn(`[skill-adapter] Unknown placeholder "${match}" — left as-is`);
        }
    }
    // Step 3: Rewrite package manager commands
    if (knowledge.packageManager) {
        adapted = rewritePackageManagerCommands(adapted, knowledge.packageManager);
    }
    else {
        console.warn('[skill-adapter] packageManager is null — package manager commands left as-is');
    }
    // Step 4: Rewrite test runner commands
    if (knowledge.testCommand) {
        adapted = rewriteTestRunnerCommands(adapted, knowledge.testCommand);
    }
    return { adapted, replaced, unknown };
}
// ---------------------------------------------------------------------------
// File I/O orchestration
// ---------------------------------------------------------------------------
/** Cache directory path relative to projectPath */
const CACHE_REL = '.planu/skill-cache';
/** Installation directory relative to projectPath */
const INSTALL_REL = '.claude/skills';
/**
 * Read raw skill from .planu/skill-cache/{source}/{skillName}.md,
 * adapt it, and write result to .claude/skills/{skillName}.md.
 * Updates .planu/skill-registry.json with new adaptedAt timestamp.
 *
 * @throws {Error} with message containing 'raw cache missing' when cache file is absent
 */
export async function adaptAndInstallSkill(projectPath, skillName, knowledge) {
    // Look up source from registry to resolve the correct cache path
    const registry = await readSkillRegistry(projectPath);
    const entry = registry.installed[skillName];
    const source = entry?.source ?? 'anthropic'; // default to 'anthropic' if not yet registered
    // Resolve raw cache path
    const cachePath = join(projectPath, CACHE_REL, source, `${skillName}.md`);
    // Read raw content — throw if missing (caller should re-fetch via SPEC-681)
    let rawContent;
    try {
        rawContent = await readFile(cachePath, 'utf-8');
    }
    catch {
        throw new Error(`raw cache missing for ${skillName} (expected at ${cachePath})`);
    }
    // Adapt content (pure function)
    const result = adaptSkillContent(rawContent, knowledge);
    // Write adapted skill to .claude/skills/{skillName}.md
    const installDir = join(projectPath, INSTALL_REL);
    await mkdir(installDir, { recursive: true });
    await writeFile(join(installDir, `${skillName}.md`), result.adapted, 'utf-8');
    // Update registry with new adaptedAt timestamp
    await writeSkillRegistryEntry(projectPath, {
        skillName,
        source: entry?.source ?? 'anthropic',
        version: entry?.version ?? null,
        adaptedAt: new Date().toISOString(),
        ttlDays: entry?.ttlDays ?? 30,
    });
    return result;
}
//# sourceMappingURL=index.js.map