/**
 * Shared types and utilities for the per-owner realize emit path.
 *
 * Extracted from the (now-deleted) per-action-emitter / per-action-runner /
 * per-action-llm-emit modules when the per-action path retired in engines
 * 6.45.0. Per-owner has been the default since 6.41.0; the per-action
 * escape hatch was scheduled to retire one cycle later (6.42.x) but was
 * pushed by the methodology refactor (6.43.0-6.44.0). This module holds
 * the small set of types + helpers that per-owner-emit and per-owner-runner
 * still need.
 *
 * Three groups of content:
 *
 *   1. **Type re-exports from @specverse/types** — `MatchResult`,
 *      `MatcherFn`, `SharedConvention`, `SharedStepContext`. The
 *      canonical home is @specverse/types (since #54 in engines 6.37);
 *      we re-export here so consumers don't have to know the cross-
 *      package path.
 *
 *   2. **Realize-internal types** — `ActionSpec`, `RealizeContext`,
 *      `CollectedAction`. The shape of "one action under one owner,
 *      ready to dispatch through the matcher and the LLM". These don't
 *      live in @specverse/types because they're emit-pipeline-internal.
 *
 *   3. **Helper functions** — `extractTypescriptBlock` (LLM-output
 *      fence extraction), `collectActions` (walk a spec, build the
 *      per-owner action list), `buildAvailableClientsByOwner` (resolve
 *      V2 component imports to per-owner clients map).
 */
// ─── Helpers ───────────────────────────────────────────────────────────
/**
 * Pull the first ```typescript fenced block out of an LLM response.
 * Tolerates plain ``` (no language tag) when the body starts with
 * recognisably-TS content. Returns null if no block is found.
 */
export function extractTypescriptBlock(text) {
    const tsBlock = text.match(/```(?:typescript|ts)\s*\n([\s\S]*?)```/);
    if (tsBlock?.[1])
        return tsBlock[1].trim();
    // Generic ``` block — accept when the content looks like TS.
    const generic = text.match(/```\s*\n([\s\S]*?)```/);
    if (generic?.[1]) {
        const candidate = generic[1].trim();
        if (/^(const|let|await|return|throw|if|try)\b/m.test(candidate)) {
            return candidate;
        }
    }
    return null;
}
/**
 * Walk an inferred spec and produce the flat list of actions across all
 * controllers and services. Per-owner-runner groups these by `ownerName`
 * to emit one `<Owner>.ai.ts` file per controller / service.
 *
 * Accepts both spec shapes: `{Name: {...}}` keyed object (the canonical
 * analyse-pipeline output) and `[{name, ...}]` array.
 */
export function collectActions(spec) {
    const out = [];
    // Controllers
    const controllerEntries = Array.isArray(spec.controllers)
        ? spec.controllers.map((c) => [c?.name ?? '', c])
        : Object.entries(spec.controllers || {});
    for (const [keyName, controller] of controllerEntries) {
        if (!controller?.actions)
            continue;
        const ownerName = controller.name || keyName;
        if (!ownerName)
            continue;
        const modelName = controller.model ||
            controller.modelReference ||
            ownerName.replace(/Controller$/, '');
        for (const [actionName, actionRaw] of Object.entries(controller.actions)) {
            const action = normalizeActionSpec(actionRaw, actionName);
            if (action.steps.length === 0)
                continue;
            out.push({
                ownerKind: 'controller',
                ownerName,
                modelName,
                actionName,
                spec: { ...action, ownerName, ownerKind: 'controller' },
            });
        }
    }
    // Services
    const serviceEntries = Array.isArray(spec.services)
        ? spec.services.map((s) => [s?.name ?? '', s])
        : Object.entries(spec.services || {});
    for (const [keyName, service] of serviceEntries) {
        if (!service?.operations)
            continue;
        const ownerName = service.name || keyName;
        if (!ownerName)
            continue;
        const modelName = ownerName.replace(/Service$/, '');
        const operationEntries = Array.isArray(service.operations)
            ? service.operations.map((op) => [op.name, op])
            : Object.entries(service.operations);
        for (const [actionName, opRaw] of operationEntries) {
            const action = normalizeActionSpec(opRaw, actionName);
            if (action.steps.length === 0)
                continue;
            out.push({
                ownerKind: 'service',
                ownerName,
                modelName,
                actionName,
                spec: { ...action, ownerName, ownerKind: 'service' },
            });
        }
    }
    return out;
}
function normalizeActionSpec(raw, actionName) {
    const stepsRaw = raw?.steps ?? raw?.implementation?.steps ?? [];
    const steps = Array.isArray(stepsRaw)
        ? stepsRaw
            .map((s) => (typeof s === 'string' ? s : s?.step))
            .filter((s) => typeof s === 'string')
        : [];
    return {
        name: actionName,
        ownerName: '', // filled in by caller
        ownerKind: 'controller', // overwritten by caller
        steps,
        description: raw?.description,
        parameters: raw?.parameters,
        returns: typeof raw?.returns === 'string'
            ? raw.returns
            : raw?.returns
                ? JSON.stringify(raw.returns)
                : undefined,
        requires: raw?.requires ?? raw?.preconditions,
        ensures: raw?.ensures ?? raw?.postconditions,
        publishes: raw?.publishes ?? raw?.events,
    };
}
/**
 * Build a single component's `availableClients` prompt string from its
 * V2 `import:` block. Returns null when the component has no imports.
 */
export function buildAvailableClientsForComponent(component, topologyByFrom) {
    const imports = Array.isArray(component?.import) ? component.import : [];
    if (imports.length === 0)
        return null;
    const lines = [];
    for (const entry of imports) {
        const fromName = entry?.from ?? entry?.package ?? entry?.file ?? '';
        if (!fromName)
            continue;
        const select = Array.isArray(entry?.select) ? entry.select : [];
        if (select.length === 0)
            continue;
        const clientName = toClientName(fromName);
        const topology = topologyByFrom?.get(fromName);
        const topoSuffix = topology ? ` (${topology})` : '';
        for (const sel of select) {
            const dotIndex = sel.indexOf('.');
            if (dotIndex === -1) {
                lines.push(`  - deps.${clientName}  // bare entity export ${sel} from ${fromName}${topoSuffix}`);
            }
            else {
                const operation = sel.slice(dotIndex + 1);
                lines.push(`  - deps.${clientName}.${operation}(input)  // from ${fromName}${topoSuffix}`);
            }
        }
    }
    if (lines.length === 0)
        return null;
    return ['Available imported clients (V2 imports):', ...lines].join('\n');
}
/**
 * Walk a parsed spec and build the per-owner `availableClients` map.
 * V2 imports are component-scoped; every controller and service inside
 * a component shares the same `availableClients` string.
 */
export function buildAvailableClientsByOwner(spec, topologyByFrom) {
    const out = new Map();
    const components = Array.isArray(spec?.components)
        ? spec.components
        : Object.values(spec?.components ?? {});
    for (const component of components) {
        const clientsString = buildAvailableClientsForComponent(component, topologyByFrom);
        if (!clientsString)
            continue;
        const controllerEntries = Array.isArray(component?.controllers)
            ? component.controllers.map((c) => [c?.name ?? '', c])
            : Object.entries(component?.controllers ?? {});
        for (const [keyName, c] of controllerEntries) {
            const ownerName = c?.name || keyName;
            if (!ownerName)
                continue;
            out.set(ownerName, clientsString);
        }
        const serviceEntries = Array.isArray(component?.services)
            ? component.services.map((s) => [s?.name ?? '', s])
            : Object.entries(component?.services ?? {});
        for (const [keyName, s] of serviceEntries) {
            const ownerName = s?.name || keyName;
            if (!ownerName)
                continue;
            out.set(ownerName, clientsString);
        }
    }
    return out;
}
function toClientName(fromName) {
    // Mirrors the library emitter's toWords: scope-strip, slash → dash,
    // split on camelCase boundary AND non-alphanumeric runs, lowercase.
    const normalised = fromName.replace(/^@/, '').replace(/\//g, '-');
    const words = normalised
        .replace(/([a-z])([A-Z])/g, '$1 $2')
        .replace(/[^a-zA-Z0-9]+/g, ' ')
        .trim()
        .split(' ')
        .filter(Boolean)
        .map((w) => w.toLowerCase());
    if (words.length === 0)
        return 'unknownClient';
    return (words[0] +
        words
            .slice(1)
            .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
            .join(''));
}
//# sourceMappingURL=owner-emit-shared.js.map