import { GenericEmbedded } from "./embedded";
import { is } from "./is";
import { Value } from "./values";
import { Writer } from "./writer";

export type Tuple<T> = Array<T> | [T];

export type Record<LabelType extends Value<T>, FieldsType extends Tuple<Value<T>>, T = GenericEmbedded>
    = FieldsType & { label: LabelType };

export type RecordGetters<Fs, R> = {
    [K in string & keyof Fs]: (r: R) => Fs[K];
};

export type CtorTypes<Fs, Names extends Tuple<keyof Fs>> =
    { [K in keyof Names]: Fs[keyof Fs & Names[K]] } & any[];

export interface RecordConstructor<L extends Value<T>, Fs, Names extends Tuple<keyof Fs>, T = GenericEmbedded> {
    (...fields: CtorTypes<Fs, Names>): Record<L, CtorTypes<Fs, Names>, T>;
    constructorInfo: RecordConstructorInfo<L, T>;
    isClassOf(v: any): v is Record<L, CtorTypes<Fs, Names>, T>;
    _: RecordGetters<Fs, Record<L, CtorTypes<Fs, Names>, T>>;
    fieldNumbers: { [K in string & keyof Fs]: number };
};

export interface RecordConstructorInfo<L extends Value<T>, T = GenericEmbedded> {
    label: L;
    arity: number;
}

export type InferredRecordType<L, FieldsType extends Tuple<any>> =
    L extends symbol ? (FieldsType extends Tuple<Value<infer T>>
        ? (Exclude<T, never> extends symbol ? Record<L, FieldsType, never> : Record<L, FieldsType, T>)
        : (FieldsType extends Tuple<Value<never>>
            ? Record<L, FieldsType, never>
            : "TYPE_ERROR_cannotInferFieldsType" & [never])) :
    L extends Value<infer T> ? (FieldsType extends Tuple<Value<T>>
        ? Record<L, FieldsType, T>
        : "TYPE_ERROR_cannotMatchFieldsTypeToLabelType" & [never]) :
    "TYPE_ERROR_cannotInferEmbeddedType" & [never];

export function Record<L, FieldsType extends Tuple<any>>(
    label: L,
    fields: FieldsType): InferredRecordType<L, FieldsType>
{
    (fields as any).label = label;
    return fields as any;
}

export namespace Record {
    export function isRecord<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T = GenericEmbedded>(x: any): x is Record<L, FieldsType, T> {
        return Array.isArray(x) && 'label' in x;
    }

    export function constructorInfo<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T = GenericEmbedded>(
        r: Record<L, FieldsType, T>): RecordConstructorInfo<L, T>
    {
        return { label: r.label, arity: r.length };
    }

    export function isClassOf<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T = GenericEmbedded>(
        ci: RecordConstructorInfo<L, T>, v: any): v is Record<L, FieldsType, T>
    {
        return (Record.isRecord(v)) && is(ci.label, v.label) && (ci.arity === v.length);
    }

    export function makeConstructor<Fs, T = GenericEmbedded>()
    : (<L extends Value<T>, Names extends Tuple<keyof Fs>>(label: L, fieldNames: Names) =>
        RecordConstructor<L, Fs, Names, T>)
    {
        return <L extends Value<T>, Names extends Tuple<keyof Fs>>(label: L, fieldNames: Names) => {
            const ctor: RecordConstructor<L, Fs, Names, T> =
                ((...fields: CtorTypes<Fs, Names>) =>
                    Record(label, fields)) as unknown as RecordConstructor<L, Fs, Names, T>;
            const constructorInfo = { label, arity: fieldNames.length };
            ctor.constructorInfo = constructorInfo;
            ctor.isClassOf = (v: any): v is Record<L, CtorTypes<Fs, Names>, T> => Record.isClassOf<L, CtorTypes<Fs, Names>, T>(constructorInfo, v);
            (ctor as any).fieldNumbers = {};
            (ctor as any)._ = {};
            fieldNames.forEach((name, i) => {
                (ctor._ as any)[name] = (r: Record<L, CtorTypes<Fs, Names>, T>) => r[i];
                (ctor.fieldNumbers as any)[name] = i;
            });
            return ctor;
        };
    }
}
