import type { DecoderState } from "./decoder";
import type { EncoderState } from "./encoder";
import type { Value } from "./values";
import { ReaderStateOptions } from "./reader";

export type EmbeddedTypeEncode<T> = {
    encode(s: EncoderState, v: T): void;
}

export type EmbeddedTypeDecode<T> = {
    decode(s: DecoderState): T;
    fromValue(v: Value<GenericEmbedded>, options: ReaderStateOptions): T;
}

export type EmbeddedType<T> = EmbeddedTypeEncode<T> & EmbeddedTypeDecode<T>;

export class Embedded<T> {
    embeddedValue: T;

    constructor(embeddedValue: T) {
        this.embeddedValue = embeddedValue;
    }

    equals(other: any, is: (a: any, b: any) => boolean) {
        return isEmbedded<T>(other) && is(this.embeddedValue, other.embeddedValue);
    }

    toString(): string {
        return '#!' + (this.embeddedValue as any).toString();
    }

    __as_preserve__<R>(): T extends R ? Value<R> : never {
        return this as any;
    }

    static __from_preserve__<T>(v: Value<T>): undefined | Embedded<T> {
        return isEmbedded<T>(v) ? v : void 0;
    }
}

export function embed<T>(embeddedValue: T): Embedded<T> {
    return new Embedded(embeddedValue);
}

export function isEmbedded<T>(v: Value<T>): v is Embedded<T> {
    return typeof v === 'object' && 'embeddedValue' in v;
}

export class GenericEmbedded {
    generic: Value;

    constructor(generic: Value) {
        this.generic = generic;
    }

    equals(other: any, is: (a: any, b: any) => boolean) {
        return typeof other === 'object' && 'generic' in other && is(this.generic, other.generic);
    }

    toString(): string {
        return this.generic.toString();
    }
}
