/**
 * Imported-model type emission — V2 Phase 3, Round 2.
 *
 * When a consumer `import:`s a registry **component library** that exports
 * MODELS (value objects / entities — e.g. `Money`, `Address`), the library
 * emitter needs to turn the fetched `.specly` into real, usable TypeScript
 * types in the consumer's output tree — not a client that throws.
 *
 * This module parses the fetched spec content (the raw `.specly` string the
 * resolver returns for `source: 'registry-component'`) and emits a
 * `export interface <Model> { ... }` per selected model, derived from the
 * model's attributes. The type mapping mirrors the canonical one used by the
 * ReactAppStarter api-types generator (`api-types-starter-generator.ts`).
 *
 * Operation imports (dotted `Service.op` selects) and bare selects that do
 * NOT resolve to a model are returned in `remaining` so the caller can keep
 * the existing (client-throw) behaviour for them — those are the
 * service/operation story, not the model-library story.
 */
import * as yaml from 'js-yaml';
/**
 * Normalise an attribute declaration into {base type, isArray, required,
 * values}. Handles BOTH the common `.specly` string form
 * (`amount: Number required min=0`, `tags: String[]`, `kind: String
 * values=[home,work]`) and the structured object form
 * (`{ type, required, values, isArray }`).
 */
export function parseAttr(attr) {
    if (typeof attr === 'string') {
        const typeTok = (attr.trim().split(/\s+/)[0] ?? '').trim();
        const isArray = /\[\]$/.test(typeTok) || /^array<.+>$/i.test(typeTok);
        const base = typeTok.replace(/\[\]$/, '').replace(/^array<(.+)>$/i, '$1').trim();
        const required = /\brequired\b/.test(attr);
        const m = attr.match(/values\s*=\s*\[([^\]]*)\]/);
        const values = m
            ? m[1].split(',').map((s) => s.trim()).filter(Boolean)
            : undefined;
        return { base, isArray, required, values };
    }
    const a = (attr ?? {});
    let base = typeof a.type === 'string' ? a.type : '';
    const isArray = a.isArray === true || /\[\]$/.test(base);
    base = base.replace(/\[\]$/, '');
    const values = Array.isArray(a.values) ? a.values.map((v) => String(v)) : undefined;
    return { base: base.trim(), isArray, required: !!a.required, values };
}
/** Map a SpecVerse attribute to a TypeScript type. */
export function mapAttrType(attr) {
    const p = parseAttr(attr);
    if (p.values && p.values.length > 0) {
        const union = p.values.map((v) => JSON.stringify(v)).join(' | ');
        return p.isArray ? `(${union})[]` : union;
    }
    let ts;
    switch (p.base.toLowerCase()) {
        case 'string':
        case 'text':
        case 'uuid':
        case 'email':
        case 'url':
        case 'phone':
            ts = 'string';
            break;
        case 'int':
        case 'integer':
        case 'float':
        case 'number':
        case 'decimal':
            ts = 'number';
            break;
        case 'boolean':
        case 'bool':
            ts = 'boolean';
            break;
        case 'datetime':
        case 'date':
        case 'time':
        case 'timestamp':
            ts = 'string';
            break;
        case 'json':
        case 'object':
            ts = 'Record<string, unknown>';
            break;
        default:
            ts = 'unknown';
    }
    return p.isArray ? `${ts}[]` : ts;
}
export function isRequired(attr) {
    return parseAttr(attr).required;
}
/** Collect every model across every component in a parsed spec object. */
export function collectModels(parsed) {
    const models = new Map();
    const components = parsed?.components;
    if (!components || typeof components !== 'object')
        return models;
    for (const comp of Object.values(components)) {
        const compModels = comp?.models;
        if (compModels && typeof compModels === 'object') {
            for (const [name, def] of Object.entries(compModels)) {
                models.set(name, (def ?? {}));
            }
        }
    }
    return models;
}
export function emitInterface(name, model) {
    const attrs = (model.attributes ?? {});
    const fields = Object.entries(attrs)
        .map(([attrName, attr]) => {
        const tsType = mapAttrType(attr);
        const optional = isRequired(attr) ? '' : '?';
        return `  ${attrName}${optional}: ${tsType};`;
    })
        .join('\n');
    return `export interface ${name} {\n${fields || '  [key: string]: unknown;'}\n}`;
}
/**
 * Parse fetched registry spec content + emit TS interfaces for the selected
 * model names. Bare selects matching a model → interface; everything else
 * (dotted ops, unknown names) → `remaining`.
 */
export function extractImportedModelInterfaces(specContent, selectedNames) {
    let parsed;
    try {
        parsed = yaml.load(specContent);
    }
    catch {
        // Unparseable content — treat every select as remaining (caller falls back).
        return { interfaces: [], emitted: [], remaining: [...selectedNames] };
    }
    const models = collectModels(parsed);
    const interfaces = [];
    const emitted = [];
    const remaining = [];
    for (const sel of selectedNames) {
        // Dotted entries are operations, never models.
        if (sel.includes('.')) {
            remaining.push(sel);
            continue;
        }
        const model = models.get(sel);
        if (model) {
            interfaces.push(emitInterface(sel, model));
            emitted.push(sel);
        }
        else {
            remaining.push(sel);
        }
    }
    return { interfaces, emitted, remaining };
}
//# sourceMappingURL=imported-model-types.js.map