import { type JsonCompatibleValue, type MaybePromise, type Uuid } from '@augment-vir/common';
import { type mapServiceDevPort } from '@rest-vir/define-service';
import { type AnyDuration } from 'date-vir';
import { ListenTarget } from 'typed-event-target';
import { type RoomInput } from '../webrtc/webrtc-multiplayer-controller.js';
import { RoomRejectionError } from './errors.js';
import { LockStepGameStateController } from './lock-step-controller.js';
import { type MultiplayerApi } from './multiplayer-api.js';
/**
 * Connection state for {@link MultiplayerController}.
 *
 * @category Internal
 */
export declare enum MultiplayerConnectionState {
    Connecting = "connecting",
    Connected = "connected",
    /** The connection has not been started or has been gracefully terminated. */
    Disconnected = "disconnected"
}
/**
 * Service and room connection state for {@link MultiplayerController}.
 *
 * @category Internal
 */
export type ServiceAndRoomConnectionState = {
    service: MultiplayerConnectionState | Error;
    room: MultiplayerConnectionState | Error;
};
/**
 * Empty or totally disconnected state for {@link ServiceAndRoomConnectionState}.
 *
 * @category Internal
 */
export declare const emptyServiceAndRoomConnectionState: Readonly<ServiceAndRoomConnectionState>;
/**
 * Constructor parameters for {@link MultiplayerController}.
 *
 * @category Internal
 */
export type MultiplayerControllerParams<Action extends JsonCompatibleValue> = {
    /**
     * Unique string id that represents your game. Your backend will need to know this game id and
     * match it to your frontend's origin.
     */
    gameId: string;
    /**
     * This is fired when a WebRTC peer attempts to connect to the host client (this will only be
     * fired if your client is the host). Return `true` to accept the connection. Return `false` to
     * reject it.
     *
     * @default accept all connections
     */
    acceptConnection?: ((connectingClientId: Uuid, controller: MultiplayerController<Action>) => MaybePromise<boolean>) | undefined;
    /**
     * The duration between each frame. This should probably always be smaller than your supported
     * render frame duration (1/FPS). If this is set to `undefined`, you'll need to manually trigger
     * frames with `MultiplayerController.runFrame`.
     */
    frameDuration?: AnyDuration | undefined;
};
/**
 * Multiplayer mode parameters for {@link MultiplayerController}.
 *
 * @category Internal
 */
export type MultiplayerParams = {
    /**
     * Set to `undefined` or `false` to disable port scanning. Set to `true` to enable port
     * scanning. Set to an options object to configure port scanning.
     *
     * It is useful to enable this so that clients can find the port that your multiplayer server is
     * running on in case it must change. Note that port scanning will not be active if your
     * `serviceOrigin` does not contain a port.
     *
     * @default undefined
     */
    portScanOptions?: undefined | Parameters<typeof mapServiceDevPort>[1] | boolean;
    /**
     * The origin of the server running the multiplayer connection service.
     *
     * @example 'http://localhost:3000'
     */
    backendOrigin: string;
    /**
     * How long to wait before fetching the list of rooms again.
     *
     * @default {seconds: 10}
     */
    roomUpdateInterval?: AnyDuration | undefined;
    /**
     * Optional stun server URLs to help with routing WebRTC connections. This is entirely optional,
     * but might help with clients attempting to establish connections to each other.
     */
    stunServerUrls?: ReadonlyArray<string> | undefined;
};
declare const ControllerFrameEvent_base: (new (eventInitDict: {
    bubbles?: boolean;
    cancelable?: boolean;
    composed?: boolean;
    detail: any;
}) => import("typed-event-target").TypedCustomEvent<any, "controller-frame">) & Pick<{
    new (type: string, eventInitDict?: EventInit): Event;
    prototype: Event;
    readonly NONE: 0;
    readonly CAPTURING_PHASE: 1;
    readonly AT_TARGET: 2;
    readonly BUBBLING_PHASE: 3;
}, "prototype" | "NONE" | "CAPTURING_PHASE" | "AT_TARGET" | "BUBBLING_PHASE"> & Pick<import("typed-event-target").TypedCustomEvent<any, "controller-frame">, "type">;
/**
 * This is fired whenever a new frame is received from the host client.
 *
 * @category Events
 */
export declare class ControllerFrameEvent<Action extends JsonCompatibleValue> extends ControllerFrameEvent_base {
    detail: ReadonlyArray<Action>;
}
declare const ControllerRoomListEvent_base: (new (eventInitDict: {
    bubbles?: boolean;
    cancelable?: boolean;
    composed?: boolean;
    detail: Readonly<Partial<Record<`${string}-${string}-${string}-${string}-${string}`, {
        roomName: string;
        roomId: `${string}-${string}-${string}-${string}-${string}`;
        clientCount: number;
        hasRoomPassword: boolean;
    }>>>;
}) => import("typed-event-target").TypedCustomEvent<Readonly<Partial<Record<`${string}-${string}-${string}-${string}-${string}`, {
    roomName: string;
    roomId: `${string}-${string}-${string}-${string}-${string}`;
    clientCount: number;
    hasRoomPassword: boolean;
}>>>, "controller-room-list">) & Pick<{
    new (type: string, eventInitDict?: EventInit): Event;
    prototype: Event;
    readonly NONE: 0;
    readonly CAPTURING_PHASE: 1;
    readonly AT_TARGET: 2;
    readonly BUBBLING_PHASE: 3;
}, "prototype" | "NONE" | "CAPTURING_PHASE" | "AT_TARGET" | "BUBBLING_PHASE"> & Pick<import("typed-event-target").TypedCustomEvent<Readonly<Partial<Record<`${string}-${string}-${string}-${string}-${string}`, {
    roomName: string;
    roomId: `${string}-${string}-${string}-${string}-${string}`;
    clientCount: number;
    hasRoomPassword: boolean;
}>>>, "controller-room-list">, "type">;
/**
 * This is called whenever the room list updates, even if there were no changes to the room list.
 * Note that room list updates are paused while the controller is connected to an actual room.
 *
 * @category Events
 */
export declare class ControllerRoomListEvent extends ControllerRoomListEvent_base {
}
declare const ControllerClientEvent_base: (new (eventInitDict: {
    bubbles?: boolean;
    cancelable?: boolean;
    composed?: boolean;
    detail: Readonly<import("type-fest/source/require-exactly-one.js")._RequireExactlyOne<{
        newHost: Uuid;
        newMember: Uuid;
        lostHost: Uuid;
        lostMember: Uuid;
    }, "newHost" | "newMember" | "lostHost" | "lostMember">>;
}) => import("typed-event-target").TypedCustomEvent<Readonly<import("type-fest/source/require-exactly-one.js")._RequireExactlyOne<{
    newHost: Uuid;
    newMember: Uuid;
    lostHost: Uuid;
    lostMember: Uuid;
}, "newHost" | "newMember" | "lostHost" | "lostMember">>, "controller-client">) & Pick<{
    new (type: string, eventInitDict?: EventInit): Event;
    prototype: Event;
    readonly NONE: 0;
    readonly CAPTURING_PHASE: 1;
    readonly AT_TARGET: 2;
    readonly BUBBLING_PHASE: 3;
}, "prototype" | "NONE" | "CAPTURING_PHASE" | "AT_TARGET" | "BUBBLING_PHASE"> & Pick<import("typed-event-target").TypedCustomEvent<Readonly<import("type-fest/source/require-exactly-one.js")._RequireExactlyOne<{
    newHost: Uuid;
    newMember: Uuid;
    lostHost: Uuid;
    lostMember: Uuid;
}, "newHost" | "newMember" | "lostHost" | "lostMember">>, "controller-client">, "type">;
/**
 * This is fired in the following situations:
 *
 * - A new host for the room was selected
 * - The room host was lost
 * - A new room client was added (only fired on the host client)
 * - A room client was lost (only fired on the host client)
 *
 * @category Events
 */
export declare class ControllerClientEvent extends ControllerClientEvent_base {
}
declare const ControllerConnectionEvent_base: (new (eventInitDict: {
    bubbles?: boolean;
    cancelable?: boolean;
    composed?: boolean;
    detail: ServiceAndRoomConnectionState;
}) => import("typed-event-target").TypedCustomEvent<ServiceAndRoomConnectionState, "controller-connection">) & Pick<{
    new (type: string, eventInitDict?: EventInit): Event;
    prototype: Event;
    readonly NONE: 0;
    readonly CAPTURING_PHASE: 1;
    readonly AT_TARGET: 2;
    readonly BUBBLING_PHASE: 3;
}, "prototype" | "NONE" | "CAPTURING_PHASE" | "AT_TARGET" | "BUBBLING_PHASE"> & Pick<import("typed-event-target").TypedCustomEvent<ServiceAndRoomConnectionState, "controller-connection">, "type">;
/**
 * Fires when the controller's connection state is updated.
 *
 * @category Events
 */
export declare class ControllerConnectionEvent extends ControllerConnectionEvent_base {
}
/**
 * All events emitted by this controller.
 *
 * @category Internal
 */
export type AllMultiplayerControllerEvents<Action extends JsonCompatibleValue> = ControllerFrameEvent<Action> | ControllerRoomListEvent | ControllerClientEvent | ControllerConnectionEvent;
/**
 * An all-in-one controller for singleplayer or lock-step multiplayer game state. Singleplayer mode
 * requires no servers. Multiplayer mode requires a backend service running the
 * {@link MultiplayerApi}.
 *
 * @category Main
 */
export declare class MultiplayerController<Action extends JsonCompatibleValue = any> extends ListenTarget<AllMultiplayerControllerEvents<Action>> {
    protected readonly params: MultiplayerControllerParams<Action>;
    /** All events emitted by this controller. */
    static readonly events: {
        ControllerFrameEvent: typeof ControllerFrameEvent;
        ControllerRoomListEvent: typeof ControllerRoomListEvent;
        ControllerClientEvent: typeof ControllerClientEvent;
        ControllerConnectionEvent: typeof ControllerConnectionEvent;
    };
    /** All events emitted by this controller. */
    readonly events: {
        ControllerFrameEvent: typeof ControllerFrameEvent;
        ControllerRoomListEvent: typeof ControllerRoomListEvent;
        ControllerClientEvent: typeof ControllerClientEvent;
        ControllerConnectionEvent: typeof ControllerConnectionEvent;
    };
    static readonly knownErrors: {
        RoomRejectionError: typeof RoomRejectionError;
    };
    readonly knownErrors: {
        RoomRejectionError: typeof RoomRejectionError;
    };
    /**
     * Set to `false` to disable room updates, even when still not connected to a room in
     * multiplayer mode.
     */
    enableRoomUpdates: boolean;
    /** Currently joined room id. If a room has not been joined yet, this will be empty. */
    readonly roomId: Uuid | undefined;
    /** The current connection state of the controller's connection to a backend service. */
    readonly serviceConnectionState: ServiceAndRoomConnectionState['service'];
    /** The current connection state of the controller's connection to a multiplayer room. */
    readonly roomConnectionState: ServiceAndRoomConnectionState['room'];
    /**
     * Current WebRTC lock step connection with the room host (when not the host) or all room
     * participants (when the host). This will only be initialized after calling
     * {@link MultiplayerController.joinOrCreateRoom}.
     */
    currentConnection: LockStepGameStateController | undefined;
    /**
     * Rooms that have rejected the current player, so the player doesn't keep trying to connect to
     * them.
     */
    protected rejectedRoomIds: Set<`${string}-${string}-${string}-${string}-${string}`>;
    /** The current MultiplayerApi. This will be `undefined` if playing in single player. */
    multiplayerApi: Promise<MultiplayerApi> | undefined;
    /**
     * Used to keep track of the room update interval. This will be set when the controller is
     * constructed in multiplayer mode or when a room is left. This will be cleared when a room is
     * joined or if the controller is destroyed.
     */
    protected roomUpdateIntervalId: ReturnType<typeof globalThis.setInterval> | undefined;
    /** This is populated if `.startMultiplayer` is called. */
    protected multiplayerParams: Readonly<MultiplayerParams> | undefined;
    /**
     * Get the current client's WebRTC client id. This will return `undefined` if there is no
     * current connection.
     */
    getClientId(): Uuid | undefined;
    /**
     * Get all connected client ids.
     *
     * - For host clients, this will indicate how many member clients are connected to the host
     *   client, _not_ including the host itself.
     * - For non-host clients, this will only list the host's client.
     *
     * For host clients, this does ont include the host client id whereas
     * {@link MultiplayerController.getAllClientIds} does.
     */
    getConnectedClientIds(): Uuid[];
    /**
     * Get all room client ids.
     *
     * - For host clients, this will indicate how many clients are connected to the room, including
     *   the host client itself.
     * - For non-host clients, this will only list the host's client.
     *
     * For host clients, this includes the host client id whereas
     * {@link MultiplayerController.getConnectedClientIds} does not.
     */
    getAllClientIds(): Uuid[];
    constructor(params: MultiplayerControllerParams<Action>);
    /**
     * Start multiplayer mode. This initializes {@link MultiplayerController.multiplayerApi} and
     * {@link MultiplayerController.roomUpdateIntervalId}.
     */
    startMultiplayer(params: Readonly<MultiplayerParams>): void;
    /** Start singleplayer mode. */
    startSingleplayer(): void;
    /**
     * Manually run the next frame.
     *
     * @throws Error if `frameDuration` has been set.
     */
    runFrame(actions?: ReadonlyArray<Action> | undefined): void;
    /** The current FPS of the data flow. */
    getFps(): number;
    /** Fire an action. This will be sent to all clients in the room so they can process it. */
    act(actions: Action | ReadonlyArray<Action>): void;
    /** Detects if this controller is the room host or not. */
    isHost(): boolean;
    /** Cleanup everything. */
    destroy(): void;
    /**
     * Join or create a room.
     *
     * @throws `Error` if this controller is already connected to a room.
     */
    joinOrCreateRoom(room: Readonly<RoomInput>): Promise<void>;
    /** Leave the current room or single player connection. */
    leaveRoom(): void;
    /** Set the current connection state and fire listeners. */
    protected updateConnectionState(state: Partial<ServiceAndRoomConnectionState>): void;
    /** Starts polling the multiplayer server for room updates and fires listeners. */
    protected startRoomInterval(): void;
}
export {};
