/**
 * mp-shim.ts — RageMP compatibility layer for FiveM (server-side).
 *
 * Exposes a global `mp` object that mirrors the RageMP server API so that
 * ported feature files compile and run with minimal changes.
 *
 * Migration status markers used in this file:
 *   COMPAT  — fully implemented equivalent
 *   PARTIAL — works for most cases, edge cases may differ
 *   STUB    — not yet implemented; logs a warning when called
 */

import { playerRegistry, PlayerWrapper } from './player/player-registry';
import { vehicleRegistry, VehicleWrapper } from './vehicle/vehicle-wrapper';
import { rpc } from './rpc/index';

// ---------------------------------------------------------------------------
// Well-known RageMP server events → FiveM event names
// ---------------------------------------------------------------------------
const RAGEMP_TO_FIVEM: Record<string, string> = {
    playerJoin:    'playerConnecting',   // COMPAT (slightly different — no char load yet)
    playerQuit:    'playerDropped',      // COMPAT
    playerReady:   'playerSpawned',      // PARTIAL — FiveM fires after first spawn
};

// Events where the handler's first argument should be a PlayerWrapper
const PLAYER_SOURCE_EVENTS = new Set([
    'playerJoin', 'playerQuit', 'playerReady', 'playerDeath',
    'playerCommand', 'requestCommandList',
    // All custom client→server events are also player-sourced
]);

// ---------------------------------------------------------------------------
// Event bus
// ---------------------------------------------------------------------------
const localEventEmitter = new (require('events').EventEmitter)();

const events = {
    delayInitialization: false,

    // COMPAT — primary event registration
    add(eventOrMap: string | Record<string, Function>, handler?: Function): void {
        if (typeof eventOrMap === 'object') {
            for (const [name, fn] of Object.entries(eventOrMap)) {
                events.add(name, fn);
            }
            return;
        }

        const name = eventOrMap as string;
        const fn = handler!;

        if (name in RAGEMP_TO_FIVEM) {
            // Map to FiveM built-in event
            const fivemName = RAGEMP_TO_FIVEM[name];
            AddEventHandler(fivemName, function (...args: any[]) {
                const src = (global as any).source as number;
                if (src > 0) {
                    const player = playerRegistry.getOrCreate(src);
                    fn(player, ...args);
                } else {
                    fn(...args);
                }
            });
        } else {
            // Custom event: register as net event (player-sourced) AND as local event
            RegisterNetEvent(name);
            AddEventHandler(name, function (...args: any[]) {
                const src = (global as any).source as number;
                if (src > 0) {
                    const player = playerRegistry.getOrCreate(src);
                    fn(player, ...args);
                } else {
                    fn(...args);
                }
            });
            // Local event bus (for mp.events.call)
            localEventEmitter.on(name, fn);
        }
    },

    // COMPAT — replaces mp.events.addProc (server-side RPC handler)
    addProc(name: string, handler: (...args: any[]) => any): void {
        rpc.register(name, handler);
    },

    // COMPAT — fire a local server-side event (no player source).
    // Use only the JS EventEmitter — TriggerEvent would msgpack-serialize
    // complex objects (e.g. PlayerWrapper) and strip their prototype getters.
    call(eventName: string, ...args: any[]): void {
        localEventEmitter.emit(eventName, ...args);
    },

    // PARTIAL — call remote on all players (no per-player targeting here)
    callRemote(eventName: string, ...args: any[]): void {
        TriggerClientEvent(eventName, -1, ...args);
    },
};

// ---------------------------------------------------------------------------
// Player pool
// ---------------------------------------------------------------------------
const players = {
    // COMPAT
    at(id: number): PlayerWrapper | undefined {
        return playerRegistry.get(id);
    },

    // COMPAT
    toArray(): PlayerWrapper[] {
        return playerRegistry.toArray();
    },

    toArrayFast(): PlayerWrapper[] {
        return playerRegistry.toArray();
    },

    forEach(fn: (player: PlayerWrapper) => void): void {
        playerRegistry.forEach(fn);
    },

    forEachFast(fn: (player: PlayerWrapper) => void): void {
        playerRegistry.forEach(fn);
    },

    // COMPAT — broadcast chat message to all
    broadcast(message: string): void {
        TriggerClientEvent('_mp:chatMessage', -1, message);
    },

    get length(): number {
        return playerRegistry.toArray().length;
    },

    exists(player: PlayerWrapper): boolean {
        return playerRegistry.get(player.id) !== undefined;
    },
};

// ---------------------------------------------------------------------------
// Vehicle pool
// ---------------------------------------------------------------------------
const vehicles = {
    // COMPAT — replaces mp.vehicles.new(model, position, options)
    new(model: string | number, position: Vector3, options?: any): VehicleWrapper {
        return vehicleRegistry.create(model, position, options);
    },

    at(handle: number): VehicleWrapper | undefined {
        return vehicleRegistry.get(handle);
    },

    toArray(): VehicleWrapper[] {
        return vehicleRegistry.toArray();
    },

    forEach(fn: (vehicle: VehicleWrapper) => void): void {
        vehicleRegistry.forEach(fn);
    },
};

// ---------------------------------------------------------------------------
// Config (replaces mp.config — reads FiveM ConVars set in server.cfg)
// ---------------------------------------------------------------------------
const config = new Proxy({} as Record<string, any>, {
    get(_target, prop: string) {
        const val = GetConvar(prop, '__undefined__');
        if (val === '__undefined__') return undefined;
        if (val === 'true')  return true;
        if (val === 'false') return false;
        const num = Number(val);
        return isNaN(num) ? val : num;
    },
});

// ---------------------------------------------------------------------------
// Objects pool — STUB (RageMP server objects = FiveM server-side peds/objects)
// ---------------------------------------------------------------------------
const objects = {
    new(...args: any[]): any {
        console.warn('[mp-shim] mp.objects.new() not yet implemented');
        return null;
    },
    at(id: number): any { return null; },
    toArray(): any[] { return []; },
    forEach(_fn: any): void {},
};

// ---------------------------------------------------------------------------
// Peds pool — STUB
// ---------------------------------------------------------------------------
const peds = {
    new(...args: any[]): any {
        console.warn('[mp-shim] mp.peds.new() not yet implemented');
        return null;
    },
    at(id: number): any { return null; },
    toArray(): any[] { return []; },
    forEach(_fn: any): void {},
};

// ---------------------------------------------------------------------------
// Markers / blips / labels — client-side constructs in FiveM; server sends
// creation events to clients. Stubs here so code compiles.
// ---------------------------------------------------------------------------
const markers = {
    new(...args: any[]): any {
        console.warn('[mp-shim] mp.markers.new() — use client-side equivalent');
        return null;
    },
};

const blips = {
    new(...args: any[]): any {
        console.warn('[mp-shim] mp.blips.new() — use client-side equivalent');
        return null;
    },
};

const labels = {
    new(...args: any[]): any {
        console.warn('[mp-shim] mp.labels.new() — use client-side equivalent');
        return null;
    },
};

const checkpoints = {
    new(...args: any[]): any {
        console.warn('[mp-shim] mp.checkpoints.new() — use client-side equivalent');
        return null;
    },
};

// Colshapes are client-side in FiveM (or use server polygons with player position tracking)
const colshapes = {
    newSphere(...args: any[]): any {
        console.warn('[mp-shim] mp.colshapes — use polygon system equivalent');
        return null;
    },
    newCuboid(...args: any[]): any {
        console.warn('[mp-shim] mp.colshapes — use polygon system equivalent');
        return null;
    },
    newCircle(...args: any[]): any {
        console.warn('[mp-shim] mp.colshapes — use polygon system equivalent');
        return null;
    },
    newRectangle(...args: any[]): any {
        console.warn('[mp-shim] mp.colshapes — use polygon system equivalent');
        return null;
    },
    newTube(...args: any[]): any {
        console.warn('[mp-shim] mp.colshapes — use polygon system equivalent');
        return null;
    },
    newPolygon(...args: any[]): any {
        console.warn('[mp-shim] mp.colshapes — use polygon system equivalent');
        return null;
    },
};

// ---------------------------------------------------------------------------
// Vector3 constructor (replaces new mp.Vector3(x, y, z))
class Vector3Impl {
    constructor(public x: number, public y: number, public z: number) {}
}

// ---------------------------------------------------------------------------
// Assemble the global mp object
// ---------------------------------------------------------------------------
(global as any).mp = {
    events,
    players,
    vehicles,
    objects,
    peds,
    markers,
    blips,
    labels,
    checkpoints,
    colshapes,
    config,
    Vector3: Vector3Impl,
};

// ---------------------------------------------------------------------------
// Player lifecycle — build/tear down PlayerWrapper on connect/disconnect
// ---------------------------------------------------------------------------
AddEventHandler('playerConnecting', (name: string, _setKickReason: Function, deferrals: any) => {
    const netId = (global as any).source as number;
    playerRegistry.add(netId);
});

AddEventHandler('playerDropped', (reason: string) => {
    const netId = (global as any).source as number;
    playerRegistry.remove(netId);
});
