"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.convertInternalToStandard = exports.RELAXED_EJSON_DEFINITIONS = exports.InternalTypeToStandardTypeMap = void 0;
const util_1 = require("../util");
exports.InternalTypeToStandardTypeMap = {
    Double: { $ref: '#/$defs/Double' },
    Number: { $ref: '#/$defs/Double' },
    String: { type: 'string' },
    Document: { type: 'object' },
    Array: { type: 'array' },
    Binary: { $ref: '#/$defs/Binary' },
    Undefined: { $ref: '#/$defs/Undefined' },
    ObjectId: { $ref: '#/$defs/ObjectId' },
    Boolean: { type: 'boolean' },
    Date: { $ref: '#/$defs/Date' },
    Null: { type: 'null' },
    RegExp: { $ref: '#/$defs/RegExp' },
    BSONRegExp: { $ref: '#/$defs/RegExp' },
    DBRef: { $ref: '#/$defs/DBRef' },
    DBPointer: { $ref: '#/$defs/DBPointer' },
    BSONSymbol: { $ref: '#/$defs/BSONSymbol' },
    Code: { $ref: '#/$defs/Code' },
    CodeWScope: { $ref: '#/$defs/CodeWScope' },
    Int32: { type: 'integer' },
    Timestamp: { $ref: '#/$defs/Timestamp' },
    Long: { type: 'integer' },
    Decimal128: { $ref: '#/$defs/Decimal128' },
    MinKey: { $ref: '#/$defs/MinKey' },
    MaxKey: { $ref: '#/$defs/MaxKey' }
};
exports.RELAXED_EJSON_DEFINITIONS = {
    ObjectId: {
        type: 'object',
        properties: {
            $oid: {
                type: 'string',
                pattern: '^[0-9a-fA-F]{24}$'
            }
        },
        required: ['$oid'],
        additionalProperties: false
    },
    BSONSymbol: {
        type: 'object',
        properties: {
            $symbol: {
                type: 'string'
            }
        },
        required: ['$symbol'],
        additionalProperties: false
    },
    Double: {
        oneOf: [
            { type: 'number' },
            {
                type: 'object',
                properties: {
                    $numberDouble: {
                        enum: ['Infinity', '-Infinity', 'NaN']
                    }
                }
            }
        ]
    },
    Decimal128: {
        type: 'object',
        properties: {
            $numberDecimal: {
                type: 'string'
            }
        },
        required: ['$numberDecimal'],
        additionalProperties: false
    },
    Binary: {
        type: 'object',
        properties: {
            $binary: {
                type: 'object',
                properties: {
                    base64: {
                        type: 'string'
                    },
                    subType: {
                        type: 'string',
                        pattern: '^[0-9a-fA-F]{1,2}$'
                    }
                },
                required: ['base64', 'subType'],
                additionalProperties: false
            }
        },
        required: ['$binary'],
        additionalProperties: false
    },
    Code: {
        type: 'object',
        properties: {
            $code: {
                type: 'string'
            }
        },
        required: ['$code'],
        additionalProperties: false
    },
    CodeWScope: {
        type: 'object',
        properties: {
            $code: {
                type: 'string'
            },
            $scope: {
                type: 'object'
            }
        },
        required: ['$code', '$scope'],
        additionalProperties: false
    },
    Timestamp: {
        type: 'object',
        properties: {
            $timestamp: {
                type: 'object',
                properties: {
                    t: {
                        type: 'integer',
                        minimum: 0
                    },
                    i: {
                        type: 'integer',
                        minimum: 0
                    }
                },
                required: ['t', 'i'],
                additionalProperties: false
            }
        },
        required: ['$timestamp'],
        additionalProperties: false
    },
    RegExp: {
        type: 'object',
        properties: {
            $regularExpression: {
                type: 'object',
                properties: {
                    pattern: {
                        type: 'string'
                    },
                    options: {
                        type: 'string',
                        pattern: '^[gimuy]*$'
                    }
                },
                required: ['pattern'],
                additionalProperties: false
            }
        },
        required: ['$regularExpression'],
        additionalProperties: false
    },
    DBPointer: {
        type: 'object',
        properties: {
            $dbPointer: {
                type: 'object',
                properties: {
                    $ref: {
                        type: 'string'
                    },
                    $id: {
                        $ref: '#/$defs/ObjectId'
                    }
                },
                required: ['$ref', '$id'],
                additionalProperties: false
            }
        },
        required: ['$dbPointer'],
        additionalProperties: false
    },
    Date: {
        type: 'object',
        properties: {
            $date: {
                type: 'string',
                format: 'date-time'
            }
        },
        required: ['$date'],
        additionalProperties: false
    },
    DBRef: {
        type: 'object',
        properties: {
            $ref: {
                type: 'string'
            },
            $id: {},
            $db: {
                type: 'string'
            }
        },
        required: ['$ref', '$id'],
        additionalProperties: true
    },
    MinKey: {
        type: 'object',
        properties: {
            $minKey: {
                type: 'integer',
                const: 1
            }
        },
        required: ['$minKey'],
        additionalProperties: false
    },
    MaxKey: {
        type: 'object',
        properties: {
            $maxKey: {
                type: 'integer',
                const: 1
            }
        },
        required: ['$maxKey'],
        additionalProperties: false
    },
    Undefined: {
        type: 'object',
        properties: {
            $undefined: {
                type: 'boolean',
                const: true
            }
        },
        required: ['$undefined'],
        additionalProperties: false
    }
};
const createConvertInternalToStandard = function () {
    const usedDefinitions = new Set();
    function getUsedDefinitions() {
        const filteredDefinitions = Object.fromEntries(Object.entries(exports.RELAXED_EJSON_DEFINITIONS).filter(([key]) => usedDefinitions.has(key)));
        return Object.freeze(filteredDefinitions);
    }
    function markUsedDefinition(ref) {
        usedDefinitions.add(ref.split('/')[2]);
    }
    function convertInternalType(internalType) {
        const type = exports.InternalTypeToStandardTypeMap[internalType];
        if (!type)
            throw new Error(`Encountered unknown type: ${internalType}`);
        return { ...type };
    }
    async function parseType(type, signal) {
        await (0, util_1.allowAbort)(signal);
        const schema = convertInternalType(type.bsonType);
        if (schema.$ref)
            markUsedDefinition(schema.$ref);
        switch (type.bsonType) {
            case 'Array':
                schema.items = await parseTypes(type.types, signal);
                break;
            case 'Document':
                Object.assign(schema, await parseFields(type.fields, signal));
                break;
        }
        return schema;
    }
    function isPlainTypesOnly(types) {
        return types.every(definition => !!definition.type && Object.keys(definition).length === 1);
    }
    async function parseTypes(types, signal) {
        await (0, util_1.allowAbort)(signal);
        const definedTypes = types.filter(type => type.bsonType.toLowerCase() !== 'undefined');
        const isSingleType = definedTypes.length === 1;
        if (isSingleType) {
            return parseType(definedTypes[0], signal);
        }
        const parsedTypes = await Promise.all(definedTypes.map(type => parseType(type, signal)));
        if (isPlainTypesOnly(parsedTypes)) {
            return {
                type: parsedTypes.map(({ type }) => type)
            };
        }
        return {
            anyOf: parsedTypes
        };
    }
    async function parseFields(fields, signal) {
        const required = [];
        const properties = {};
        for (const field of fields) {
            if (field.probability === 1)
                required.push(field.name);
            properties[field.name] = await parseTypes(field.types, signal);
        }
        return { required, properties };
    }
    return async function convert(internalSchema, options = {}) {
        const { required, properties } = await parseFields(internalSchema.fields, options.signal);
        const schema = {
            $schema: 'https://json-schema.org/draft/2020-12/schema',
            type: 'object',
            required,
            properties,
            $defs: getUsedDefinitions()
        };
        return schema;
    };
};
function convertInternalToStandard(internalSchema, options = {}) {
    return createConvertInternalToStandard()(internalSchema, options);
}
exports.convertInternalToStandard = convertInternalToStandard;
//# sourceMappingURL=internalToStandard.js.map