import { CharacterEntity } from '../../entities/character.entity';
import { SessionEntity } from '../../entities/session.entity';
import { AccountDto } from '../../features/sw-rp-api/index';

export class PlayerWrapper {
    readonly id: number; // FiveM net ID (= RageMP player.id)

    state: PlayerState;
    character?: CharacterEntity;
    account?: AccountDto;
    session?: SessionEntity;
    groups: number[] = [];
    permissions: EntityPermission[] = [];

    constructor(netId: number) {
        this.id = netId;
        this.state = createDefaultPlayerState();
    }

    // --- Identity ---
    get name(): string {
        return GetPlayerName(String(this.id)) ?? `Player${this.id}`;
    }

    get ip(): string {
        const endpoint = GetPlayerEndpoint(String(this.id)) ?? '';
        const colonIdx = endpoint.lastIndexOf(':');
        return colonIdx > 0 ? endpoint.slice(0, colonIdx) : endpoint;
    }

    get socialClub(): string {
        return GetPlayerIdentifierByType(String(this.id), 'steam') ?? '';
    }

    // --- Position (server-side via ped) ---
    get position(): Vector3 {
        const ped = GetPlayerPed(String(this.id));
        const [x, y, z] = GetEntityCoords(ped);
        return { x, y, z } as Vector3;
    }

    get heading(): number {
        return GetEntityHeading(GetPlayerPed(String(this.id)));
    }

    // --- Health ---
    get health(): number {
        return GetEntityHealth(GetPlayerPed(String(this.id)));
    }

    set health(value: number) {
        SetEntityHealth(GetPlayerPed(String(this.id)), value);
    }

    // --- Dimension (stored server-side, synced via state bag) ---
    get dimension(): number {
        return (Player(this.id).state.dimension as number) ?? 0;
    }

    setDimension(dimension: number): void {
        Player(this.id).state.set('dimension', dimension, true);
    }

    // --- Server → Client event (replaces player.call) ---
    // RageMP: player.call('eventName', [arg1, arg2])
    // FiveM:  TriggerClientEvent('eventName', netId, arg1, arg2)
    call(eventName: string, args: any[] = []): void {
        TriggerClientEvent(eventName, this.id, ...args);
    }

    // --- Notifications (RageMP player.notify) ---
    notify(message: string): void {
        TriggerClientEvent('_mp:notify', this.id, message);
    }

    outputChatBox(message: string): void {
        TriggerClientEvent('_mp:chatMessage', this.id, message);
    }

    // --- Synced variables (replaces player.setVariable / getVariable) ---
    // Non-serialisable values (timers, promises, entity refs) must stay in .state;
    // only primitives & plain objects should go into state bags.
    setVariable(key: string, value: any): void {
        try {
            Player(this.id).state.set(key, value, true);
        } catch {
            // State bags can only hold serialisable values; silently ignore others.
        }
    }

    getVariable<T = any>(key: string): T {
        return Player(this.id).state[key] as T;
    }

    setOwnVariable(key: string, value: any): void {
        Player(this.id).state.set(key, value, false); // not broadcast to other clients
    }

    // --- Player management ---
    kick(reason: string): void {
        DropPlayer(String(this.id), reason);
    }

    kickSilent(): void {
        DropPlayer(String(this.id), '');
    }

    ban(reason: string): void {
        // Record the ban then kick — ban storage handled by admin system
        this.kick(`[Banned] ${reason}`);
    }

    // --- Appearance helper (sends to client for ped apply) ---
    spawn(position: Vector3): void {
        TriggerClientEvent('_mp:spawn', this.id, position);
    }

    // --- Vehicle (replaces RageMP player.vehicle / player.seat) ---
    get vehicle(): import('../vehicle/vehicle-wrapper').VehicleWrapper | null {
        const { vehicleRegistry } = require('../vehicle/vehicle-wrapper');
        const ped = GetPlayerPed(String(this.id));
        const handle = GetVehiclePedIsIn(ped, false);
        if (handle === 0) return null;
        return vehicleRegistry.get(handle) ?? null;
    }

    // Seat number: -1 = driver, 0+ = passenger (matches RageMP/GTA convention)
    get seat(): number {
        const ped = GetPlayerPed(String(this.id));
        const handle = GetVehiclePedIsIn(ped, false);
        if (handle === 0) return -2;
        if (GetPedInVehicleSeat(handle, -1) === ped) return -1;
        for (let i = 0; i < 8; i++) {
            if (GetPedInVehicleSeat(handle, i) === ped) return i;
        }
        return -2;
    }

    // --- Distance ---
    dist(other: Vector3): number {
        const p = this.position;
        return Math.sqrt((p.x - other.x) ** 2 + (p.y - other.y) ** 2 + (p.z - other.z) ** 2);
    }

    distSquared(other: Vector3): number {
        const p = this.position;
        return (p.x - other.x) ** 2 + (p.y - other.y) ** 2 + (p.z - other.z) ** 2;
    }

    // --- Type guard helpers (mirrors RageMP pattern) ---
    get isLoggedIn(): boolean {
        return !!(this.character && this.account && this.session);
    }
}

function createDefaultPlayerState(): PlayerState {
    return {
        reconnecting: false,
        phoneAnim: { currentStatus: 'out' },
        currentHairSlot: 0,
        togLevel: false,
        loginAttempts: 0,
        damageHistory: [],
        deathState: 'alive' as DeathState,
        falling: false,
        pmWatchers: [],
        pmWatched: [],
        freqWatched: [],
        factionWatched: [],
        companyWatched: [],
        report: [],
        props: [],
        trackedDutyIds: [],
        wikiBookmarks: [],
        paymentMutex: null as any, // initialised by MoneySystem
        outfitOverrides: [],
        serverWeapons: {},
        actionLock: 0,
        maxHealth: 200,
        dutyHandcuffs: 0,
    } as any;
}

export class PlayerRegistry {
    private players: Map<number, PlayerWrapper> = new Map();

    add(netId: number): PlayerWrapper {
        const wrapper = new PlayerWrapper(netId);
        this.players.set(netId, wrapper);
        return wrapper;
    }

    remove(netId: number): void {
        this.players.delete(netId);
    }

    get(netId: number): PlayerWrapper | undefined {
        return this.players.get(netId);
    }

    getOrCreate(netId: number): PlayerWrapper {
        return this.players.get(netId) ?? this.add(netId);
    }

    toArray(): PlayerWrapper[] {
        return [...this.players.values()];
    }

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

    forEach(fn: (player: PlayerWrapper) => void): void {
        this.players.forEach(fn);
    }
}

export const playerRegistry = new PlayerRegistry();
