/**
 * Library-runtime emitter — V2 Phase 3, Round 1.
 *
 * Handles `topology: 'library'` factories: providers that are consumed
 * as in-process imports, with no deployment instance entry.
 *
 * For each `import:` entry the consumer declares, this emitter produces:
 *   clients/<provider-name>.ts   — typed client module
 *
 * The typed client module:
 *   - Exports a `<ProviderName>Client` TypeScript interface matching
 *     the selected operations from `import.select`.
 *   - Exports a `<providerName>Client` constant that implements the
 *     interface by delegating to the resolved provider:
 *       * source = 'local'  → relative import to the provider's module.
 *       * source = 'npm'    → `import { X } from '<package-specifier>'`.
 *       * source = 'registry-*' → TODO comment + runtime throw (Round 1).
 *
 * The consuming controller (SalesReportController.ts, emitted by the
 * per-action-emitter's mechanical scaffold) imports this client module
 * and receives the client via constructor injection.
 *
 * --- Select grammar ---
 *
 * `import.select` entries follow two forms (V2 D2 resolution):
 *   - Dotted: `Charge.create`  → typed function for the specific operation.
 *   - Bare:   `AuthService`    → re-export of the entity's interface.
 *
 * Both forms share the same file; the emitter deduplicates by head entity.
 *
 * --- Round 1 limitations ---
 *
 * - No actual operation signature lookup (operations are typed as
 *   `(...args: unknown[]) => Promise<unknown>` unless the spec is provided
 *   and the target operation can be located). Exact signatures land in
 *   Round 2 when the resolver wires in full cross-spec type lookup.
 * - Registry sources: the resolver fetches real content (the registry resolver
 *   is wired). For `registry-component` the fetched `.specly` is persisted next
 *   to the client (`clients/<from>.imported.specly`) so the consume-half is
 *   non-lossy.
 *
 * --- Round 2 (model libraries) ---
 *
 * For `registry-component` imports that select MODELS (value objects /
 * entities — bare names matching a model in the fetched spec), the emitter
 * now produces REAL TypeScript interfaces (`export interface Money { ... }`)
 * derived from the imported model's attributes, instead of a throwing client.
 * See `imported-model-types.ts`. These are types, not runtime values, so no
 * client constant is emitted and `imports` stays empty — the consumer imports
 * the interfaces directly. Operation imports (dotted `Service.op` selects) and
 * registry-factory sources still emit the Round-1 throwing client (their
 * real-signature wiring is the remaining V2 "Round 2" for service topologies).
 */
import { extractImportedModelInterfaces } from './imported-model-types.js';
import { extractImportedOperationSignatures } from './imported-operation-signatures.js';
/**
 * Parse a select entry into head + optional tail.
 *
 * Dotted: `Charge.create`  → { head: 'Charge', tail: 'create' }
 * Bare:   `AuthService`    → { head: 'AuthService', tail: undefined }
 *
 * The head is the entity/service name; the tail is the operation name.
 * Dotted entries can have multiple tail segments (`A.B.C` → head A, tail 'B.C')
 * but in practice V2 only uses one level (entity.operation).
 */
function parseSelectEntry(entry) {
    const dotIndex = entry.indexOf('.');
    if (dotIndex === -1) {
        return { head: entry, tail: undefined };
    }
    return {
        head: entry.slice(0, dotIndex),
        tail: entry.slice(dotIndex + 1),
    };
}
/**
 * Normalise a `from:` name into an array of words for identifier generation.
 *
 * Handles:
 *   'ProfitCalculator'         → ['profit', 'calculator']
 *   '@stripe/api'              → ['stripe', 'api']   (scope + package combined)
 *   '@specverse/gitnexus-adapter' → ['specverse', 'gitnexus', 'adapter']
 *   'gitnexus'                 → ['gitnexus']
 */
function toWords(fromName) {
    // For scoped packages (@scope/pkg), replace the / with a space and strip @.
    const normalised = fromName.replace(/^@/, '').replace(/\//g, '-');
    // Split on camelCase boundaries and non-alphanumeric runs.
    return normalised
        .replace(/([a-z])([A-Z])/g, '$1 $2')
        .replace(/[^a-zA-Z0-9]+/g, ' ')
        .trim()
        .split(' ')
        .filter(Boolean)
        .map((w) => w.toLowerCase());
}
/**
 * Convert a from-name to a safe identifier prefix (lower-camelCase, no
 * special characters). Used for the client constant name.
 *
 * Examples:
 *   'ProfitCalculator'   → 'profitCalculator'
 *   '@stripe/api'        → 'stripeApi'
 *   'gitnexus'           → 'gitnexus'
 */
function toClientName(fromName) {
    const words = toWords(fromName);
    if (words.length === 0)
        return 'unknownClient';
    return (words[0] +
        words
            .slice(1)
            .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
            .join(''));
}
/**
 * Convert a from-name to a PascalCase interface name suffix.
 *
 * Examples:
 *   'ProfitCalculator'   → 'ProfitCalculator'
 *   '@stripe/api'        → 'StripeApi'
 *   'gitnexus'           → 'Gitnexus'
 */
function toInterfaceName(fromName) {
    const words = toWords(fromName);
    return words.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join('');
}
/**
 * Convert a from-name to a filesystem-safe filename (kebab-case).
 *
 * Examples:
 *   'ProfitCalculator'           → 'profit-calculator'
 *   '@stripe/api'                → 'stripe-api'
 *   '@specverse/gitnexus-adapter'→ 'specverse-gitnexus-adapter'
 *   'gitnexus'                   → 'gitnexus'
 */
function toFileName(fromName) {
    return toWords(fromName).join('-');
}
/**
 * Generate the TypeScript interface for the client type.
 * Uses a placeholder signature `(...args: unknown[]) => Promise<unknown>`
 * in Round 1; Round 2 will wire in real operation signatures.
 */
function generateInterface(interfaceName, selectEntries) {
    const lines = [];
    lines.push(`export type ${interfaceName}Client = {`);
    // Collect unique operations grouped by head entity for clarity.
    // Bare entries produce a `<entity>: unknown` member (shape TBD by Round 2).
    // Dotted entries produce a typed function.
    const seen = new Set();
    for (const entry of selectEntries) {
        if (seen.has(entry))
            continue;
        seen.add(entry);
        const { head, tail } = parseSelectEntry(entry);
        if (tail === undefined) {
            // Bare entity import — expose as an opaque type in Round 1.
            // Round 2 will look up the entity interface and substitute the real type.
            lines.push(`  /** Re-export of ${head} entity interface. Round 2: substitute real type. */`);
            lines.push(`  ${head}: unknown;`);
        }
        else {
            // Dotted operation import — typed function.
            // Round 2 will substitute real input/output types from the spec.
            lines.push(`  /** ${head}.${tail} — Round 2: substitute real input/output types. */`);
            lines.push(`  ${tail}(...args: unknown[]): Promise<unknown>;`);
        }
    }
    lines.push('};');
    return lines.join('\n');
}
/**
 * Generate the import statement block for the client implementation.
 * Returns the import lines and the identifiers made available.
 */
function generateImportBlock(ctx, selectEntries) {
    const { resolved } = ctx;
    const importLines = [];
    // Maps select entry → the identifier available in the module scope.
    const availableIds = new Map();
    if (resolved.source === 'local') {
        // Relative import to the provider's module in the same spec's output tree.
        //
        // The fallback path `../${toFileName(...)}` is a heuristic that
        // assumes the provider lives one level up from the V2 client. In
        // practice the realize pipeline doesn't currently emit per-component
        // aggregate modules (`components/<Provider>/<from>.ts`) — providers
        // are emitted directly to deterministic backend dirs (services/,
        // controllers/, etc.). V2 client files end up with broken module
        // references regardless of which depth we compute. The resolver's
        // explicit `localRelativePath` is the authoritative form when
        // available; the fallback is best-effort.
        //
        // We append `.js` extension when missing — NodeNext module
        // resolution (the realized backend's default) requires explicit
        // extensions on relative imports. Without this, the stub trial
        // surfaced TS2834.
        const rawPath = resolved.localRelativePath ?? `../${toFileName(resolved.resolvedName)}`;
        const relativePath = /\.(m?[jt]sx?|json)$/.test(rawPath) ? rawPath : `${rawPath}.js`;
        // Group dotted entries by head (entity) — one import per head.
        const heads = new Set();
        const bareEntries = [];
        for (const entry of selectEntries) {
            const { head, tail } = parseSelectEntry(entry);
            if (tail !== undefined) {
                heads.add(head);
            }
            else {
                bareEntries.push(head);
            }
        }
        const allImports = [...Array.from(heads), ...bareEntries];
        if (allImports.length > 0) {
            importLines.push(`import { ${allImports.join(', ')} } from '${relativePath}';`);
            for (const id of allImports) {
                availableIds.set(id, id);
            }
        }
        // Map dotted entries: Charge.create → availableIds.get('Charge.create') = 'Charge'
        for (const entry of selectEntries) {
            const { head, tail } = parseSelectEntry(entry);
            if (tail !== undefined) {
                availableIds.set(entry, head);
            }
        }
    }
    else if (resolved.source === 'npm') {
        // npm package import — use package specifier directly.
        const pkg = resolved.packageSpecifier ?? resolved.resolvedName;
        const heads = new Set();
        const bareEntries = [];
        for (const entry of selectEntries) {
            const { head, tail } = parseSelectEntry(entry);
            if (tail !== undefined) {
                heads.add(head);
            }
            else {
                bareEntries.push(head);
            }
        }
        const allImports = [...Array.from(heads), ...bareEntries];
        if (allImports.length > 0) {
            importLines.push(`import { ${allImports.join(', ')} } from '${pkg}';`);
            for (const id of allImports) {
                availableIds.set(id, id);
            }
        }
        for (const entry of selectEntries) {
            const { head, tail } = parseSelectEntry(entry);
            if (tail !== undefined) {
                availableIds.set(entry, head);
            }
        }
    }
    else {
        // registry-component or registry-factory — Round 1 TODO.
        // No real import; the constant body will throw.
    }
    return { importLines, availableIds };
}
/**
 * Generate the client constant — the exported object that implements the
 * typed interface by delegating to the resolved provider.
 */
function generateClientConstant(clientName, interfaceName, selectEntries, resolved, availableIds) {
    const lines = [];
    lines.push(`export const ${clientName}Client: ${interfaceName}Client = {`);
    if (resolved.source === 'registry-component' || resolved.source === 'registry-factory') {
        // Round 1 placeholder — registry resolution not yet implemented.
        lines.push('  // TODO (Round 2): wire in registry resolver for this provider.');
        lines.push(`  // provider: '${resolved.resolvedName}' (source: ${resolved.source})`);
        // Emit a throw for each member so the type is satisfied but blows up
        // clearly at runtime rather than silently returning undefined.
        const seen = new Set();
        for (const entry of selectEntries) {
            if (seen.has(entry))
                continue;
            seen.add(entry);
            const { tail } = parseSelectEntry(entry);
            if (tail === undefined) {
                lines.push(`  get ${entry}(): never { throw new Error('${entry} (${resolved.resolvedName}): imported from registry but not yet realized into code — Round 2'); },`);
            }
            else {
                lines.push(`  ${tail}(..._args: unknown[]): never { throw new Error('${tail} (${resolved.resolvedName}): imported from registry but not yet realized into code — Round 2'); },`);
            }
        }
    }
    else {
        // local or npm — delegate to the imported identifiers.
        const seen = new Set();
        for (const entry of selectEntries) {
            if (seen.has(entry))
                continue;
            seen.add(entry);
            const { head, tail } = parseSelectEntry(entry);
            const srcId = availableIds.get(entry) ?? availableIds.get(head) ?? head;
            if (tail === undefined) {
                // Bare entity — expose directly.
                lines.push(`  ${head}: ${srcId},`);
            }
            else {
                // Dotted operation — delegate as method.
                lines.push(`  ${tail}: ${srcId}.${tail},`);
            }
        }
    }
    lines.push('};');
    return lines.join('\n');
}
/**
 * Build the client interface + constant for a REGISTRY operation import (#81).
 * The interface carries the REAL typed signatures derived from the provider
 * spec (`realSignatures`); the constant is a typed throw per operation — the
 * signature is real (consumers get type-safety + autocomplete), but actually
 * calling a remote operation needs the provider's transport/deployment
 * coordinates, which is separate work. Unresolved selects (`remaining`) keep
 * the Round-1 placeholder shape.
 */
function buildRegistryOperationClient(interfaceName, clientName, providerName, realSignatures, emittedDotted, remaining) {
    const iface = [`export type ${interfaceName}Client = {`, ...realSignatures];
    const constant = [`export const ${clientName}Client: ${interfaceName}Client = {`];
    // Resolved operations — real signature on the interface (already in
    // `realSignatures`); the body throws until transport is wired. The method is
    // contextually typed by the `: ${interfaceName}Client` annotation, so an
    // arg-less throwing impl still satisfies the typed member.
    for (const entry of emittedDotted) {
        const tail = entry.slice(entry.indexOf('.') + 1);
        constant.push(`  ${tail}() { throw new Error('${entry} (${providerName}): operation signature is real, but transport is not wired yet (Round 2 — needs the provider deployment coordinates).'); },`);
    }
    // Unresolved selects — keep the Round-1 placeholder member + throw.
    const seen = new Set();
    for (const entry of remaining) {
        if (seen.has(entry))
            continue;
        seen.add(entry);
        const dot = entry.indexOf('.');
        if (dot === -1) {
            iface.push(`  /** ${entry} — not found in provider spec. */`, `  ${entry}: unknown;`);
            constant.push(`  get ${entry}(): never { throw new Error('${entry} (${providerName}): not found in the provider spec.'); },`);
        }
        else {
            const tail = entry.slice(dot + 1);
            iface.push(`  /** ${entry} — not found in provider spec. */`, `  ${tail}(...args: unknown[]): Promise<unknown>;`);
            constant.push(`  ${tail}(..._args: unknown[]): never { throw new Error('${entry} (${providerName}): not found in the provider spec.'); },`);
        }
    }
    iface.push('};');
    constant.push('};');
    return { interfaceBlock: iface.join('\n'), constantBlock: constant.join('\n') };
}
// ─── Library emitter ─────────────────────────────────────────────────────────
/**
 * Library-runtime emitter implementation.
 *
 * One call to `emit()` processes a single `import:` entry and produces
 * one client file under `clients/<provider-filename>.ts`.
 */
// ── Local (intra-spec) component client (TODO #58 local-import close) ──────
// A `source:'local'` import resolves to a SIBLING component in the same spec,
// realized into the SAME backend — there is no separate deployable to build a
// transport client for; the provider's symbols already live in the flat output
// tree. We emit a thin, tsc-clean client that re-exports the selected
// SERVICES/CONTROLLERS (classes) from their flat emitted location and inlines
// selected MODELS as self-contained interfaces (models have no standalone type
// module in the flat layout), skipping VIEW selects (frontend, no backend
// client). This is a re-export shim suited to the CURRENT flat realize layout;
// the structural alternative (per-component subtrees with real module
// boundaries) is proposal 2026-06-01-COMPONENT-SCOPED-EMISSION.
function lookupLocalComponent(spec, from) {
    const comps = spec?.components;
    if (!comps)
        return null;
    if (Array.isArray(comps)) {
        return comps.find((c) => c?.name === from) ?? null;
    }
    if (typeof comps === 'object') {
        return comps[from] ?? null;
    }
    return null;
}
/** Section can be a name-keyed object or an array of `{name}` — return names. */
function sectionNames(section) {
    if (!section)
        return [];
    if (Array.isArray(section))
        return section.map((e) => e?.name).filter((n) => !!n);
    if (typeof section === 'object')
        return Object.keys(section);
    return [];
}
/** Section can be a name-keyed object or an array of `{name}` — return [name, def] pairs. */
function sectionEntries(section) {
    if (!section)
        return [];
    if (Array.isArray(section)) {
        return section.filter((e) => e?.name).map((e) => [e.name, e]);
    }
    if (typeof section === 'object')
        return Object.entries(section);
    return [];
}
function emitLocalComponentClient(ctx) {
    const { importEntry, spec } = ctx;
    const selectEntries = importEntry.select ?? [];
    const fileName = toFileName(importEntry.from);
    const provider = lookupLocalComponent(spec, importEntry.from);
    // Backend-importable surfaces: services + controllers (operation owners);
    // models (value objects); views are frontend-only and not backend clients.
    const serviceNames = new Set([
        ...sectionNames(provider?.services),
        ...sectionNames(provider?.controllers),
    ]);
    const modelEntries = new Map(sectionEntries(provider?.models));
    const viewNames = new Set(sectionNames(provider?.views));
    const reexportServices = [];
    const modelSelects = [];
    const skippedViews = [];
    const unresolved = [];
    for (const sel of selectEntries) {
        const head = sel.split('.')[0];
        if (serviceNames.has(head))
            reexportServices.push(head); // bare or dotted on a service/controller → re-export the class
        else if (!sel.includes('.') && modelEntries.has(head))
            modelSelects.push(head);
        else if (viewNames.has(head))
            skippedViews.push(head);
        else
            unresolved.push(sel);
    }
    const uniqueServices = [...new Set(reexportServices)];
    let modelInterfaces = [];
    let emittedModels = [];
    if (modelSelects.length > 0) {
        const miniModels = {};
        for (const m of modelSelects)
            miniModels[m] = modelEntries.get(m);
        // JSON is valid YAML — feed the model machinery directly without a dumper.
        const miniSpec = JSON.stringify({ components: { [importEntry.from]: { models: miniModels } } });
        const r = extractImportedModelInterfaces(miniSpec, modelSelects);
        modelInterfaces = r.interfaces;
        emittedModels = r.emitted;
    }
    // Nothing backend-importable (e.g. a view-only import) → emit no client.
    if (uniqueServices.length === 0 && emittedModels.length === 0) {
        const reason = skippedViews.length > 0
            ? `selects only view-layer symbols [${skippedViews.join(', ')}] — no backend client (views are frontend)`
            : `has no backend-importable selects`;
        return { files: [], imports: [], notes: [`V2 local import '${importEntry.from}' ${reason}.`] };
    }
    const parts = [
        '// FACTORY-EMITTED — DO NOT EDIT (R8)',
        `// Local-component client — in-process surface of sibling component '${importEntry.from}'.`,
        `// Provider is realized into the same backend (TODO #58 local-import close).`,
        '',
    ];
    // Re-export services/controllers from their flat emitted location. The client
    // lives at backend/src/components/<Consumer>/clients/<from>.ts; services land
    // flat at backend/src/services/<Name>.ts → reach them via ../../../services.
    for (const svc of uniqueServices) {
        parts.push(`export { ${svc} } from '../../../services/${svc}.js';`);
    }
    if (modelInterfaces.length > 0) {
        parts.push('', `// ${emittedModels.length} imported model type(s): ${emittedModels.join(', ')}`, '', modelInterfaces.join('\n\n'));
    }
    parts.push('');
    const notes = [];
    if (uniqueServices.length > 0)
        notes.push(`Local import '${importEntry.from}' → re-exported ${uniqueServices.length} service(s) [${uniqueServices.join(', ')}].`);
    if (emittedModels.length > 0)
        notes.push(`Local import '${importEntry.from}' → emitted ${emittedModels.length} model interface(s) [${emittedModels.join(', ')}].`);
    if (skippedViews.length > 0)
        notes.push(`Skipped ${skippedViews.length} view select(s) [${skippedViews.join(', ')}] (frontend).`);
    if (unresolved.length > 0)
        notes.push(`Unresolved local selects [${unresolved.join(', ')}] from '${importEntry.from}'.`);
    return { files: [{ path: `clients/${fileName}.ts`, content: parts.join('\n') }], imports: [], notes };
}
export class LibraryRuntimeEmitter {
    topology = 'library';
    async emit(ctx) {
        const { importEntry, resolved, factory, outputDir } = ctx;
        const selectEntries = importEntry.select ?? [];
        // TODO #58 close (local case): when the resolver reports `source: 'local'`
        // (a sibling component in the same spec) without an explicit
        // `localRelativePath`, emit a working re-export/interface client against
        // the flat output tree instead of orphan-skipping. The provider is
        // realized into the SAME backend, so its services/models are reachable;
        // see emitLocalComponentClient. (Pre-6.96.11 this silently emitted nothing,
        // leaving the consumer to fall back to its own imports.)
        if (resolved.source === 'local' && !resolved.localRelativePath) {
            return emitLocalComponentClient(ctx);
        }
        // Derive naming from the import's `from:` field, not the factory name.
        const fileName = toFileName(importEntry.from);
        const clientName = toClientName(importEntry.from);
        const interfaceName = toInterfaceName(importEntry.from);
        // File path relative to outputDir.
        const relFilePath = `clients/${fileName}.ts`;
        const absoluteFilePath = `${outputDir}/${relFilePath}`;
        // ─── Round 2: registry component library — MODELS + OPERATIONS ──────────
        // When the import resolves to a registry component with fetched spec
        // content, emit real artifacts (not a throwing client):
        //   - bare model selects → real TS interfaces (6.80.0 model-library path);
        //   - dotted operation selects → real typed signatures (#81), with any
        //     models their params/returns reference emitted as interfaces too.
        // The operation client BODY still throws (transport isn't wired — that
        // needs the provider's deployment coordinates), but the SIGNATURE is real,
        // so consumers get type-safety. Pure-model imports emit no runtime client.
        if (resolved.source === 'registry-component' && resolved.specContent) {
            const modelResult = extractImportedModelInterfaces(resolved.specContent, selectEntries);
            const opResult = extractImportedOperationSignatures(resolved.specContent, modelResult.remaining);
            if (modelResult.emitted.length > 0 || opResult.emitted.length > 0) {
                const parts = [
                    '// FACTORY-EMITTED — DO NOT EDIT (R8)',
                    '// Generated by library-runtime emitter — V2 Phase 3, Round 2',
                    `// Imported from registry library: ${resolved.resolvedName}@${resolved.resolvedVersion ?? 'latest'}`,
                    `// Source spec persisted at ./${fileName}.imported.specly`,
                    '',
                ];
                // Model interfaces: selected models ∪ models referenced by op signatures.
                const modelIfaces = [...modelResult.interfaces];
                const modelsEmitted = new Set(modelResult.emitted);
                opResult.referencedModels.forEach((name, i) => {
                    if (!modelsEmitted.has(name)) {
                        modelIfaces.push(opResult.modelInterfaces[i]);
                        modelsEmitted.add(name);
                    }
                });
                if (modelIfaces.length > 0) {
                    parts.push(`// ${modelsEmitted.size} model type(s): ${Array.from(modelsEmitted).join(', ')}`, '', modelIfaces.join('\n\n'), '');
                }
                // Operation client (#81): real signatures + typed-throw body; also
                // covers any truly-unresolved selects (placeholder shape).
                let consumerImport;
                if (opResult.emitted.length > 0 || opResult.remaining.length > 0) {
                    const { interfaceBlock, constantBlock } = buildRegistryOperationClient(interfaceName, clientName, resolved.resolvedName, opResult.signatures, opResult.emitted, opResult.remaining);
                    parts.push(interfaceBlock, '', constantBlock, '');
                    consumerImport = { from: `./clients/${fileName}`, specifier: `${clientName}Client` };
                }
                const files = [
                    { path: relFilePath, content: parts.join('\n') },
                    { path: `clients/${fileName}.imported.specly`, content: resolved.specContent },
                ];
                const notes = [];
                if (modelResult.emitted.length > 0) {
                    notes.push(`Registry library '${resolved.resolvedName}' → emitted ${modelResult.emitted.length} model type(s) [${modelResult.emitted.join(', ')}] as TypeScript interfaces (Round 2).`);
                }
                if (opResult.emitted.length > 0) {
                    notes.push(`Emitted ${opResult.emitted.length} real operation signature(s) [${opResult.emitted.join(', ')}] — typed throws until transport is wired (Round 2).`);
                }
                if (opResult.remaining.length > 0) {
                    notes.push(`Unresolved selects [${opResult.remaining.join(', ')}] from '${importEntry.from}' kept placeholder throwing members.`);
                }
                return {
                    files,
                    imports: consumerImport ? [consumerImport] : [],
                    notes,
                };
            }
            // Nothing resolved (no models, no ops) — fall through to existing behaviour.
        }
        // Build file header.
        const headerLines = [
            '// FACTORY-EMITTED — DO NOT EDIT (R8)',
            `// Generated by library-runtime emitter — V2 Phase 3`,
            `// Provider: ${importEntry.from} (${resolved.source}) via factory: ${factory.name}`,
        ];
        if (resolved.source === 'local') {
            headerLines.push(`// Local component: ${resolved.resolvedName}`);
        }
        else if (resolved.source === 'npm') {
            const pkg = resolved.packageSpecifier ?? resolved.resolvedName;
            headerLines.push(`// npm package: ${pkg}`);
        }
        else if (resolved.source === 'registry-component' && resolved.specContent) {
            headerLines.push(`// Registry library: ${resolved.resolvedName}@${resolved.resolvedVersion ?? 'latest'}`);
            headerLines.push(`// Fetched spec persisted at ./${fileName}.imported.specly — realize it to wire real code (Round 2).`);
        }
        else {
            headerLines.push(`// Registry source: ${resolved.resolvedName} (Round 2 TODO)`);
        }
        headerLines.push('');
        // Build import block.
        const { importLines, availableIds } = generateImportBlock(ctx, selectEntries);
        // Build interface + constant.
        const interfaceBlock = generateInterface(interfaceName, selectEntries);
        const constantBlock = generateClientConstant(clientName, interfaceName, selectEntries, resolved, availableIds);
        // Assemble file.
        const parts = [...headerLines];
        if (importLines.length > 0) {
            parts.push(...importLines, '');
        }
        parts.push(interfaceBlock, '', constantBlock, '');
        const content = parts.join('\n');
        const files = [{ path: relFilePath, content }];
        // Registry library: persist the resolver-fetched `.specly` next to the
        // client so the consume-half is non-lossy and Round 2 can realize it into
        // runnable modules. (Round 1: the client still throws at runtime — the
        // imported library isn't realized into importable code yet.)
        let persistedSpecPath;
        if (resolved.source === 'registry-component' && resolved.specContent) {
            persistedSpecPath = `clients/${fileName}.imported.specly`;
            files.push({ path: persistedSpecPath, content: resolved.specContent });
        }
        // Build the import entry for the consumer's controller file.
        const consumerImport = {
            from: `./clients/${fileName}`,
            specifier: `${clientName}Client`,
        };
        // Warnings for Round 1 limitations.
        const notes = [];
        if (selectEntries.length === 0) {
            notes.push(`Warning: import entry from '${importEntry.from}' has empty select: — no client members emitted.`);
        }
        if (resolved.source === 'registry-component' && persistedSpecPath) {
            notes.push(`Registry library '${resolved.resolvedName}' fetched + persisted at ${persistedSpecPath}; ` +
                `client throws until the imported spec is realized (Round 2).`);
        }
        else if (resolved.source === 'registry-component' ||
            resolved.source === 'registry-factory') {
            notes.push(`TODO (Round 2): registry provider '${resolved.resolvedName}' — client constant emits runtime throws until the imported provider is realized.`);
        }
        return {
            files,
            imports: [consumerImport],
            notes: notes.length > 0 ? notes : undefined,
        };
    }
}
//# sourceMappingURL=library.js.map