import { DurableObject } from "cloudflare:workers";

type ImmutablePrimitive = undefined | null | boolean | string | number;
type Immutable<T> = T extends ImmutablePrimitive
  ? T
  : T extends Array<infer U>
    ? ImmutableArray<U>
    : T extends Map<infer K, infer V>
      ? ImmutableMap<K, V>
      : T extends Set<infer M>
        ? ImmutableSet<M>
        : ImmutableObject<T>;
type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
type ImmutableObject<T> = {
  readonly [K in keyof T]: Immutable<T[K]>;
};
type ConnectionState<T> = ImmutableObject<T> | null;
type ConnectionSetStateFn<T> = (prevState: ConnectionState<T>) => T;
type ConnectionContext = {
  request: Request;
};
/** A WebSocket connected to the Server */
type Connection<TState = unknown> = WebSocket & {
  /** Connection identifier */
  id: string;
  /**
   * Arbitrary state associated with this connection.
   * Read-only, use Connection.setState to update the state.
   */
  state: ConnectionState<TState>;
  setState(
    state: TState | ConnectionSetStateFn<TState> | null
  ): ConnectionState<TState>;
  /** @deprecated use Connection.setState instead */
  serializeAttachment<T = unknown>(attachment: T): void;
  /** @deprecated use Connection.state instead */
  deserializeAttachment<T = unknown>(): T | null;
  /**
   * Server's name
   */
  server: string;
};

type WSMessage = ArrayBuffer | ArrayBufferView | string;
/**
 * For a given server namespace, create a server with a name.
 */
declare function getServerByName<Env, T extends Server<Env>>(
  serverNamespace: DurableObjectNamespace<T>,
  name: string,
  options?: {
    jurisdiction?: DurableObjectJurisdiction;
    locationHint?: DurableObjectLocationHint;
  }
): Promise<DurableObjectStub<T>>;
interface PartyServerOptions<Env> {
  prefix?: string;
  jurisdiction?: DurableObjectJurisdiction;
  locationHint?: DurableObjectLocationHint;
  onBeforeConnect?: (
    req: Request,
    lobby: {
      party: keyof Env;
      name: string;
    }
  ) => Response | Request | void | Promise<Response | Request | void>;
  onBeforeRequest?: (
    req: Request,
    lobby: {
      party: keyof Env;
      name: string;
    }
  ) =>
    | Response
    | Request
    | void
    | Promise<Response | Request | undefined | void>;
}
/**
 * A utility function for PartyKit style routing.
 */
declare function routePartykitRequest<
  Env = unknown,
  T extends Server<Env> = Server<Env>
>(
  req: Request,
  env: Record<string, unknown>,
  options?: PartyServerOptions<typeof env>
): Promise<Response | null>;
declare class Server<Env = unknown> extends DurableObject<Env> {
  #private;
  static options: {
    hibernate: boolean;
  };
  constructor(ctx: DurableObjectState, env: Env);
  /**
   * Handle incoming requests to the server.
   */
  fetch(request: Request): Promise<Response>;
  webSocketMessage(ws: WebSocket, message: WSMessage): Promise<void>;
  webSocketClose(
    ws: WebSocket,
    code: number,
    reason: string,
    wasClean: boolean
  ): Promise<void>;
  webSocketError(ws: WebSocket, error: unknown): Promise<void>;
  /**
   * The name for this server. Write-once-only.
   */
  get name(): string;
  setName(name: string): Promise<void>;
  /** Send a message to all connected clients, except connection ids listed in `without` */
  broadcast(
    msg: string | ArrayBuffer | ArrayBufferView,
    without?: string[] | undefined
  ): void;
  /** Get a connection by connection id */
  getConnection<TState = unknown>(id: string): Connection<TState> | undefined;
  /**
   * Get all connections. Optionally, you can provide a tag to filter returned connections.
   * Use `Server#getConnectionTags` to tag the connection on connect.
   */
  getConnections<TState = unknown>(tag?: string): Iterable<Connection<TState>>;
  /**
   * You can tag a connection to filter them in Server#getConnections.
   * Each connection supports up to 9 tags, each tag max length is 256 characters.
   */
  getConnectionTags(
    connection: Connection,
    context: ConnectionContext
  ): string[] | Promise<string[]>;
  /**
   * Called when the server is started for the first time.
   */
  onStart(): void | Promise<void>;
  /**
   * Called when a new connection is made to the server.
   */
  onConnect(
    connection: Connection,
    ctx: ConnectionContext
  ): void | Promise<void>;
  /**
   * Called when a message is received from a connection.
   */
  onMessage(connection: Connection, message: WSMessage): void | Promise<void>;
  /**
   * Called when a connection is closed.
   */
  onClose(
    connection: Connection,
    code: number,
    reason: string,
    wasClean: boolean
  ): void | Promise<void>;
  /**
   * Called when an error occurs on a connection.
   */
  onError(connection: Connection, error: unknown): void | Promise<void>;
  /**
   * Called when a request is made to the server.
   */
  onRequest(request: Request): Response | Promise<Response>;
  onAlarm(): void | Promise<void>;
  alarm(): Promise<void>;
}

export {
  type Connection,
  type ConnectionContext,
  type ConnectionSetStateFn,
  type ConnectionState,
  type PartyServerOptions,
  Server,
  type WSMessage,
  getServerByName,
  routePartykitRequest
};
