/**
 * Array-level property filter functions. Each takes Property[] and returns a filtered subset.
 * Built on composable primitives so identical filter chains share implementation.
 *
 * Also re-exports schema-level helpers (e.g. display-field resolvers) under the
 * same namespace so templates can reach them via `it.filters.<name>`.
 */
import { v4 as uuidv4, v5 as uuidv5 } from 'uuid';
import { isPathAllowedByExclusions } from '../utils/path-allowed.js';
import { toCamelCase } from '../utils/string-utils.js';
import { DELETED_AT_FIELD, TIMESTAMP_FIELDS } from './config.js';
import { hasBelongsToDecorator, hasBelongsToManyDecorator, hasColumnDecorator, hasHasManyDecorator, hasHasOneDecorator, isPivot, } from './property-predicates.js';
import { WidgetType } from './widget-type.js';
export { getAdditionalApiFileName, getClassNameAdditionalApi, getResolverNameAdditionalApi, } from '../utils/additional-api.js';
export { getColumnSizingForProperty, } from './column-width.js';
export { getDisplayField, getDisplayFieldExpr, getDisplayFieldFirstIdentifier, } from './display-field.js';
export { getPivotFrontendExcludedFiles, getPivotFrontendKeptFiles, isPivotAggregate, } from './pivot.js';
const excludeTimestamps = (p) => !TIMESTAMP_FIELDS.includes(p.name);
const excludeDeletedAt = (p) => !DELETED_AT_FIELD.includes(p.name);
const excludeOneToMany = (p) => p.relationship?.type !== 'one-to-many';
const excludeOneToOneWithoutField = (p) => !(p.relationship?.type === 'one-to-one' && !p.relationship.field);
const excludeManyToMany = (p) => p.relationship?.type !== 'many-to-many';
const excludeManyToOne = (p) => p.relationship?.type !== 'many-to-one';
/** Base filter: no timestamps, no one-to-many, no one-to-one without field */
function baseWriteFilter(props) {
    return props
        .filter((p) => excludeTimestamps(p))
        .filter((p) => excludeOneToMany(p))
        .filter((p) => excludeOneToOneWithoutField(p));
}
/** Extends base filter with i18n internal field exclusions. */
function crudFilter(props, moduleName) {
    const camelModule = toCamelCase(moduleName);
    const allHaveI18n = props.some((p) => p.isI18n);
    return baseWriteFilter(props)
        .filter((p) => !p.isI18n || p.name !== 'id')
        .filter((p) => !p.isI18n || p.name !== 'rowId')
        .filter((p) => !p.isI18n || p.name !== `${camelModule}Id`)
        .filter((p) => !allHaveI18n || p.name !== 'availableLangs');
}
/** Filter for read-only queries (exclude deletedAt + relationships except 1:1 with field). */
function readQueryFilter(props) {
    return props
        .filter((p) => excludeDeletedAt(p))
        .filter((p) => excludeOneToMany(p))
        .filter((p) => excludeManyToOne(p))
        .filter((p) => excludeManyToMany(p))
        .filter((p) => excludeOneToOneWithoutField(p));
}
export const hasProperties = (props) => Array.isArray(props) && props.length > 0;
export const hasI18nProperties = (props) => props?.some((p) => p.isI18n);
export const hasEnumProperties = (props) => props?.some((p) => p.type === 'enum');
export const hasBooleanProperties = (props) => props?.some((p) => p.type === 'boolean');
export const hasDateProperties = (props) => props?.some((p) => p.type === 'date');
export const hasTimestampProperties = (props) => props
    ?.filter((p) => excludeTimestamps(p))
    .some((p) => p.type === 'timestamp');
export const hasIndexProperties = (props) => props?.some((p) => (p.index === 'index' || p.index === 'unique') && !p.isI18n);
export const hasIndexI18nProperties = (props) => props?.some((p) => (p.index === 'index' || p.index === 'unique') && p.isI18n);
export const hasPivotProperties = (props) => props?.some(isPivot);
export const hasCompositePrimaryKeys = (props) => props.filter((p) => p.primaryKey).length > 1;
export const hasForeignKeyDecoratorProperties = (props) => props?.some((p) => (hasColumnDecorator(p) && p.relationship?.type === 'one-to-one') ||
    (hasColumnDecorator(p) && p.relationship?.type === 'many-to-one'));
export const hasBelongsToManyDecoratorProperties = (props) => props?.some(hasBelongsToManyDecorator);
export const hasHasManyDecoratorProperties = (props) => props?.some(hasHasManyDecorator);
export const hasHasOneDecoratorProperties = (props) => props?.some(hasHasOneDecorator);
export const hasBelongsToDecoratorProperties = (props) => props?.some(hasBelongsToDecorator);
export const getI18nProperties = (props) => props?.filter((p) => p.isI18n);
export const getNotI18nProperties = (props) => props?.filter((p) => !p.isI18n);
export const getEnumProperties = (props) => props?.filter((p) => p.type === 'enum' ||
    (p.type === 'array' && p.arrayOptions?.type === 'enum'));
export const getBooleanProperties = (props) => props?.filter((p) => p.type === 'boolean');
export const getTimestampProperties = (props) => props
    ?.filter((p) => excludeTimestamps(p))
    .filter((p) => p.type === 'timestamp');
export const getDecimalProperties = (props) => props?.filter((p) => p.type === 'decimal');
export const getIntegerProperties = (props) => props?.filter((p) => p.type === 'int');
export const getIntegerUnsignedProperties = (props) => props?.filter((p) => p.type === 'int' && p.unsigned);
export const getMaxLengthProperties = (props) => props?.filter((p) => Boolean(p.maxLength));
export const getMinLengthProperties = (props) => props?.filter((p) => Boolean(p.minLength));
export const getIndexesProperties = (props) => props?.filter((p) => p.index);
export const getPrimaryKeyProperties = (props) => props
    ?.filter((p) => p.primaryKey)
    .filter((p, i, arr) => arr.findIndex((o) => o.name === p.name) === i);
export const getValueObjectsProperties = (props) => props
    ?.filter((p) => excludeOneToMany(p))
    .filter((p) => excludeOneToOneWithoutField(p));
export const getWithoutTimestampsProperties = (props) => props?.filter((p) => excludeTimestamps(p));
export const getWithoutDeletedAtProperties = (props) => props?.filter((p) => excludeDeletedAt(p));
export const getWithoutTimestampsWithoutRelationshipProperties = (props) => props?.filter((p) => excludeTimestamps(p)).filter((p) => !p.relationship);
// Aggregate / Response / Seed / Mapper / Mock all share: exclude one-to-many and one-to-one without field.
const aggregateBaseFilter = (props) => props
    .filter((p) => excludeOneToMany(p))
    .filter((p) => excludeOneToOneWithoutField(p));
export const getAggregateProperties = aggregateBaseFilter;
export const getResponseProperties = aggregateBaseFilter;
export const getMapperProperties = aggregateBaseFilter;
export const getMockProperties = aggregateBaseFilter;
export const getSeedProperties = (props) => props
    .filter((p) => excludeTimestamps(p))
    .filter((p) => excludeOneToMany(p))
    .filter((p) => excludeOneToOneWithoutField(p));
export const getPropertiesWithRelationship = (props) => props?.filter((p) => Boolean(p.relationship));
export const getForeignRelationshipProperties = (props, boundedContextName) => getPropertiesWithRelationship(props).filter((p) => p.relationship?.modulePath?.split('/')[0] !== boundedContextName);
export const getManyToManyRelationshipProperties = (props) => props?.filter((p) => p.relationship?.type === 'many-to-many');
/** Deduplicated relationship getters (avoid duplicate imports for same modulePath). */
function deduplicatedByRelationship(props, type) {
    return props
        .filter((p, i, self) => i ===
        self.findIndex((t) => t.relationship?.modulePath === p.relationship?.modulePath &&
            t.relationship?.aggregateName === p.relationship?.aggregateName))
        .filter((p) => p.relationship?.type === type);
}
export const getWithImportRelationshipOneToOneProperties = (props) => deduplicatedByRelationship(props, 'one-to-one');
export const getWithImportRelationshipManyToOneProperties = (props) => deduplicatedByRelationship(props, 'many-to-one');
export const getWithImportRelationshipOneToManyProperties = (props) => deduplicatedByRelationship(props, 'one-to-many');
export const getWithImportRelationshipManyToManyProperties = (props) => deduplicatedByRelationship(props, 'many-to-many');
export const getWithRelationshipOneToOneProperties = (props) => props?.filter((p) => p.relationship?.type === 'one-to-one');
export const getWithRelationshipOneToOneWithFieldProperties = (props) => getWithRelationshipOneToOneProperties(props).filter((p) => Boolean(p.relationship?.field));
export const getWithRelationshipOneToOneWithoutFieldProperties = (props) => getWithRelationshipOneToOneProperties(props).filter((p) => !p.relationship?.field);
export const getWithRelationshipManyToOneProperties = (props) => props?.filter((p) => p.relationship?.type === 'many-to-one');
export const getWithRelationshipOneToManyProperties = (props) => props?.filter((p) => p.relationship?.type === 'one-to-many');
export const getPrimaryKeyPropertiesFromPivotTable = (props) => hasPivotProperties(props) ? getPrimaryKeyProperties(props) : [];
export const getCreateControllerProperties = crudFilter;
export const getUpdateControllerProperties = crudFilter;
export const getCreateServiceProperties = crudFilter;
export const getUpdateServiceProperties = crudFilter;
export const getUpsertServiceProperties = crudFilter;
export const getTestProperties = crudFilter;
export const getNotNullableProperties = (props, moduleName) => crudFilter(props.filter((p) => p.nullable === false), moduleName);
export const getLengthProperties = (props, moduleName) => crudFilter(props.filter((p) => p.length !== undefined && p.length > 0), moduleName);
/** createItemsService: same as crud but without availableLangs exclusion. */
export function getCreateItemsServiceProperties(props, moduleName) {
    const camelModule = toCamelCase(moduleName);
    return baseWriteFilter(props)
        .filter((p) => !p.isI18n || p.name !== 'id')
        .filter((p) => !p.isI18n || p.name !== 'rowId')
        .filter((p) => !p.isI18n || p.name !== `${camelModule}Id`);
}
// Command/Handler: same as baseWriteFilter (no i18n exclusions needed).
export const getCreateCommandProperties = baseWriteFilter;
export const getUpdateCommandProperties = baseWriteFilter;
export const getUpsertCommandProperties = baseWriteFilter;
export const getCreateCommandHandlerProperties = baseWriteFilter;
export const getUpdateCommandHandlerProperties = baseWriteFilter;
export const getUpsertCommandHandlerProperties = baseWriteFilter;
// Event properties: same as aggregate (no timestamps filter).
export const getCreatedEventProperties = aggregateBaseFilter;
export const getUpdatedEventProperties = aggregateBaseFilter;
export const getDeletedEventProperties = aggregateBaseFilter;
export const getGraphqlProperties = (props) => props;
export const getGraphqlInputProperties = (props) => props?.filter((p) => excludeTimestamps(p));
export const getDtoProperties = (props) => props;
export const getDtoInputProperties = (props) => props?.filter((p) => excludeTimestamps(p));
export const getPostmanGraphqlCreateMutationProperties = crudFilter;
export const getPostmanGraphqlCreateVariablesProperties = crudFilter;
export const getPostmanGraphqlGetQueryProperties = readQueryFilter;
export const getPostmanGraphqlFindQueryProperties = readQueryFilter;
export const getPostmanGraphqlFindByIdQueryProperties = readQueryFilter;
export const getPostmanGraphqlUpdateMutationProperties = readQueryFilter;
export const getPostmanGraphqlDeleteMutationProperties = readQueryFilter;
export function getPostmanGraphqlUpdateVariablesProperties(props, moduleName) {
    return crudFilter(props, moduleName);
}
export const getPostmanRestCreateProperties = baseWriteFilter;
export const getPostmanRestUpdateProperties = baseWriteFilter;
export function getI18nSearchableProperties(props, avoid = []) {
    return props
        .filter((p) => p.isI18n)
        .filter((p) => p.name !== 'id' &&
        p.name !== 'langId' &&
        !TIMESTAMP_FIELDS.includes(p.name) &&
        !avoid.includes(p.name));
}
export const getGridFieldsProperties = (props) => props
    ?.filter((p) => excludeTimestamps(p))
    .filter((p) => p.name !== 'availableLangs' && p.name !== 'meta' && p.name !== 'id');
export const getFormDetailFieldsProperties = (props) => props
    ?.filter((p) => excludeTimestamps(p))
    .filter((p) => excludeOneToOneWithoutField(p))
    .filter((p) => p.name !== 'id')
    .filter((p) => p.name !== 'rowId')
    .filter((p) => !p.widget?.isDetailHidden)
    .sort((a, b) => {
    const va = a.widget?.detailSort ?? Number.POSITIVE_INFINITY;
    const vb = b.widget?.detailSort ?? Number.POSITIVE_INFINITY;
    return va - vb;
});
/**
 * SPEC-12: returns the visible+sorted form fields belonging to a single tab.
 * Builds on `getFormDetailFieldsProperties` (which already excludes timestamps,
 * id, rowId, and `widget.isDetailHidden`) and further restricts to fields whose
 * `widget.tab` matches the given id. Match is exact and case-sensitive.
 */
export const getFormDetailFieldsPropertiesForTab = (props, tabId) => getFormDetailFieldsProperties(props).filter((p) => p.widget?.tab === tabId);
/**
 * SPEC-12: returns the ordered list of unique tab ids in first-appearance
 * order across the visible form fields. Empty when no field declares
 * `widget.tab`.
 */
export const getTabsFromProperties = (props) => {
    const visible = getFormDetailFieldsProperties(props);
    const seen = new Set();
    const order = [];
    for (const p of visible) {
        const tabId = p.widget?.tab;
        if (typeof tabId !== 'string' || tabId.length === 0)
            continue;
        if (seen.has(tabId))
            continue;
        seen.add(tabId);
        order.push(tabId);
    }
    return order;
};
/**
 * SPEC-12: true when at least one visible form field declares a non-empty
 * `widget.tab`.
 */
export const hasFormTabsProperties = (props) => getTabsFromProperties(props).length > 0;
export const getFormGroupFieldsProperties = (props) => props
    ?.filter((p) => excludeTimestamps(p))
    .filter((p) => excludeOneToOneWithoutField(p))
    .filter((p) => p.name !== 'availableLangs' && p.name !== 'rowId' && p.name !== 'meta');
export const getFormGroupFieldsIsNotI18nProperties = (props) => getFormGroupFieldsProperties(props).filter((p) => !p.isI18n);
const byWidget = (type) => (props) => props?.filter((p) => p.widget?.type === type);
export const getSelectWidgetsProperties = byWidget(WidgetType.SELECT);
export const getMultipleSelectWidgetsProperties = byWidget(WidgetType.MULTIPLE_SELECT);
export const getAsyncMultipleSearchSelectWidgetsProperties = byWidget(WidgetType.ASYNC_MULTIPLE_SEARCH_SELECT);
export const getAsyncSearchSelectWidgetsProperties = byWidget(WidgetType.ASYNC_SEARCH_SELECT);
export const getGridSelectElementWidgetsProperties = byWidget(WidgetType.GRID_SELECT_ELEMENT);
export const getGridSelectMultipleElementsWidgetsProperties = byWidget(WidgetType.GRID_SELECT_MULTIPLE_ELEMENTS);
export const getGridElementsManagerWidgetsProperties = byWidget(WidgetType.GRID_ELEMENTS_MANAGER);
export const getWidgetsProperties = (props) => props?.filter((p) => Boolean(p.widget?.type));
export const hasSelectWidgetsProperties = (props) => props?.some((p) => p.widget?.type === WidgetType.SELECT);
export const hasAsyncMultipleSearchSelectWidgetProperty = (props) => props?.some((p) => p.widget?.type === WidgetType.ASYNC_MULTIPLE_SEARCH_SELECT);
export const countWidgetsProperties = (props) => getWidgetsProperties(props).length;
export const countSelectElementWidgetsProperties = (props) => getSelectWidgetsProperties(props).length;
export const countMultipleSelectElementWidgetsProperties = (props) => getMultipleSelectWidgetsProperties(props).length;
export const countGridSelectElementWidgetsProperties = (props) => getGridSelectElementWidgetsProperties(props).length;
export const countGridSelectMultipleElementsWidgetsProperties = (props) => getGridSelectMultipleElementsWidgetsProperties(props).length;
export const countGridElementsManagerWidgetsProperties = (props) => getGridElementsManagerWidgetsProperties(props).length;
export const countAsyncMultipleSearchSelectWidgetProperty = (props) => getAsyncMultipleSearchSelectWidgetsProperties(props).length;
function getIdPropertyImports(p) {
    const webType = p.widget?.type;
    if (webType === WidgetType.SELECT)
        return ['MatSelectModule'];
    if (webType === WidgetType.GRID_SELECT_ELEMENT)
        return ['GridSelectElementModule'];
    return [];
}
function getRelationshipPropertyImports(p) {
    const webType = p.widget?.type;
    if (webType === WidgetType.ASYNC_MULTIPLE_SEARCH_SELECT) {
        return [
            'HasValidatorPipe',
            'MatSelectAddSelectedDirective',
            'MatSelectModule',
            'NgxMatSelectSearchModule',
            'ScrollEndDirective',
        ];
    }
    if (webType === WidgetType.GRID_ELEMENTS_MANAGER)
        return ['GridElementsManagerModule'];
    if (webType === WidgetType.GRID_SELECT_MULTIPLE_ELEMENTS)
        return ['GridSelectMultipleElementsModule'];
    return [];
}
function getPropertyImports(p) {
    switch (p.type) {
        case 'array': {
            return p.arrayOptions?.type === 'enum' ? ['MatSelectModule'] : [];
        }
        case 'boolean': {
            return ['MatCheckboxModule'];
        }
        case 'date': {
            return ['MatDatepickerModule'];
        }
        case 'enum': {
            return ['MatSelectModule'];
        }
        case 'id': {
            return getIdPropertyImports(p);
        }
        case 'relationship': {
            return getRelationshipPropertyImports(p);
        }
        case 'timestamp': {
            return ['MtxDatetimepickerModule'];
        }
        default: {
            return [];
        }
    }
}
export function getImportWidgetsManagerProperties(props, sortImports) {
    const imports = new Set();
    if (hasI18nProperties(props))
        imports.add('FlagLangComponent');
    for (const p of props) {
        if (TIMESTAMP_FIELDS.includes(p.name))
            continue;
        for (const imp of getPropertyImports(p))
            imports.add(imp);
    }
    const arr = [...imports];
    if (sortImports)
        arr.sort();
    return arr;
}
// Aliases: templates call these with the longer "WithRelationship" / "WithoutRelationship" naming.
export const getWithRelationshipOneToOneWithRelationshipFieldProperties = getWithRelationshipOneToOneWithFieldProperties;
export const getWithRelationshipOneToOneWithoutRelationshipFieldProperties = getWithRelationshipOneToOneWithoutFieldProperties;
/** Removes newlines and collapses extra spaces into a single line. */
export function singleLine(text) {
    // eslint-disable-next-line unicorn/prefer-string-replace-all
    return text.replace(/\n/g, '').replace(/\s+/g, ' ').trim();
}
/** Escapes single quotes and backslashes for use in single-quoted string literals. */
export function escapeSingleQuotes(text) {
    // eslint-disable-next-line unicorn/prefer-string-replace-all
    return text.replace(/\\/g, String.raw `\\`).replace(/'/g, String.raw `\'`);
}
/** Generates a random UUID v4. */
export function uuid(seed) {
    if (seed) {
        const uuidSpaceName = '3eb6ecd7-2f06-4d41-934c-3f813c96982a';
        return uuidv5(seed, uuidSpaceName);
    }
    return uuidv4();
}
/** Wraps a string in escaped quotes for Postman JSON bodies. */
export function postmanQuotes(value) {
    return String.raw `\"` + value + String.raw `\"`;
}
export function getAdditionalApiQueries(apis) {
    if (!Array.isArray(apis))
        return [];
    return apis.filter((api) => api.resolverType === 'query');
}
export function getAdditionalApiMutations(apis) {
    if (!Array.isArray(apis))
        return [];
    return apis.filter((api) => api.resolverType === 'mutation');
}
export function hasAdditionalApiQueries(apis) {
    return getAdditionalApiQueries(apis).length > 0;
}
export function hasAdditionalApiMutations(apis) {
    return getAdditionalApiMutations(apis).length > 0;
}
/**
 * Unified path exclusion check for templates.
 * Evaluates both `excludedFiles` and `excludedOperations` from the schema.
 * Templates call: `it.filters.isAllowPath(it.schema, operation, ...pathSegments)`
 * Pass `''` as operation to check only excludedFiles.
 */
export function isAllowPath(schema, operation, ...pathSegments) {
    return isPathAllowedByExclusions(schema.excludedFiles, schema.excludedOperations, schema.boundedContextName, schema.moduleName, schema.moduleNames, operation, ...pathSegments);
}
export { addI18nPropertySignature, getAggregateNameFromPropertyRelationship, getBoundedContextNameFromPropertyRelationship, getModuleNameFromPropertyRelationship, getModuleNamesFromPropertyRelationship, getPropertyEnumOptions, getPropertyName, isAllowProperty, } from './property-mappers.js';
export { hasQuotation as hasQuotationProperty, isI18nRelationProperty, isPivot as isPivotProperty, isPrimaryKey as isPrimaryKeyProperty, } from './property-predicates.js';
