// 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";
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) {
        this._id = _id;
        this.policy = 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));
    }
}
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);
        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 = {}) {
    var _a;
    const completeData = {
        ...data,
        enumeration: data.enumeration
            ? new Int32Array(data.enumeration)
            : undefined,
    };
    const completeBase = {
        ...base,
        asId: (_a = base.asId) !== null && _a !== void 0 ? _a : 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);
        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);
        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 = {}) {
    var _a;
    const completeBase = {
        ...base,
        asId: (_a = base.asId) !== null && _a !== void 0 ? _a : 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" });
}
// 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);
        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);
        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);
        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);
        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);
        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);
        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: false,
    }, completeBase), {
        props,
    }, completeBase);
}
export class Func extends Typedef {
    constructor(_id, inp, out, mat) {
        super(_id, {});
        this.inp = inp;
        this.out = out;
        this.mat = mat;
    }
    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);
    }
    rate(inp) {
        var _a;
        return func(this.inp, this.out, this.mat, { rateCalls: (_a = inp.calls) !== null && _a !== void 0 ? _a : 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, { rateCalls = false, rateWeight = undefined } = {}) {
    return new Func(core.funcb({
        inp: inp._id,
        out: out._id,
        mat: mat._id,
        rateCalls,
        rateWeight,
    }), inp, out, mat);
}
