import { readFileSync } from 'fs';
import { parseSubscribeBlock, SubscribeParseError, } from '../skills/parse-subscribe.js';
import { parseYamlObject, splitFrontmatter } from '../utils/frontmatter.js';
import { logError } from '../utils/message-queue.js';
/**
 * Set of frontmatter keys that are always parsed as arrays
 */
const ARRAY_KEYS = new Set([
    'aliases',
    'parameters',
    'tags',
    'triggers',
    'examples',
    'references',
    'dependencies',
]);
/**
 * Set of frontmatter keys that are parsed as numbers
 */
const NUMBER_KEYS = new Set(['estimated-tokens']);
/**
 * Parse a value that may be a JSON-style array or a single item.
 * Returns an array of strings.
 */
function parseArrayValue(value) {
    const trimmed = value.trim();
    if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
        const content = trimmed.slice(1, -1);
        return content
            .split(',')
            .map(s => s.trim().replace(/^["']|["']$/g, ''))
            .filter(s => s.length > 0);
    }
    const cleaned = trimmed.replace(/^["']|["']$/g, '');
    return cleaned ? [cleaned] : [];
}
/**
 * Enhanced YAML frontmatter parser with support for multi-line strings,
 * arrays, and all command/skill metadata fields.
 */
function parseEnhancedFrontmatter(frontmatter) {
    const raw = {};
    const lines = frontmatter.split('\n');
    let currentKey = null;
    let currentValue = [];
    let isMultiline = false;
    let indentLevel = 0;
    const storeValue = (key, value) => {
        const trimmedValue = value.trim();
        if (ARRAY_KEYS.has(key)) {
            raw[key] = parseArrayValue(trimmedValue);
        }
        else if (NUMBER_KEYS.has(key)) {
            const num = Number(trimmedValue);
            if (!Number.isNaN(num)) {
                raw[key] = num;
            }
        }
        else {
            raw[key] = trimmedValue.replace(/^["']|["']$/g, '');
        }
    };
    for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        if (!line)
            continue; // Skip if line is undefined
        const trimmedLine = line.trim();
        // Skip empty lines and comments
        if (!trimmedLine || trimmedLine.startsWith('#')) {
            continue;
        }
        // Check for YAML dash syntax (array items)
        if (trimmedLine.startsWith('- ') &&
            currentKey &&
            ARRAY_KEYS.has(currentKey)) {
            const arrayItem = trimmedLine
                .slice(2)
                .trim()
                .replace(/^["']|["']$/g, '');
            const arr = raw[currentKey] ?? [];
            arr.push(arrayItem);
            raw[currentKey] = arr;
            continue;
        }
        // Check for multi-line string indicators
        if (trimmedLine.endsWith('|') || trimmedLine.endsWith('>')) {
            const colonIndex = line.indexOf(':');
            if (colonIndex !== -1) {
                currentKey = line.slice(0, colonIndex).trim();
                isMultiline = true;
                currentValue = [];
                indentLevel = 0;
                continue;
            }
        }
        // Handle multi-line content
        if (isMultiline && currentKey) {
            const lineIndent = line.length - line.trimStart().length;
            if (trimmedLine && indentLevel === 0) {
                indentLevel = lineIndent;
            }
            if (trimmedLine && lineIndent >= indentLevel) {
                currentValue.push(line.slice(indentLevel));
            }
            else if (trimmedLine && lineIndent < indentLevel) {
                // End of multi-line block
                const multilineContent = currentValue.join('\n').trim();
                storeValue(currentKey, multilineContent);
                isMultiline = false;
                currentKey = null;
                currentValue = [];
                indentLevel = 0;
                // Re-process this line as a regular key-value pair
                i--; // Reprocess current line
                continue;
            }
            else if (!trimmedLine) {
                currentValue.push('');
            }
            // If this is the last line, process the accumulated multi-line value
            if (i === lines.length - 1 && currentValue.length > 0) {
                const multilineContent = currentValue.join('\n').trim();
                storeValue(currentKey, multilineContent);
            }
            continue;
        }
        // Handle regular key-value pairs — find colon outside quotes
        const colonIndex = findColonOutsideQuotes(line);
        if (colonIndex === -1)
            continue;
        const key = line.slice(0, colonIndex).trim();
        const value = line.slice(colonIndex + 1).trim();
        if (value) {
            storeValue(key, value);
            currentKey = key; // For potential array items following
        }
        else {
            currentKey = key; // Key with no immediate value, might have array items below
        }
    }
    return mapRawToMetadata(raw);
}
/**
 * Find the first colon that is not inside single or double quotes.
 */
function findColonOutsideQuotes(line) {
    let inSingleQuote = false;
    let inDoubleQuote = false;
    for (let i = 0; i < line.length; i++) {
        const char = line[i];
        if (char === "'" && !inDoubleQuote) {
            inSingleQuote = !inSingleQuote;
        }
        else if (char === '"' && !inSingleQuote) {
            inDoubleQuote = !inDoubleQuote;
        }
        else if (char === ':' && !inSingleQuote && !inDoubleQuote) {
            return i;
        }
    }
    return -1;
}
/**
 * Map raw parsed key-value pairs to typed CustomCommandMetadata.
 */
function mapRawToMetadata(raw) {
    const metadata = {};
    if (raw.description)
        metadata.description = raw.description;
    if (raw.aliases)
        metadata.aliases = raw.aliases;
    if (raw.parameters)
        metadata.parameters = raw.parameters;
    if (raw.tags)
        metadata.tags = raw.tags;
    if (raw.triggers)
        metadata.triggers = raw.triggers;
    if (typeof raw['estimated-tokens'] === 'number')
        metadata.estimatedTokens = raw['estimated-tokens'];
    if (raw.category)
        metadata.category = raw.category;
    if (raw.version)
        metadata.version = raw.version;
    if (raw.author)
        metadata.author = raw.author;
    if (raw.examples)
        metadata.examples = raw.examples;
    if (raw.references)
        metadata.references = raw.references;
    if (raw.dependencies)
        metadata.dependencies = raw.dependencies;
    return metadata;
}
/**
 * Parse a markdown file with optional YAML frontmatter
 */
export function parseCommandFile(filePath) {
    const fileContent = readFileSync(filePath, 'utf-8');
    const split = splitFrontmatter(fileContent);
    // Preserve historical behavior: files with frontmatter but no body fall
    // through to "entire file is content" so the metadata block isn't silently
    // hidden from the user.
    if (split.hasFrontmatter && split.frontmatter && split.body) {
        let metadata = {};
        try {
            metadata = parseEnhancedFrontmatter(split.frontmatter);
        }
        catch (error) {
            // If parsing fails, treat entire file as content
            logError(`Failed to parse frontmatter in ${filePath}: ${String(error)}`);
            return {
                metadata: {},
                content: fileContent,
            };
        }
        const subscribe = extractSubscribe(split.frontmatter, filePath);
        return {
            metadata,
            content: split.body,
            subscribe,
        };
    }
    // No frontmatter (or frontmatter with no body) — entire file is content.
    return {
        metadata: {},
        content: fileContent.trim(),
    };
}
/**
 * The hand-rolled frontmatter parser above does not handle list-of-objects,
 * which `subscribe:` requires. Do a second pass with the real YAML parser
 * just to lift the `subscribe:` block out cleanly. On any error, log and
 * skip — `subscribe:` is optional and a malformed block should not block
 * the command from loading.
 */
function extractSubscribe(frontmatter, filePath) {
    const raw = parseYamlObject(frontmatter);
    if (!raw || raw.subscribe === undefined)
        return undefined;
    try {
        return parseSubscribeBlock(raw.subscribe);
    }
    catch (err) {
        if (err instanceof SubscribeParseError) {
            logError(`Invalid subscribe block in ${filePath}: ${err.message}`);
            return undefined;
        }
        throw err;
    }
}
/**
 * Parse a single `parameters:` entry. An entry is a positional name, with an
 * optional inline default after `=`:
 *
 *   "pr_number"            -> {name: 'pr_number', defaultValue: ''}
 *   "base=origin/main"     -> {name: 'base', defaultValue: 'origin/main'}
 *
 * The default is everything after the first `=`, so values may themselves
 * contain `=`. A defaulted parameter is the optional form: omit the argument
 * and the default is substituted instead of an empty string.
 */
export function parseCommandParameterSpec(spec) {
    const eq = spec.indexOf('=');
    if (eq === -1)
        return { name: spec.trim(), defaultValue: '' };
    return { name: spec.slice(0, eq).trim(), defaultValue: spec.slice(eq + 1) };
}
/**
 * Replace template variables in command content
 */
export function substituteTemplateVariables(content, variables) {
    let result = content;
    // Replace {{variable}} patterns
    for (const [key, value] of Object.entries(variables)) {
        const pattern = new RegExp(`\\{\\{\\s*${key}\\s*\\}\\}`, 'g');
        result = result.replace(pattern, value);
    }
    return result;
}
//# sourceMappingURL=parser.js.map