/** Small, identity-free hosted-presence status for a caller-owned container. */

export type PresenceConnectionState = "off" | "connecting" | "live" | "reconnecting" | "offline";

export interface PresenceIndicatorState {
  signedIn: boolean;
  connection: PresenceConnectionState;
  /** Aggregate only. Individual peer identity never enters this adapter. */
  nearbyPeerCount: number;
}

export interface PresenceIndicatorOptions {
  container: HTMLElement;
  onRetry?: () => void;
}

export interface PresenceIndicator {
  root: HTMLElement;
  update(state: PresenceIndicatorState): PresenceIndicatorState;
  state(): PresenceIndicatorState;
  dispose(): void;
}

const CONNECTIONS = new Set<PresenceConnectionState>([
  "off", "connecting", "live", "reconnecting", "offline",
]);

function copy(state: PresenceIndicatorState): PresenceIndicatorState {
  return { ...state };
}

function normalize(input: PresenceIndicatorState): PresenceIndicatorState {
  if (!input || typeof input !== "object") throw new TypeError("presence indicator: invalid state");
  if (typeof input.signedIn !== "boolean" || !CONNECTIONS.has(input.connection)) {
    throw new TypeError("presence indicator: invalid connection state");
  }
  if (!Number.isSafeInteger(input.nearbyPeerCount) || input.nearbyPeerCount < 0 || input.nearbyPeerCount > 10_000) {
    throw new RangeError("presence indicator: nearbyPeerCount must be an integer from 0 to 10000");
  }
  if (!input.signedIn) return { signedIn: false, connection: "off", nearbyPeerCount: 0 };
  return {
    signedIn: true,
    connection: input.connection,
    nearbyPeerCount: input.connection === "live" ? input.nearbyPeerCount : 0,
  };
}

function label(state: PresenceIndicatorState): string {
  if (!state.signedIn || state.connection === "off") return "Hosted presence is off.";
  switch (state.connection) {
    case "connecting": return "Hosted presence is connecting.";
    case "reconnecting": return "Hosted presence is reconnecting.";
    case "offline": return "Hosted presence is offline.";
    case "live": {
      const nearby = state.nearbyPeerCount === 1 ? "1 nearby peer" : `${state.nearbyPeerCount} nearby peers`;
      return `Hosted presence is live · ${nearby}.`;
    }
  }
}

export function createPresenceIndicator(options: PresenceIndicatorOptions): PresenceIndicator {
  if (!options.container || typeof options.container.append !== "function") {
    throw new TypeError("presence indicator: container must be an HTMLElement");
  }
  if (options.onRetry !== undefined && typeof options.onRetry !== "function") {
    throw new TypeError("presence indicator: onRetry must be a function");
  }

  const doc = options.container.ownerDocument;
  const root = doc.createElement("div");
  root.className = "tera-presence";
  root.setAttribute("data-presence", "off");

  const statusLine = doc.createElement("span");
  statusLine.className = "tera-presence__status";
  statusLine.setAttribute("role", "status");
  statusLine.setAttribute("aria-live", "polite");
  statusLine.setAttribute("aria-atomic", "true");

  const retry = doc.createElement("button");
  retry.type = "button";
  retry.className = "tera-presence__retry";
  retry.textContent = "Retry presence";
  retry.hidden = true;
  root.append(statusLine, retry);
  options.container.append(root);

  let current: PresenceIndicatorState = { signedIn: false, connection: "off", nearbyPeerCount: 0 };
  let disposed = false;
  let onRetry: (() => void) | null = options.onRetry ?? null;

  function render(): void {
    statusLine.textContent = label(current);
    root.setAttribute("data-presence", current.connection);
    retry.hidden = onRetry === null || !current.signedIn ||
      (current.connection !== "offline" && current.connection !== "reconnecting");
  }

  retry.addEventListener("click", () => {
    if (!disposed && !retry.hidden) onRetry?.();
  });
  render();

  return {
    root,
    update(next) {
      if (disposed) return copy(current);
      current = normalize(next);
      render();
      return copy(current);
    },
    state: () => copy(current),
    dispose() {
      if (disposed) return;
      disposed = true;
      onRetry = null;
      current = { signedIn: false, connection: "off", nearbyPeerCount: 0 };
      root.remove();
    },
  };
}
