import { addHashCode, areEqual, arraySortByInto, definedMap, definedMapWithDefault, hashCodeOf, hashString, setUnionInto, } from "collection-utils";
import { TypeAttributeKind } from "./attributes/TypeAttributes.js";
import { assert, indentationString, panic } from "./support/Support.js";
import { EnumType, PrimitiveType, UnionType, } from "./Type/index.js";
import { derefTypeRef } from "./Type/TypeRef.js";
function debugStringForType(t) {
    const target = followTargetType(t);
    if (t === target) {
        return t.kind;
    }
    return `${t.kind} (${target.kind})`;
}
function getNumberOfNodes(xfer) {
    return definedMapWithDefault(xfer, 0, (x) => x.getNumberOfNodes());
}
export class Transformer {
    constructor(kind, graph, sourceTypeRef) {
        this.kind = kind;
        this.graph = graph;
        this.sourceTypeRef = sourceTypeRef;
    }
    get sourceType() {
        return derefTypeRef(this.sourceTypeRef, this.graph);
    }
    /** This must return a newly constructed set. */
    getChildren() {
        return new Set([this.sourceType]);
    }
    getNumberOfNodes() {
        return 1;
    }
    equals(other) {
        return this.sourceTypeRef === other.sourceTypeRef;
    }
    hashCode() {
        return hashCodeOf(this.sourceTypeRef);
    }
    debugDescription() {
        return `${debugStringForType(this.sourceType)} -> ${this.kind}`;
    }
    debugPrintContinuations(_indent) {
        return;
    }
    debugPrint(indent) {
        console.log(indentationString(indent) + this.debugDescription());
        this.debugPrintContinuations(indent + 1);
    }
}
export class ProducerTransformer extends Transformer {
    constructor(kind, graph, sourceTypeRef, consumer) {
        super(kind, graph, sourceTypeRef);
        this.consumer = consumer;
    }
    getChildren() {
        const children = super.getChildren();
        if (this.consumer === undefined)
            return children;
        return setUnionInto(children, this.consumer.getChildren());
    }
    getNumberOfNodes() {
        return super.getNumberOfNodes() + getNumberOfNodes(this.consumer);
    }
    equals(other) {
        if (!super.equals(other))
            return false;
        if (!(other instanceof ProducerTransformer))
            return false;
        return areEqual(this.consumer, other.consumer);
    }
    hashCode() {
        const h = super.hashCode();
        return addHashCode(h, hashCodeOf(this.consumer));
    }
    debugPrintContinuations(indent) {
        if (this.consumer === undefined)
            return;
        this.consumer.debugPrint(indent);
    }
}
export class MatchTransformer extends Transformer {
    constructor(kind, graph, sourceTypeRef, transformer) {
        super(kind, graph, sourceTypeRef);
        this.transformer = transformer;
    }
    getChildren() {
        return setUnionInto(super.getChildren(), this.transformer.getChildren());
    }
    getNumberOfNodes() {
        return super.getNumberOfNodes() + this.transformer.getNumberOfNodes();
    }
    equals(other) {
        if (!super.equals(other))
            return false;
        if (!(other instanceof MatchTransformer))
            return false;
        return this.transformer.equals(other.transformer);
    }
    hashCode() {
        const h = super.hashCode();
        return addHashCode(h, this.transformer.hashCode());
    }
    debugPrintContinuations(indent) {
        this.transformer.debugPrint(indent);
    }
}
export class DecodingTransformer extends ProducerTransformer {
    constructor(graph, sourceTypeRef, consumer) {
        super("decode", graph, sourceTypeRef, consumer);
    }
    get canFail() {
        return false;
    }
    reverse(targetTypeRef, continuationTransformer) {
        if (continuationTransformer !== undefined) {
            return panic("Reversing a decoding transformer cannot have a continuation");
        }
        if (this.consumer === undefined) {
            return new EncodingTransformer(this.graph, targetTypeRef);
        }
        return this.consumer.reverse(targetTypeRef, new EncodingTransformer(this.graph, this.consumer.sourceTypeRef));
    }
    reconstitute(builder) {
        return new DecodingTransformer(builder.typeGraph, builder.reconstituteTypeRef(this.sourceTypeRef), definedMap(this.consumer, (xfer) => xfer.reconstitute(builder)));
    }
    equals(other) {
        if (!super.equals(other))
            return false;
        return other instanceof DecodingTransformer;
    }
}
export class EncodingTransformer extends Transformer {
    constructor(graph, sourceTypeRef) {
        super("encode", graph, sourceTypeRef);
    }
    get canFail() {
        return false;
    }
    reverse(_targetTypeRef, _continuationTransformer) {
        return panic("Can't reverse encoding transformer");
    }
    reconstitute(builder) {
        return new EncodingTransformer(builder.typeGraph, builder.reconstituteTypeRef(this.sourceTypeRef));
    }
    equals(other) {
        if (!super.equals(other))
            return false;
        if (!(other instanceof EncodingTransformer))
            return false;
        return true;
    }
}
export class ArrayDecodingTransformer extends ProducerTransformer {
    constructor(graph, sourceTypeRef, consumer, _itemTargetTypeRef, itemTransformer) {
        super("decode-array", graph, sourceTypeRef, consumer);
        this._itemTargetTypeRef = _itemTargetTypeRef;
        this.itemTransformer = itemTransformer;
    }
    getChildren() {
        const children = super.getChildren();
        children.add(this.itemTargetType);
        return setUnionInto(children, this.itemTransformer.getChildren());
    }
    getNumberOfNodes() {
        return (super.getNumberOfNodes() + this.itemTransformer.getNumberOfNodes());
    }
    get canFail() {
        return false;
    }
    get itemTargetType() {
        return derefTypeRef(this._itemTargetTypeRef, this.graph);
    }
    reverse(targetTypeRef, continuationTransformer) {
        if (continuationTransformer !== undefined) {
            return panic("Reversing a decoding transformer cannot have a continuation");
        }
        const itemTransformer = this.itemTransformer.reverse(this._itemTargetTypeRef, undefined);
        if (this.consumer === undefined) {
            return new ArrayEncodingTransformer(this.graph, targetTypeRef, this.itemTransformer.sourceTypeRef, itemTransformer);
        }
        return this.consumer.reverse(targetTypeRef, new ArrayEncodingTransformer(this.graph, this.consumer.sourceTypeRef, this.itemTransformer.sourceTypeRef, itemTransformer));
    }
    reconstitute(builder) {
        return new ArrayDecodingTransformer(builder.typeGraph, builder.reconstituteTypeRef(this.sourceTypeRef), definedMap(this.consumer, (xfer) => xfer.reconstitute(builder)), builder.reconstituteTypeRef(this._itemTargetTypeRef), this.itemTransformer.reconstitute(builder));
    }
    hashCode() {
        let h = super.hashCode();
        h = addHashCode(h, hashCodeOf(this._itemTargetTypeRef));
        h = addHashCode(h, this.itemTransformer.hashCode());
        return h;
    }
    equals(other) {
        if (!super.equals(other))
            return false;
        if (!(other instanceof ArrayDecodingTransformer))
            return false;
        if (!areEqual(this._itemTargetTypeRef, other._itemTargetTypeRef))
            return false;
        return this.itemTransformer.equals(other.itemTransformer);
    }
    debugPrintContinuations(indent) {
        this.itemTransformer.debugPrint(indent);
        super.debugPrintContinuations(indent);
    }
}
export class ArrayEncodingTransformer extends Transformer {
    constructor(graph, sourceTypeRef, _itemTargetTypeRef, itemTransformer) {
        super("encode-array", graph, sourceTypeRef);
        this._itemTargetTypeRef = _itemTargetTypeRef;
        this.itemTransformer = itemTransformer;
    }
    getChildren() {
        const children = super.getChildren();
        children.add(this.itemTargetType);
        return setUnionInto(children, this.itemTransformer.getChildren());
    }
    getNumberOfNodes() {
        return (super.getNumberOfNodes() + this.itemTransformer.getNumberOfNodes());
    }
    get canFail() {
        return false;
    }
    get itemTargetType() {
        return derefTypeRef(this._itemTargetTypeRef, this.graph);
    }
    reverse(_targetTypeRef, _continuationTransformer) {
        return panic("Can't reverse array encoding transformer");
    }
    reconstitute(builder) {
        return new ArrayEncodingTransformer(builder.typeGraph, builder.reconstituteTypeRef(this.sourceTypeRef), builder.reconstituteTypeRef(this._itemTargetTypeRef), this.itemTransformer.reconstitute(builder));
    }
    hashCode() {
        let h = super.hashCode();
        h = addHashCode(h, hashCodeOf(this._itemTargetTypeRef));
        return addHashCode(h, this.itemTransformer.hashCode());
    }
    equals(other) {
        if (!super.equals(other))
            return false;
        if (!(other instanceof ArrayEncodingTransformer))
            return false;
        if (!areEqual(this._itemTargetTypeRef, other._itemTargetTypeRef))
            return false;
        return this.itemTransformer.equals(other.itemTransformer);
    }
    debugPrintContinuations(indent) {
        this.itemTransformer.debugPrint(indent);
        super.debugPrintContinuations(indent);
    }
}
export class ChoiceTransformer extends Transformer {
    constructor(graph, sourceTypeRef, transformers) {
        super("choice", graph, sourceTypeRef);
        this.transformers = transformers;
        assert(transformers.length > 0, "Choice must have at least one transformer");
    }
    getChildren() {
        const children = super.getChildren();
        for (const xfer of this.transformers) {
            setUnionInto(children, xfer.getChildren());
        }
        return children;
    }
    getNumberOfNodes() {
        let n = 0;
        for (const xfer of this.transformers) {
            n += xfer.getNumberOfNodes();
        }
        return super.getNumberOfNodes() + n;
    }
    get canFail() {
        return this.transformers.some((xfer) => xfer.canFail);
    }
    reverse(targetTypeRef, continuationTransformer) {
        const transformers = this.transformers.map((xfer) => xfer.reverse(targetTypeRef, continuationTransformer));
        if (transformers.every((xfer) => xfer instanceof UnionMemberMatchTransformer)) {
            const memberMatchers = transformers;
            const first = memberMatchers[0];
            if (memberMatchers.every((xfer) => first.memberType.equals(xfer.memberType))) {
                const subTransformers = memberMatchers.map((xfer) => xfer.transformer);
                return new UnionMemberMatchTransformer(this.graph, targetTypeRef, new ChoiceTransformer(this.graph, subTransformers[0].sourceTypeRef, subTransformers), first.memberTypeRef);
            }
        }
        return new ChoiceTransformer(this.graph, targetTypeRef, transformers);
    }
    reconstitute(builder) {
        return new ChoiceTransformer(builder.typeGraph, builder.reconstituteTypeRef(this.sourceTypeRef), this.transformers.map((xfer) => xfer.reconstitute(builder)));
    }
    equals(other) {
        if (!super.equals(other))
            return false;
        if (!(other instanceof ChoiceTransformer))
            return false;
        return areEqual(this.transformers, other.transformers);
    }
    hashCode() {
        const h = super.hashCode();
        return addHashCode(h, hashCodeOf(this.transformers));
    }
    debugPrintContinuations(indent) {
        for (const xfer of this.transformers) {
            xfer.debugPrint(indent);
        }
    }
}
export class DecodingChoiceTransformer extends Transformer {
    constructor(graph, sourceTypeRef, nullTransformer, integerTransformer, doubleTransformer, boolTransformer, stringTransformer, arrayTransformer, objectTransformer) {
        super("decoding-choice", graph, sourceTypeRef);
        this.nullTransformer = nullTransformer;
        this.integerTransformer = integerTransformer;
        this.doubleTransformer = doubleTransformer;
        this.boolTransformer = boolTransformer;
        this.stringTransformer = stringTransformer;
        this.arrayTransformer = arrayTransformer;
        this.objectTransformer = objectTransformer;
    }
    get transformers() {
        const transformers = [];
        function add(xfer) {
            if (xfer === undefined)
                return;
            transformers.push(xfer);
        }
        add(this.nullTransformer);
        add(this.integerTransformer);
        add(this.doubleTransformer);
        add(this.boolTransformer);
        add(this.stringTransformer);
        add(this.arrayTransformer);
        add(this.objectTransformer);
        return transformers;
    }
    getChildren() {
        const children = super.getChildren();
        for (const xfer of this.transformers) {
            setUnionInto(children, xfer.getChildren());
        }
        return children;
    }
    getNumberOfNodes() {
        let n = super.getNumberOfNodes();
        for (const xfer of this.transformers) {
            n += getNumberOfNodes(xfer);
        }
        return n;
    }
    get canFail() {
        return false;
    }
    reverse(targetTypeRef, continuationTransformer) {
        assert(continuationTransformer === undefined, "Reversing a decoding transformer can't have a target transformer");
        const transformers = new Map();
        const memberMatchTransformers = new Map();
        function addCase(reversed) {
            if (reversed instanceof UnionMemberMatchTransformer) {
                const memberType = reversed.memberType;
                let arr = memberMatchTransformers.get(memberType);
                if (arr === undefined) {
                    arr = [];
                    memberMatchTransformers.set(memberType, arr);
                }
                arr.push(reversed);
            }
            else {
                const kind = reversed.sourceType.kind;
                let arr = transformers.get(kind);
                if (arr === undefined) {
                    arr = [];
                    transformers.set(kind, arr);
                }
                arr.push(reversed);
            }
        }
        function reverseAndAdd(transformer) {
            const reversed = transformer.reverse(targetTypeRef, undefined);
            let cases = [];
            // Flatten nested ChoiceTransformers
            if (reversed instanceof ChoiceTransformer) {
                cases = reversed.transformers;
            }
            else {
                cases = [reversed];
            }
            for (const xfer of cases) {
                addCase(xfer);
            }
        }
        // FIXME: Actually, keep all the failing transformers and put them first, then
        // finally do the simplest non-failing one.  Actually actually, maybe not, since
        // we're reversing to encode?  What's a case where this would be useful?
        // If there are non-failing transformers, we ignore the ones that can fail and
        // just pick the "simplest" non-failing one, being the one with the least number
        // of nodes.
        function filter(xfers) {
            assert(xfers.length > 0, "Must have at least one transformer");
            const nonfailing = xfers.filter((xfer) => {
                // For member match transformers we're deciding between
                // multiple that match against the same member, so the fact
                // that the match can fail is not important, since if it fails
                // it will fail for all candidates.  The question is whether
                // its continuation can fail.
                if (xfer instanceof UnionMemberMatchTransformer) {
                    return !xfer.transformer.canFail;
                }
                return !xfer.canFail;
            });
            if (nonfailing.length === 0)
                return xfers;
            const smallest = arraySortByInto(nonfailing.map((x) => [x.getNumberOfNodes(), x]), ([c, _]) => c)[0][1];
            return [smallest];
        }
        this.transformers.forEach(reverseAndAdd);
        const allTransformers = Array.from(transformers.values()).concat(Array.from(memberMatchTransformers.values()));
        const resultingTransformers = [].concat(...allTransformers.map(filter));
        // No choice needed if there's only one
        if (resultingTransformers.length === 1) {
            return resultingTransformers[0];
        }
        return new ChoiceTransformer(this.graph, targetTypeRef, resultingTransformers);
    }
    reconstitute(builder) {
        function reconstitute(xf) {
            if (xf === undefined)
                return undefined;
            return xf.reconstitute(builder);
        }
        return new DecodingChoiceTransformer(builder.typeGraph, builder.reconstituteTypeRef(this.sourceTypeRef), reconstitute(this.nullTransformer), reconstitute(this.integerTransformer), reconstitute(this.doubleTransformer), reconstitute(this.boolTransformer), reconstitute(this.stringTransformer), reconstitute(this.arrayTransformer), reconstitute(this.objectTransformer));
    }
    equals(other) {
        if (!super.equals(other))
            return false;
        if (!(other instanceof DecodingChoiceTransformer))
            return false;
        if (!areEqual(this.nullTransformer, other.nullTransformer))
            return false;
        if (!areEqual(this.integerTransformer, other.integerTransformer))
            return false;
        if (!areEqual(this.doubleTransformer, other.doubleTransformer))
            return false;
        if (!areEqual(this.boolTransformer, other.boolTransformer))
            return false;
        if (!areEqual(this.stringTransformer, other.stringTransformer))
            return false;
        if (!areEqual(this.arrayTransformer, other.arrayTransformer))
            return false;
        if (!areEqual(this.objectTransformer, other.objectTransformer))
            return false;
        return true;
    }
    hashCode() {
        let h = super.hashCode();
        h = addHashCode(h, hashCodeOf(this.nullTransformer));
        h = addHashCode(h, hashCodeOf(this.integerTransformer));
        h = addHashCode(h, hashCodeOf(this.doubleTransformer));
        h = addHashCode(h, hashCodeOf(this.boolTransformer));
        h = addHashCode(h, hashCodeOf(this.stringTransformer));
        h = addHashCode(h, hashCodeOf(this.arrayTransformer));
        h = addHashCode(h, hashCodeOf(this.objectTransformer));
        return h;
    }
    debugPrintContinuations(indent) {
        for (const xfer of this.transformers) {
            xfer.debugPrint(indent);
        }
    }
}
export class UnionMemberMatchTransformer extends MatchTransformer {
    constructor(graph, sourceTypeRef, transformer, memberTypeRef) {
        super("union-member-match", graph, sourceTypeRef, transformer);
        this.memberTypeRef = memberTypeRef;
    }
    get sourceType() {
        const t = derefTypeRef(this.sourceTypeRef, this.graph);
        if (!(t instanceof UnionType)) {
            return panic("The source of a union member match transformer must be a union type");
        }
        return t;
    }
    get canFail() {
        return true;
    }
    get memberType() {
        return derefTypeRef(this.memberTypeRef, this.graph);
    }
    getChildren() {
        return super.getChildren().add(this.memberType);
    }
    reverse(_targetTypeRef, _continuationTransformer) {
        return panic("Can't reverse union member match transformer");
    }
    reconstitute(builder) {
        return new UnionMemberMatchTransformer(builder.typeGraph, builder.reconstituteTypeRef(this.sourceTypeRef), this.transformer.reconstitute(builder), builder.reconstituteTypeRef(this.memberTypeRef));
    }
    equals(other) {
        if (!super.equals(other))
            return false;
        if (!(other instanceof UnionMemberMatchTransformer))
            return false;
        return this.memberTypeRef === other.memberTypeRef;
    }
    hashCode() {
        const h = super.hashCode();
        return addHashCode(h, hashCodeOf(this.memberTypeRef));
    }
    debugDescription() {
        return `${super.debugDescription()} - member: ${debugStringForType(this.memberType)}`;
    }
}
/**
 * This matches strings and enum cases.
 */
export class StringMatchTransformer extends MatchTransformer {
    constructor(graph, sourceTypeRef, transformer, stringCase) {
        super("string-match", graph, sourceTypeRef, transformer);
        this.stringCase = stringCase;
    }
    get sourceType() {
        const t = derefTypeRef(this.sourceTypeRef, this.graph);
        if (!(t instanceof EnumType) &&
            !(t instanceof PrimitiveType && t.kind === "string")) {
            return panic("The source of a string match transformer must be an enum or string type");
        }
        return t;
    }
    get canFail() {
        return true;
    }
    reverse(targetTypeRef, continuationTransformer) {
        return this.transformer.reverse(targetTypeRef, new StringProducerTransformer(this.graph, this.transformer.sourceTypeRef, continuationTransformer, this.stringCase));
    }
    reconstitute(builder) {
        return new StringMatchTransformer(builder.typeGraph, builder.reconstituteTypeRef(this.sourceTypeRef), this.transformer.reconstitute(builder), this.stringCase);
    }
    equals(other) {
        if (!super.equals(other))
            return false;
        if (!(other instanceof StringMatchTransformer))
            return false;
        return this.stringCase !== other.stringCase;
    }
    hashCode() {
        const h = super.hashCode();
        return addHashCode(h, hashString(this.stringCase));
    }
    debugDescription() {
        return `${super.debugDescription()} - case: ${this.stringCase}`;
    }
}
export class UnionInstantiationTransformer extends Transformer {
    constructor(graph, sourceTypeRef) {
        super("union-instantiation", graph, sourceTypeRef);
    }
    get canFail() {
        return false;
    }
    reverse(targetTypeRef, continuationTransformer) {
        if (continuationTransformer === undefined) {
            return panic("Union instantiation transformer reverse must have a continuation");
        }
        return new UnionMemberMatchTransformer(this.graph, targetTypeRef, continuationTransformer, this.sourceTypeRef);
    }
    reconstitute(builder) {
        return new UnionInstantiationTransformer(builder.typeGraph, builder.reconstituteTypeRef(this.sourceTypeRef));
    }
    equals(other) {
        if (!super.equals(other))
            return false;
        return other instanceof UnionInstantiationTransformer;
    }
}
/**
 * Produces a string or an enum case.
 */
export class StringProducerTransformer extends ProducerTransformer {
    constructor(graph, sourceTypeRef, consumer, result) {
        super("string-producer", graph, sourceTypeRef, consumer);
        this.result = result;
    }
    get canFail() {
        return false;
    }
    reverse(targetTypeRef, continuationTransformer) {
        if (continuationTransformer === undefined) {
            return panic("Reversing a string producer transformer must have a continuation");
        }
        if (this.consumer === undefined) {
            return new StringMatchTransformer(this.graph, targetTypeRef, continuationTransformer, this.result);
        }
        return this.consumer.reverse(targetTypeRef, new StringMatchTransformer(this.graph, this.consumer.sourceTypeRef, continuationTransformer, this.result));
    }
    reconstitute(builder) {
        return new StringProducerTransformer(builder.typeGraph, builder.reconstituteTypeRef(this.sourceTypeRef), definedMap(this.consumer, (xfer) => xfer.reconstitute(builder)), this.result);
    }
    equals(other) {
        if (!super.equals(other))
            return false;
        if (!(other instanceof StringProducerTransformer))
            return false;
        return this.result === other.result;
    }
    hashCode() {
        const h = super.hashCode();
        return addHashCode(h, hashCodeOf(this.consumer));
    }
    debugDescription() {
        return `${super.debugDescription()} - result: ${this.result}`;
    }
}
export class ParseStringTransformer extends ProducerTransformer {
    constructor(graph, sourceTypeRef, consumer) {
        super("parse-string", graph, sourceTypeRef, consumer);
    }
    get canFail() {
        return true;
    }
    reverse(targetTypeRef, continuationTransformer) {
        if (this.consumer === undefined) {
            return new StringifyTransformer(this.graph, targetTypeRef, continuationTransformer);
        }
        return this.consumer.reverse(targetTypeRef, new StringifyTransformer(this.graph, this.consumer.sourceTypeRef, continuationTransformer));
    }
    reconstitute(builder) {
        return new ParseStringTransformer(builder.typeGraph, builder.reconstituteTypeRef(this.sourceTypeRef), definedMap(this.consumer, (xfer) => xfer.reconstitute(builder)));
    }
    equals(other) {
        if (!super.equals(other))
            return false;
        return other instanceof ParseStringTransformer;
    }
}
export class StringifyTransformer extends ProducerTransformer {
    constructor(graph, sourceTypeRef, consumer) {
        super("stringify", graph, sourceTypeRef, consumer);
    }
    get canFail() {
        return false;
    }
    reverse(targetTypeRef, continuationTransformer) {
        if (this.consumer === undefined) {
            return new ParseStringTransformer(this.graph, targetTypeRef, continuationTransformer);
        }
        return this.consumer.reverse(targetTypeRef, new ParseStringTransformer(this.graph, this.consumer.sourceTypeRef, continuationTransformer));
    }
    reconstitute(builder) {
        return new StringifyTransformer(builder.typeGraph, builder.reconstituteTypeRef(this.sourceTypeRef), definedMap(this.consumer, (xfer) => xfer.reconstitute(builder)));
    }
    equals(other) {
        if (!super.equals(other))
            return false;
        return other instanceof StringifyTransformer;
    }
}
export class MinMaxLengthCheckTransformer extends ProducerTransformer {
    constructor(graph, sourceTypeRef, consumer, minLength, maxLength) {
        super("min-max-length-check", graph, sourceTypeRef, consumer);
        this.minLength = minLength;
        this.maxLength = maxLength;
    }
    get canFail() {
        return true;
    }
    reverse(targetTypeRef, continuationTransformer) {
        if (this.consumer === undefined) {
            return new MinMaxLengthCheckTransformer(this.graph, targetTypeRef, continuationTransformer, this.minLength, this.maxLength);
        }
        return this.consumer.reverse(targetTypeRef, new MinMaxLengthCheckTransformer(this.graph, this.consumer.sourceTypeRef, continuationTransformer, this.minLength, this.maxLength));
    }
    reconstitute(builder) {
        return new MinMaxLengthCheckTransformer(builder.typeGraph, builder.reconstituteTypeRef(this.sourceTypeRef), definedMap(this.consumer, (xfer) => xfer.reconstitute(builder)), this.minLength, this.maxLength);
    }
    equals(other) {
        if (!super.equals(other))
            return false;
        return (other instanceof MinMaxLengthCheckTransformer &&
            this.minLength === other.minLength &&
            this.maxLength === other.maxLength);
    }
}
export class MinMaxValueTransformer extends ProducerTransformer {
    constructor(graph, sourceTypeRef, consumer, minimum, maximum) {
        super("min-max-value-check", graph, sourceTypeRef, consumer);
        this.minimum = minimum;
        this.maximum = maximum;
    }
    get canFail() {
        return true;
    }
    reverse(targetTypeRef, continuationTransformer) {
        if (this.consumer === undefined) {
            return new MinMaxValueTransformer(this.graph, targetTypeRef, continuationTransformer, this.minimum, this.maximum);
        }
        return this.consumer.reverse(targetTypeRef, new MinMaxValueTransformer(this.graph, this.consumer.sourceTypeRef, continuationTransformer, this.minimum, this.maximum));
    }
    reconstitute(builder) {
        return new MinMaxValueTransformer(builder.typeGraph, builder.reconstituteTypeRef(this.sourceTypeRef), definedMap(this.consumer, (xfer) => xfer.reconstitute(builder)), this.minimum, this.maximum);
    }
    equals(other) {
        if (!super.equals(other))
            return false;
        return (other instanceof MinMaxValueTransformer &&
            this.minimum === other.minimum &&
            this.maximum === other.maximum);
    }
}
export class Transformation {
    constructor(_graph, _targetTypeRef, transformer) {
        this._graph = _graph;
        this._targetTypeRef = _targetTypeRef;
        this.transformer = transformer;
    }
    get sourceType() {
        return this.transformer.sourceType;
    }
    get targetType() {
        return derefTypeRef(this._targetTypeRef, this._graph);
    }
    get reverse() {
        return new Transformation(this._graph, this.transformer.sourceTypeRef, this.transformer.reverse(this._targetTypeRef, undefined));
    }
    getChildren() {
        return this.transformer.getChildren().add(this.targetType);
    }
    reconstitute(builder) {
        return new Transformation(builder.typeGraph, builder.reconstituteTypeRef(this._targetTypeRef), this.transformer.reconstitute(builder));
    }
    equals(other) {
        if (!(other instanceof Transformation))
            return false;
        return (this._targetTypeRef === other._targetTypeRef &&
            this.transformer.equals(other.transformer));
    }
    hashCode() {
        let h = hashCodeOf(this._targetTypeRef);
        h = addHashCode(h, this.transformer.hashCode());
        return h;
    }
    debugPrint() {
        this.transformer.debugPrint(0);
        console.log(`-> ${debugStringForType(this.targetType)}`);
    }
}
class TransformationTypeAttributeKind extends TypeAttributeKind {
    constructor() {
        super("transformation");
    }
    appliesToTypeKind(_kind) {
        return true;
    }
    get inIdentity() {
        return true;
    }
    children(xf) {
        return xf.getChildren();
    }
    reconstitute(builder, xf) {
        return xf.reconstitute(builder);
    }
    stringify(_) {
        return "transformation";
    }
}
export const transformationTypeAttributeKind = new TransformationTypeAttributeKind();
export function transformationForType(t) {
    return transformationTypeAttributeKind.tryGetInAttributes(t.getAttributes());
}
export function followTargetType(t) {
    for (;;) {
        const xf = transformationForType(t);
        if (xf === undefined) {
            return t;
        }
        t = xf.targetType;
    }
}
