/**
 * Server-side RPC — replaces RageMP's mp.events.addProc / mp.events.callRemoteProc.
 *
 * Server registers a proc:
 *   rpc.register('getClosestTrunk', async (player, ...args) => { return result; });
 *
 * Client calls it via the client RPC module (see src/client/core/rpc/index.ts):
 *   const result = await rpc.call('getClosestTrunk', ...args);
 *
 * The wire protocol uses two net events:
 *   _rpc:call   player → server  { id, name, args }
 *   _rpc:result server → player  { id, result, error }
 */

import { playerRegistry } from '../player/player-registry';

type ProcHandler = (player: import('../player/player-registry').PlayerWrapper, ...args: any[]) => any;

const handlers = new Map<string, ProcHandler>();

let clientCallCounter = 0;
const clientPending = new Map<string, { resolve: (v: any) => void; reject: (e: any) => void }>();

RegisterNetEvent('_rpc:clientResult');
AddEventHandler('_rpc:clientResult', (callId: string, result: any, error: string | null) => {
    const cb = clientPending.get(callId);
    if (!cb) return;
    clientPending.delete(callId);
    if (error) cb.reject(new Error(error));
    else cb.resolve(result);
});

export const rpc = {
    register(name: string, handler: ProcHandler): void {
        handlers.set(name, handler);
    },

    // Server → client proc call (replaces mp.events.callRemoteProc / player.callProc)
    callClient<T = any>(player: import('../player/player-registry').PlayerWrapper, name: string, ...args: any[]): Promise<T> {
        const id = `${Date.now()}_${clientCallCounter++}`;
        return new Promise<T>((resolve, reject) => {
            clientPending.set(id, { resolve, reject });
            TriggerClientEvent('_rpc:clientCall', player.id, id, name, args);
            setTimeout(() => {
                if (clientPending.has(id)) {
                    clientPending.delete(id);
                    reject(new Error(`Client RPC timeout: ${name}`));
                }
            }, 10000);
        });
    },
};

RegisterNetEvent('_rpc:call');
AddEventHandler('_rpc:call', async (callId: string, name: string, args: any[]) => {
    const netId = (global as any).source as number;
    const player = playerRegistry.get(netId);

    if (!player) return;

    const handler = handlers.get(name);
    if (!handler) {
        TriggerClientEvent('_rpc:result', netId, callId, null, `No proc registered: ${name}`);
        return;
    }

    try {
        const result = await handler(player, ...(args ?? []));
        TriggerClientEvent('_rpc:result', netId, callId, result, null);
    } catch (err: any) {
        TriggerClientEvent('_rpc:result', netId, callId, null, err?.message ?? String(err));
    }
});
