/**
 * Helpers for the `front.displayField` schema attribute.
 *
 * `front.displayField` carries the human-readable display label of an
 * aggregate and accepts two shapes (auto-detected at codegen time):
 *   - **Identifier shape**: a bare JS identifier (matches
 *     `/^[a-zA-Z_$][a-zA-Z0-9_$]*$/`) naming a single property —
 *     e.g. `'email'`. Returned as-is for accessor-key emission and
 *     property access.
 *   - **Template shape**: an interpolation template containing one or
 *     more `{<identifier>}` placeholders — e.g. `'{code} - {name}'`.
 *     Substituted with `<rowExpr>.<identifier>` when composing a
 *     template-string fragment. Non-matching segments (including `{}`,
 *     `{not.a.field}`, `{123invalid}`) emit verbatim.
 *
 * Default (unset) → `'name'` (identifier shape).
 *
 * Used by the list-component template (delete confirmation toast), the
 * columns template (FK auto-include accessor), and the cross-schema
 * resolver. See `openspec/specs/frontend-module-generator/spec.md` →
 * "Schema — `front.displayField` attribute".
 */
const IDENTIFIER_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
const PLACEHOLDER_GLOBAL_RE = /\{([a-zA-Z_$][a-zA-Z0-9_$]*)\}/g;
const PLACEHOLDER_FIRST_RE = /\{([a-zA-Z_$][a-zA-Z0-9_$]*)\}/;
/**
 * Classify the aggregate's `front.displayField` as identifier or template
 * shape. Default (unset) → `{ kind: 'identifier', field: 'name' }`.
 */
export const getDisplayField = (schema) => {
    const value = schema.front?.displayField;
    if (value === undefined) {
        return { field: 'name', kind: 'identifier' };
    }
    if (IDENTIFIER_RE.test(value)) {
        return { field: value, kind: 'identifier' };
    }
    return { kind: 'template', template: value };
};
/**
 * Compose a JS template-string fragment that renders the aggregate's
 * display label using its `front.displayField`. Suitable for emission
 * inside a backtick-delimited string literal.
 *
 * @param schema   the aggregate's module definition schema
 * @param rowExpr  the row reference expression to substitute into
 *                 placeholders (e.g. `'row'`, `'this.userToDelete()'`,
 *                 `'permission'`)
 * @returns        a string suitable for emission inside a JS
 *                 template-string literal — the caller wraps it with
 *                 `\`...\`` or interpolates as needed
 *
 * Examples (with `rowExpr = 'row'`):
 *   - schema with no displayField → `'${row.name}'`
 *   - schema with displayField='email' → `'${row.email}'`
 *   - schema with displayField='{code} - {name}' → `'${row.code} - ${row.name}'`
 *   - schema with displayField='Plan: {planId}' → `'Plan: ${row.planId}'`
 *   - schema with displayField='{not.a.field}' → `'{not.a.field}'` (literal)
 *   - schema with displayField='{}' → `'{}'` (literal — empty braces)
 */
export const getDisplayFieldExpr = (schema, rowExpr) => {
    const ref = getDisplayField(schema);
    if (ref.kind === 'identifier') {
        return `\${${rowExpr}.${ref.field}}`;
    }
    return ref.template.replaceAll(PLACEHOLDER_GLOBAL_RE, (_match, identifier) => `\${${rowExpr}.${identifier}}`);
};
/**
 * Compose the aggregate's `displayField` as a placeholder template suitable
 * for use as the second argument of the `ToOptionsPipe` runtime pipe in the
 * Aurora frontend lib.
 *
 * Always returns a `{<identifier>}` placeholder template, regardless of
 * whether the schema declares an identifier-shape or template-shape value:
 *   - **Identifier shape** (`'email'`) → returns `'{email}'`.
 *   - **Template shape** (`'{code} - {name}'`) → returns verbatim.
 *   - **Default (unset)** → returns `'{name}'`.
 *
 * Used by the search-select-family relationship partials to emit the pipe
 * argument inside the form-component template. Distinct from
 * `getDisplayFieldExpr`, which composes a `${rowExpr.x}` JS template-string
 * fragment for delete-toast / accessor-key consumers.
 */
export const getDisplayFieldTemplate = (schema) => {
    const ref = getDisplayField(schema);
    if (ref.kind === 'identifier') {
        return `{${ref.field}}`;
    }
    return ref.template;
};
/**
 * Resolve a single property identifier from the aggregate's `displayField`,
 * regardless of whether the schema declares an identifier-shape or
 * template-shape value.
 *
 * Used by consumers that emit a single accessor key (e.g. the FK column
 * `accessorKey: '<rel>.<field>'`) and cannot consume a multi-field
 * template.
 *
 * Resolution rules:
 *   - **Identifier shape** → returns the identifier directly.
 *   - **Template shape** → returns the **first** identifier matched by
 *     the placeholder regex (left-to-right). If no valid placeholder
 *     exists in the template (e.g. `'literal text'`, `'{}'`,
 *     `'{not.a.field}'`), falls back to `'name'`.
 *   - **Default (unset)** → returns `'name'`.
 */
export const getDisplayFieldFirstIdentifier = (schema) => {
    const ref = getDisplayField(schema);
    if (ref.kind === 'identifier') {
        return ref.field;
    }
    const match = ref.template.match(PLACEHOLDER_FIRST_RE);
    return match ? match[1] : 'name';
};
