// engine/storybook/story-parser.ts — Parse .stories.* files without external deps (SPEC-432)
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { glob } from 'glob';
// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
function extractTitle(content) {
    const match = /title\s*:\s*['"]([^'"]+)['"]/.exec(content);
    return match?.[1] ?? null;
}
function extractCategory(title) {
    const slashIndex = title.indexOf('/');
    return slashIndex === -1 ? title : title.slice(0, slashIndex);
}
function extractComponentName(content, title) {
    // Try to find import statement for a capitalized identifier from a relative path
    const importPattern = /import\s+(?:type\s+)?\{([^}]+)\}\s+from\s+['"](\.[^'"]+)['"]/g;
    let importMatch;
    while ((importMatch = importPattern.exec(content)) !== null) {
        const identifiers = importMatch[1];
        if (identifiers !== undefined) {
            const parts = identifiers.split(',').map((s) => s.trim());
            const capitalized = parts.find((p) => /^[A-Z]/.test(p));
            if (capitalized !== undefined) {
                return capitalized;
            }
        }
    }
    // Fallback: last segment of title
    const segments = title.split('/');
    return segments[segments.length - 1] ?? title;
}
function extractImportPath(content) {
    // Prefer relative imports (starting with '.')
    const relativeMatch = /import\s+(?:type\s+)?\{[^}]+\}\s+from\s+['"](\.[^'"]+)['"]/.exec(content);
    if (relativeMatch?.[1] !== undefined) {
        return relativeMatch[1];
    }
    const anyMatch = /import\s+(?:type\s+)?\{[^}]+\}\s+from\s+['"]([^'"]+)['"]/.exec(content);
    return anyMatch?.[1] ?? '';
}
function extractStoryNames(content) {
    const names = [];
    // Match "export const Name: Story = " or "export const Name = {"
    const pattern = /^\s*export\s+const\s+([A-Z][A-Za-z0-9_]*)\s*[=:]/gm;
    let match;
    while ((match = pattern.exec(content)) !== null) {
        const name = match[1];
        if (name !== undefined && name !== 'default') {
            names.push(name);
        }
    }
    return names;
}
/**
 * Extract the content of a block starting at the character after `keyword: {`.
 * Handles nested braces and brackets by tracking depth.
 */
function extractBlockContent(content, keyword) {
    const start = content.indexOf(`${keyword}:`);
    if (start === -1) {
        return null;
    }
    const braceStart = content.indexOf('{', start + keyword.length);
    if (braceStart === -1) {
        return null;
    }
    let depth = 0;
    let i = braceStart;
    while (i < content.length) {
        const ch = content[i];
        if (ch === '{' || ch === '[') {
            depth++;
        }
        else if (ch === '}' || ch === ']') {
            depth--;
            if (depth === 0) {
                return content.slice(braceStart + 1, i);
            }
        }
        i++;
    }
    return null;
}
function extractTopLevelKeys(block) {
    // Only capture keys at depth 0 (direct children of the block)
    const keys = [];
    const keyPattern = /^(\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/gm;
    let m;
    while ((m = keyPattern.exec(block)) !== null) {
        // Count depth by scanning up to this match position
        const upTo = block.slice(0, m.index);
        let depth = 0;
        for (const ch of upTo) {
            if (ch === '{' || ch === '[') {
                depth++;
            }
            else if (ch === '}' || ch === ']') {
                depth--;
            }
        }
        if (depth === 0 && m[2] !== undefined) {
            keys.push(m[2]);
        }
    }
    return keys;
}
function extractPropNames(content) {
    const keys = new Set();
    // Extract keys from argTypes: { ... } (handles nested braces)
    const argTypesBlock = extractBlockContent(content, 'argTypes');
    if (argTypesBlock !== null) {
        for (const key of extractTopLevelKeys(argTypesBlock)) {
            keys.add(key);
        }
    }
    // Extract keys from args: { ... } blocks in story definitions
    let searchStart = 0;
    while (searchStart < content.length) {
        const idx = content.indexOf('args:', searchStart);
        if (idx === -1) {
            break;
        }
        const argsBlock = extractBlockContent(content.slice(idx), 'args');
        if (argsBlock !== null) {
            for (const key of extractTopLevelKeys(argsBlock)) {
                keys.add(key);
            }
        }
        searchStart = idx + 5;
    }
    return Array.from(keys);
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
export function parseStoryFile(filePath, content) {
    const title = extractTitle(content);
    if (title === null) {
        return null;
    }
    const category = extractCategory(title);
    const componentName = extractComponentName(content, title);
    const importPath = extractImportPath(content);
    const storyNames = extractStoryNames(content);
    const propNames = extractPropNames(content);
    return { componentName, title, category, storyNames, propNames, filePath, importPath };
}
export async function scanStorybookFiles(projectPath) {
    const startMs = Date.now();
    const pattern = join(projectPath, '**/*.stories.{ts,tsx,js,jsx}').replace(/\\/g, '/');
    const files = await glob(pattern, { ignore: ['**/node_modules/**'] });
    const components = [];
    const errors = [];
    for (const filePath of files) {
        try {
            const content = await readFile(filePath, 'utf-8');
            const component = parseStoryFile(filePath, content);
            if (component !== null) {
                components.push(component);
            }
        }
        catch (err) {
            errors.push({
                file: filePath,
                error: err instanceof Error ? err.message : String(err),
            });
        }
    }
    const elapsedSec = ((Date.now() - startMs) / 1000).toFixed(2);
    return {
        totalFiles: files.length,
        totalComponents: components.length,
        components,
        errors,
        scannedAt: new Date().toISOString(),
        // Store elapsed and path for formatting — attached as extra fields
        ...(elapsedSec ? { _elapsedSec: elapsedSec, _projectPath: projectPath } : {}),
    };
}
export function formatComponentsAsMarkdown(result) {
    if (result.components.length === 0) {
        return 'No Storybook story files found. Create .stories.tsx files to get started.';
    }
    const totalStories = result.components.reduce((sum, c) => sum + c.storyNames.length, 0);
    const lines = [
        `## Storybook Components (${result.components.length} components, ${totalStories} stories)`,
        '',
        '| Component | Category | Stories | Props |',
        '|-----------|----------|---------|-------|',
    ];
    for (const comp of result.components) {
        const stories = comp.storyNames.join(', ');
        const props = comp.propNames.join(', ');
        lines.push(`| ${comp.componentName} | ${comp.category} | ${stories} | ${props} |`);
    }
    const extended = result;
    const elapsedSec = extended._elapsedSec ?? '0.00';
    const projectPath = extended._projectPath ?? '';
    lines.push('');
    lines.push(`Scanned: ${projectPath} — ${result.totalFiles} files in ${elapsedSec}s`);
    return lines.join('\n');
}
export function getComponentContext(components, query) {
    const lowerQuery = query.toLowerCase();
    return components.filter((c) => c.componentName.toLowerCase().includes(lowerQuery) ||
        c.category.toLowerCase().includes(lowerQuery) ||
        c.title.toLowerCase().includes(lowerQuery));
}
//# sourceMappingURL=story-parser.js.map