/**
 * Field Utilities
 *
 * Consolidated utilities for field validation, processing, and manipulation.
 * Brings together scattered field-related functions into a single module.
 */
import { schemaManager } from './schemaManager.js';
/**
 * Field utilities object
 */
const fieldUtils = {
    /**
     * Get flattened list of all fields in a collection, including nested relations
     * @param collectionName - Name of the collection
     * @param prefix - Prefix for nested fields (used in recursion)
     * @param visited - Set of visited collections to prevent circular references
     * @returns Array of flattened field objects
     *
     * @example
     * ```typescript
     * const fields = fieldUtils.getFlattenedFields('users');
     * // Returns: [
     * //   { name: 'id', fullPath: 'id', type: 'integer', isRelation: false },
     * //   { name: 'email', fullPath: 'email', type: 'string', isRelation: false },
     * //   { name: 'posts', fullPath: 'posts', type: 'o2m', isRelation: true, relationCollection: 'posts' },
     * //   { name: 'title', fullPath: 'posts.title', type: 'string', isRelation: false }
     * // ]
     * ```
     */
    getFlattenedFields(collectionName, prefix = '', visited = new Set()) {
        // Prevent circular references
        if (visited.has(collectionName)) {
            return [];
        }
        visited.add(collectionName);
        const fields = [];
        const schema = schemaManager.getSchema(collectionName);
        if (!schema) {
            console.warn(`Schema not found for collection: ${collectionName}`);
            return fields;
        }
        // Add direct fields
        for (const [fieldName, fieldDef] of Object.entries(schema.columns)) {
            const fullPath = prefix ? `${prefix}.${fieldName}` : fieldName;
            const fieldDefTyped = fieldDef;
            fields.push({
                name: fieldName,
                fullPath,
                type: fieldDefTyped.dataType || 'unknown',
                collection: collectionName,
                isRelation: false,
            });
        }
        // Add relation fields and their nested fields
        const relations = schemaManager.getRelations(collectionName);
        if (relations) {
            for (const [relName, relDef] of Object.entries(relations)) {
                const fullPath = prefix ? `${prefix}.${relName}` : relName;
                const relDefTyped = relDef;
                const relationType = relDefTyped.type;
                const relatedCollection = relDefTyped.relatedCollection;
                // Add the relation field itself
                fields.push({
                    name: relName,
                    fullPath,
                    type: relationType,
                    collection: collectionName,
                    isRelation: true,
                    relationCollection: relatedCollection,
                    relationType,
                });
                // Add nested fields from related collection (max depth of 3 levels)
                if (prefix.split('.').length < 2 && relatedCollection) {
                    const nestedFields = fieldUtils.getFlattenedFields(relatedCollection, fullPath, new Set(visited));
                    fields.push(...nestedFields);
                }
            }
        }
        return fields;
    },
    /**
     * Validate if a field exists in a collection
     * @param collectionName - Name of the collection
     * @param fieldPath - Field path (can include dots for nested fields)
     * @returns True if field exists
     *
     * @example
     * ```typescript
     * fieldUtils.fieldExists('users', 'email'); // true
     * fieldUtils.fieldExists('users', 'posts.title'); // true (if relation exists)
     * fieldUtils.fieldExists('users', 'nonexistent'); // false
     * ```
     */
    fieldExists(collectionName, fieldPath) {
        const parts = fieldPath.split('.');
        // Check direct field
        if (parts.length === 1) {
            const schema = schemaManager.getSchema(collectionName);
            return schema ? fieldPath in schema.columns : false;
        }
        // Check nested field through relation
        const [relationName, ...restPath] = parts;
        const relations = schemaManager.getRelations(collectionName);
        if (!relations || !(relationName in relations)) {
            return false;
        }
        const relatedCollection = relations[relationName].relatedCollection;
        if (!relatedCollection) {
            return false;
        }
        return fieldUtils.fieldExists(relatedCollection, restPath.join('.'));
    },
    /**
     * Get field type
     * @param collectionName - Name of the collection
     * @param fieldPath - Field path
     * @returns Field type or null if not found
     */
    getFieldType(collectionName, fieldPath) {
        const parts = fieldPath.split('.');
        // Get direct field type
        if (parts.length === 1) {
            const schema = schemaManager.getSchema(collectionName);
            if (!schema || !(fieldPath in schema.columns)) {
                return null;
            }
            return schema.columns[fieldPath].dataType || null;
        }
        // Get nested field type through relation
        const [relationName, ...restPath] = parts;
        const relations = schemaManager.getRelations(collectionName);
        if (!relations || !(relationName in relations)) {
            // Check if it's a relation type
            return null;
        }
        const relation = relations[relationName];
        // If this is the last part, return relation type
        if (restPath.length === 0) {
            return relation.type;
        }
        // Recurse into related collection
        const relatedCollection = relation.relatedCollection;
        if (!relatedCollection) {
            return null;
        }
        return fieldUtils.getFieldType(relatedCollection, restPath.join('.'));
    },
    /**
     * Validate field permissions for a user
     * @param collectionName - Name of the collection
     * @param fieldNames - Array of field names to check
     * @param userPermissions - User's permissions object
     * @returns Object with allowed and denied fields
     *
     * @example
     * ```typescript
     * const result = fieldUtils.validateFieldPermissions('users', ['email', 'password'], userPerms);
     * // { allowed: ['email'], denied: ['password'] }
     * ```
     */
    validateFieldPermissions(collectionName, fieldNames, userPermissions) {
        const allowed = [];
        const denied = [];
        const collectionPerms = userPermissions[collectionName];
        if (!collectionPerms) {
            // No permissions defined, deny all
            return { allowed: [], denied: fieldNames };
        }
        for (const fieldName of fieldNames) {
            const fieldPerms = collectionPerms.fields?.[fieldName];
            if (fieldPerms === undefined) {
                // No specific field permission, check collection-level permission
                if (collectionPerms.read === true || collectionPerms.read === '*') {
                    allowed.push(fieldName);
                }
                else {
                    denied.push(fieldName);
                }
            }
            else if (fieldPerms.read === true || fieldPerms.read === '*') {
                allowed.push(fieldName);
            }
            else {
                denied.push(fieldName);
            }
        }
        return { allowed, denied };
    },
    /**
     * Get required fields for a collection
     * @param collectionName - Name of the collection
     * @returns Array of required field names
     */
    getRequiredFields(collectionName) {
        const schema = schemaManager.getSchema(collectionName);
        if (!schema) {
            return [];
        }
        const required = [];
        for (const [fieldName, fieldDef] of Object.entries(schema.columns)) {
            const fieldDefTyped = fieldDef;
            if (fieldDefTyped.notNull && !fieldDefTyped.hasDefault) {
                required.push(fieldName);
            }
        }
        return required;
    },
    /**
     * Get unique fields for a collection
     * @param collectionName - Name of the collection
     * @returns Array of unique field names
     */
    getUniqueFields(collectionName) {
        const schema = schemaManager.getSchema(collectionName);
        if (!schema) {
            return [];
        }
        const unique = [];
        for (const [fieldName, fieldDef] of Object.entries(schema.columns)) {
            const fieldDefTyped = fieldDef;
            if (fieldDefTyped.isUnique || fieldDefTyped.primaryKey) {
                unique.push(fieldName);
            }
        }
        return unique;
    },
    /**
     * Filter object to only include specified fields
     * @param data - Data object
     * @param allowedFields - Array of allowed field names
     * @returns Filtered object
     *
     * @example
     * ```typescript
     * const data = { id: 1, email: 'test@test.com', password: 'secret' };
     * const safe = fieldUtils.filterFields(data, ['id', 'email']);
     * // { id: 1, email: 'test@test.com' }
     * ```
     */
    filterFields(data, allowedFields) {
        const filtered = {};
        for (const field of allowedFields) {
            if (field in data) {
                filtered[field] = data[field];
            }
        }
        return filtered;
    },
    /**
     * Remove specified fields from object
     * @param data - Data object
     * @param excludeFields - Array of field names to exclude
     * @returns Object without excluded fields
     */
    excludeFields(data, excludeFields) {
        const result = { ...data };
        for (const field of excludeFields) {
            delete result[field];
        }
        return result;
    },
    /**
     * Validate required fields are present in data
     * @param collectionName - Name of the collection
     * @param data - Data object to validate
     * @returns Object with isValid flag and missing fields
     */
    validateRequiredFields(collectionName, data) {
        const required = fieldUtils.getRequiredFields(collectionName);
        const missing = [];
        for (const field of required) {
            if (!(field in data) || data[field] === null || data[field] === undefined) {
                missing.push(field);
            }
        }
        return {
            isValid: missing.length === 0,
            missing,
        };
    },
    /**
     * Get default values for fields that have them
     * @param collectionName - Name of the collection
     * @returns Object with field names and default values
     */
    getDefaultValues(collectionName) {
        const schema = schemaManager.getSchema(collectionName);
        if (!schema) {
            return {};
        }
        const defaults = {};
        for (const [fieldName, fieldDef] of Object.entries(schema.columns)) {
            const fieldDefTyped = fieldDef;
            if (fieldDefTyped.hasDefault && fieldDefTyped.default !== undefined) {
                defaults[fieldName] = fieldDefTyped.default;
            }
        }
        return defaults;
    },
    /**
     * Apply default values to data object
     * @param collectionName - Name of the collection
     * @param data - Data object
     * @returns Data object with defaults applied
     */
    applyDefaults(collectionName, data) {
        const defaults = fieldUtils.getDefaultValues(collectionName);
        const result = { ...data };
        for (const [field, defaultValue] of Object.entries(defaults)) {
            if (!(field in result) || result[field] === undefined) {
                result[field] = defaultValue;
            }
        }
        return result;
    },
    /**
     * Get system fields (created_at, updated_at, deleted_at, etc.)
     * @param collectionName - Name of the collection
     * @returns Array of system field names
     */
    getSystemFields(collectionName) {
        const systemFields = ['createdAt', 'updatedAt', 'deletedAt', 'createdBy', 'updatedBy'];
        const schema = schemaManager.getSchema(collectionName);
        if (!schema) {
            return [];
        }
        return systemFields.filter(field => field in schema.columns);
    },
    /**
     * Get user-defined fields (excludes system fields)
     * @param collectionName - Name of the collection
     * @returns Array of user-defined field names
     */
    getUserFields(collectionName) {
        const schema = schemaManager.getSchema(collectionName);
        if (!schema) {
            return [];
        }
        const systemFields = fieldUtils.getSystemFields(collectionName);
        return Object.keys(schema.columns).filter(field => !systemFields.includes(field));
    },
    /**
     * Sanitize field name for safe use in queries
     * @param fieldName - Field name to sanitize
     * @returns Sanitized field name
     */
    sanitizeFieldName(fieldName) {
        // Remove dangerous characters, only allow alphanumeric, underscore, and dot
        return fieldName.replace(/[^a-zA-Z0-9_.]/g, '');
    },
    /**
     * Parse field path into parts
     * @param fieldPath - Field path (e.g., "user.posts.title")
     * @returns Array of path parts
     */
    parseFieldPath(fieldPath) {
        return fieldPath.split('.').filter(part => part.length > 0);
    },
    /**
     * Build field path from parts
     * @param parts - Array of path parts
     * @returns Field path string
     */
    buildFieldPath(parts) {
        return parts.join('.');
    },
    /**
     * Get list of hidden fields in a collection
     * Hidden fields should never be returned in API responses
     * @param collectionName - Name of the collection
     * @returns Array of hidden field names
     */
    getHiddenFields(collectionName) {
        // The `hidden` flag lives on the JSON schema DEFINITION, not the runtime
        // Drizzle table — so delegate to schemaManager, which reads the definition.
        // (Reading schema.columns here previously returned [] and leaked hidden fields.)
        return schemaManager.getHiddenFieldNames(collectionName);
    },
    /**
     * Strip hidden fields from a record
     * @param collectionName - Name of the collection
     * @param record - Record to strip hidden fields from
     * @returns Record without hidden fields
     */
    stripHiddenFields(collectionName, record) {
        const hiddenFields = fieldUtils.getHiddenFields(collectionName);
        if (hiddenFields.length === 0) {
            return record;
        }
        const result = { ...record };
        for (const field of hiddenFields) {
            delete result[field];
        }
        return result;
    },
    /**
     * Strip hidden fields from an array of records
     * @param collectionName - Name of the collection
     * @param records - Array of records to strip hidden fields from
     * @returns Array of records without hidden fields
     */
    stripHiddenFieldsFromRecords(collectionName, records) {
        const hiddenFields = fieldUtils.getHiddenFields(collectionName);
        if (hiddenFields.length === 0) {
            return records;
        }
        return records.map(record => {
            const result = { ...record };
            for (const field of hiddenFields) {
                delete result[field];
            }
            return result;
        });
    },
};
export default fieldUtils;
//# sourceMappingURL=fieldUtils.js.map