/**
 * Type-mapping functions: convert a Property into the type string for each context
 * (Sequelize, GraphQL, JavaScript, DTO, Swagger, etc.).
 */
import { toCamelCase, toPascalCase } from '../utils/string-utils.js';
import { WidgetType } from './widget-type.js';
import { DASHBOARD_COLUMN_TYPES, DTO_TYPES, GENDER, GRAPHQL_TYPES, JAVASCRIPT_MODEL_TYPES, JAVASCRIPT_TYPES, SEQUELIZE_TYPES, SWAGGER_TYPES, } from './config.js';
import { loadSchema, parseBoundedContextModule } from './schema-loader.js';
/** Get the effective property name (handles many-to-many → singularNameIds). */
export function getPropertyName(property) {
    if (property.relationship?.type === 'many-to-many') {
        return `${property.relationship.singularName}Ids`;
    }
    return property.name;
}
/**
 * Get the singular name for a relationship property — used to derive stable
 * per-relation identifiers in generated code (e.g. `<rel>Options`,
 * `<rel>Columns` inputs in form components, `<rel>Options$` observables in
 * resolvers).
 *
 * Convention (mirrors what YAML schemas declare):
 * - Many-to-many: `relationship.singularName` is required (e.g. `role` for a
 *   `roleIds` array property).
 * - Many-to-one / one-to-one / one-to-many: `relationship.field` carries the
 *   singular target name (e.g. `boundedContext` for a `boundedContextId` FK);
 *   `singularName` is typically NOT declared on these.
 *
 * Falls back through both. Returns `''` if the property has no relationship —
 * callers should only invoke this for relationship properties.
 */
export function getRelationshipSingularName(property) {
    return property.relationship?.singularName ?? property.relationship?.field ?? '';
}
/** Get enum options, uppercased and trimmed. */
export function getPropertyEnumOptions(property) {
    if (property.type === 'array' &&
        property.arrayOptions?.type === 'enum') {
        return property.arrayOptions.enumOptions?.map((o) => o.trim().toUpperCase());
    }
    return property.enumOptions?.map((o) => o.trim().toUpperCase());
}
/** Get enum options as a single quoted string: 'A','B','C' */
export function getPropertyStringEnumOptions(property) {
    return (getPropertyEnumOptions(property)
        ?.map((o) => `'${o}'`)
        .join(',') ?? '');
}
/** Generate union literal type for enum: 'ACTIVE' | 'INACTIVE' */
export function getEnumUnionLiteralType(property) {
    const options = getPropertyEnumOptions(property);
    if (!options || options.length === 0)
        return 'string';
    return options.map((o) => `'${o}'`).join(' | ');
}
/** Get the reference key for a relationship (defaults to 'id'). */
export function getReferenceKey(property) {
    return property.relationship?.key ?? 'id';
}
/** Get the default value for a property, formatted for code output. */
export function getDefaultValue(property) {
    if (property.defaultValue === undefined)
        return undefined;
    if (typeof property.defaultValue === 'boolean' ||
        typeof property.defaultValue === 'number') {
        return property.defaultValue;
    }
    if (property.type === 'array') {
        if (Array.isArray(property.defaultValue)) {
            const items = property.defaultValue
                .map((item) => `'${item}'`)
                .join(', ');
            return `[${items}]`;
        }
        return property.defaultValue;
    }
    return `'${property.defaultValue}'`;
}
export function getSequelizeType(property) {
    let param;
    if (property.type === 'char' && property.length !== undefined)
        param = property.length;
    else
        switch (property.type) {
            case 'array': {
                param = property.arrayOptions;
                break;
            }
            case 'decimal': {
                param = property.decimals;
                break;
            }
            case 'enum': {
                param = property;
                break;
            }
            case 'varchar': {
                param = property;
                break;
            }
            default: {
                param = property.length;
            }
        }
    return SEQUELIZE_TYPES[property.type](param);
}
export function getJavascriptType(property) {
    if (property.type === 'relationship') {
        return `${property.relationship?.aggregateName}[]`;
    }
    return JAVASCRIPT_TYPES[property.type];
}
export function getJavascriptModelType(property) {
    if (property.relationship?.type === 'many-to-many') {
        return JAVASCRIPT_TYPES.manyToMany;
    }
    if (property.type === 'relationship') {
        return `${property.relationship?.aggregateName}[]`;
    }
    if (property.type === 'array' && property.arrayOptions) {
        return `${JAVASCRIPT_MODEL_TYPES[property.arrayOptions.type]}[]`;
    }
    return JAVASCRIPT_MODEL_TYPES[property.type];
}
function getJavascriptCrudType(property) {
    if (property.relationship?.type === 'many-to-many') {
        return JAVASCRIPT_TYPES.manyToMany;
    }
    if (property.type === 'relationship') {
        return `${property.relationship?.aggregateName}[]`;
    }
    if (property.type === 'array' && property.arrayOptions) {
        return `${JAVASCRIPT_TYPES[property.arrayOptions.type]}[]`;
    }
    if (property.type === 'enum') {
        return getEnumUnionLiteralType(property);
    }
    return JAVASCRIPT_TYPES[property.type];
}
export const getJavascriptCreateType = getJavascriptCrudType;
export const getJavascriptUpdateType = getJavascriptCrudType;
export const getJavascriptDeleteType = getJavascriptCrudType;
export const getJavascriptResponseType = getJavascriptCrudType;
export function getGraphqlType(property) {
    if (property.relationship?.type === 'one-to-many' ||
        property.relationship?.type === 'many-to-many') {
        return `[${property.relationship.aggregateName}]`;
    }
    if (property.relationship?.type === 'many-to-one' ||
        property.relationship?.type === 'one-to-one') {
        return property.relationship.aggregateName;
    }
    if (property.type === 'array' && property.arrayOptions) {
        return `[${GRAPHQL_TYPES[property.arrayOptions.type]}]`;
    }
    return GRAPHQL_TYPES[property.type];
}
export function getGraphqlCreateType(property, resolveModuleName, resolveBcName) {
    if (property.relationship?.type === 'many-to-many') {
        return GRAPHQL_TYPES.manyToMany;
    }
    if (property.relationship?.type === 'one-to-one' &&
        !property.relationship.field &&
        resolveModuleName &&
        resolveBcName) {
        const bc = resolveBcName(property.relationship.modulePath);
        const mod = resolveModuleName(property.relationship.modulePath);
        return `${toPascalCase(bc ?? '')}Create${toPascalCase(mod ?? '')}Input`;
    }
    if (property.type === 'array' && property.arrayOptions) {
        return `[${GRAPHQL_TYPES[property.arrayOptions.type]}]`;
    }
    return GRAPHQL_TYPES[property.type];
}
export function getGraphqlUpdateType(property, resolveModuleName, resolveBcName) {
    if (property.relationship?.type === 'many-to-many') {
        return GRAPHQL_TYPES.manyToMany;
    }
    if (property.relationship?.type === 'one-to-one' &&
        !property.relationship.field &&
        resolveModuleName &&
        resolveBcName) {
        const bc = resolveBcName(property.relationship.modulePath);
        const mod = resolveModuleName(property.relationship.modulePath);
        return `${toPascalCase(bc ?? '')}Update${toPascalCase(mod ?? '')}Input`;
    }
    if (property.type === 'array' && property.arrayOptions) {
        return `[${GRAPHQL_TYPES[property.arrayOptions.type]}]`;
    }
    return GRAPHQL_TYPES[property.type];
}
export function getDtoType(property) {
    return DTO_TYPES[property.type](property);
}
export function getSwaggerType(property) {
    return SWAGGER_TYPES[property.type];
}
export function getColumnDataType(property) {
    return DASHBOARD_COLUMN_TYPES[property.type];
}
function loadRelationshipSchema(modulePath) {
    try {
        const { boundedContextName, moduleName } = parseBoundedContextModule(modulePath);
        return loadSchema(boundedContextName, moduleName);
    }
    catch {
        return null;
    }
}
/** Get module name (singular) from a relationship modulePath like 'iam/account'. */
export function getModuleNameFromPropertyRelationship(modulePath) {
    return loadRelationshipSchema(modulePath)?.moduleName ?? null;
}
/** Get module names (plural) from a relationship modulePath like 'iam/account'. */
export function getModuleNamesFromPropertyRelationship(modulePath) {
    return loadRelationshipSchema(modulePath)?.moduleNames ?? null;
}
/** Get aggregate name from a relationship modulePath. */
export function getAggregateNameFromPropertyRelationship(modulePath) {
    return loadRelationshipSchema(modulePath)?.aggregateName ?? null;
}
/** Get bounded context name from a relationship modulePath. */
export function getBoundedContextNameFromPropertyRelationship(modulePath) {
    return loadRelationshipSchema(modulePath)?.boundedContextName ?? null;
}
/**
 * Get the gendered i18n key suffix (`'F'` | `'M'`) for the module referenced
 * by a relationship modulePath. Used by partials that render buttons/toasts
 * about the RELATED module (e.g. "New Tag" inside the User detail page) —
 * the gender must match that module's `front.gender`, not the host module's.
 *
 * Defaults to `'M'` (masculine) when the related schema cannot be loaded or
 * declares no explicit gender.
 */
export function getGenderKeyFromPropertyRelationship(modulePath) {
    return loadRelationshipSchema(modulePath)?.front?.gender === GENDER.FEMININE ? 'F' : 'M';
}
/** Get aggregate properties from a relationship modulePath. */
export function getPropertiesFromPropertyRelationship(modulePath) {
    return loadRelationshipSchema(modulePath)?.aggregateProperties ?? null;
}
/** Get full schema from a relationship modulePath. */
export function getSchemaFromPropertyRelationship(modulePath) {
    return loadRelationshipSchema(modulePath);
}
/** CSS column class for a form group field based on property type/length. */
export function calculateFormGroupCol(property) {
    switch (property.type) {
        case 'boolean': {
            return 'col-4';
        }
        case 'char': {
            if (property.length === undefined || property.length === 0)
                return 'col-12';
            if (property.length < 11)
                return 'col-2';
            if (property.length < 31)
                return 'col-4';
            if (property.length < 51)
                return 'col-6';
            if (property.length < 71)
                return 'col-8';
            if (property.length < 91)
                return 'col-10';
            return 'col-12';
        }
        case 'date':
        case 'enum':
        case 'timestamp': {
            return 'col-4';
        }
        case 'decimal': {
            if (!property.decimals)
                return 'col-12';
            if (property.decimals[0] < 11)
                return 'col-2';
            if (property.decimals[0] < 31)
                return 'col-4';
            if (property.decimals[0] < 51)
                return 'col-6';
            if (property.decimals[0] < 71)
                return 'col-8';
            if (property.decimals[0] < 91)
                return 'col-10';
            return 'col-12';
        }
        case 'id': {
            if (property.widget?.type === WidgetType.GRID_SELECT_ELEMENT)
                return 'col-start-1 col-6';
            return 'col-6';
        }
        case 'int':
        case 'smallint': {
            if (!property.maxLength)
                return 'col-12';
            if (property.maxLength < 11)
                return 'col-2';
            if (property.maxLength < 31)
                return 'col-4';
            if (property.maxLength < 51)
                return 'col-6';
            if (property.maxLength < 71)
                return 'col-8';
            if (property.maxLength < 91)
                return 'col-10';
            return 'col-12';
        }
        case 'relationship': {
            if (property.widget?.type === WidgetType.MULTIPLE_SELECT)
                return 'col-6';
            return 'col-12';
        }
        case 'text': {
            return 'col-12';
        }
        case 'varchar': {
            if (!property.maxLength)
                return 'col-12';
            if (property.maxLength < 11)
                return 'col-2';
            if (property.maxLength < 31)
                return 'col-4';
            if (property.maxLength < 51)
                return 'col-6';
            if (property.maxLength < 71)
                return 'col-8';
            if (property.maxLength < 91)
                return 'col-10';
            return 'col-12';
        }
        default: {
            return 'col-12';
        }
    }
}
/** Whether a property should be rendered (filters out internal i18n fields, etc.). */
export function isAllowProperty(moduleName, property, options = {}) {
    const { allowAutoIncrement = true, allowManyToManyRelationShip = true, allowOneToManyRelationShip = false, allowReadonly = true, } = options;
    if (property.isI18n) {
        const camelMod = toCamelCase(moduleName);
        return (property.name !== 'id' &&
            property.name !== 'rowId' &&
            property.name !== camelMod + 'Id' &&
            property.name !== 'createdAt' &&
            property.name !== 'updatedAt' &&
            property.name !== 'deletedAt');
    }
    if (property.autoIncrement && !allowAutoIncrement)
        return false;
    if (property.readonly && !allowReadonly)
        return false;
    if (property.relationship?.type === 'one-to-many')
        return allowOneToManyRelationShip;
    if (property.relationship?.type === 'many-to-many')
        return allowManyToManyRelationShip;
    return true;
}
/** Returns 'I18n' (or 'i18n-' when `kebab`) if the property is i18n, otherwise empty string. */
export function addI18nPropertySignature(property, kebab = false) {
    return property.isI18n ? (kebab ? 'i18n-' : 'I18n') : '';
}
/** Wrapper for getSequelizeType that accepts an optional config argument (ignored). */
export function getPropertySequelizeType(property, _config) {
    return getSequelizeType(property);
}
const STRING_TYPES_REQUIRING_MIN_LENGTH = new Set(['char', 'text', 'varchar']);
export function buildFieldSchemaEntries(properties) {
    const out = [];
    for (const prop of properties) {
        if (prop.autoIncrement)
            continue;
        const isShape2 = prop.type === 'relationship' && prop.relationship !== undefined;
        const isShape1 = prop.type !== 'relationship' && prop.relationship !== undefined;
        if (isShape2 && prop.relationship) {
            out.push({
                cardinality: cardinalityOfShape2(prop.relationship.type, prop),
                kind: 'relation',
                name: prop.name,
                targetAggregate: prop.relationship.aggregateName,
                targetModulePath: prop.relationship.modulePath,
            });
            continue;
        }
        out.push(makeScalarEntry(prop));
        if (isShape1 && prop.relationship) {
            out.push({
                cardinality: cardinalityOfShape1(prop.relationship.type),
                kind: 'relation',
                name: resolveFieldName(prop),
                targetAggregate: prop.relationship.aggregateName,
                targetModulePath: prop.relationship.modulePath,
            });
        }
    }
    assertNoNameCollisions(out);
    return out;
}
function assertNoNameCollisions(entries) {
    const seen = new Map();
    for (const entry of entries) {
        const existing = seen.get(entry.name);
        if (existing) {
            throw new Error(`Field schema name collision: "${entry.name}" is emitted by both a ${existing.kind} entry and a ${entry.kind} entry. Rename one of the contributing properties (e.g. via relationship.field).`);
        }
        seen.set(entry.name, entry);
    }
}
function cardinalityOfShape1(type) {
    if (type === 'many-to-one' || type === 'one-to-one')
        return 'one';
    throw new Error(`Unsupported shape 1 relationship type: ${type}`);
}
function cardinalityOfShape2(type, _prop) {
    if (type === 'one-to-one')
        return 'one';
    if (type === 'one-to-many' || type === 'many-to-many')
        return 'many';
    throw new Error(`Unsupported shape 2 relationship type: ${type}`);
}
function resolveFieldName(prop) {
    if (prop.relationship?.field)
        return prop.relationship.field;
    if (/Id$/i.test(prop.name))
        return prop.name.replace(/Id$/i, '');
    const aggregate = prop.aggregateName ?? '<unknown>';
    throw new Error(`Property "${prop.name}" in aggregate "${aggregate}" declares a relationship but omits relationship.field and does not end in "Id". Add relationship.field explicitly.`);
}
function makeScalarEntry(prop) {
    const entry = {
        kind: 'scalar',
        name: prop.name,
        type: prop.type,
    };
    if (prop.nullable !== undefined)
        entry.nullable = prop.nullable;
    const maxLength = prop.maxLength ?? prop.length;
    if (maxLength !== undefined)
        entry.maxLength = maxLength;
    if (prop.type === 'enum' && Array.isArray(prop.enumOptions) && prop.enumOptions.length > 0) {
        entry.enumOptions = prop.enumOptions;
    }
    if (Array.isArray(prop.decimals) && prop.decimals.length === 2) {
        entry.decimals = [prop.decimals[0], prop.decimals[1]];
    }
    if (STRING_TYPES_REQUIRING_MIN_LENGTH.has(prop.type) && prop.nullable === false) {
        entry.rules = { minLength: 1 };
    }
    return entry;
}
export function buildFieldSchemaImports(entries) {
    const byPath = new Map();
    for (const entry of entries) {
        if (entry.kind !== 'relation')
            continue;
        byPath.set(`@app/${entry.targetModulePath}`, `${entry.targetAggregate}FieldSchema`);
    }
    return [...byPath.entries()].map(([path, identifier]) => ({
        items: [identifier],
        path,
    }));
}
// Aliases — templates reference these functions with the "Property" prefix/suffix.
export const getDefaultValueProperty = getDefaultValue;
export const getDtoTypeProperty = getDtoType;
export const getGraphqlTypeProperty = getGraphqlType;
export const getJavascriptModelTypeProperty = getJavascriptModelType;
export const getPropertyColumnDataType = getColumnDataType;
export const getPropertyJavascriptCreateType = getJavascriptCreateType;
export const getPropertyJavascriptDeleteType = getJavascriptDeleteType;
export const getPropertyJavascriptResponseType = getJavascriptResponseType;
export const getPropertyJavascriptType = getJavascriptType;
export const getPropertyJavascriptUpdateType = getJavascriptUpdateType;
export const getReferenceKeyProperty = getReferenceKey;
export const getSwaggerTypeProperty = getSwaggerType;
