export class ResultOk<T, E> {
    public ok: T;

    constructor(value: T) {
        this.ok = value;
    }

    public map<U>(mapper: (t: T) => U): Result<U, E> {
        return new ResultOk(mapper(this.ok));
    }

    public mapError<UE>(mapper: (t: E) => UE): Result<T, UE> {
        return new ResultOk(this.ok);
    }

    public flatMap<U, E2>(
        mapper: (t: T) => Result<U, [E] extends [E2] ? E2 : E>,
    ): Result<U, [E] extends [E2] ? E2 : E> {
        return mapper(this.ok);
    }

    public errorInto<E2>(): Result<T, [E] extends [E2] ? E2 : E> {
        return this as Result<T, [E] extends [E2] ? E2 : E>;
    }

    public isOk(): this is ResultOk<T, E> {
        return true;
    }

    public isErr(): this is ResultErr<T, E> {
        return false;
    }

    public unwrap(): T {
        return this.ok;
    }
}

export class ResultErr<T, E> {
    public error: E;

    constructor(errorValue: E) {
        this.error = errorValue;
    }

    public map<U>(_mapper: (t: T) => U): Result<T, E> {
        return this;
    }

    public mapError<UE>(mapper: (t: E) => UE): Result<T, UE> {
        return new ResultErr(mapper(this.error));
    }

    public flatMap<U, E2>(
        _mapper: (t: T) => Result<U, [E] extends [E2] ? E2 : E>,
    ): Result<U, [E] extends [E2] ? E2 : E> {
        return Err<[E] extends [E2] ? E2 : E, U>(
            this.error as [E] extends [E2] ? E2 : E,
        );
    }

    public errorInto<E2>(): Result<T, [E] extends [E2] ? E2 : E> {
        return this as Result<T, [E] extends [E2] ? E2 : E>;
    }

    public isOk(): this is ResultOk<T, E> {
        return false;
    }

    public isErr(): this is ResultOk<T, E> {
        return true;
    }

    public unwrap(): never {
        throw this.error;
    }
}

export function Ok<T, E = never>(value: T): Result<T, E> {
    return new ResultOk(value);
}

export function Err<E, T = never>(errorValue: E): Result<T, E> {
    return new ResultErr(errorValue);
}

export type Result<T, E> = ResultOk<T, E> | ResultErr<T, E>;
