/**
 * GitHub Issue Template Parser (Core Logic)
 *
 * Native TypeScript implementation for parsing GitHub issue template YAML files
 * and producing structured JSON config. The CLI
 * can consume the generated config at .cleo/issue-templates.json.
 *
 * Supports: parseIssueTemplates, getTemplateForSubcommand, generateTemplateConfig, validateLabels
 *
 * @task T5705
 * @epic T5701
 */
import { existsSync, readdirSync, readFileSync } from 'fs';
import { join } from 'path';
import { parse as parseYaml } from 'yaml';
import { getDataPath, writeJsonFileAtomic } from '../platform.js';
/**
 * Known filename suffixes to strip when deriving subcommand.
 * Pattern: strip _report, _request, _question, then take the first word.
 */
const SUFFIX_PATTERNS = ['_report', '_request', '_question'];
/**
 * Derive a subcommand name from a template filename.
 *
 * Examples:
 *   bug_report.yml     -> "bug"
 *   feature_request.yml -> "feature"
 *   help_question.yml   -> "help"
 */
function deriveSubcommand(filename) {
    // Remove .yml / .yaml extension
    let stem = filename.replace(/\.ya?ml$/i, '');
    // Strip known suffixes
    for (const suffix of SUFFIX_PATTERNS) {
        if (stem.endsWith(suffix)) {
            stem = stem.slice(0, -suffix.length);
            break;
        }
    }
    // Take the first word (split on underscore, hyphen, or space)
    const firstWord = stem.split(/[_\-\s]/)[0];
    return firstWord.toLowerCase();
}
/**
 * Parse a single YAML template file into an IssueTemplate.
 */
function parseTemplateFile(templateDir, filename) {
    const filePath = join(templateDir, filename);
    const raw = readFileSync(filePath, 'utf-8');
    const parsed = parseYaml(raw);
    const name = typeof parsed.name === 'string' ? parsed.name : filename;
    const titlePrefix = typeof parsed.title === 'string' ? parsed.title : '';
    // Parse labels - can be an array of strings
    let labels = [];
    if (Array.isArray(parsed.labels)) {
        labels = parsed.labels.map((l) => String(l));
    }
    // Parse body sections
    const sections = [];
    if (Array.isArray(parsed.body)) {
        for (const item of parsed.body) {
            if (typeof item !== 'object' || item === null)
                continue;
            const entry = item;
            const type = String(entry.type || 'unknown');
            const attributes = (entry.attributes || {});
            const validations = (entry.validations || {});
            // For markdown type, use 'value' hash as a pseudo-id since they lack an id field
            const id = typeof entry.id === 'string'
                ? entry.id
                : type === 'markdown'
                    ? `markdown-${sections.length}`
                    : `section-${sections.length}`;
            const label = typeof attributes.label === 'string'
                ? attributes.label
                : type === 'markdown'
                    ? 'Markdown'
                    : '';
            const required = validations.required === true;
            const section = {
                id,
                type,
                label,
                required,
            };
            // Add options for dropdown type
            if (type === 'dropdown' && Array.isArray(attributes.options)) {
                section.options = attributes.options.map((o) => String(o));
            }
            // Add options for checkboxes type
            if (type === 'checkboxes' && Array.isArray(attributes.options)) {
                section.options = attributes.options.map((o) => typeof o.label === 'string' ? o.label : String(o));
            }
            // Add placeholder if present
            if (typeof attributes.placeholder === 'string') {
                section.placeholder = attributes.placeholder;
            }
            sections.push(section);
        }
    }
    return {
        filename,
        subcommand: deriveSubcommand(filename),
        name,
        titlePrefix,
        labels,
        sections,
    };
}
/**
 * Parse all templates from the repo's .github/ISSUE_TEMPLATE/ directory.
 *
 * Reads YAML files directly (live parse, no caching).
 * Excludes config.yml which is the GitHub template chooser config.
 */
export function parseIssueTemplates(projectRoot) {
    const templateDir = join(projectRoot, '.github', 'ISSUE_TEMPLATE');
    if (!existsSync(templateDir)) {
        return {
            success: false,
            error: { code: 'E_NOT_FOUND', message: `Issue template directory not found: ${templateDir}` },
        };
    }
    let files;
    try {
        files = readdirSync(templateDir).filter((f) => /\.ya?ml$/i.test(f) && f !== 'config.yml');
    }
    catch (error) {
        return {
            success: false,
            error: {
                code: 'E_FILE_ERROR',
                message: `Failed to read template directory: ${error.message}`,
            },
        };
    }
    if (files.length === 0) {
        return {
            success: false,
            error: {
                code: 'E_NOT_FOUND',
                message: 'No issue template YAML files found (excluding config.yml)',
            },
        };
    }
    const templates = [];
    for (const file of files.sort()) {
        try {
            templates.push(parseTemplateFile(templateDir, file));
        }
        catch (error) {
            return {
                success: false,
                error: {
                    code: 'E_PARSE_ERROR',
                    message: `Failed to parse template ${file}: ${error.message}`,
                    details: { filename: file },
                },
            };
        }
    }
    return {
        success: true,
        data: {
            templates,
            generatedAt: new Date().toISOString(),
            sourceDir: templateDir,
        },
    };
}
/**
 * Get template config for a specific subcommand (bug/feature/help).
 *
 * Performs a live parse and filters to the matching template.
 */
export function getTemplateForSubcommand(projectRoot, subcommand) {
    const result = parseIssueTemplates(projectRoot);
    if (!result.success || !result.data) {
        return result.error
            ? { success: false, error: result.error }
            : {
                success: false,
                error: { code: 'E_PARSE_ERROR', message: 'Failed to parse issue templates' },
            };
    }
    const template = result.data.templates.find((t) => t.subcommand === subcommand.toLowerCase());
    if (!template) {
        const available = result.data.templates.map((t) => t.subcommand).join(', ');
        return {
            success: false,
            error: {
                code: 'E_NOT_FOUND',
                message: `No template found for subcommand '${subcommand}'. Available: ${available}`,
                details: { available: result.data.templates.map((t) => t.subcommand) },
            },
        };
    }
    return { success: true, data: template };
}
/**
 * Generate and cache the config as .cleo/issue-templates.json.
 *
 * Performs a live parse, then writes the result using writeJsonFileAtomic.
 */
export async function generateTemplateConfig(projectRoot) {
    const result = parseIssueTemplates(projectRoot);
    if (!result.success || !result.data) {
        return result.error
            ? { success: false, error: result.error }
            : {
                success: false,
                error: { code: 'E_PARSE_ERROR', message: 'Failed to parse issue templates' },
            };
    }
    const outputPath = getDataPath(projectRoot, 'issue-templates.json');
    try {
        writeJsonFileAtomic(outputPath, result.data);
    }
    catch (error) {
        return {
            success: false,
            error: {
                code: 'E_FILE_ERROR',
                message: `Failed to write template config: ${error.message}`,
                details: { outputPath },
            },
        };
    }
    return { success: true, data: result.data };
}
/**
 * Validate that labels exist on a GitHub repo.
 *
 * Compares the template labels against a list of known repo labels.
 * Returns which labels exist and which are missing.
 */
export function validateLabels(labels, repoLabels) {
    const repoLabelSet = new Set(repoLabels.map((l) => l.toLowerCase()));
    const existing = [];
    const missing = [];
    for (const label of labels) {
        if (repoLabelSet.has(label.toLowerCase())) {
            existing.push(label);
        }
        else {
            missing.push(label);
        }
    }
    return {
        success: true,
        data: { existing, missing },
    };
}
//# sourceMappingURL=parser.js.map