// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0.
// SPDX-License-Identifier: MPL-2.0
import { core, wit_utils } from "./wit.js";
import { mapValues } from "./deps/mod.js";
import Policy, { PolicyPerEffectObject } from "./policy.js";
import { buildReduceData, serializeRecordValues } 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, base) {
        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, "runtimeConfig", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "policy", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: null
        });
        this.name = base.name;
        this.runtimeConfig = base.runtimeConfig;
    }
    get repr() {
        return core.getTypeRepr(this._id);
    }
    withPolicy(policy) {
        const id = core.withPolicy(this._id, getPolicyChain(policy));
        const chain = Array.isArray(policy) ? policy : [policy];
        return new Proxy(this, {
            get(target, prop, receiver) {
                if (prop === "_id") {
                    return id;
                }
                else if (prop === "policy") {
                    return chain;
                }
                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, { name: this.name });
    }
    optional(data = {}) {
        if (this instanceof Optional) {
            return this;
        }
        return optional(this, data);
    }
    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);
                }
            },
        });
    }
    set(value) {
        return this.withInjection(serializeStaticInjection(value));
    }
    inject(value) {
        return this.withInjection(serializeGenericInjection("dynamic", value));
    }
    fromContext(value) {
        return this.withInjection(serializeGenericInjection("context", value));
    }
    fromSecret(value) {
        return this.withInjection(serializeGenericInjection("secret", value));
    }
    fromParent(value) {
        return this.withInjection(serializeFromParentInjection(value));
    }
    fromRandom() {
        return this.withInjection(serializeGenericInjection("random", null));
    }
}
class Boolean extends Typedef {
    constructor(_id, base) {
        super(_id, base);
    }
}
export function boolean(base = {}) {
    const completeBase = {
        ...base,
        asId: false,
        runtimeConfig: base.config && serializeRecordValues(base.config),
    };
    return new Boolean(core.booleanb(completeBase), completeBase);
}
class Integer extends Typedef {
    constructor(_id, data, base) {
        super(_id, base);
        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;
    }
}
export function integer(data = {}, base = {}) {
    const completeData = {
        ...data,
        enumeration: data.enumeration
            ? new Int32Array(data.enumeration)
            : undefined,
    };
    const completeBase = {
        ...base,
        asId: base.asId ?? false,
        runtimeConfig: base.config && serializeRecordValues(base.config),
    };
    return new Integer(core.integerb(completeData, completeBase), completeData, completeBase);
}
class Float extends Typedef {
    constructor(_id, data, base) {
        super(_id, base);
        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;
    }
}
export function float(data = {}, base = {}) {
    const completeData = {
        ...data,
        enumeration: data.enumeration
            ? new Float64Array(data.enumeration)
            : undefined,
    };
    const completeBase = {
        ...base,
        asId: false,
        runtimeConfig: base.config && serializeRecordValues(base.config),
    };
    return new Float(core.floatb(completeData, completeBase), completeData, completeBase);
}
class StringT extends Typedef {
    constructor(_id, data, base) {
        super(_id, base);
        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;
    }
}
export function string(data = {}, base = {}) {
    const completeBase = {
        ...base,
        asId: base.asId ?? false,
        runtimeConfig: base.config && serializeRecordValues(base.config),
    };
    return new StringT(core.stringb(data, completeBase), data, completeBase);
}
export function uuid(base = {}) {
    return string({ format: "uuid" }, base);
}
export function email() {
    return string({ format: "email" });
}
export function uri() {
    return string({ format: "uri" });
}
export function ean() {
    return string({ format: "ean" });
}
export function path() {
    return string({ format: "path" });
}
export function datetime() {
    return string({ format: "date-time" });
}
export function json() {
    return string({ format: "json" });
}
export function hostname() {
    return string({ format: "hostname" });
}
export function phone() {
    return string({ format: "phone" });
}
// Note: enum is a reserved word
export function enum_(variants, base = {}) {
    return string({
        enumeration: variants.map((variant) => JSON.stringify(variant)),
    }, base);
}
class File extends Typedef {
    constructor(_id, data, base) {
        super(_id, base);
        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;
    }
}
export function file(data = {}, base = {}) {
    const completeBase = {
        ...base,
        asId: false,
        runtimeConfig: base.config && serializeRecordValues(base.config),
    };
    return new File(core.fileb(data, completeBase), data, completeBase);
}
class List extends Typedef {
    constructor(_id, data, base) {
        super(_id, base);
        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;
    }
}
export function list(variant, data = {}, base = {}) {
    const completeData = {
        of: variant._id,
        ...data,
    };
    const completeBase = {
        ...base,
        asId: false,
        runtimeConfig: base.config && serializeRecordValues(base.config),
    };
    return new List(core.listb(completeData, completeBase), completeData, completeBase);
}
class Optional extends Typedef {
    constructor(_id, data, base) {
        super(_id, base);
        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;
    }
}
export function optional(variant, data = {}, base = {}) {
    const completeData = {
        of: variant._id,
        ...data,
        defaultItem: JSON.stringify(data.defaultItem),
    };
    const completeBase = {
        ...base,
        asId: false,
        runtimeConfig: base.config && serializeRecordValues(base.config),
    };
    return new Optional(core.optionalb(completeData, completeBase), completeData, completeBase);
}
class Union extends Typedef {
    constructor(_id, data, base) {
        super(_id, base);
        Object.defineProperty(this, "variants", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.variants = Array.from(data.variants);
    }
}
export function union(variants, base = {}) {
    const data = {
        variants: new Uint32Array(variants.map((variant) => variant._id)),
    };
    const completeBase = {
        ...base,
        asId: false,
        runtimeConfig: base.config && serializeRecordValues(base.config),
    };
    return new Union(core.unionb(data, completeBase), data, completeBase);
}
class Either extends Typedef {
    constructor(_id, data, base) {
        super(_id, base);
        Object.defineProperty(this, "variants", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.variants = Array.from(data.variants);
    }
}
export function either(variants, base = {}) {
    const data = {
        variants: new Uint32Array(variants.map((variant) => variant._id)),
    };
    const completeBase = {
        ...base,
        asId: false,
        runtimeConfig: base.config && serializeRecordValues(base.config),
    };
    return new Either(core.eitherb(data, completeBase), data, completeBase);
}
export class Struct extends Typedef {
    constructor(_id, { props }, base) {
        super(_id, base);
        Object.defineProperty(this, "props", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.props = props;
    }
}
export function struct(props, base = {}) {
    const completeBase = {
        ...base,
        asId: false,
        runtimeConfig: base.config && serializeRecordValues(base.config),
    };
    return new Struct(core.structb({
        props: Object.entries(props).map(([name, typ]) => [name, typ._id]),
        additionalProps: completeBase.additionalProps ?? false,
    }, completeBase), {
        props,
    }, completeBase);
}
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.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(value) {
        const data = {
            paths: buildReduceData(value),
        };
        const reducedId = wit_utils.genReduceb(this.inp._id, data);
        return func(new Typedef(reducedId, {}), this.out, this.mat);
    }
    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);
    }
    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 });
    }
}
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);
}
