import { RpcTimeout } from "errors";
import { IMessage } from "./IMessage";

export type AsyncMessageTrackerTimeoutCallback = (entry: AsyncMessageTrackerEntry) => void | Promise<void>;

export interface AsyncMessageTrackerConfig {

    forcedTimeoutInMilliseconds?: number;

}

export interface AsyncMessageTrackerEntry {
    id: string;
    message: IMessage;

    timeoutInMilliseconds?: number;
}

export interface AsyncMessageTrackerWaitResult {

    id: string;
    message: IMessage;

}

export class AsyncMessageTracker {

    private entries: Record<string, {
        entry: AsyncMessageTrackerEntry,
        resolve: (value: AsyncMessageTrackerWaitResult) => void,
        reject: (reason: any) => void
    }>;

    constructor(
        private config?: AsyncMessageTrackerConfig
    )
    {
        this.entries = {};
    }

    public waitForResponseOrTimeout(entry: AsyncMessageTrackerEntry): Promise<AsyncMessageTrackerWaitResult> {
        return new Promise<AsyncMessageTrackerWaitResult>((resolve, reject) => {
            const timeout = entry.timeoutInMilliseconds ?? this.config?.forcedTimeoutInMilliseconds;

            this.entries[entry.id] = {
                entry,
                resolve,
                reject
            };

            if (timeout) {
                setTimeout(() => {

                    delete this.entries[entry.id];
                    reject(new RpcTimeout('Request was timed out.'));

                }, timeout);
            }
        });
    }

    public respond(id: string, message: IMessage): boolean {
        const entry = this.entries[id];

        if (!!entry) {
            entry.resolve({
                id,
                message
            });

            delete this.entries[id];

            return true;
        }

        return false;
    }

    public reject(id: string, reason: any): boolean {
        const entry = this.entries[id];

        if (!!entry) {
            entry.reject(reason);

            delete this.entries[id];

            return true;
        }

        return false;
    }

}
