import * as cache from "./cache"

function _sequentialGenerator() {
    let _id = 0;
    return (): number => {
        let res = _id;
        _id += 1;
        return res;
    }
}

const nextObjectId = _sequentialGenerator();

let idmap = new Map<unknown, number>()
let objmap = new Map<number, unknown>()

export function objectId(obj: unknown): number {
    let v = idmap.get(obj)
    if (v === undefined) {
        v = nextObjectId()
        idmap.set(obj, v)
        objmap.set(v, obj)
    }
    return v
}

export function objectById<T = any>(id: number): T | null {
    return objmap.get(id) as T ?? null;
}

export function clearRefs() {
    idmap.clear()
    objmap.clear()
}

declare const _ref_type: unique symbol;
export type Ref<T = any> = RawRef & {
    [_ref_type]: T
}

export type RawRef<T = any> = {
    obj: T;
    key: keyof T;
}

function _ref<T, K extends keyof T>(obj: T, key: K): Ref<T[K]> {
    return { obj, key } as Ref<T[K]>
}

// returns a stable Ref: same identity when called with the same (obj, key) pair
// across re-renders.
export function ref<T, K extends keyof T>(obj: T, key: K): Ref<T[K]> {
    // circular import!!
    return cache.fn(_ref, obj, key)
}

export function value<T>(r: Ref<T>, v?: T): T {
    if (v !== undefined) {
        r.obj[r.key] = v
        return v
    } else {
        return r.obj[r.key]
    }
}

export function get<T>(r: Ref<T>): T {
    return r.obj[r.key]
}

export function set<T>(r: Ref<T>, value: T) {
    r.obj[r.key] = value
}
