"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.unwrapValueType = unwrapValueType;
exports.mergedIntersectionObjectLiteral = mergedIntersectionObjectLiteral;
exports.mergeStructuredProperties = mergeStructuredProperties;
exports.objectLiteralPropertiesFromType = objectLiteralPropertiesFromType;
exports.objectLiteralIndexType = objectLiteralIndexType;
exports.isMarkerType = isMarkerType;
exports.allowsUndefined = allowsUndefined;
exports.allowsNull = allowsNull;
exports.resolveClassType = resolveClassType;
exports.tryResolveClassType = tryResolveClassType;
exports.normalizeTypeMetadata = normalizeTypeMetadata;
exports.isClass = isClass;
exports.expandUtilityTypeMetadata = expandUtilityTypeMetadata;
exports.recordPropertiesFromKeyType = recordPropertiesFromKeyType;
exports.recordLiteralKeysFromType = recordLiteralKeysFromType;
exports.flattenRuntimeUnionTypes = flattenRuntimeUnionTypes;
exports.isExtractAssignable = isExtractAssignable;
exports.typesOverlap = typesOverlap;
exports.literalMatchesType = literalMatchesType;
exports.utilitySourcePropertiesFromType = utilitySourcePropertiesFromType;
exports.mergeUtilityProperties = mergeUtilityProperties;
exports.cloneUtilityProperty = cloneUtilityProperty;
exports.isUtilityTypeName = isUtilityTypeName;
exports.getUtilityTypeName = getUtilityTypeName;
exports.visitType = visitType;
exports.visitTypeSurface = visitTypeSurface;
exports.findNested = findNested;
exports.findNestedSurface = findNestedSurface;
exports.hasNestedType = hasNestedType;
exports.literalNumber = literalNumber;
exports.resolveRuntimeValue = resolveRuntimeValue;
exports.joinPath = joinPath;
const reflection_class_1 = require("./reflection-class");
const model_1 = require("./model");
function unwrapValueType(type) {
    if (type.kind === model_1.ReflectionKind.union) {
        const concrete = type.types.filter(item => item.kind !== model_1.ReflectionKind.undefined && item.kind !== model_1.ReflectionKind.null);
        return concrete.length === 1 ? unwrapValueType(concrete[0]) : type;
    }
    if (type.kind === model_1.ReflectionKind.intersection) {
        const mergedObject = mergedIntersectionObjectLiteral(type);
        if (mergedObject)
            return mergedObject;
        const meaningful = type.types.find(item => !isMarkerType(item));
        return meaningful ? unwrapValueType(meaningful) : type;
    }
    return type;
}
function mergedIntersectionObjectLiteral(type) {
    if (type.kind !== model_1.ReflectionKind.intersection)
        return undefined;
    if (!isObjectLiteralIntersection(type))
        return undefined;
    const properties = mergeStructuredProperties(type);
    if (properties.length === 0)
        return undefined;
    return { kind: model_1.ReflectionKind.objectLiteral, typeName: type.typeName, types: properties };
}
function isObjectLiteralIntersection(type) {
    if (type.kind !== model_1.ReflectionKind.intersection)
        return false;
    return type.types.every(item => {
        if (isMarkerType(item))
            return true;
        if (item.kind === model_1.ReflectionKind.intersection)
            return isObjectLiteralIntersection(item);
        return item.kind === model_1.ReflectionKind.objectLiteral;
    });
}
function mergeStructuredProperties(type) {
    const order = [];
    const byName = new Map();
    const addProperty = (property) => {
        const key = String(property.name);
        if (!byName.has(key))
            order.push(key);
        byName.set(key, {
            kind: model_1.ReflectionKind.propertySignature,
            name: property.name,
            type: property.type,
            optional: property.optional
        });
    };
    const collect = (item) => {
        if (isMarkerType(item))
            return;
        if (item.kind === model_1.ReflectionKind.intersection) {
            for (const child of item.types)
                collect(child);
            return;
        }
        if (item.kind === model_1.ReflectionKind.objectLiteral) {
            for (const property of objectLiteralPropertiesFromType(item))
                addProperty(property);
            return;
        }
    };
    collect(type);
    return order.map(key => byName.get(key));
}
function objectLiteralPropertiesFromType(type, seen = new Set()) {
    if (seen.has(type))
        return [];
    if (isMarkerType(type))
        return [];
    seen.add(type);
    const inherited = [];
    for (const base of type.implements ?? []) {
        const unwrapped = unwrapValueType(base);
        if (seen.has(unwrapped))
            continue;
        if (unwrapped.kind === model_1.ReflectionKind.objectLiteral) {
            inherited.push(...objectLiteralPropertiesFromType(unwrapped, seen));
        }
        else if (unwrapped.kind === model_1.ReflectionKind.intersection) {
            inherited.push(...mergeStructuredProperties(unwrapped));
        }
        else if (unwrapped.kind === model_1.ReflectionKind.class) {
            inherited.push(...utilitySourcePropertiesFromType(unwrapped));
        }
    }
    return mergeUtilityProperties([...inherited, ...type.types.map(cloneUtilityProperty)]);
}
function objectLiteralIndexType(type, seen = new Set()) {
    if (type.index)
        return type.index;
    if (seen.has(type))
        return undefined;
    seen.add(type);
    for (const base of type.implements ?? []) {
        const unwrapped = unwrapValueType(base);
        if (unwrapped.kind === model_1.ReflectionKind.objectLiteral) {
            const index = objectLiteralIndexType(unwrapped, seen);
            if (index)
                return index;
        }
    }
}
function isMarkerType(type) {
    return Boolean(type.annotations || type.validation || type.database || isBareKnownMarkerType(type) || isTypiaTagOnlyObject(type));
}
function isBareKnownMarkerType(type) {
    return type.kind === model_1.ReflectionKind.unknown && isKnownMarkerTypeName(type.typeName);
}
function isKnownMarkerTypeName(typeName) {
    switch (typeName) {
        case 'ApiName':
        case 'AutoIncrement':
        case 'DatabaseField':
        case 'GreaterThan':
        case 'HasDefault':
        case 'Index':
        case 'LessThan':
        case 'Maximum':
        case 'MaxLength':
        case 'Minimum':
        case 'MinLength':
        case 'MySQL':
        case 'OnUpdate':
        case 'Pattern':
        case 'PrimaryKey':
        case 'Reference':
        case 'TypeAnnotation':
        case 'Unique':
            return true;
        default:
            return false;
    }
}
function isTypiaTagOnlyObject(type) {
    return (type.kind === model_1.ReflectionKind.objectLiteral &&
        type.types.length > 0 &&
        type.types.every(property => property.kind === model_1.ReflectionKind.propertySignature && String(property.name) === 'typia.tag'));
}
function allowsUndefined(type) {
    return (type.kind === model_1.ReflectionKind.any ||
        type.kind === model_1.ReflectionKind.undefined ||
        (type.kind === model_1.ReflectionKind.union && type.types.some(allowsUndefined)));
}
function allowsNull(type) {
    return (type.kind === model_1.ReflectionKind.any || type.kind === model_1.ReflectionKind.null || (type.kind === model_1.ReflectionKind.union && type.types.some(allowsNull)));
}
function resolveClassType(value) {
    const resolved = tryResolveClassType(value);
    if (!resolved)
        throw new Error('Target is not a constructor');
    return resolved;
}
function tryResolveClassType(value) {
    if (isClass(value))
        return value;
    try {
        const resolved = value();
        return isClass(resolved) ? resolved : undefined;
    }
    catch {
        return undefined;
    }
}
function normalizeTypeMetadata(value, seen = new Set()) {
    if (!value || typeof value !== 'object' || seen.has(value))
        return;
    seen.add(value);
    const record = value;
    if (record.kind === model_1.ReflectionKind.class && typeof record.classType === 'function' && !isClass(record.classType)) {
        try {
            const resolved = record.classType();
            if (isClass(resolved))
                record.classType = resolved;
        }
        catch {
            // Leave unresolved circular references as thunks until the next metadata read.
        }
    }
    for (const child of Object.values(record)) {
        if (typeof child === 'function')
            continue;
        if (Array.isArray(child)) {
            for (const item of child)
                normalizeTypeMetadata(item, seen);
        }
        else {
            normalizeTypeMetadata(child, seen);
        }
    }
    expandUtilityTypeMetadata(record);
}
function isClass(value) {
    return Boolean(typeof value === 'function' && value.prototype && value.prototype.constructor === value);
}
function expandUtilityTypeMetadata(record) {
    const utilityType = getUtilityTypeName(record);
    if (!utilityType)
        return;
    const typeArguments = Array.isArray(record.typeArguments) ? record.typeArguments : [];
    if (utilityType === 'Record') {
        if (record.kind !== model_1.ReflectionKind.objectLiteral || (record.types ?? []).length > 0)
            return;
        const keyType = typeArguments[0];
        const valueType = typeArguments[1];
        if (!(0, model_1.isReflectedType)(keyType) || !(0, model_1.isReflectedType)(valueType))
            return;
        const properties = recordPropertiesFromKeyType(keyType, valueType);
        if (properties.length > 0) {
            record.types = properties;
            delete record.index;
        }
        else {
            record.index = valueType;
        }
        return;
    }
    if ((record.types ?? []).length > 0)
        return;
    const source = typeArguments[0];
    if (!(0, model_1.isReflectedType)(source))
        return;
    if (utilityType === 'Extract') {
        const target = typeArguments[1];
        if (record.kind !== model_1.ReflectionKind.union || !(0, model_1.isReflectedType)(target))
            return;
        record.types = flattenRuntimeUnionTypes(source).filter(item => isExtractAssignable(item, target));
        return;
    }
    if (record.kind !== model_1.ReflectionKind.objectLiteral)
        return;
    const sourceProperties = utilitySourcePropertiesFromType(source);
    const sourceIndex = source.kind === model_1.ReflectionKind.objectLiteral ? objectLiteralIndexType(source) : undefined;
    if (sourceProperties.length === 0) {
        if (sourceIndex)
            record.index = sourceIndex;
        return;
    }
    const keys = new Set((Array.isArray(record.utilityKeys) ? record.utilityKeys : []).filter((item) => typeof item === 'string'));
    let properties = sourceProperties.map(cloneUtilityProperty);
    if (utilityType === 'Pick')
        properties = properties.filter(property => keys.has(String(property.name)));
    else if (utilityType === 'Omit')
        properties = properties.filter(property => !keys.has(String(property.name)));
    else if (utilityType === 'Partial')
        properties = properties.map(property => ({ ...property, optional: true }));
    else if (utilityType === 'Required')
        properties = properties.map(property => ({ ...property, optional: undefined }));
    else if (utilityType === 'OptionalNulls') {
        properties = properties.map(property => (allowsNull(property.type) ? { ...property, optional: true } : property));
    }
    record.types = properties;
    if (sourceIndex)
        record.index = sourceIndex;
}
function recordPropertiesFromKeyType(keyType, valueType) {
    const keys = recordLiteralKeysFromType(keyType);
    return keys.map(key => ({
        kind: model_1.ReflectionKind.propertySignature,
        name: key,
        type: valueType,
        optional: undefined
    }));
}
function recordLiteralKeysFromType(type) {
    const keys = [];
    for (const item of flattenRuntimeUnionTypes(type)) {
        if (item.kind !== model_1.ReflectionKind.literal || (typeof item.literal !== 'string' && typeof item.literal !== 'number'))
            return [];
        keys.push(item.literal);
    }
    return keys;
}
function flattenRuntimeUnionTypes(type) {
    return type.kind === model_1.ReflectionKind.union ? type.types.flatMap(flattenRuntimeUnionTypes) : [type];
}
function isExtractAssignable(source, target) {
    const sourceType = unwrapValueType(source);
    const targetType = unwrapValueType(target);
    if (targetType.kind === model_1.ReflectionKind.union)
        return targetType.types.some(item => isExtractAssignable(sourceType, item));
    if (sourceType.kind === model_1.ReflectionKind.union)
        return sourceType.types.every(item => isExtractAssignable(item, targetType));
    if (targetType.kind === model_1.ReflectionKind.objectLiteral) {
        const targetProperties = objectLiteralPropertiesFromType(targetType).filter(property => !property.optional && !allowsUndefined(property.type));
        if (targetProperties.length === 0)
            return sourceType.kind === model_1.ReflectionKind.objectLiteral || sourceType.kind === model_1.ReflectionKind.class;
        const sourceProperties = utilitySourcePropertiesFromType(sourceType);
        if (sourceProperties.length === 0)
            return false;
        return targetProperties.every(targetProperty => {
            const sourceProperty = sourceProperties.find(property => String(property.name) === String(targetProperty.name));
            return sourceProperty ? typesOverlap(sourceProperty.type, targetProperty.type) : false;
        });
    }
    return typesOverlap(sourceType, targetType);
}
function typesOverlap(left, right) {
    const leftType = unwrapValueType(left);
    const rightType = unwrapValueType(right);
    if (leftType.kind === model_1.ReflectionKind.any ||
        leftType.kind === model_1.ReflectionKind.unknown ||
        rightType.kind === model_1.ReflectionKind.any ||
        rightType.kind === model_1.ReflectionKind.unknown) {
        return true;
    }
    if (leftType.kind === model_1.ReflectionKind.union)
        return leftType.types.some(item => typesOverlap(item, rightType));
    if (rightType.kind === model_1.ReflectionKind.union)
        return rightType.types.some(item => typesOverlap(leftType, item));
    if (leftType.kind === model_1.ReflectionKind.literal && rightType.kind === model_1.ReflectionKind.literal)
        return leftType.literal === rightType.literal;
    if (leftType.kind === model_1.ReflectionKind.literal)
        return literalMatchesType(leftType.literal, rightType);
    if (rightType.kind === model_1.ReflectionKind.literal)
        return literalMatchesType(rightType.literal, leftType);
    return leftType.kind === rightType.kind;
}
function literalMatchesType(value, type) {
    if (value === null)
        return type.kind === model_1.ReflectionKind.null;
    if (value === undefined)
        return type.kind === model_1.ReflectionKind.undefined;
    if (typeof value === 'string')
        return type.kind === model_1.ReflectionKind.string || type.kind === model_1.ReflectionKind.templateLiteral;
    if (typeof value === 'number')
        return type.kind === model_1.ReflectionKind.number;
    if (typeof value === 'boolean')
        return type.kind === model_1.ReflectionKind.boolean;
    if (typeof value === 'bigint')
        return type.kind === model_1.ReflectionKind.bigint;
    return false;
}
function utilitySourcePropertiesFromType(type) {
    if (type.kind === model_1.ReflectionKind.class) {
        const Target = tryResolveClassType(type.classType);
        if (!Target)
            return [];
        try {
            return reflection_class_1.ReflectionClass.from(Target)
                .getProperties()
                .map(property => ({
                kind: model_1.ReflectionKind.propertySignature,
                name: property.name,
                type: property.getType(),
                optional: property.isOptional() ? true : undefined,
                description: property.getDescription() || undefined
            }));
        }
        catch (error) {
            if (error instanceof Error && error.message.startsWith('No runtime type metadata for '))
                return [];
            throw error;
        }
    }
    if (type.kind === model_1.ReflectionKind.objectLiteral)
        return objectLiteralPropertiesFromType(type);
    if (type.kind === model_1.ReflectionKind.intersection)
        return mergeUtilityProperties(type.types.flatMap(utilitySourcePropertiesFromType));
    return [];
}
function mergeUtilityProperties(properties) {
    const order = [];
    const byName = new Map();
    for (const property of properties) {
        const key = String(property.name);
        if (!byName.has(key))
            order.push(key);
        byName.set(key, cloneUtilityProperty(property));
    }
    return order.map(key => byName.get(key));
}
function cloneUtilityProperty(property) {
    return {
        kind: model_1.ReflectionKind.propertySignature,
        name: property.name,
        type: property.type,
        optional: property.optional ? true : undefined,
        description: property.description
    };
}
function isUtilityTypeName(value) {
    return (value === 'Pick' ||
        value === 'Omit' ||
        value === 'Partial' ||
        value === 'Required' ||
        value === 'Extract' ||
        value === 'Record' ||
        value === 'OptionalNulls');
}
function getUtilityTypeName(record) {
    if (isUtilityTypeName(record.utilityType))
        return record.utilityType;
    if (isUtilityTypeName(record.typeName))
        return record.typeName;
}
function visitType(type, visitor) {
    visitor(type);
    if ((type.kind === model_1.ReflectionKind.union || type.kind === model_1.ReflectionKind.intersection) && 'types' in type) {
        for (const child of type.types)
            visitType(child, visitor);
    }
    else if ((type.kind === model_1.ReflectionKind.array || type.kind === model_1.ReflectionKind.promise) && 'type' in type) {
        visitType(type.type, visitor);
    }
    else if (type.kind === model_1.ReflectionKind.objectLiteral && 'types' in type) {
        for (const property of type.types)
            visitType(property.type, visitor);
        if (type.index)
            visitType(type.index, visitor);
    }
}
function visitTypeSurface(type, visitor) {
    visitor(type);
    if ((type.kind === model_1.ReflectionKind.union || type.kind === model_1.ReflectionKind.intersection) && 'types' in type) {
        for (const child of type.types)
            visitTypeSurface(child, visitor);
    }
}
function findNested(type, predicate) {
    let result;
    visitType(type, child => {
        if (result !== undefined || child === type)
            return;
        result = predicate(child);
    });
    return result;
}
function findNestedSurface(type, predicate) {
    let result;
    visitTypeSurface(type, child => {
        if (result !== undefined || child === type)
            return;
        result = predicate(child);
    });
    return result;
}
function hasNestedType(type, predicate) {
    let found = false;
    visitType(type, child => {
        found ||= predicate(child);
    });
    return found;
}
function literalNumber(type) {
    return type?.kind === model_1.ReflectionKind.literal && typeof type.literal === 'number' ? type.literal : undefined;
}
function resolveRuntimeValue(type) {
    if (!type)
        return undefined;
    const runtime = type.runtime;
    if (runtime !== undefined)
        return typeof runtime === 'function' ? runtime() : runtime;
    if (type.kind === model_1.ReflectionKind.literal)
        return type.literal;
    return undefined;
}
function joinPath(base, key) {
    return base ? `${base}.${key}` : key;
}
//# sourceMappingURL=type-utils.js.map