// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0.
// SPDX-License-Identifier: MPL-2.0
import "./_dnt.polyfills.js";
import { core, wit_utils } from "./wit.js";
import { mapValues } from "./deps/mod.js";
import Policy, { PolicyPerEffectObject } from "./policy.js";
import { buildReduceEntries, withBase, } from "./utils/func_utils.js";
import { serializeFromParentInjection, serializeGenericInjection, serializeStaticInjection, } from "./utils/injection_utils.js";
import { ApplyFromArg, ApplyFromContext, ApplyFromParent, ApplyFromSecret, ApplyFromStatic, } from "./typegraph.js";
export function getPolicyChain(policy) {
    const chain = Array.isArray(policy) ? policy : [policy];
    return chain.map((p) => {
        if (p instanceof Policy) {
            return { tag: "simple", val: p._id };
        }
        return {
            tag: "per-effect",
            val: mapValues(p instanceof PolicyPerEffectObject ? p.value : p, (v) => v?._id),
        };
    });
}
export class Typedef {
    constructor(_id) {
        Object.defineProperty(this, "_id", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: _id
        });
        Object.defineProperty(this, "name", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "policy", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: null
        });
    }
    get repr() {
        return core.getTypeRepr(this._id);
    }
    withPolicy(policy) {
        const chain = getPolicyChain(policy);
        const newChain = [...(this.policy ?? []), ...chain];
        const id = core.withPolicy(this._id, newChain);
        return new Proxy(this, {
            get(target, prop, receiver) {
                if (prop === "_id") {
                    return id;
                }
                else if (prop === "policy") {
                    return newChain;
                }
                else {
                    return Reflect.get(target, prop, receiver);
                }
            },
        });
    }
    rename(name) {
        const id = core.renameType(this._id, name);
        return new Proxy(this, {
            get(target, prop, receiver) {
                if (prop === "_id") {
                    return id;
                }
                else if (prop === "name") {
                    return name;
                }
                else {
                    return Reflect.get(target, prop, receiver);
                }
            },
        });
    }
    asTypedef() {
        return new Typedef(this._id);
    }
    optional(data = {}) {
        if (this instanceof Optional) {
            return this;
        }
        return optional(this, data);
    }
    /** inject value to type (serialized injection) */
    withInjection(injection) {
        const wrapperId = core.withInjection(this._id, injection);
        return new Proxy(this, {
            get(target, prop, receiver) {
                switch (prop) {
                    case "_id":
                        return wrapperId;
                    case "injection":
                        return injection;
                    default:
                        return Reflect.get(target, prop, receiver);
                }
            },
        });
    }
    /** inject static value */
    set(value) {
        return this.withInjection(serializeStaticInjection(value));
    }
    /** inject value to type (generic source) */
    inject(value) {
        return this.withInjection(serializeGenericInjection("dynamic", value));
    }
    /** inject value to type */
    fromContext(value) {
        return this.withInjection(serializeGenericInjection("context", value));
    }
    /** inject value from secret */
    fromSecret(value) {
        return this.withInjection(serializeGenericInjection("secret", value));
    }
    /** inject value from parent */
    fromParent(value) {
        return this.withInjection(serializeFromParentInjection(value));
    }
    /** inject random value */
    fromRandom() {
        return this.withInjection(serializeGenericInjection("random", null));
    }
}
class Boolean extends Typedef {
    constructor(_id) {
        super(_id);
    }
}
/** boolean type */
export function boolean(base = {}) {
    return new Boolean(withBase(core.booleanb(), base));
}
export class Integer extends Typedef {
    constructor(_id, data) {
        super(_id);
        Object.defineProperty(this, "min", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "max", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "exclusiveMinimum", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "exclusiveMaximum", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "multipleOf", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "enumeration", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.min = data.min;
        this.max = data.max;
        this.exclusiveMinimum = data.exclusiveMinimum;
        this.exclusiveMaximum = data.exclusiveMaximum;
        this.multipleOf = data.multipleOf;
        this.enumeration = data.enumeration;
    }
    id(asId = true) {
        const id = core.asId(this._id, asId === "composite");
        return new Typedef(id);
    }
}
/** integer type */
export function integer(data = {}, base = {}) {
    const completeData = {
        ...data,
        enumeration: data.enumeration
            ? new Int32Array(data.enumeration)
            : undefined,
    };
    return new Integer(withBase(core.integerb(completeData), base), completeData);
}
class Float extends Typedef {
    constructor(_id, data) {
        super(_id);
        Object.defineProperty(this, "min", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "max", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "exclusiveMinimum", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "exclusiveMaximum", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "multipleOf", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "enumeration", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.min = data.min;
        this.max = data.max;
        this.exclusiveMinimum = data.exclusiveMinimum;
        this.exclusiveMaximum = data.exclusiveMaximum;
        this.multipleOf = data.multipleOf;
        this.enumeration = data.enumeration;
    }
}
/** floating-point type */
export function float(data = {}, base = {}) {
    const completeData = {
        ...data,
        enumeration: data.enumeration
            ? new Float64Array(data.enumeration)
            : undefined,
    };
    return new Float(withBase(core.floatb(completeData), base), completeData);
}
export class String extends Typedef {
    constructor(_id, data) {
        super(_id);
        Object.defineProperty(this, "min", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "max", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "format", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "pattern", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "enumeration", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.min = data.min;
        this.max = data.max;
        this.pattern = data.pattern;
        this.format = data.format;
        this.enumeration = data.enumeration;
    }
    id(asId = true) {
        const id = core.asId(this._id, asId === "composite");
        return new Typedef(id);
    }
}
/** string type */
export function string(data = {}, base = {}) {
    return new String(withBase(core.stringb(data), base), data);
}
/** uuid type */
export function uuid(base = {}) {
    return string({ format: "uuid" }, base);
}
/** email type */
export function email() {
    return string({ format: "email" });
}
/** uri type */
export function uri() {
    return string({ format: "uri" });
}
/** ean type */
export function ean() {
    return string({ format: "ean" });
}
/** path type */
export function path() {
    return string({ format: "path" });
}
/** datetime type */
export function datetime() {
    return string({ format: "date-time" });
}
/** json type */
export function json() {
    return string({ format: "json" });
}
/** hostname type */
export function hostname() {
    return string({ format: "hostname" });
}
/** phone number type */
export function phone() {
    return string({ format: "phone" });
}
// Note: enum is a reserved word
/** string enum type */
export function enum_(variants, base = {}) {
    return string({
        enumeration: variants.map((variant) => JSON.stringify(variant)),
    }, base);
}
class File extends Typedef {
    constructor(_id, data) {
        super(_id);
        Object.defineProperty(this, "min", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "max", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "allow", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.min = data.min;
        this.max = data.max;
        this.allow = data.allow;
    }
}
/** file type */
export function file(data = {}, base = {}) {
    return new File(withBase(core.fileb(data), base), data);
}
export class List extends Typedef {
    constructor(_id, data) {
        super(_id);
        Object.defineProperty(this, "min", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "max", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "items", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "uniqueItems", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.min = data.min;
        this.max = data.max;
        this.items = data.of;
        this.uniqueItems = data.uniqueItems;
    }
}
/** list type */
export function list(variant, data = {}, base = {}) {
    const completeData = {
        of: variant._id,
        ...data,
    };
    return new List(withBase(core.listb(completeData), base), completeData);
}
export class Optional extends Typedef {
    constructor(_id, data) {
        super(_id);
        Object.defineProperty(this, "item", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "defaultItem", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.item = data.of;
        this.defaultItem = data.defaultItem;
    }
}
/** optional type */
export function optional(variant, data = {}, base = {}) {
    const completeData = {
        of: variant._id,
        ...data,
        defaultItem: JSON.stringify(data.defaultItem),
    };
    return new Optional(withBase(core.optionalb(completeData), base), completeData);
}
class Union extends Typedef {
    constructor(_id, data) {
        super(_id);
        Object.defineProperty(this, "variants", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.variants = Array.from(data.variants);
    }
}
/** union type */
export function union(variants, base = {}) {
    const data = {
        variants: new Uint32Array(variants.map((variant) => variant._id)),
    };
    return new Union(withBase(core.unionb(data), base), data);
}
class Either extends Typedef {
    constructor(_id, data) {
        super(_id);
        Object.defineProperty(this, "variants", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.variants = Array.from(data.variants);
    }
}
/** either type */
export function either(variants, base = {}) {
    const data = {
        variants: new Uint32Array(variants.map((variant) => variant._id)),
    };
    return new Either(withBase(core.eitherb(data), base), data);
}
export class Struct extends Typedef {
    constructor(_id, { props }) {
        super(_id);
        Object.defineProperty(this, "props", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.props = props;
    }
}
/** struct type */
export function struct(props, { additionalProps, ...base } = {}) {
    return new Struct(withBase(core.structb({
        props: Object.entries(props).map(([name, typ]) => [name, typ._id]),
        additionalProps: additionalProps ?? false,
    }), base), { props });
}
function serializeApplyParamNode(node) {
    if (node instanceof ApplyFromArg) {
        return { source: "arg", name: node.name, typeId: node.type };
    }
    else if (node instanceof ApplyFromStatic) {
        return { source: "static", value_json: JSON.stringify(node.value) };
    }
    else if (node instanceof ApplyFromContext) {
        return { source: "context", key: node.key, typeId: node.type };
    }
    else if (node instanceof ApplyFromSecret) {
        return { source: "secret", key: node.key };
    }
    else if (node instanceof ApplyFromParent) {
        return { source: "parent", typeName: node.typeName };
    }
    else if (Array.isArray(node)) {
        return {
            type: "array",
            items: node.map(serializeApplyParamNode),
        };
    }
    else if (typeof node === "object" && node !== null) {
        return {
            type: "object",
            fields: mapValues(node, serializeApplyParamNode),
        };
    }
    throw new Error(`Unexpected node type: ${node}`);
}
export class Func extends Typedef {
    constructor(_id, inp, out, mat, parameterTransform = null, config = null) {
        super(_id);
        Object.defineProperty(this, "inp", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "out", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "mat", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "parameterTransform", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "config", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.inp = inp;
        this.out = out;
        this.mat = mat;
        this.parameterTransform = parameterTransform;
        this.config = config;
    }
    extend(fields) {
        const output = core.extendStruct(this.out._id, Object.entries(fields).map(([name, typ]) => [name, typ._id]));
        return func(this.inp, new Typedef(output), this.mat, this.parameterTransform, this.config);
    }
    /** reduce input type;
     * see [parameter transformations](https://metatype.dev/docs/reference/types/parameter-transformations)
     */
    reduce(value) {
        const reducedId = wit_utils.reduceb(this._id, buildReduceEntries(value));
        return new Func(reducedId, this.inp, this.out, this.mat, this.parameterTransform, this.config);
    }
    /** apply injections to the input type;
     * see [parameter transformations](https://metatype.dev/docs/reference/types/parameter-transformations)
     */
    apply(value) {
        const serialized = serializeApplyParamNode(value);
        if (typeof serialized !== "object" ||
            serialized == null ||
            serialized.type !== "object") {
            throw new Error("Invalid apply value: root must be an object");
        }
        const transformTree = JSON.stringify(serialized.fields);
        const transformData = core.getTransformData(this.inp._id, transformTree);
        return func(new Typedef(transformData.queryInput), this.out, this.mat, transformData.parameterTransform, this.config);
    }
    /** set rate limiting configs */
    rate(inp) {
        return func(this.inp, this.out, this.mat, this.parameterTransform, {
            rateCalls: inp.calls ?? false,
            rateWeight: inp.weight,
        });
    }
    /** */
    static fromTypeFunc(data) {
        return func(new Typedef(data.inp), new Typedef(data.out), { _id: data.mat });
    }
}
/** function type */
export function func(inp, out, mat, transformData = null, config = null) {
    const rateCalls = config?.rateCalls ?? false;
    const rateWeight = config?.rateWeight ?? undefined;
    return new Func(core.funcb({
        inp: inp._id,
        out: out._id,
        mat: mat._id,
        parameterTransform: transformData ?? undefined,
        rateCalls,
        rateWeight,
    }), inp, out, mat, transformData, config);
}
