import { RpcError } from "errors";
import { ContractReference, KnownTransports } from "../types";
import { IEndpointContext } from "./IEndpointContext";
import { IInterceptor } from "./IInterceptor";
import { IInterceptorProvider } from "./IInterceptorProvider";
import { ITransport } from "./ITransport";
import { ITransportProvider } from "./ITransportProvider";

type ContextFactory<TCtx extends IEndpointContext> = new (
    contextDomain: string,
    transportProvider: ITransportProvider,
    interceptorProvider: IInterceptorProvider
) => TCtx;

export class ContextDomain<TCtx extends IEndpointContext> implements ITransportProvider, IInterceptorProvider {

    private ctx: Record<string, TCtx>;
    private transports: Record<string, ITransport>;
    private interceptors: Record<string, IInterceptor>;

    constructor(
        private factory: ContextFactory<TCtx>
    ) {
        this.ctx = {};
        this.transports = {};
        this.interceptors = {};
    }

    public getInterceptor(name: string): IInterceptor {
        const result = this.interceptors[name];

        if (typeof result === 'undefined') {
            throw new RpcError(`Couldn't find a interceptor with the name of "${name}".`);
        }

        return result;
    }

    public addInterceptor(interceptor: IInterceptor, name?: string): void {
        this.interceptors[name ?? interceptor.getName()] = interceptor;
    }

    public getTransport(
        name: KnownTransports,
        customDriverName?: string
    ): ITransport {
        let fullName = name;
        if (customDriverName) {
            fullName += `/${customDriverName}`;
        }

        const result = this.transports[fullName];

        if (typeof result === 'undefined') {
            throw new RpcError(`Couldn't find a transport with the name of "${fullName}".`);
        }

        return result;
    }

    public addTransport(
        transport: ITransport,
        name?: KnownTransports,
        customDriverName?: string
    ): void {
        let fullName = name ?? transport.getName();
        if (customDriverName) {
            fullName += `/${customDriverName}`;
        } else if (transport.getCustomDriverName) {
            fullName += `/${transport.getCustomDriverName()}`;
        }

        this.transports[fullName] = transport;
    }

    public getContext(
        name?: string
    ): TCtx {

        if (typeof name === 'undefined') {
            name = 'default';
        }

        let instance = this.ctx[name];

        if (typeof instance === 'undefined') {
            instance = new this.factory(name, this, this);
            this.ctx[name] = instance;
        }

        return instance;
    }

    public clear(
        name: string
    ): void {
        delete this.ctx[name];
    }
}