/**
 * Stub of `@nitrots/nitro-renderer` for Vitest.
 *
 * The real package eagerly loads Pixi v8 + a few hundred Habbo message
 * parsers/composers at module import time, which jsdom cannot host:
 * any `tests/**` file that transitively pulls a renderer symbol throws
 * before a single assertion runs.
 *
 * This module replaces it via `resolve.alias` in `vitest.config.mts`.
 * We provide explicit named exports for the symbols a test currently
 * needs (logger, event dispatcher, doorbell event class); everything
 * else mentioned in the comments below is a generic stub kept just to
 * keep the side-effectful imports happy as `src/api/index.ts` and
 * friends are pulled in transitively by the barrel cascade.
 *
 * Grow this file as new tests require additional symbols. Prefer adding
 * a real (deterministic) stub over wiring `vi.fn()` — it keeps the
 * mocks readable and avoids state bleed between cases.
 */

import { vi } from 'vitest';

// ---------------------------------------------------------------------------
// Logger
// ---------------------------------------------------------------------------

export const NitroLogger = {
    log: vi.fn(),
    warn: vi.fn(),
    error: vi.fn(),
    enableContexts: vi.fn(),
    setDebug: vi.fn()
};

// ---------------------------------------------------------------------------
// Event dispatcher
// ---------------------------------------------------------------------------
//
// `GetEventDispatcher()` in the real SDK returns the renderer-wide event
// bus. Tests use `mockEventDispatcher.dispatchEvent(event)` to simulate
// a server push. `clearMockEventDispatcher()` resets the listener map
// between cases so subscriptions from a previous test don't leak.

type Listener = (event: any) => void;

// NitroEvent listeners — registered via GetEventDispatcher() / useNitroEvent.
// Cleared by clearMockEventDispatcher() between test cases.
const listeners = new Map<string, Set<Listener>>();

// MessageEvent listeners — registered via GetCommunication().registerMessageEvent
// (i.e. useMessageEvent). NOT cleared by clearMockEventDispatcher() so that
// useBetween-based hooks (which register effects once and persist the
// singleton across tests) keep their subscriptions alive throughout the
// suite. State isolation between tests is maintained by the useBetween
// instance preserving INITIAL values across renders (each test's renderHook
// shares the same useBetween singleton — tests that check a specific
// post-dispatch state rely on the event changing it, not on a reset).
const msgListeners = new Map<string, Set<Listener>>();

export const mockEventDispatcher = {
    addEventListener(type: string, handler: Listener)
    {
        let bucket = listeners.get(type);

        if(!bucket)
        {
            bucket = new Set();
            listeners.set(type, bucket);
        }

        bucket.add(handler);
    },
    removeEventListener(type: string, handler: Listener)
    {
        listeners.get(type)?.delete(handler);
    },
    dispatchEvent(event: { type: string })
    {
        // Fire NitroEvent listeners first, then MessageEvent listeners.
        const bucket = listeners.get(event.type);
        if(bucket) for(const handler of bucket) handler(event);

        const msgBucket = msgListeners.get(event.type);
        if(msgBucket) for(const handler of msgBucket) handler(event);
    },
    hasListeners(type: string)
    {
        return (listeners.get(type)?.size ?? 0) > 0 ||
               (msgListeners.get(type)?.size ?? 0) > 0;
    }
};

// Clears only the NitroEvent listener map (GetEventDispatcher / useNitroEvent
// registrations). MessageEvent listeners (useMessageEvent / GetCommunication)
// are intentionally preserved so useBetween-based hooks stay subscribed.
export const clearMockEventDispatcher = () =>
{
    listeners.clear();
};

export const GetEventDispatcher = vi.fn(() => mockEventDispatcher);

// ---------------------------------------------------------------------------
// Event type enums (string-keyed Proxies)
// ---------------------------------------------------------------------------
//
// The real `*EventType` is a `class { static readonly FOO = '...'; … }`
// with stable wire strings. Tests only need each constant to be a
// unique string so dispatch + listener agree.

const makeEnumProxy = (label: string) => new Proxy({}, {
    get: (_, prop) => typeof prop === 'string' ? `mock:${ label }:${ prop }` : undefined
}) as Record<string, string>;

export const NitroEventType = makeEnumProxy('NitroEventType');
export const MouseEventType = makeEnumProxy('MouseEventType');
export const TouchEventType = makeEnumProxy('TouchEventType');
export const RoomObjectPlacementSource = makeEnumProxy('RoomObjectPlacementSource');
export const RoomObjectType = makeEnumProxy('RoomObjectType');
export const RoomObjectVariable = makeEnumProxy('RoomObjectVariable');
export const RoomTradingLevelEnum = makeEnumProxy('RoomTradingLevelEnum');
export const HabboClubLevelEnum = makeEnumProxy('HabboClubLevelEnum');
export const FurnitureType = makeEnumProxy('FurnitureType');
export const PetType = makeEnumProxy('PetType');
export const AvatarFigurePartType = makeEnumProxy('AvatarFigurePartType');
export const AvatarScaleType = makeEnumProxy('AvatarScaleType');
export const AvatarSetType = makeEnumProxy('AvatarSetType');
export const AvatarAction = makeEnumProxy('AvatarAction');
export const RoomWidgetEnumItemExtradataParameter = makeEnumProxy('RoomWidgetEnumItemExtradataParameter');

// Numeric enums — values mirror the real renderer SDK so comparisons
// (`controllerLevel >= GUILD_ADMIN`, category branching) keep working.

export class RoomControllerLevel
{
    static readonly NONE = 0;
    static readonly GUEST = 1;
    static readonly GUILD_MEMBER = 2;
    static readonly GUILD_ADMIN = 3;
    static readonly ROOM_OWNER = 4;
    static readonly MODERATOR = 5;
}

// Mirrors `packages/api/src/nitro/session/enum/SecurityLevel.ts`. Kept
// around so any consumer that still imports the renderer enum
// (non-deprecated code path) compiles cleanly under the mock.
export class SecurityLevel
{
    static readonly NONE = 0;
    static readonly CELEBRITY = 1;
    static readonly PARTNER = 2;
    static readonly BUS_PARTNER = 3;
    static readonly EMPLOYEE = 4;
    static readonly MODERATOR = 5;
    static readonly PLAYER_SUPPORT = 6;
    static readonly COMMUNITY = 7;
    static readonly ADMINISTRATOR = 8;
    static readonly SUPER_USER = 9;
}

export class RoomObjectCategory
{
    static readonly MINIMUM = 0;
    static readonly ROOM = 10;
    static readonly UNIT = 20;
    static readonly FLOOR = 30;
    static readonly WALL = 40;
    static readonly MAXIMUM = 50;
}

// ---------------------------------------------------------------------------
// Doorbell event class
// ---------------------------------------------------------------------------

export class RoomSessionDoorbellEvent
{
    // Wire strings copied from the real class so any consumer that
    // ignores the indirection through the `.DOORBELL` static still
    // matches.
    static readonly DOORBELL = 'RSDE_DOORBELL';
    static readonly RSDE_ACCEPTED = 'RSDE_ACCEPTED';
    static readonly RSDE_REJECTED = 'RSDE_REJECTED';

    // Mirrors the real constructor signature `(type, session, userName)`
    // so `tsgo` is happy. Tests can pass `null` for the session: the
    // SUT only reads `.userName` and `.type`.
    constructor(public readonly type: string, public readonly _session: unknown, public readonly userName: string) {}
}

// ---------------------------------------------------------------------------
// Generic classes — placeholders for symbols that need to exist as
// constructors so module-level `new X(...)` calls don't crash during
// the barrel cascade, but whose behavior tests don't yet exercise.
// ---------------------------------------------------------------------------

class StubClass
{
    constructor(..._args: unknown[]) {}
}

export class NitroAlphaFilter extends StubClass {}
export class NitroContainer extends StubClass {}
export class NitroRectangle extends StubClass {}
export class NitroSprite extends StubClass {}
export class NitroTexture extends StubClass {}
export class NitroSoundEvent extends StubClass {}
export class NitroEvent extends StubClass {}

// MessageEvent — stores the handler so GetCommunication (below) can
// route dispatches through mockEventDispatcher. Each concrete subclass
// exposes a `.type` equal to its constructor name so dispatchEvent
// can match registered listeners.
export class MessageEvent
{
    private _callBack: Function | null;

    constructor(callBack?: Function)
    {
        this._callBack = callBack ?? null;
    }

    public get callBack(): Function | null { return this._callBack; }

    // Each concrete subclass is identified by its class name.
    public get type(): string { return this.constructor.name; }

    // Concrete subclasses override this; the no-arg construction path used
    // by makeParserlessEvent in tests leaves it returning null — tests
    // override it with (ev as any).getParser = () => parser.
    public getParser(): any { return null; }
}

// ---------------------------------------------------------------------------
// IMessageEvent-based event classes used by useDoorState
//
// The real renderer classes take a `callBack` constructor arg and store it
// in MessageEvent._callBack. The communication manager later calls
// `event.callBack(event)` when the matching packet arrives.
//
// In tests we construct them with NO args (makeParserlessEvent does
// `new klass()`) and override `getParser`. GetCommunication (below)
// registers `event.callBack` on mockEventDispatcher under `event.type`
// (the class name). When the test calls
// `mockEventDispatcher.dispatchEvent(ev)`, listeners for that class name
// fire, receiving `ev` — and the implementation reads `ev.getParser()`.
// ---------------------------------------------------------------------------

export class DoorbellMessageEvent extends MessageEvent {}
export class RoomDoorbellAcceptedEvent extends MessageEvent {}
export class FlatAccessDeniedMessageEvent extends MessageEvent {}
export class GenericErrorEvent extends MessageEvent {}
export class GetGuestRoomResultEvent extends MessageEvent {}

// Mentions system — incoming events extend MessageEvent (they expose
// getParser()); the request/mark composers are symbol-only constructors.
export class MentionReceivedEvent extends MessageEvent {}
export class MentionsListEvent extends MessageEvent {}

// ---------------------------------------------------------------------------
// Navigator event classes — MessageEvent subclasses needed by useNavigatorStore
// ---------------------------------------------------------------------------

export class CanCreateRoomEventEvent extends MessageEvent {}
export class FavouriteChangedEvent extends MessageEvent {}
export class FavouritesEvent extends MessageEvent {}
export class FlatCreatedEvent extends MessageEvent {}
export class NavigatorHomeRoomEvent extends MessageEvent {}
export class NavigatorMetadataEvent extends MessageEvent {}
export class NavigatorOpenRoomCreatorEvent extends MessageEvent {}
export class NavigatorSearchesEvent extends MessageEvent {}
export class NavigatorSearchEvent extends MessageEvent {}
export class RoomEnterErrorEvent extends MessageEvent {}
export class RoomEntryInfoMessageEvent extends MessageEvent {}
export class RoomForwardEvent extends MessageEvent {}
export class RoomScoreEvent extends MessageEvent {}
export class RoomSettingsUpdatedEvent extends MessageEvent {}
export class UserEventCatsEvent extends MessageEvent {}
export class UserFlatCatsEvent extends MessageEvent {}
export class UserInfoEvent extends MessageEvent {}
export class UserPermissionsEvent extends MessageEvent {}

// ---------------------------------------------------------------------------
// Notification event classes — MessageEvent subclasses needed by
// useNotificationStore (called via useNotification() inside useNavigatorStore).
// The real renderer classes take a `callBack` constructor arg; the pattern
// here is the same as the Navigator event stubs above.
// ---------------------------------------------------------------------------

export class AchievementNotificationMessageEvent extends MessageEvent {}
export class ActivityPointNotificationMessageEvent extends MessageEvent {}
export class BadgeReceivedEvent extends MessageEvent {}
export class ClubGiftNotificationEvent extends MessageEvent {}
export class ClubGiftSelectedEvent extends MessageEvent {}
export class ConnectionErrorEvent extends MessageEvent {}
export class HabboBroadcastMessageEvent extends MessageEvent {}
export class HotelClosedAndOpensEvent extends MessageEvent {}
export class HotelClosesAndWillOpenAtEvent extends MessageEvent {}
export class HotelWillCloseInMinutesEvent extends MessageEvent {}
export class InfoFeedEnableMessageEvent extends MessageEvent {}
export class MaintenanceStatusMessageEvent extends MessageEvent {}
export class ModeratorCautionEvent extends MessageEvent {}
export class ModeratorMessageEvent extends MessageEvent {}
export class MOTDNotificationEvent extends MessageEvent {}
export class NotificationDialogMessageEvent extends MessageEvent {}
export class PetLevelNotificationEvent extends MessageEvent {}
export class PetReceivedMessageEvent extends MessageEvent {}
export class RespectReceivedEvent extends MessageEvent {}
export class RoomEnterEvent extends MessageEvent {}
export class SimpleAlertMessageEvent extends MessageEvent {}
export class UserBannedMessageEvent extends MessageEvent {}
export class WiredRewardResultMessageEvent extends MessageEvent
{
    static readonly PRODUCT_DONATED_CODE = 7;
    static readonly BADGE_DONATED_CODE = 8;
}

// RoomEnterEffect — used by useNotificationStore to check if the room-enter
// animation is still running before showing the mod disclaimer bubble.
export const RoomEnterEffect = {
    isRunning: () => false,
    totalRunningTime: 0
};

export class RoomEngineObjectEvent extends StubClass {}
export class CreateLinkEvent extends StubClass {}
export class EventDispatcher extends StubClass {}
export class AdvancedMap extends StubClass {}
export class AvatarFigureContainer extends StubClass {}
export class Vector3d extends StubClass {}
export class ObjectDataFactory extends StubClass {}

// RoomDataParser — real static constants needed by useDoorState and its tests.
export class RoomDataParser
{
    static readonly DOORBELL_STATE = 1;
    static readonly PASSWORD_STATE = 2;
}

export class RoomModerationSettings extends StubClass {}
export class StringDataType extends StubClass {}

// Navigator data/parser stubs
export class NavigatorCategoryDataParser extends StubClass {}
export class NavigatorEventCategoryDataParser extends StubClass {}
export class NavigatorSavedSearch extends StubClass {}
export class NavigatorSearchResultSet extends StubClass {}
export class NavigatorTopLevelContext extends StubClass {}
export class CantConnectMessageParser extends StubClass
{
    static readonly REASON_FULL = 1;
    static readonly REASON_QUEUE_ERROR = 2;
    static readonly REASON_BANNED = 3;
}

export class LegacyExternalInterface
{
    static readonly available = false;
    static call(..._args: unknown[]): void {}
}
export class SellablePetPaletteData extends StubClass {}
export class PetFigureData extends StubClass {}
export class PetData extends StubClass {}
export class NodeData extends StubClass {}
export class ItemDataStructure extends StubClass {}
export class HabboGroupEntryData extends StubClass {}
export class FriendParser extends StubClass {}
export class FriendCategoryData extends StubClass {}
export class FriendRequestData extends StubClass {}
export class FurnitureListItemParser extends StubClass {}
export class BotData extends StubClass {}
export class AchievementData extends StubClass {}
export class CatalogPageMessageProductData extends StubClass {}
export class GiftWrappingConfigurationParser extends StubClass {}
export class WiredFilter extends StubClass {}
export class HabboWebTools extends StubClass {}

// Composers — symbol-only constructors; only their identity matters in the
// codebase ("did the SUT call SendMessageComposer(new FooComposer(args))").
export class AddFavouriteRoomMessageComposer extends StubClass {}
export class DeleteFavouriteRoomMessageComposer extends StubClass {}
export class FollowFriendMessageComposer extends StubClass {}
export class GetUserEventCatsMessageComposer extends StubClass {}
export class GetUserFlatCatsMessageComposer extends StubClass {}
export class NavigatorSearchComposer extends StubClass {}
export class RequestMentionsComposer extends StubClass {}
export class MarkMentionsReadComposer extends StubClass {}
export class DeleteMentionComposer extends StubClass {}
export class DesktopViewComposer extends StubClass {}
export class FurniturePlacePaintComposer extends StubClass {}
export class GetGuestRoomMessageComposer extends StubClass {}
export class GetProductOfferComposer extends StubClass {}
export class GroupFavoriteComposer extends StubClass {}
export class GroupInformationComposer extends StubClass {}
export class GroupJoinComposer extends StubClass {}
export class GroupUnfavoriteComposer extends StubClass {}
export class UserProfileComposer extends StubClass {}

// `ChooserSelectionFilter` is used as a string enum in some call sites.
export const ChooserSelectionFilter = makeEnumProxy('ChooserSelectionFilter');

// ---------------------------------------------------------------------------
// Floor plan constants
// ---------------------------------------------------------------------------

export const FloorAction = makeEnumProxy('FloorAction');

export const TILE_SIZE = 32;

export const HEIGHT_SCHEME = 'x0123456789abcdefghijklmnopq';

export const MAX_NUM_TILE_PER_AXIS = 64;

export const COLORMAP: object = {
    'x': '101010',
    '0': '0065ff', '1': '0091ff', '2': '00bcff', '3': '00e8ff',
    '4': '00ffea', '5': '00ffbf', '6': '00ff93', '7': '00ff68',
    '8': '00ff3d', '9': '19ff00',
    'a': '44ff00', 'b': '70ff00', 'c': '9bff00', 'd': 'f2ff00',
    'e': 'ffe000', 'f': 'ffb500', 'g': 'ff8900', 'h': 'ff5e00',
    'i': 'ff3200', 'j': 'ff0700', 'k': 'ff0023', 'l': 'ff007a',
    'm': 'ff00a5', 'n': 'ff00d1', 'o': 'ff00fc',
    'p': 'd600ff', 'q': 'aa00ff'
};

// ---------------------------------------------------------------------------
// Floor plan editor — composer stubs and event classes
// ---------------------------------------------------------------------------

// Composer stubs for floor-plan-editor message events
export class GetRoomEntryTileMessageComposer extends StubClass {}
export class GetOccupiedTilesMessageComposer extends StubClass {}
export class UpdateFloorPropertiesMessageComposer extends StubClass
{
    public tilemap: string;
    public doorX: number;
    public doorY: number;
    public dir: number;
    public thicknessWall: number;
    public thicknessFloor: number;
    public wallHeight: number;
    constructor(tilemap: string, doorX: number, doorY: number, dir: number, thicknessWall: number, thicknessFloor: number, wallHeight: number)
    {
        super();
        this.tilemap = tilemap;
        this.doorX = doorX;
        this.doorY = doorY;
        this.dir = dir;
        this.thicknessWall = thicknessWall;
        this.thicknessFloor = thicknessFloor;
        this.wallHeight = wallHeight;
    }
}

// Event class stubs for useMessageEvent registration
export class FloorHeightMapEvent extends StubClass {}
export class RoomVisualizationSettingsEvent extends StubClass {}
export class RoomEntryTileMessageEvent extends StubClass {}
export class RoomOccupiedTilesMessageEvent extends StubClass {}
export const RoomEngineEvent = makeEnumProxy('RoomEngineEvent');

// Link tracker stubs
export type ILinkEventTracker = { linkReceived: (url: string) => void; eventUrlPrefix: string };
export const AddLinkEventTracker = vi.fn();
export const RemoveLinkEventTracker = vi.fn();

// Thickness conversion helpers — mirror the renderer's real mapping
export const convertNumbersForSaving = (v: number): number =>
{
    switch(v)
    {
        case 0: return -2;
        case 1: return -1;
        case 3: return 1;
        default: return 0;
    }
};

export const convertSettingToNumber = (v: number): number =>
{
    switch(v)
    {
        case 0.25: return 0;
        case 0.5: return 1;
        case 2: return 3;
        default: return 2;
    }
};

// ---------------------------------------------------------------------------
// Singleton getters
// ---------------------------------------------------------------------------

const stubManager = () =>
{
    const sentinel = new Proxy(() => undefined, {
        get(target, prop)
        {
            if(prop === 'then') return undefined;
            const cached = (target as any)[prop];
            if(cached !== undefined) return cached;
            // Most fields read from a real manager are either methods
            // (return functions) or sub-objects (return proxies). We
            // return another callable proxy so chained access works.
            const value = stubManager();
            (target as any)[prop] = value;
            return value;
        },
        apply()
        {
            return undefined;
        }
    });

    return sentinel;
};

export const GetAssetManager = vi.fn(stubManager);
export const GetAvatarRenderManager = vi.fn(stubManager);
// GetCommunication — routes IMessageEvent registration through the
// msgListeners map (separate from the NitroEvent listeners map) so that
// clearMockEventDispatcher() does NOT wipe these subscriptions. This
// keeps useBetween-based hooks (like useDoorState) subscribed across
// test cases without needing to recreate the useBetween singleton.
//
// A WeakMap stores the wrapper fn keyed by the MessageEvent instance so
// that removeMessageEvent can remove the exact listener added by
// registerMessageEvent.
const _msgEventWrappers = new WeakMap<MessageEvent, (ev: any) => void>();

export const GetCommunication = vi.fn(() => ({
    registerMessageEvent(event: MessageEvent)
    {
        if(!event.callBack) return;
        const wrapper = (ev: any) => event.callBack!(ev);
        _msgEventWrappers.set(event, wrapper);
        let bucket = msgListeners.get(event.type);
        if(!bucket) { bucket = new Set(); msgListeners.set(event.type, bucket); }
        bucket.add(wrapper);
    },
    removeMessageEvent(event: MessageEvent)
    {
        const wrapper = _msgEventWrappers.get(event);
        if(wrapper) msgListeners.get(event.type)?.delete(wrapper);
    },
    // Stub for SendMessageComposer which calls GetCommunication().connection.send(...)
    connection: { send: vi.fn() }
}));
export const GetConfiguration = vi.fn(stubManager);
export const GetLocalizationManager = vi.fn(stubManager);
export const GetRoomEngine = vi.fn(stubManager);
export const GetRoomMessageHandler = vi.fn(stubManager);
export const GetRoomSessionManager = vi.fn(stubManager);

// RoomPreviewer — only the bits the editor's FloorplanRoomPreview
// component touches. PREVIEW_COUNTER is a static field that the
// real renderer increments to allocate unique preview-room IDs;
// keeping it as a mutable static lets the editor mount/unmount
// repeatedly across tests without colliding.
export class RoomPreviewer
{
    static PREVIEW_COUNTER = 0;

    constructor(public readonly _engine: unknown, public readonly _id: number) {}

    public updatePreviewModel(_model: string, _wallHeight: number, _scale?: boolean): void {}
    public modifyRoomCanvas(_w: number, _h: number): void {}
    public getRoomCanvas(_w: number, _h: number): unknown { return null; }
    public getRenderingCanvas(): unknown { return null; }
    public updatePreviewRoomView(): void {}
    public changeRoomObjectDirection(): void {}
    public changeRoomObjectState(): void {}
    public dispose(): void {}
}
export const GetSessionDataManager = vi.fn(stubManager);
export const GetTickerTime = vi.fn(() => 0);
export const GetTicker = vi.fn(stubManager);
export const GetRenderer = vi.fn(stubManager);
export class NitroTicker {}
// TextureUtils — a real-enough stub of the createRenderTexture
// roundtrip. Tests that mount LayoutRoomPreviewerView allocate a
// texture on mount and destroy it on unmount; without a real
// `.destroy` method the unmount cleanup throws.
export const TextureUtils = {
    createRenderTexture: (_w: number, _h: number) => ({
        destroy: (_options?: unknown) => undefined
    }),
    generateImage: () => null
};
export const NitroVersion = stubManager();

// ---------------------------------------------------------------------------
// Type-only re-exports (interfaces erase at compile time, but listing them
// here documents what the codebase imports through the type channel).
//
//   IAvatarFigureContainer · IEventDispatcher · IFigurePart · IFigurePartSet ·
//   IFurnitureData · IFurnitureItemData · IGraphicAsset · IMessageComposer ·
//   IMessageEvent · IObjectData · IPartColor · IPollQuestion · IProductData ·
//   IRoomEngine · IRoomModerationSettings · IRoomObject · IRoomObjectController ·
//   IRoomObjectSpriteVisualization · IRoomPetData · IRoomSession · IRoomUserData
//
// No need to alias them — TypeScript only consults them in the type
// system, and the production `tsconfig.json` resolves them against the
// real renderer via `node_modules/@nitrots/nitro-renderer/src`.

// ---------------------------------------------------------------------------
// Catch-all
// ---------------------------------------------------------------------------
//
// Anything else still resolves to `undefined`. If a test fails with
// "X is not a constructor" / "X.SOMETHING is not a function", add the
// missing symbol above with a real stub. Avoid the temptation to
// blanket-mock everything — explicit stubs surface intent and let
// failing tests pinpoint what behavior they actually rely on.
