/**
 * `zod-schemas` adapter — engines 6.30.2+, TODO #51 phase 2B.
 *
 * Consumes `facts.constSchemas` (populated by phase 1 from GitNexus's
 * Const nodes when capability is true) and merges Zod field declarations
 * into matching entity attributes. Closes the data-driven-codebase gap
 * Dom Williams flagged in the 2026-05-06 review: idle-meta uses Zod
 * heavily for runtime validation, and previously the analyse pipeline
 * dropped those declarations entirely.
 *
 * Matching: a schema named `<EntityName>Schema` (or `<EntityName>Shape`)
 * maps to a detected entity `<EntityName>`. Case-sensitive PascalCase
 * match; the `Schema` / `Shape` suffix is stripped before comparison.
 *
 * Parsing: regex-only on the immediate `z.object({...})` field list.
 * Handles the common Zod patterns:
 *   - z.string() / z.number() / z.boolean() / z.date() → scalar types
 *   - .optional() / .nullable() / .nullish() → required: false
 *   - .email() / .url() / .uuid() → no specly slot for those today
 *     (validation hints), but type stays String
 *   - z.array(z.X) → not surfaced as a specly attribute today (specly
 *     attributes are typically scalar); emit comment in description
 *   - z.literal('foo') / z.enum([...]) → emit as `values=[...]`-style
 *     hint in description (specly schema supports `values=` modifier)
 *
 * Not handled (silently skipped):
 *   - z.object spread (`...UserBase.shape`) — can't resolve without
 *     cross-schema graph walk
 *   - z.intersection / z.union — composite types
 *   - .merge() / .extend() — schema composition
 *   - Discriminated unions
 *
 * Skipped fields stay out of the merged entity. Other adapters (TSI
 * walker, etc.) may have already populated the entity from interface
 * declarations; this adapter ADDS Zod-only fields rather than
 * overwriting existing ones.
 */
/**
 * Parse a `z.object({...})` declaration into its immediate field list.
 *
 * The content argument is the RAW RHS of `export const X = ...` — may
 * include leading whitespace, trailing semicolon, etc. Best-effort
 * brace-balance scan to find the object literal, then field-by-field
 * regex extraction. Returns empty array if the content doesn't match
 * `z.object({...})` shape.
 */
export function parseZodObjectShape(content) {
    // Locate `z.object(` — accept whitespace + leading `export const X = `.
    const objMatch = content.match(/z\s*\.\s*object\s*\(\s*\{/);
    if (!objMatch)
        return [];
    // Find matching `}` for the object literal.
    const startIdx = objMatch.index + objMatch[0].length;
    const fieldsStart = startIdx - 1; // points at the `{`
    const fieldsEnd = findMatchingBrace(content, fieldsStart);
    if (fieldsEnd < 0)
        return [];
    const inner = content.slice(fieldsStart + 1, fieldsEnd);
    // Split by top-level commas, respecting nested braces / parens / strings.
    const fieldDecls = splitTopLevelCommas(inner);
    const out = [];
    for (const decl of fieldDecls) {
        // Field shape: `fieldName: <zodExpression>` or `'fieldName': <zodExpression>`.
        const trimmed = decl.trim();
        if (!trimmed)
            continue;
        const m = trimmed.match(/^['"]?([a-zA-Z_$][\w$]*)['"]?\s*:\s*([\s\S]+)$/);
        if (!m)
            continue;
        const fieldName = m[1];
        const zodExpr = m[2].trim().replace(/[,;]+\s*$/, '');
        const parsed = parseZodExpression(zodExpr);
        if (!parsed)
            continue;
        out.push({ name: fieldName, ...parsed });
    }
    return out;
}
/**
 * Map a single Zod expression like `z.string().optional()` or
 * `z.number().int().min(0)` to specly type + modifiers.
 */
function parseZodExpression(expr) {
    // Detect base type — first `z.X(` call.
    const baseMatch = expr.match(/^z\s*\.\s*([a-zA-Z]+)\s*\(/);
    if (!baseMatch) {
        // Could be a custom schema reference (e.g. `userBaseSchema`) — skip
        // since we can't resolve it without a cross-schema graph.
        return null;
    }
    const baseType = baseMatch[1];
    // Detect optional/nullable/nullish chain markers.
    const required = !/\.\s*(optional|nullable|nullish)\s*\(/.test(expr);
    // Detect validation hints.
    let validationHint;
    for (const hint of ['email', 'url', 'uuid', 'cuid', 'cuid2', 'datetime', 'ip', 'date']) {
        if (new RegExp(`\\.\\s*${hint}\\s*\\(`).test(expr)) {
            validationHint = hint;
            break;
        }
    }
    // Map base type → specly vocabulary.
    let declaredType;
    switch (baseType) {
        case 'string':
            declaredType = 'String';
            break;
        case 'number':
            declaredType = 'Number';
            break;
        case 'bigint':
            declaredType = 'Number';
            break;
        case 'boolean':
            declaredType = 'Boolean';
            break;
        case 'date':
            declaredType = 'DateTime';
            break;
        case 'literal': {
            // z.literal('foo') → String with values=['foo']
            const litMatch = expr.match(/z\s*\.\s*literal\s*\(\s*['"]([^'"]+)['"]\s*\)/);
            if (litMatch) {
                return { declaredType: 'String', required, values: [litMatch[1]] };
            }
            return { declaredType: 'String', required };
        }
        case 'enum': {
            // z.enum(['a', 'b', 'c']) → String with values=['a','b','c']
            const enumMatch = expr.match(/z\s*\.\s*enum\s*\(\s*\[([^\]]+)\]/);
            if (enumMatch) {
                const items = enumMatch[1]
                    .split(',')
                    .map((s) => s.trim().replace(/^['"]|['"]$/g, ''))
                    .filter(Boolean);
                return { declaredType: 'String', required, values: items };
            }
            return { declaredType: 'String', required };
        }
        case 'array':
            // z.array(z.string()) — specly attributes don't model arrays as
            // first-class today; emit Json as a placeholder.
            declaredType = 'Json';
            break;
        case 'object':
            // Nested object — emit Json placeholder.
            declaredType = 'Json';
            break;
        case 'record':
        case 'map':
            declaredType = 'Json';
            break;
        case 'union':
        case 'intersection':
        case 'discriminatedUnion':
            // Composite types — Json placeholder.
            declaredType = 'Json';
            break;
        case 'unknown':
        case 'any':
            declaredType = 'Json';
            break;
        case 'null':
        case 'undefined':
            // Specly doesn't have these as standalone scalar types — skip.
            return null;
        default:
            // Unknown / future Zod type — fall back to String.
            declaredType = 'String';
    }
    return { declaredType, required, validationHint };
}
/**
 * Strip the `Schema` or `Shape` suffix from a const name to get the
 * candidate entity name. Also handles lowercased first-char convention
 * (`userSchema` → `User`).
 */
function schemaNameToEntityName(schemaName) {
    // First, strip Schema / Shape suffix.
    const m = schemaName.match(/^(.+?)(Schema|Shape)$/);
    if (!m)
        return null;
    let base = m[1];
    // PascalCase the first char if it's lowercase (e.g. `user` → `User`).
    if (base.length > 0 && base[0] >= 'a' && base[0] <= 'z') {
        base = base[0].toUpperCase() + base.slice(1);
    }
    return base;
}
export function mergeZodSchemasIntoEntities(input) {
    let entitiesEnriched = 0;
    let fieldsAdded = 0;
    // Index entities by name for O(1) lookup.
    const entityByName = new Map(input.entities.map((e) => [e.name, e]));
    for (const schema of input.schemas) {
        // Only process schemas where content shape suggests Zod
        // (library==='zod' OR content explicitly references z.object).
        const isZodLike = (schema.library === 'zod')
            || /z\s*\.\s*object\s*\(/.test(schema.content);
        if (!isZodLike)
            continue;
        const entityName = schemaNameToEntityName(schema.name);
        if (!entityName)
            continue;
        const entity = entityByName.get(entityName);
        if (!entity)
            continue;
        const fields = parseZodObjectShape(schema.content);
        if (fields.length === 0)
            continue;
        if (!entity.attributes)
            entity.attributes = [];
        const existingAttrNames = new Set(entity.attributes.map((a) => a.name));
        let added = 0;
        for (const f of fields) {
            if (existingAttrNames.has(f.name))
                continue; // don't override existing
            entity.attributes.push({
                name: f.name,
                declaredType: f.declaredType,
                required: f.required,
            });
            existingAttrNames.add(f.name);
            added++;
        }
        if (added > 0) {
            entitiesEnriched++;
            fieldsAdded += added;
        }
    }
    return { entitiesEnriched, fieldsAdded };
}
// ─── Helpers ────────────────────────────────────────────────────────
function findMatchingBrace(s, openIdx) {
    let depth = 1;
    let inString = null;
    for (let i = openIdx + 1; i < s.length; i++) {
        const c = s[i];
        if (inString) {
            if (c === '\\') {
                i++;
                continue;
            }
            if (c === inString)
                inString = null;
            continue;
        }
        if (c === '"' || c === "'" || c === '`') {
            inString = c;
            continue;
        }
        if (c === '{')
            depth++;
        else if (c === '}') {
            depth--;
            if (depth === 0)
                return i;
        }
    }
    return -1;
}
function splitTopLevelCommas(s) {
    const out = [];
    let depth = 0;
    let inString = null;
    let start = 0;
    for (let i = 0; i < s.length; i++) {
        const c = s[i];
        if (inString) {
            if (c === '\\') {
                i++;
                continue;
            }
            if (c === inString)
                inString = null;
            continue;
        }
        if (c === '"' || c === "'" || c === '`') {
            inString = c;
            continue;
        }
        if (c === '(' || c === '[' || c === '{')
            depth++;
        else if (c === ')' || c === ']' || c === '}')
            depth--;
        else if (c === ',' && depth === 0) {
            out.push(s.slice(start, i));
            start = i + 1;
        }
    }
    if (start < s.length)
        out.push(s.slice(start));
    return out;
}
//# sourceMappingURL=zod-schemas.js.map