/**
 * GitHub Issue Template Parser.
 *
 * Parses .github/ISSUE_TEMPLATE/*.yml files into JSON config.
 * Supports three resolution strategies:
 *   1. Live parse from YAML templates (if in a repo with templates)
 *   2. Cached config from .cleo/issue-templates.json
 *   3. Hardcoded fallback defaults
 *
 * @task T4454
 * @epic T4454
 */
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
import { basename, join } from 'node:path';
import { BUILD_CONFIG } from '../config/build-config.js';
import { getCleoDir, getProjectRoot } from '../paths.js';
import { getPackageRoot } from '../scaffold.js';
const TEMPLATE_DIR = '.github/ISSUE_TEMPLATE';
const PACKAGED_TEMPLATE_DIR = BUILD_CONFIG.templates.issueTemplatesDir;
const CACHE_FILE = 'issue-templates.json';
/** Known filename-to-subcommand mappings. */
const SUBCOMMAND_MAP = {
    bug_report: 'bug',
    feature_request: 'feature',
    help_question: 'help',
};
/** Fallback default templates. */
const FALLBACK_TEMPLATES = [
    {
        name: 'Bug Report',
        description: 'Report a bug',
        title: '[Bug] ',
        labels: ['bug'],
        subcommand: 'bug',
        fileName: 'bug_report.yml',
    },
    {
        name: 'Feature Request',
        description: 'Suggest an enhancement',
        title: '[Feature] ',
        labels: ['enhancement'],
        subcommand: 'feature',
        fileName: 'feature_request.yml',
    },
    {
        name: 'Help Question',
        description: 'Ask a question or get help',
        title: '[Help] ',
        labels: ['help', 'question'],
        subcommand: 'help',
        fileName: 'help_question.yml',
    },
];
/**
 * Extract a YAML field value from template content.
 * Handles simple top-level scalar fields only (not nested).
 */
function extractYamlField(content, field) {
    const regex = new RegExp(`^${field}:\\s*["']?(.+?)["']?\\s*$`, 'm');
    const match = content.match(regex);
    return match?.[1]?.trim() ?? '';
}
/**
 * Extract YAML array values (labels).
 * Handles both inline `[a, b]` and multi-line `- a\n- b` formats.
 */
function extractYamlArray(content, field) {
    // Try inline format: labels: [bug, enhancement]
    const inlineRegex = new RegExp(`^${field}:\\s*\\[([^\\]]+)\\]`, 'm');
    const inlineMatch = content.match(inlineRegex);
    if (inlineMatch) {
        return inlineMatch[1]
            .split(',')
            .map((s) => s.trim().replace(/^["']|["']$/g, ''))
            .filter(Boolean);
    }
    // Try multi-line format
    const lines = content.split('\n');
    const items = [];
    let inArray = false;
    for (const line of lines) {
        if (line.match(new RegExp(`^${field}:`))) {
            inArray = true;
            continue;
        }
        if (inArray) {
            const itemMatch = line.match(/^\s+-\s+["']?(.+?)["']?\s*$/);
            if (itemMatch) {
                items.push(itemMatch[1].trim());
            }
            else if (line.match(/^\S/)) {
                break; // New top-level key, done
            }
        }
    }
    return items;
}
/**
 * Parse a single YAML template file.
 */
function parseTemplateFile(filePath) {
    if (!existsSync(filePath))
        return null;
    try {
        const content = readFileSync(filePath, 'utf-8');
        const fileName = basename(filePath);
        const stem = fileName.replace(/\.ya?ml$/, '');
        const name = extractYamlField(content, 'name');
        const description = extractYamlField(content, 'description');
        const title = extractYamlField(content, 'title');
        const labels = extractYamlArray(content, 'labels');
        const subcommand = SUBCOMMAND_MAP[stem] ?? stem;
        if (!name)
            return null;
        return { name, description, title, labels, subcommand, fileName };
    }
    catch {
        return null;
    }
}
/**
 * Parse all issue templates from available sources.
 * Priority:
 *   1. Packaged templates in the CLEO installation (for npm-installed users)
 *   2. Project's .github/ISSUE_TEMPLATE/ (for contributors working on CLEO)
 */
export function parseIssueTemplates(projectDir) {
    // Strategy 1: Check packaged templates (for npm-installed users)
    try {
        const packageRoot = getPackageRoot();
        const packagedTemplateDir = join(packageRoot, PACKAGED_TEMPLATE_DIR);
        if (existsSync(packagedTemplateDir)) {
            const templates = [];
            for (const file of readdirSync(packagedTemplateDir)) {
                if (!file.endsWith('.yml') && !file.endsWith('.yaml'))
                    continue;
                const template = parseTemplateFile(join(packagedTemplateDir, file));
                if (template)
                    templates.push(template);
            }
            if (templates.length > 0)
                return templates;
        }
    }
    catch {
        // Fall through to next strategy
    }
    // Strategy 2: Check project's .github/ISSUE_TEMPLATE/ (for contributors)
    const dir = projectDir ?? getProjectRoot();
    const templateDir = join(dir, TEMPLATE_DIR);
    if (!existsSync(templateDir))
        return [];
    const templates = [];
    for (const file of readdirSync(templateDir)) {
        if (!file.endsWith('.yml') && !file.endsWith('.yaml'))
            continue;
        const template = parseTemplateFile(join(templateDir, file));
        if (template)
            templates.push(template);
    }
    return templates;
}
/**
 * Get template configuration - tries live parse, cache, then fallback.
 */
export function getTemplateConfig(cwd) {
    // Strategy 1: Live parse
    const projectDir = cwd ?? getProjectRoot();
    const liveTemplates = parseIssueTemplates(projectDir);
    if (liveTemplates.length > 0)
        return liveTemplates;
    // Strategy 2: Cached config
    const cachePath = join(getCleoDir(cwd), CACHE_FILE);
    if (existsSync(cachePath)) {
        try {
            const cached = JSON.parse(readFileSync(cachePath, 'utf-8'));
            if (cached.templates?.length > 0)
                return cached.templates;
        }
        catch {
            // Fall through to defaults
        }
    }
    // Strategy 3: Fallback defaults
    return FALLBACK_TEMPLATES;
}
/**
 * Get the template for a specific subcommand (bug, feature, etc.).
 */
export function getTemplateForSubcommand(subcommand, cwd) {
    const templates = getTemplateConfig(cwd);
    return templates.find((t) => t.subcommand === subcommand) ?? null;
}
/**
 * Cache parsed templates to .cleo/issue-templates.json.
 */
export function cacheTemplates(templates, cwd) {
    const cachePath = join(getCleoDir(cwd), CACHE_FILE);
    const cache = {
        version: '1.0.0',
        generatedAt: new Date().toISOString(),
        templates,
    };
    writeFileSync(cachePath, JSON.stringify(cache, null, 2) + '\n', 'utf-8');
}
/**
 * Validate that labels referenced in issue templates are consistent.
 *
 * Collects all labels used across templates and checks that each label
 * appears in at least one template definition. Reports any labels that
 * are referenced by only one template but not defined as a known label
 * across the template set. This catches typos and orphaned labels
 * without requiring GitHub API access.
 */
export function validateLabelsExist(templates) {
    if (templates.length === 0) {
        return { valid: true, missingLabels: [] };
    }
    // Build a frequency map of all labels across templates
    const labelFrequency = new Map();
    for (const template of templates) {
        for (const label of template.labels) {
            labelFrequency.set(label, (labelFrequency.get(label) ?? 0) + 1);
        }
    }
    // Cross-reference: each template's labels should be known across the template set.
    // A label used by only one template AND not matching any known subcommand is suspicious.
    const knownSubcommands = new Set(templates.map((t) => t.subcommand));
    const knownNames = new Set(templates.map((t) => t.name.toLowerCase()));
    const missingLabels = [];
    for (const template of templates) {
        for (const label of template.labels) {
            // Check if the label is completely unknown: used once, and not matching
            // any template subcommand or name (which would make it a valid unique label)
            const freq = labelFrequency.get(label) ?? 0;
            if (freq === 1 &&
                !knownSubcommands.has(label) &&
                !knownNames.has(label.toLowerCase()) &&
                !WELL_KNOWN_LABELS.has(label)) {
                missingLabels.push(label);
            }
        }
    }
    return {
        valid: missingLabels.length === 0,
        missingLabels,
    };
}
/** Labels that are well-known in GitHub/issue trackers and always valid. */
const WELL_KNOWN_LABELS = new Set([
    'bug',
    'enhancement',
    'feature',
    'help',
    'question',
    'documentation',
    'good first issue',
    'duplicate',
    'invalid',
    'wontfix',
    'priority',
    'critical',
    'high',
    'medium',
    'low',
]);
//# sourceMappingURL=template-parser.js.map