/**
 * Login client — FiveM port of game-client/src/login/index.ts
 */

const loginCameras = [
    { pos: [2026.90, 3731.27, 50.40],  rot: [0, 0, 71.62],   fov: 45 },
    { pos: [-325.87, 4485.50, 106.75], rot: [0, 0, 117.66],  fov: 45 },
    { pos: [-382.56, 4721.71, 309.94], rot: [0, 0, -28.25],  fov: 45 },
];
const pickedCam = loginCameras[Math.floor(Math.random() * loginCameras.length)];

let loginCamHandle: number = -1;
let loginActive = false;
let overlayReady = false;

// Pending spawn data set by _mp:spawn / _mp:setModel before login:characterLoaded fires
let pendingSpawnPos: { x: number; y: number; z: number } | null = null;
let pendingModel = 'mp_m_freemode_01';

// ---------------------------------------------------------------------------
// Register all server→client net events
// ---------------------------------------------------------------------------

const NET_EVENTS = [
    'login:show', 'login:hide', 'login:success', 'login:error',
    'login:otp', 'login:characters', 'login:spawns', 'login:characterLoaded',
    'login:characterError', '_mp:spawn', '_mp:setModel', '_mp:notify',
];
for (const e of NET_EVENTS) RegisterNetEvent(e);

// ---------------------------------------------------------------------------
// NUI bridge helpers
// ---------------------------------------------------------------------------

function nuiSend(eventName: string, ...args: any[]): void {
    SendNUIMessage({ type: 'mp:event', eventName, args });
}

RegisterNuiCallback('nuiEvent', (data: { eventName: string; args: any[] }, cb: Function) => {
    const { eventName, args } = data;
    TriggerEvent(eventName, ...(args ?? []));
    cb({});
});

// ---------------------------------------------------------------------------
// Camera helpers
// ---------------------------------------------------------------------------

function createLoginCamera(): void {
    if (loginCamHandle !== -1) return;

    const [px, py, pz] = pickedCam.pos;
    const [rx, ry, rz] = pickedCam.rot;

    loginCamHandle = CreateCam('DEFAULT_SCRIPTED_CAMERA', true);
    SetCamCoord(loginCamHandle, px, py, pz);
    SetCamRot(loginCamHandle, rx, ry, rz, 2);
    SetCamFov(loginCamHandle, pickedCam.fov);
    SetCamActive(loginCamHandle, true);
    RenderScriptCams(true, false, 0, true, false);

    const ped = PlayerPedId();
    SetEntityVisible(ped, false, false);
    FreezeEntityPosition(ped, true);

    SetNuiFocus(true, true);
    DisplayHud(false);
    DisplayRadar(false);
}

function destroyLoginCamera(): void {
    if (loginCamHandle === -1) return;

    RenderScriptCams(false, true, 500, true, false);
    SetCamActive(loginCamHandle, false);
    DestroyCam(loginCamHandle, false);
    loginCamHandle = -1;

    SetNuiFocus(false, false);
    DisplayHud(true);
    DisplayRadar(true);
    loginActive = false;
}

// ---------------------------------------------------------------------------
// Token storage
// ---------------------------------------------------------------------------

function getStoredToken(): string {
    return GetResourceKvpString('sw-rp:auth-token') ?? '';
}

function setStoredToken(token: string): void {
    SetResourceKvp('sw-rp:auth-token', token);
}

function clearStoredToken(): void {
    DeleteResourceKvp('sw-rp:auth-token');
}

// ---------------------------------------------------------------------------
// Boot
// ---------------------------------------------------------------------------

function startLogin(): void {
    if (loginActive) return;
    loginActive = true;
    overlayReady = true;
    createLoginCamera();
    TriggerServerEvent('login:init', getStoredToken());
}

AddEventHandler('overlay:ready', startLogin);

setTimeout(() => {
    if (!overlayReady) startLogin();
}, 3000);

// ---------------------------------------------------------------------------
// Server → client events — forward to NUI
// ---------------------------------------------------------------------------

AddEventHandler('login:show', () => {
    if (!loginActive) { loginActive = true; createLoginCamera(); }
    nuiSend('login:show');
});

AddEventHandler('login:hide', () => { nuiSend('login:hide'); });

AddEventHandler('login:success', (token?: string) => {
    if (token) setStoredToken(token);
    nuiSend('login:success', token);
});

AddEventHandler('login:error',          (error: string) => { nuiSend('login:error', error); });
AddEventHandler('login:otp',            (failure: boolean) => { nuiSend('login:otp', failure); });
AddEventHandler('login:characters',     (characters: string) => { nuiSend('login:characters', characters); });
AddEventHandler('login:spawns',         (spawns: string) => { nuiSend('login:spawns', spawns); });
AddEventHandler('login:characterError', (msg: string) => { nuiSend('login:error', msg); });

// ---------------------------------------------------------------------------
// Character loaded — run the full FiveM spawn sequence ourselves:
//   model load → SetPlayerModel → NetworkResurrectLocalPlayer →
//   collision wait → ShutdownLoadingScreen (clears "Awaiting scripts") →
//   destroy login camera
// ---------------------------------------------------------------------------

AddEventHandler('login:characterLoaded', async () => {
    nuiSend('login:hide');

    const pos = pendingSpawnPos ?? { x: -1037.43, y: -2733.075, z: 13.7566 };

    // 1. Load model by name (string form is safest across FiveM JS)
    const model = pendingModel;
    RequestModel(model as any);
    let modelTries = 0;
    while (!HasModelLoaded(model as any) && modelTries < 100) {
        await new Promise<void>((r) => setTimeout(r, 50));
        modelTries++;
    }

    // 2. Swap model — SetPlayerModel creates a new ped; PlayerPedId() updates after
    if (HasModelLoaded(model as any)) {
        SetPlayerModel(PlayerId(), model as any);
        SetModelAsNoLongerNeeded(model as any);
    }

    // 3. Always fetch ped AFTER SetPlayerModel — it creates a new ped handle
    let ped = PlayerPedId();

    // 4. Place player — NetworkResurrectLocalPlayer is what spawnmanager uses
    RequestCollisionAtCoord(pos.x, pos.y, pos.z);
    SetEntityCoordsNoOffset(ped, pos.x, pos.y, pos.z, false, false, false, true);
    NetworkResurrectLocalPlayer(pos.x, pos.y, pos.z, 0.0, true, true, false);

    // 5. Fetch ped AGAIN after resurrection (NetworkResurrectLocalPlayer can swap it)
    ped = PlayerPedId();
    SetEntityVisible(ped, true, false);
    FreezeEntityPosition(ped, false);

    // 6. Wait for collision (up to 5 s), then clear "Awaiting scripts"
    const colStart = Date.now();
    while (!HasCollisionLoadedAroundEntity(ped) && Date.now() - colStart < 5000) {
        await new Promise<void>((r) => setTimeout(r, 100));
    }

    ShutdownLoadingScreen();

    // 7. Hand camera back to game
    destroyLoginCamera();
    pendingSpawnPos = null;
});

// ---------------------------------------------------------------------------
// NUI → server events
// ---------------------------------------------------------------------------

AddEventHandler('login:submit', (username: string, password: string, otp = '', remember = false) => {
    TriggerServerEvent('login:attempt', username, password, otp || null, Boolean(remember));
});

AddEventHandler('login:characterSelect', (characterId: number) => {
    TriggerServerEvent('login:character', Number(characterId));
});

AddEventHandler('login:spawnSelect', (spawnId: number) => {
    TriggerServerEvent('login:spawn', Number(spawnId));
});

AddEventHandler('login:reset', () => {
    clearStoredToken();
    TriggerServerEvent('login:init', '');
    nuiSend('login:show');
});

// ---------------------------------------------------------------------------
// _mp:spawn — store position for spawnmanager call in login:characterLoaded
// ---------------------------------------------------------------------------

AddEventHandler('_mp:spawn', (pos: { x: number; y: number; z: number }) => {
    pendingSpawnPos = pos;
});

// ---------------------------------------------------------------------------
// _mp:setModel — store model name for spawnmanager call in login:characterLoaded
// ---------------------------------------------------------------------------

AddEventHandler('_mp:setModel', (modelName: string) => {
    pendingModel = modelName || 'mp_m_freemode_01';
});

// ---------------------------------------------------------------------------
// Version check on connect
// ---------------------------------------------------------------------------

AddEventHandler('connect', () => {
    TriggerServerEvent('versionCheck', '1.0');
});
