import * as M from "../meta";
import { ANY_TYPE, FieldType, FieldMap, SimpleType, Type } from "./type";

export type RefResolver = (ref: M.Ref) => FieldType;

export function typeForDefinition(resolver: RefResolver, d: M.Definition): Type {
    switch (d._variant) {
        case 'or':
            return Type.union(
                new Map([d.pattern0, d.pattern1, ... d.patternN].map(a =>
                    [a.variantLabel, typeFor(resolver, a.pattern)])));
        case 'and':
            return typeForIntersection(resolver, [d.pattern0, d.pattern1, ... d.patternN]);
        case 'Pattern':
            return typeFor(resolver, d.value);
    }
}

export function typeForIntersection(resolver: RefResolver, ps: M.NamedPattern[]): SimpleType {
    const fs = new Map();
    ps.forEach(p => gatherFields(fs, resolver, p));
    return fs.size > 0 ? Type.record(fs) : Type.unit();
}

export function typeFor(resolver: RefResolver, p: M.Pattern): SimpleType {
    if (p._variant === 'SimplePattern') {
        return simpleType(resolver, p.value);
    } else {
        return typeForIntersection(resolver, [M.NamedPattern.anonymous(p)]);
    }
}

export function simpleType(resolver: RefResolver, p: M.SimplePattern): FieldType {
    switch (p._variant) {
        case 'any':
            return ANY_TYPE;
        case 'atom':
            switch (p.atomKind._variant) {
                case 'Boolean': return Type.ref(`boolean`, null);
                case 'Float': return Type.ref(`number`, null);
                case 'Double': return Type.ref(`number`, null);
                case 'SignedInteger': return Type.ref(`number`, null);
                case 'String': return Type.ref(`string`, null);
                case 'ByteString': return Type.ref(`_.Bytes`, null);
                case 'Symbol': return Type.ref(`symbol`, null);
            }
        case 'embedded':
            return Type.ref(`_embedded`, null);
        case 'lit':
            return Type.unit();
        case 'seqof':
            return Type.array(simpleType(resolver, p.pattern));
        case 'setof':
            return Type.set(simpleType(resolver, p.pattern));
        case 'dictof':
            return Type.dictionary(simpleType(resolver, p.key), simpleType(resolver, p.value));
        case 'Ref':
            return resolver(p.value);
        default:
            ((_p: never) => {})(p);
            throw new Error("Unreachable");
    }
}

function compoundFields(fs: FieldMap, resolver: RefResolver, p: M.CompoundPattern): void {
    switch (p._variant) {
        case 'rec':
            gatherFields(fs, resolver, p.label);
            gatherFields(fs, resolver, p.fields);
            break;
        case 'tuple':
            p.patterns.forEach(pp => gatherFields(fs, resolver, pp));
            break;
        case 'tuplePrefix':
            p.fixed.forEach(pp => gatherFields(fs, resolver, pp));
            gatherFields(fs, resolver, M.promoteNamedSimplePattern(p.variable));
            break;
        case 'dict':
            p.entries.forEach((n, _k) =>
                gatherFields(fs, resolver, M.promoteNamedSimplePattern(n)));
            break;
        default:
            ((_p: never) => {})(p);
            throw new Error("Unreachable");
    }
}

function gatherFields(fs: FieldMap, resolver: RefResolver, n: M.NamedPattern): void {
    if (n._variant === 'named') {
        const t = simpleType(resolver, n.value.pattern);
        if (t.kind !== 'unit') {
            fs.set(n.value.name.description!, t);
        }
    } else if (n.value._variant === 'CompoundPattern') {
        compoundFields(fs, resolver, n.value.value);
    }
}
