import { readFileSync, readdirSync } from 'node:fs';
import { join } from 'node:path';
import { logger } from '../utils/logger.js';
import { summarizeError } from '../utils/error-display.js';
import { collectMarkdownReferences, extractMarkdownPreview, extractMarkdownSections, materializeMarkdownBody, } from '../utils/markdown-disclosure.js';
// ---------------------------------------------------------------------------
// Built-in fallback templates
// Mirrors the AGENT_TEMPLATES in src/tools/agent/index.ts so that
// archetypes.ts has no circular dependency on the tool module.
// ---------------------------------------------------------------------------
const BUILT_IN_ARCHETYPES = [
    {
        name: 'engineer',
        description: 'Full-stack implementation agent',
        tools: ['read', 'write', 'edit', 'find', 'exec', 'analyze', 'inspect', 'fetch', 'registry'],
        isCustom: false,
        origin: 'builtin',
    },
    {
        name: 'reviewer',
        description: 'Code review and quality assessment',
        tools: ['read', 'find', 'analyze', 'inspect', 'fetch', 'registry'],
        isCustom: false,
        origin: 'builtin',
    },
    {
        name: 'tester',
        description: 'Test writing and execution',
        tools: ['read', 'write', 'find', 'exec', 'analyze', 'inspect'],
        isCustom: false,
        origin: 'builtin',
    },
    {
        name: 'researcher',
        description: 'Codebase exploration and analysis',
        tools: ['read', 'find', 'analyze', 'inspect', 'fetch', 'registry'],
        isCustom: false,
        origin: 'builtin',
    },
    {
        name: 'general',
        description: 'General purpose agent',
        tools: ['read', 'write', 'edit', 'find', 'exec', 'analyze', 'inspect', 'fetch', 'registry'],
        isCustom: false,
        origin: 'builtin',
    },
];
function parseSimpleYaml(yaml) {
    const result = {};
    const lines = yaml.split('\n');
    let i = 0;
    let currentKey = null;
    let collectingList = false;
    const listBuffer = [];
    const flushList = () => {
        if (currentKey && collectingList) {
            result[currentKey] = listBuffer.slice();
        }
        collectingList = false;
        listBuffer.length = 0;
    };
    while (i < lines.length) {
        const line = lines[i];
        i++;
        // Skip comments and blank lines
        if (!line.trim() || line.trim().startsWith('#')) {
            if (collectingList)
                flushList();
            continue;
        }
        // List item under current key
        const listItemMatch = line.match(/^\s+-\s+(.+)$/);
        if (listItemMatch && collectingList) {
            listBuffer.push(listItemMatch[1].trim());
            continue;
        }
        // Key: value line
        const kvMatch = line.match(/^([\w_]+):\s*(.*)$/);
        if (kvMatch) {
            // Flush any pending list
            if (collectingList)
                flushList();
            currentKey = kvMatch[1];
            const rawValue = kvMatch[2].trim();
            if (!rawValue) {
                // Value will be on following list lines
                collectingList = true;
                continue;
            }
            // Inline list: [a, b, c]
            const inlineListMatch = rawValue.match(/^\[(.*)\]$/);
            if (inlineListMatch) {
                result[currentKey] = inlineListMatch[1]
                    .split(',')
                    .map((s) => s.trim())
                    .filter(Boolean);
                continue;
            }
            // Multi-line string with | (just take first line for simple values)
            const stripped = rawValue.replace(/^['"]|['"]$/g, '');
            result[currentKey] = stripped;
        }
    }
    // Flush any trailing list
    if (collectingList)
        flushList();
    return result;
}
/** Extract `--- frontmatter ---` + body from markdown text.
 *  Returns null if no frontmatter block is present. */
function extractFrontmatter(content) {
    const trimmed = content.trimStart();
    if (!trimmed.startsWith('---'))
        return null;
    const secondDash = trimmed.indexOf('\n---', 3);
    if (secondDash === -1)
        return null;
    const raw = trimmed.slice(3, secondDash).trim();
    const body = trimmed.slice(secondDash + 4).trimStart();
    return { raw, body };
}
function validateArchetype(archetype) {
    const issues = [];
    if (!archetype.name.trim())
        issues.push('missing name');
    if (!archetype.description.trim())
        issues.push('missing description');
    if (archetype.tools.length === 0)
        issues.push('no tools declared');
    const uniqueTools = new Set(archetype.tools.map((tool) => tool.trim()).filter(Boolean));
    if (uniqueTools.size !== archetype.tools.length)
        issues.push('duplicate or empty tool declarations');
    return issues;
}
// ---------------------------------------------------------------------------
// ArchetypeLoader
// ---------------------------------------------------------------------------
/** Loads and caches agent archetypes from .goodvibes/agents/*.md files.
 *
 * Progressive loading:
 *  - `listArchetypes()` returns all known archetypes (frontmatter only);
 *    full body (system_prompt) is populated on first call to `loadArchetype()`.
 *  - Falls back to built-in templates when no .md file is found.
 */
export class ArchetypeLoader {
    /** Map from archetype name -> AgentArchetype (may be partially loaded) */
    cache = new Map();
    /** Map from archetype name -> raw markdown body (lazy) */
    bodyCache = new Map();
    /** Whether the directory scan has been run */
    scanned = false;
    /** Directory to scan for .md files */
    dir;
    constructor(dir) {
        this.dir = dir ?? '';
    }
    // -------------------------------------------------------------------------
    // Scanning
    // -------------------------------------------------------------------------
    /** Scan the agents directory and populate cache with frontmatter-only entries.
   *  Called automatically on first access. */
    scan() {
        if (this.scanned)
            return;
        this.scanned = true;
        // Seed with built-ins first so they act as fallbacks
        for (const builtin of BUILT_IN_ARCHETYPES) {
            this.cache.set(builtin.name, { ...builtin });
        }
        if (!this.dir) {
            logger.debug('ArchetypeLoader: no archetype directory configured, using built-ins only');
            return;
        }
        let files;
        try {
            files = readdirSync(this.dir).filter((f) => f.endsWith('.md'));
        }
        catch {
            // Directory doesn't exist — built-ins are the only archetypes
            logger.debug('ArchetypeLoader: agents dir not found, using built-ins', { dir: this.dir });
            return;
        }
        for (const file of files) {
            try {
                const filePath = join(this.dir, file);
                const content = readFileSync(filePath, 'utf-8');
                const parsed = extractFrontmatter(content);
                if (!parsed)
                    continue;
                const fm = parseSimpleYaml(parsed.raw);
                const name = fm.name ?? file.replace(/\.md$/, '');
                const archetype = {
                    name,
                    description: fm.description ?? '',
                    tools: fm.tools ?? [],
                    model: fm.model,
                    provider: fm.provider,
                    // systemPrompt intentionally omitted here — lazy loaded
                    isCustom: true,
                    origin: 'local-markdown',
                    sourcePath: filePath,
                };
                // Store body for lazy loading
                if (fm.system_prompt) {
                    // If system_prompt is inline in frontmatter, set it directly
                    archetype.systemPrompt = fm.system_prompt;
                }
                else {
                    // Otherwise, the markdown body is the system prompt
                    this.bodyCache.set(name, parsed.body);
                    archetype.preview = extractMarkdownPreview(parsed.body);
                    archetype.includes = collectMarkdownReferences(parsed.body);
                    archetype.sections = extractMarkdownSections(parsed.body);
                }
                archetype.validationIssues = validateArchetype(archetype);
                this.cache.set(name, archetype);
            }
            catch (err) {
                logger.error('ArchetypeLoader: failed to parse agent file', {
                    file,
                    error: summarizeError(err),
                });
            }
        }
    }
    // -------------------------------------------------------------------------
    // Public API
    // -------------------------------------------------------------------------
    /** Load a named archetype with full content (including system prompt).
     *  Returns null if not found in files or built-ins. */
    loadArchetype(name) {
        this.scan();
        const archetype = this.cache.get(name);
        if (!archetype)
            return null;
        // Lazy-load body as system prompt if not yet resolved
        if (!archetype.systemPrompt && this.bodyCache.has(name)) {
            const body = this.bodyCache.get(name);
            archetype.systemPrompt = archetype.sourcePath
                ? materializeMarkdownBody(archetype.sourcePath, body)
                : body;
            this.bodyCache.delete(name);
        }
        return { ...archetype };
    }
    /** List all known archetypes (frontmatter only, no body loading). */
    listArchetypes() {
        this.scan();
        return Array.from(this.cache.values()).map((a) => ({ ...a }));
    }
    /** Merge archetype config with runtime overrides.
     *  Override properties take precedence over archetype defaults.
     *  Returns null if the named archetype is not found. */
    mergeWithOverrides(name, overrides) {
        const archetype = this.loadArchetype(name);
        if (!archetype)
            return null;
        return {
            ...archetype,
            model: overrides.model ?? archetype.model,
            provider: overrides.provider ?? archetype.provider,
            tools: overrides.tools ?? archetype.tools,
        };
    }
    /** Clear all cached archetypes and force re-scan on next access.
     *  Primarily for testing. */
    clear() {
        this.cache.clear();
        this.bodyCache.clear();
        this.scanned = false;
    }
}
