import { embed, GenericEmbedded } from "./embedded";
import { Bytes } from "./bytes";
import { Record, Tuple } from "./record";
import { Value } from "./values";
import { Dictionary, Set } from "./dictionary";

export function fromJS<T = GenericEmbedded>(x: any): Value<T> {
    switch (typeof x) {
        case 'number':
            if (!Number.isInteger(x)) {
                // We require that clients be explicit about integer vs. non-integer types.
                throw new TypeError("Refusing to autoconvert non-integer number to Single or Double");
            }
            // FALL THROUGH
        case 'string':
        case 'symbol':
        case 'boolean':
            return x;

        case 'undefined':
        case 'function':
        case 'bigint':
            break;

        case 'object':
            if (x === null) {
                break;
            }
            if (typeof x.__as_preserve__ === 'function') {
                return x.__as_preserve__();
            }
            if (Record.isRecord<Value<T>, Tuple<Value<T>>, T>(x)) {
                return x;
            }
            if (Array.isArray(x)) {
                return x.map<Value<T>>(fromJS);
            }
            if (ArrayBuffer.isView(x) || x instanceof ArrayBuffer) {
                return Bytes.from(x);
            }
            if (Map.isMap(x)) {
                const d = new Dictionary<T>();
                x.forEach((v, k) => d.set(fromJS(k), fromJS(v)));
                return d;
            }
            if (Set.isSet(x)) {
                const s = new Set<T>();
                x.forEach(v => s.add(fromJS(v)));
                return s;
            }
            // Just... assume it's a T.
            return embed(x as T);

        default:
            break;
    }

    throw new TypeError("Cannot represent JavaScript value as Preserves: " + x);
}

declare module "./dictionary" {
    namespace Dictionary {
        export function fromJS<T = GenericEmbedded, V = GenericEmbedded>(x: object): Dictionary<T, Value<V>>;
    }
}

Dictionary.fromJS = function <T = GenericEmbedded, V = GenericEmbedded>(x: object): Dictionary<T, Value<V>> {
    if (Dictionary.isDictionary<T, Value<V>>(x)) return x;
    const d = new Dictionary<T, Value<V>>();
    Object.entries(x).forEach(([key, value]) => d.set(key, fromJS(value)));
    return d;
};
