"use strict";
/*--------------------------------------------------------------------------

@sinclair/typebox/compiler

The MIT License (MIT)

Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

---------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypeCompiler = exports.Policy = exports.TypeCompilerTypeGuardError = exports.TypeCompilerUnknownTypeError = exports.TypeCheck = void 0;
const transform_1 = require("../value/transform");
const guard_1 = require("../value/guard");
const errors_1 = require("../errors/errors");
const index_1 = require("../system/index");
const deref_1 = require("../value/deref");
const hash_1 = require("../value/hash");
const Types = require("../typebox");
// -------------------------------------------------------------------
// TypeCheck
// -------------------------------------------------------------------
class TypeCheck {
    constructor(schema, references, checkFunc, code) {
        this.schema = schema;
        this.references = references;
        this.checkFunc = checkFunc;
        this.code = code;
        this.hasTransform = transform_1.HasTransform.Has(schema, references);
    }
    /** Returns the generated assertion code used to validate this type. */
    Code() {
        return this.code;
    }
    /** Returns an iterator for each error in this value. */
    Errors(value) {
        return (0, errors_1.Errors)(this.schema, this.references, value);
    }
    /** Returns true if the value matches the compiled type. */
    Check(value) {
        return this.checkFunc(value);
    }
    /** Decodes a value or throws if error */
    Decode(value) {
        if (!this.checkFunc(value))
            throw new transform_1.TransformDecodeCheckError(this.schema, value, this.Errors(value).First());
        return this.hasTransform ? transform_1.DecodeTransform.Decode(this.schema, this.references, value) : value;
    }
    /** Encodes a value or throws if error */
    Encode(value) {
        const encoded = this.hasTransform ? transform_1.EncodeTransform.Encode(this.schema, this.references, value) : value;
        if (!this.checkFunc(encoded))
            throw new transform_1.TransformEncodeCheckError(this.schema, value, this.Errors(value).First());
        return encoded;
    }
}
exports.TypeCheck = TypeCheck;
// -------------------------------------------------------------------
// Character
// -------------------------------------------------------------------
var Character;
(function (Character) {
    function DollarSign(code) {
        return code === 36;
    }
    Character.DollarSign = DollarSign;
    function IsUnderscore(code) {
        return code === 95;
    }
    Character.IsUnderscore = IsUnderscore;
    function IsAlpha(code) {
        return (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
    }
    Character.IsAlpha = IsAlpha;
    function IsNumeric(code) {
        return code >= 48 && code <= 57;
    }
    Character.IsNumeric = IsNumeric;
})(Character || (Character = {}));
// -------------------------------------------------------------------
// MemberExpression
// -------------------------------------------------------------------
var MemberExpression;
(function (MemberExpression) {
    function IsFirstCharacterNumeric(value) {
        if (value.length === 0)
            return false;
        return Character.IsNumeric(value.charCodeAt(0));
    }
    function IsAccessor(value) {
        if (IsFirstCharacterNumeric(value))
            return false;
        for (let i = 0; i < value.length; i++) {
            const code = value.charCodeAt(i);
            const check = Character.IsAlpha(code) || Character.IsNumeric(code) || Character.DollarSign(code) || Character.IsUnderscore(code);
            if (!check)
                return false;
        }
        return true;
    }
    function EscapeHyphen(key) {
        return key.replace(/'/g, "\\'");
    }
    function Encode(object, key) {
        return IsAccessor(key) ? `${object}.${key}` : `${object}['${EscapeHyphen(key)}']`;
    }
    MemberExpression.Encode = Encode;
})(MemberExpression || (MemberExpression = {}));
// -------------------------------------------------------------------
// Identifier
// -------------------------------------------------------------------
var Identifier;
(function (Identifier) {
    function Encode($id) {
        const buffer = [];
        for (let i = 0; i < $id.length; i++) {
            const code = $id.charCodeAt(i);
            if (Character.IsNumeric(code) || Character.IsAlpha(code)) {
                buffer.push($id.charAt(i));
            }
            else {
                buffer.push(`_${code}_`);
            }
        }
        return buffer.join('').replace(/__/g, '_');
    }
    Identifier.Encode = Encode;
})(Identifier || (Identifier = {}));
// -------------------------------------------------------------------
// LiteralString
// -------------------------------------------------------------------
var LiteralString;
(function (LiteralString) {
    function Escape(content) {
        return content.replace(/'/g, "\\'");
    }
    LiteralString.Escape = Escape;
})(LiteralString || (LiteralString = {}));
// -------------------------------------------------------------------
// Errors
// -------------------------------------------------------------------
class TypeCompilerUnknownTypeError extends Types.TypeBoxError {
    constructor(schema) {
        super('Unknown type');
        this.schema = schema;
    }
}
exports.TypeCompilerUnknownTypeError = TypeCompilerUnknownTypeError;
class TypeCompilerTypeGuardError extends Types.TypeBoxError {
    constructor(schema) {
        super('Preflight validation check failed to guard for the given schema');
        this.schema = schema;
    }
}
exports.TypeCompilerTypeGuardError = TypeCompilerTypeGuardError;
// -------------------------------------------------------------------
// Policy
// -------------------------------------------------------------------
var Policy;
(function (Policy) {
    function IsExactOptionalProperty(value, key, expression) {
        return index_1.TypeSystemPolicy.ExactOptionalPropertyTypes ? `('${key}' in ${value} ? ${expression} : true)` : `(${MemberExpression.Encode(value, key)} !== undefined ? ${expression} : true)`;
    }
    Policy.IsExactOptionalProperty = IsExactOptionalProperty;
    function IsObjectLike(value) {
        return !index_1.TypeSystemPolicy.AllowArrayObject ? `(typeof ${value} === 'object' && ${value} !== null && !Array.isArray(${value}))` : `(typeof ${value} === 'object' && ${value} !== null)`;
    }
    Policy.IsObjectLike = IsObjectLike;
    function IsRecordLike(value) {
        return !index_1.TypeSystemPolicy.AllowArrayObject
            ? `(typeof ${value} === 'object' && ${value} !== null && !Array.isArray(${value}) && !(${value} instanceof Date) && !(${value} instanceof Uint8Array))`
            : `(typeof ${value} === 'object' && ${value} !== null && !(${value} instanceof Date) && !(${value} instanceof Uint8Array))`;
    }
    Policy.IsRecordLike = IsRecordLike;
    function IsNumberLike(value) {
        return !index_1.TypeSystemPolicy.AllowNaN ? `(typeof ${value} === 'number' && Number.isFinite(${value}))` : `typeof ${value} === 'number'`;
    }
    Policy.IsNumberLike = IsNumberLike;
    function IsVoidLike(value) {
        return index_1.TypeSystemPolicy.AllowNullVoid ? `(${value} === undefined || ${value} === null)` : `${value} === undefined`;
    }
    Policy.IsVoidLike = IsVoidLike;
})(Policy || (exports.Policy = Policy = {}));
/** Compiles Types for Runtime Type Checking */
var TypeCompiler;
(function (TypeCompiler) {
    // ----------------------------------------------------------------------
    // Guards
    // ----------------------------------------------------------------------
    function IsAnyOrUnknown(schema) {
        return schema[Types.Kind] === 'Any' || schema[Types.Kind] === 'Unknown';
    }
    // -------------------------------------------------------------------
    // Types
    // -------------------------------------------------------------------
    function* TAny(schema, references, value) {
        yield 'true';
    }
    function* TArray(schema, references, value) {
        yield `Array.isArray(${value})`;
        const [parameter, accumulator] = [CreateParameter('value', 'any'), CreateParameter('acc', 'number')];
        if ((0, guard_1.IsNumber)(schema.maxItems))
            yield `${value}.length <= ${schema.maxItems}`;
        if ((0, guard_1.IsNumber)(schema.minItems))
            yield `${value}.length >= ${schema.minItems}`;
        const elementExpression = CreateExpression(schema.items, references, 'value');
        yield `${value}.every((${parameter}) => ${elementExpression})`;
        if (Types.TypeGuard.TSchema(schema.contains) || (0, guard_1.IsNumber)(schema.minContains) || (0, guard_1.IsNumber)(schema.maxContains)) {
            const containsSchema = Types.TypeGuard.TSchema(schema.contains) ? schema.contains : Types.Type.Never();
            const checkExpression = CreateExpression(containsSchema, references, 'value');
            const checkMinContains = (0, guard_1.IsNumber)(schema.minContains) ? [`(count >= ${schema.minContains})`] : [];
            const checkMaxContains = (0, guard_1.IsNumber)(schema.maxContains) ? [`(count <= ${schema.maxContains})`] : [];
            const checkCount = `const count = value.reduce((${accumulator}, ${parameter}) => ${checkExpression} ? acc + 1 : acc, 0)`;
            const check = [`(count > 0)`, ...checkMinContains, ...checkMaxContains].join(' && ');
            yield `((${parameter}) => { ${checkCount}; return ${check}})(${value})`;
        }
        if (schema.uniqueItems === true) {
            const check = `const hashed = hash(element); if(set.has(hashed)) { return false } else { set.add(hashed) } } return true`;
            const block = `const set = new Set(); for(const element of value) { ${check} }`;
            yield `((${parameter}) => { ${block} )(${value})`;
        }
    }
    function* TAsyncIterator(schema, references, value) {
        yield `(typeof value === 'object' && Symbol.asyncIterator in ${value})`;
    }
    function* TBigInt(schema, references, value) {
        yield `(typeof ${value} === 'bigint')`;
        if ((0, guard_1.IsBigInt)(schema.exclusiveMaximum))
            yield `${value} < BigInt(${schema.exclusiveMaximum})`;
        if ((0, guard_1.IsBigInt)(schema.exclusiveMinimum))
            yield `${value} > BigInt(${schema.exclusiveMinimum})`;
        if ((0, guard_1.IsBigInt)(schema.maximum))
            yield `${value} <= BigInt(${schema.maximum})`;
        if ((0, guard_1.IsBigInt)(schema.minimum))
            yield `${value} >= BigInt(${schema.minimum})`;
        if ((0, guard_1.IsBigInt)(schema.multipleOf))
            yield `(${value} % BigInt(${schema.multipleOf})) === 0`;
    }
    function* TBoolean(schema, references, value) {
        yield `(typeof ${value} === 'boolean')`;
    }
    function* TConstructor(schema, references, value) {
        yield* Visit(schema.returns, references, `${value}.prototype`);
    }
    function* TDate(schema, references, value) {
        yield `(${value} instanceof Date) && Number.isFinite(${value}.getTime())`;
        if ((0, guard_1.IsNumber)(schema.exclusiveMaximumTimestamp))
            yield `${value}.getTime() < ${schema.exclusiveMaximumTimestamp}`;
        if ((0, guard_1.IsNumber)(schema.exclusiveMinimumTimestamp))
            yield `${value}.getTime() > ${schema.exclusiveMinimumTimestamp}`;
        if ((0, guard_1.IsNumber)(schema.maximumTimestamp))
            yield `${value}.getTime() <= ${schema.maximumTimestamp}`;
        if ((0, guard_1.IsNumber)(schema.minimumTimestamp))
            yield `${value}.getTime() >= ${schema.minimumTimestamp}`;
        if ((0, guard_1.IsNumber)(schema.multipleOfTimestamp))
            yield `(${value}.getTime() % ${schema.multipleOfTimestamp}) === 0`;
    }
    function* TFunction(schema, references, value) {
        yield `(typeof ${value} === 'function')`;
    }
    function* TInteger(schema, references, value) {
        yield `(typeof ${value} === 'number' && Number.isInteger(${value}))`;
        if ((0, guard_1.IsNumber)(schema.exclusiveMaximum))
            yield `${value} < ${schema.exclusiveMaximum}`;
        if ((0, guard_1.IsNumber)(schema.exclusiveMinimum))
            yield `${value} > ${schema.exclusiveMinimum}`;
        if ((0, guard_1.IsNumber)(schema.maximum))
            yield `${value} <= ${schema.maximum}`;
        if ((0, guard_1.IsNumber)(schema.minimum))
            yield `${value} >= ${schema.minimum}`;
        if ((0, guard_1.IsNumber)(schema.multipleOf))
            yield `(${value} % ${schema.multipleOf}) === 0`;
    }
    function* TIntersect(schema, references, value) {
        const check1 = schema.allOf.map((schema) => CreateExpression(schema, references, value)).join(' && ');
        if (schema.unevaluatedProperties === false) {
            const keyCheck = CreateVariable(`${new RegExp(Types.KeyResolver.ResolvePattern(schema))};`);
            const check2 = `Object.getOwnPropertyNames(${value}).every(key => ${keyCheck}.test(key))`;
            yield `(${check1} && ${check2})`;
        }
        else if (Types.TypeGuard.TSchema(schema.unevaluatedProperties)) {
            const keyCheck = CreateVariable(`${new RegExp(Types.KeyResolver.ResolvePattern(schema))};`);
            const check2 = `Object.getOwnPropertyNames(${value}).every(key => ${keyCheck}.test(key) || ${CreateExpression(schema.unevaluatedProperties, references, `${value}[key]`)})`;
            yield `(${check1} && ${check2})`;
        }
        else {
            yield `(${check1})`;
        }
    }
    function* TIterator(schema, references, value) {
        yield `(typeof value === 'object' && Symbol.iterator in ${value})`;
    }
    function* TLiteral(schema, references, value) {
        if (typeof schema.const === 'number' || typeof schema.const === 'boolean') {
            yield `(${value} === ${schema.const})`;
        }
        else {
            yield `(${value} === '${LiteralString.Escape(schema.const)}')`;
        }
    }
    function* TNever(schema, references, value) {
        yield `false`;
    }
    function* TNot(schema, references, value) {
        const expression = CreateExpression(schema.not, references, value);
        yield `(!${expression})`;
    }
    function* TNull(schema, references, value) {
        yield `(${value} === null)`;
    }
    function* TNumber(schema, references, value) {
        yield Policy.IsNumberLike(value);
        if ((0, guard_1.IsNumber)(schema.exclusiveMaximum))
            yield `${value} < ${schema.exclusiveMaximum}`;
        if ((0, guard_1.IsNumber)(schema.exclusiveMinimum))
            yield `${value} > ${schema.exclusiveMinimum}`;
        if ((0, guard_1.IsNumber)(schema.maximum))
            yield `${value} <= ${schema.maximum}`;
        if ((0, guard_1.IsNumber)(schema.minimum))
            yield `${value} >= ${schema.minimum}`;
        if ((0, guard_1.IsNumber)(schema.multipleOf))
            yield `(${value} % ${schema.multipleOf}) === 0`;
    }
    function* TObject(schema, references, value) {
        yield Policy.IsObjectLike(value);
        if ((0, guard_1.IsNumber)(schema.minProperties))
            yield `Object.getOwnPropertyNames(${value}).length >= ${schema.minProperties}`;
        if ((0, guard_1.IsNumber)(schema.maxProperties))
            yield `Object.getOwnPropertyNames(${value}).length <= ${schema.maxProperties}`;
        const knownKeys = Object.getOwnPropertyNames(schema.properties);
        for (const knownKey of knownKeys) {
            const memberExpression = MemberExpression.Encode(value, knownKey);
            const property = schema.properties[knownKey];
            if (schema.required && schema.required.includes(knownKey)) {
                yield* Visit(property, references, memberExpression);
                if (Types.ExtendsUndefined.Check(property) || IsAnyOrUnknown(property))
                    yield `('${knownKey}' in ${value})`;
            }
            else {
                const expression = CreateExpression(property, references, memberExpression);
                yield Policy.IsExactOptionalProperty(value, knownKey, expression);
            }
        }
        if (schema.additionalProperties === false) {
            if (schema.required && schema.required.length === knownKeys.length) {
                yield `Object.getOwnPropertyNames(${value}).length === ${knownKeys.length}`;
            }
            else {
                const keys = `[${knownKeys.map((key) => `'${key}'`).join(', ')}]`;
                yield `Object.getOwnPropertyNames(${value}).every(key => ${keys}.includes(key))`;
            }
        }
        if (typeof schema.additionalProperties === 'object') {
            const expression = CreateExpression(schema.additionalProperties, references, `${value}[key]`);
            const keys = `[${knownKeys.map((key) => `'${key}'`).join(', ')}]`;
            yield `(Object.getOwnPropertyNames(${value}).every(key => ${keys}.includes(key) || ${expression}))`;
        }
    }
    function* TPromise(schema, references, value) {
        yield `(typeof value === 'object' && typeof ${value}.then === 'function')`;
    }
    function* TRecord(schema, references, value) {
        yield Policy.IsRecordLike(value);
        if ((0, guard_1.IsNumber)(schema.minProperties))
            yield `Object.getOwnPropertyNames(${value}).length >= ${schema.minProperties}`;
        if ((0, guard_1.IsNumber)(schema.maxProperties))
            yield `Object.getOwnPropertyNames(${value}).length <= ${schema.maxProperties}`;
        const [patternKey, patternSchema] = Object.entries(schema.patternProperties)[0];
        const variable = CreateVariable(`${new RegExp(patternKey)}`);
        const check1 = CreateExpression(patternSchema, references, 'value');
        const check2 = Types.TypeGuard.TSchema(schema.additionalProperties) ? CreateExpression(schema.additionalProperties, references, value) : schema.additionalProperties === false ? 'false' : 'true';
        const expression = `(${variable}.test(key) ? ${check1} : ${check2})`;
        yield `(Object.entries(${value}).every(([key, value]) => ${expression}))`;
    }
    function* TRef(schema, references, value) {
        const target = (0, deref_1.Deref)(schema, references);
        // Reference: If we have seen this reference before we can just yield and return the function call.
        // If this isn't the case we defer to visit to generate and set the function for subsequent passes.
        if (state.functions.has(schema.$ref))
            return yield `${CreateFunctionName(schema.$ref)}(${value})`;
        yield* Visit(target, references, value);
    }
    function* TString(schema, references, value) {
        yield `(typeof ${value} === 'string')`;
        if ((0, guard_1.IsNumber)(schema.maxLength))
            yield `${value}.length <= ${schema.maxLength}`;
        if ((0, guard_1.IsNumber)(schema.minLength))
            yield `${value}.length >= ${schema.minLength}`;
        if (schema.pattern !== undefined) {
            const variable = CreateVariable(`${new RegExp(schema.pattern)};`);
            yield `${variable}.test(${value})`;
        }
        if (schema.format !== undefined) {
            yield `format('${schema.format}', ${value})`;
        }
    }
    function* TSymbol(schema, references, value) {
        yield `(typeof ${value} === 'symbol')`;
    }
    function* TTemplateLiteral(schema, references, value) {
        yield `(typeof ${value} === 'string')`;
        const variable = CreateVariable(`${new RegExp(schema.pattern)};`);
        yield `${variable}.test(${value})`;
    }
    function* TThis(schema, references, value) {
        // Note: This types are assured to be hoisted prior to this call. Just yield the function.
        yield `${CreateFunctionName(schema.$ref)}(${value})`;
    }
    function* TTuple(schema, references, value) {
        yield `Array.isArray(${value})`;
        if (schema.items === undefined)
            return yield `${value}.length === 0`;
        yield `(${value}.length === ${schema.maxItems})`;
        for (let i = 0; i < schema.items.length; i++) {
            const expression = CreateExpression(schema.items[i], references, `${value}[${i}]`);
            yield `${expression}`;
        }
    }
    function* TUndefined(schema, references, value) {
        yield `${value} === undefined`;
    }
    function* TUnion(schema, references, value) {
        const expressions = schema.anyOf.map((schema) => CreateExpression(schema, references, value));
        yield `(${expressions.join(' || ')})`;
    }
    function* TUint8Array(schema, references, value) {
        yield `${value} instanceof Uint8Array`;
        if ((0, guard_1.IsNumber)(schema.maxByteLength))
            yield `(${value}.length <= ${schema.maxByteLength})`;
        if ((0, guard_1.IsNumber)(schema.minByteLength))
            yield `(${value}.length >= ${schema.minByteLength})`;
    }
    function* TUnknown(schema, references, value) {
        yield 'true';
    }
    function* TVoid(schema, references, value) {
        yield Policy.IsVoidLike(value);
    }
    function* TKind(schema, references, value) {
        const instance = state.instances.size;
        state.instances.set(instance, schema);
        yield `kind('${schema[Types.Kind]}', ${instance}, ${value})`;
    }
    function* Visit(schema, references, value, useHoisting = true) {
        const references_ = (0, guard_1.IsString)(schema.$id) ? [...references, schema] : references;
        const schema_ = schema;
        // ----------------------------------------------------------------------------------
        // Hoisting
        // ----------------------------------------------------------------------------------
        if (useHoisting && (0, guard_1.IsString)(schema.$id)) {
            const functionName = CreateFunctionName(schema.$id);
            if (state.functions.has(functionName)) {
                return yield `${functionName}(${value})`;
            }
            else {
                const functionCode = CreateFunction(functionName, schema, references, 'value', false);
                state.functions.set(functionName, functionCode);
                return yield `${functionName}(${value})`;
            }
        }
        switch (schema_[Types.Kind]) {
            case 'Any':
                return yield* TAny(schema_, references_, value);
            case 'Array':
                return yield* TArray(schema_, references_, value);
            case 'AsyncIterator':
                return yield* TAsyncIterator(schema_, references_, value);
            case 'BigInt':
                return yield* TBigInt(schema_, references_, value);
            case 'Boolean':
                return yield* TBoolean(schema_, references_, value);
            case 'Constructor':
                return yield* TConstructor(schema_, references_, value);
            case 'Date':
                return yield* TDate(schema_, references_, value);
            case 'Function':
                return yield* TFunction(schema_, references_, value);
            case 'Integer':
                return yield* TInteger(schema_, references_, value);
            case 'Intersect':
                return yield* TIntersect(schema_, references_, value);
            case 'Iterator':
                return yield* TIterator(schema_, references_, value);
            case 'Literal':
                return yield* TLiteral(schema_, references_, value);
            case 'Never':
                return yield* TNever(schema_, references_, value);
            case 'Not':
                return yield* TNot(schema_, references_, value);
            case 'Null':
                return yield* TNull(schema_, references_, value);
            case 'Number':
                return yield* TNumber(schema_, references_, value);
            case 'Object':
                return yield* TObject(schema_, references_, value);
            case 'Promise':
                return yield* TPromise(schema_, references_, value);
            case 'Record':
                return yield* TRecord(schema_, references_, value);
            case 'Ref':
                return yield* TRef(schema_, references_, value);
            case 'String':
                return yield* TString(schema_, references_, value);
            case 'Symbol':
                return yield* TSymbol(schema_, references_, value);
            case 'TemplateLiteral':
                return yield* TTemplateLiteral(schema_, references_, value);
            case 'This':
                return yield* TThis(schema_, references_, value);
            case 'Tuple':
                return yield* TTuple(schema_, references_, value);
            case 'Undefined':
                return yield* TUndefined(schema_, references_, value);
            case 'Union':
                return yield* TUnion(schema_, references_, value);
            case 'Uint8Array':
                return yield* TUint8Array(schema_, references_, value);
            case 'Unknown':
                return yield* TUnknown(schema_, references_, value);
            case 'Void':
                return yield* TVoid(schema_, references_, value);
            default:
                if (!Types.TypeRegistry.Has(schema_[Types.Kind]))
                    throw new TypeCompilerUnknownTypeError(schema);
                return yield* TKind(schema_, references_, value);
        }
    }
    // -------------------------------------------------------------------
    // Compiler State
    // -------------------------------------------------------------------
    // prettier-ignore
    const state = {
        language: 'javascript',
        functions: new Map(),
        variables: new Map(),
        instances: new Map() // exterior kind instances
    };
    // -------------------------------------------------------------------
    // Compiler Factory
    // -------------------------------------------------------------------
    function CreateExpression(schema, references, value, useHoisting = true) {
        return `(${[...Visit(schema, references, value, useHoisting)].join(' && ')})`;
    }
    function CreateFunctionName($id) {
        return `check_${Identifier.Encode($id)}`;
    }
    function CreateVariable(expression) {
        const variableName = `local_${state.variables.size}`;
        state.variables.set(variableName, `const ${variableName} = ${expression}`);
        return variableName;
    }
    function CreateFunction(name, schema, references, value, useHoisting = true) {
        const [newline, pad] = ['\n', (length) => ''.padStart(length, ' ')];
        const parameter = CreateParameter('value', 'any');
        const returns = CreateReturns('boolean');
        const expression = [...Visit(schema, references, value, useHoisting)].map((expression) => `${pad(4)}${expression}`).join(` &&${newline}`);
        return `function ${name}(${parameter})${returns} {${newline}${pad(2)}return (${newline}${expression}${newline}${pad(2)})\n}`;
    }
    function CreateParameter(name, type) {
        const annotation = state.language === 'typescript' ? `: ${type}` : '';
        return `${name}${annotation}`;
    }
    function CreateReturns(type) {
        return state.language === 'typescript' ? `: ${type}` : '';
    }
    // -------------------------------------------------------------------
    // Compile
    // -------------------------------------------------------------------
    function Build(schema, references, options) {
        const functionCode = CreateFunction('check', schema, references, 'value'); // will populate functions and variables
        const parameter = CreateParameter('value', 'any');
        const returns = CreateReturns('boolean');
        const functions = [...state.functions.values()];
        const variables = [...state.variables.values()];
        // prettier-ignore
        const checkFunction = (0, guard_1.IsString)(schema.$id) // ensure top level schemas with $id's are hoisted
            ? `return function check(${parameter})${returns} {\n  return ${CreateFunctionName(schema.$id)}(value)\n}`
            : `return ${functionCode}`;
        return [...variables, ...functions, checkFunction].join('\n');
    }
    /** Generates the code used to assert this type and returns it as a string */
    function Code(...args) {
        const defaults = { language: 'javascript' };
        // prettier-ignore
        const [schema, references, options] = (args.length === 2 && (0, guard_1.IsArray)(args[1]) ? [args[0], args[1], defaults] :
            args.length === 2 && !(0, guard_1.IsArray)(args[1]) ? [args[0], [], args[1]] :
                args.length === 3 ? [args[0], args[1], args[2]] :
                    args.length === 1 ? [args[0], [], defaults] :
                        [null, [], defaults]);
        // compiler-reset
        state.language = options.language;
        state.variables.clear();
        state.functions.clear();
        state.instances.clear();
        if (!Types.TypeGuard.TSchema(schema))
            throw new TypeCompilerTypeGuardError(schema);
        for (const schema of references)
            if (!Types.TypeGuard.TSchema(schema))
                throw new TypeCompilerTypeGuardError(schema);
        return Build(schema, references, options);
    }
    TypeCompiler.Code = Code;
    /** Compiles a TypeBox type for optimal runtime type checking. Types must be valid TypeBox types of TSchema */
    function Compile(schema, references = []) {
        const generatedCode = Code(schema, references, { language: 'javascript' });
        const compiledFunction = globalThis.Function('kind', 'format', 'hash', generatedCode);
        const instances = new Map(state.instances);
        function typeRegistryFunction(kind, instance, value) {
            if (!Types.TypeRegistry.Has(kind) || !instances.has(instance))
                return false;
            const checkFunc = Types.TypeRegistry.Get(kind);
            const schema = instances.get(instance);
            return checkFunc(schema, value);
        }
        function formatRegistryFunction(format, value) {
            if (!Types.FormatRegistry.Has(format))
                return false;
            const checkFunc = Types.FormatRegistry.Get(format);
            return checkFunc(value);
        }
        function hashFunction(value) {
            return (0, hash_1.Hash)(value);
        }
        const checkFunction = compiledFunction(typeRegistryFunction, formatRegistryFunction, hashFunction);
        return new TypeCheck(schema, references, checkFunction, generatedCode);
    }
    TypeCompiler.Compile = Compile;
})(TypeCompiler || (exports.TypeCompiler = TypeCompiler = {}));
