// engine/figma/token-sync-writer.ts — Design Token Sync writer (SPEC-433)
import { mkdir, writeFile } from 'node:fs/promises';
import { dirname, join } from 'node:path';
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function tokenVarName(name, prefix) {
    return `--${prefix}-${name.replace(/\./g, '-')}`;
}
function setNestedValue(obj, parts, value) {
    let current = obj;
    for (let i = 0; i < parts.length - 1; i++) {
        const part = parts[i];
        if (part === undefined) {
            continue;
        }
        if (current[part] === undefined || typeof current[part] === 'string') {
            current[part] = {};
        }
        current = current[part];
    }
    const lastPart = parts[parts.length - 1];
    if (lastPart !== undefined) {
        current[lastPart] = value;
    }
}
function sortNestedKeys(obj) {
    const sorted = {};
    for (const key of Object.keys(obj).sort()) {
        const val = obj[key];
        if (val === undefined) {
            continue;
        }
        if (typeof val === 'string') {
            sorted[key] = val;
        }
        else {
            sorted[key] = sortNestedKeys(val);
        }
    }
    return sorted;
}
function categoryToType(category) {
    switch (category) {
        case 'color':
            return 'color';
        case 'spacing':
        case 'radius':
            return 'dimension';
        case 'typography':
            return 'typography';
        case 'shadow':
            return 'shadow';
        case 'other':
            return 'unknown';
    }
}
// ---------------------------------------------------------------------------
// CSS generator
// ---------------------------------------------------------------------------
export function generateCssTokens(tokens, prefix) {
    if (tokens.length === 0) {
        return '/* No design tokens found */';
    }
    const effectivePrefix = prefix ?? 'planu';
    // Group by category preserving insertion order
    const groups = new Map();
    for (const token of tokens) {
        const existing = groups.get(token.category) ?? [];
        existing.push(token);
        groups.set(token.category, existing);
    }
    const lines = [];
    for (const [category, categoryTokens] of groups) {
        if (categoryTokens.length === 0) {
            continue;
        }
        lines.push(`  /* === ${category} === */`);
        for (const token of categoryTokens) {
            lines.push(`  ${tokenVarName(token.name, effectivePrefix)}: ${token.value};`);
        }
        lines.push('');
    }
    // Remove trailing empty line
    if (lines[lines.length - 1] === '') {
        lines.pop();
    }
    return `:root {\n${lines.join('\n')}\n}`;
}
// ---------------------------------------------------------------------------
// TypeScript generator
// ---------------------------------------------------------------------------
export function generateTsTokens(tokens) {
    const byCategory = {};
    for (const token of tokens) {
        const parts = token.name.split('.');
        if (parts.length === 0) {
            continue;
        }
        setNestedValue(byCategory, parts, token.value);
    }
    // Sort keys per category
    const sorted = sortNestedKeys(byCategory);
    const header = [
        '// Auto-generated by Planu — do not edit manually',
        '// Sync with: sync_figma_tokens_to_code',
        '',
    ].join('\n');
    const body = `export const tokens = ${JSON.stringify(sorted, null, 2)} as const;`;
    const typeExport = '\nexport type TokenPath = keyof typeof tokens;';
    return `${header}${body}${typeExport}\n`;
}
function isDtcgLeaf(val) {
    return '$value' in val;
}
function setDtcgValue(obj, parts, value, type) {
    let current = obj;
    for (let i = 0; i < parts.length - 1; i++) {
        const rawPart = parts[i];
        if (rawPart === undefined) {
            continue;
        }
        if (current[rawPart] === undefined || isDtcgLeaf(current[rawPart])) {
            current[rawPart] = {};
        }
        current = current[rawPart];
    }
    const lastPart = parts[parts.length - 1];
    if (lastPart !== undefined) {
        current[lastPart] = { $value: value, $type: type };
    }
}
export function generateJsonTokens(tokens) {
    const root = {};
    for (const token of tokens) {
        const parts = token.name.split('.');
        if (parts.length === 0) {
            continue;
        }
        const type = categoryToType(token.category);
        setDtcgValue(root, parts, token.value, type);
    }
    return JSON.stringify(root, null, 2);
}
// ---------------------------------------------------------------------------
// File writer
// ---------------------------------------------------------------------------
export async function writeTokenFile(projectPath, options, tokens) {
    let content;
    switch (options.format) {
        case 'css':
            content = generateCssTokens(tokens, options.prefix);
            break;
        case 'ts':
            content = generateTsTokens(tokens);
            break;
        case 'json':
            content = generateJsonTokens(tokens);
            break;
    }
    const absPath = join(projectPath, options.outputPath);
    await mkdir(dirname(absPath), { recursive: true });
    await writeFile(absPath, content, 'utf-8');
    return {
        format: options.format,
        outputPath: options.outputPath,
        tokensWritten: tokens.length,
        content,
        syncedAt: new Date().toISOString(),
    };
}
// ---------------------------------------------------------------------------
// Drift detector
// ---------------------------------------------------------------------------
export function detectTokenDrift(storedTokens, fileContent, format) {
    if (format === 'css') {
        // Parse CSS var lines: --prefix-name-parts: value;
        const varPattern = /--([^:]+):\s*([^;]+);/g;
        const fileValues = new Map();
        let match;
        while ((match = varPattern.exec(fileContent)) !== null) {
            const rawName = match[1]?.trim() ?? '';
            const val = match[2]?.trim() ?? '';
            fileValues.set(rawName, val);
        }
        return storedTokens.map((token) => {
            const tokenNameDashed = token.name.replace(/\./g, '-');
            let codeValue = '';
            for (const [cssVarName, cssVal] of fileValues) {
                if (cssVarName.endsWith(`-${tokenNameDashed}`) || cssVarName === tokenNameDashed) {
                    codeValue = cssVal;
                    break;
                }
            }
            return {
                name: token.name,
                storedValue: token.value,
                codeValue,
                drifted: codeValue !== token.value,
            };
        });
    }
    // For ts and json: simple string presence check
    return storedTokens.map((token) => {
        const valueInFile = fileContent.includes(token.value);
        return {
            name: token.name,
            storedValue: token.value,
            codeValue: valueInFile ? token.value : '',
            drifted: !valueInFile,
        };
    });
}
//# sourceMappingURL=token-sync-writer.js.map