// ---------------------------------------------------------------------------
// ConnectedUsers
//
// Tiny presence indicator for the editor top bar - shows the avatars of
// every OTHER user currently editing the document, overlapping pill
// style. The local user isn't included (they already see their own
// chip on the far right).
//
// Data source: the Hocuspocus provider's Y.js awareness, populated by
// CollaborationCursorV3 with `{ name, color, avatarUrl }` per client.
// We re-read on every awareness "change" event so joins/leaves /
// rename flips update within a tick.
//
// The provider is created lazily by <Editor> and assigned to
// providerRef AFTER this component mounts, so we use the same
// "poll until ref populates, then attach listener" pattern as
// SyncIndicator (which had the exact same latent bug). Without that,
// the effect would early-return on the first run and never re-fire.
// ---------------------------------------------------------------------------

import { useEffect, useState } from "react";
import type { HocuspocusProvider } from "@hocuspocus/provider";
import { Tooltip } from "./Tooltip";

/**
 * One unique person currently editing, after collapsing every awareness
 * client that shares the same identity. `count` is how many live
 * connections (tabs / devices / lingering pre-timeout sockets) that person
 * has - surfaced as a small badge instead of repeating the same avatar.
 */
interface ConnectedUser {
  key: string;
  clientId: number;
  name: string;
  color: string;
  avatarUrl?: string;
  count: number;
}

/**
 * Stable identity key for deduplication. The HF username (`name`) is unique
 * per account, so it's the primary key; we fall back to the avatar URL and
 * finally the clientId so anonymous / malformed states still render once.
 */
function identityKey(name: string, avatarUrl?: string, clientId?: number): string {
  const n = name.trim().toLowerCase();
  if (n) return `name:${n}`;
  if (avatarUrl) return `avatar:${avatarUrl}`;
  return `client:${clientId ?? "?"}`;
}

interface Props {
  providerRef: { current: HocuspocusProvider | null };
}

/** Max avatars shown inline before collapsing the tail into a "+N" badge. */
const MAX_VISIBLE = 4;

export function ConnectedUsers({ providerRef }: Props) {
  const [users, setUsers] = useState<ConnectedUser[]>([]);

  useEffect(() => {
    let cleanup: (() => void) | null = null;
    let pollId: ReturnType<typeof setInterval> | null = null;

    const attach = (provider: HocuspocusProvider) => {
      const awareness = (provider as any).awareness;
      if (!awareness) return () => {};

      const sync = () => {
        const localClientId = awareness.clientID;

        // First pass: one entry per awareness client (raw connections).
        const raw: Omit<ConnectedUser, "key" | "count">[] = [];
        awareness.getStates().forEach((state: any, clientId: number) => {
          if (clientId === localClientId) return;
          const user = state?.user;
          if (!user || !user.name) return;
          raw.push({
            clientId,
            name: String(user.name),
            color: String(user.color || "#888"),
            avatarUrl: user.avatarUrl ? String(user.avatarUrl) : undefined,
          });
        });
        // Stable order: by clientId so the avatars don't dance around
        // every time someone moves their cursor and emits an update.
        raw.sort((a, b) => a.clientId - b.clientId);

        // Second pass: collapse same-identity clients into one avatar.
        // The same person opening two tabs - or a stale pre-timeout socket
        // lingering next to a fresh reconnect - used to render the exact
        // same circle N times. Now we show it once with a "xN" badge.
        const byKey = new Map<string, ConnectedUser>();
        for (const u of raw) {
          const key = identityKey(u.name, u.avatarUrl, u.clientId);
          const existing = byKey.get(key);
          if (existing) {
            existing.count += 1;
          } else {
            byKey.set(key, { ...u, key, count: 1 });
          }
        }
        setUsers([...byKey.values()]);
      };

      sync();
      awareness.on("change", sync);
      return () => awareness.off("change", sync);
    };

    if (providerRef.current) {
      cleanup = attach(providerRef.current);
    } else {
      pollId = setInterval(() => {
        const p = providerRef.current;
        if (!p) return;
        if (pollId) {
          clearInterval(pollId);
          pollId = null;
        }
        cleanup = attach(p);
      }, 100);
    }

    return () => {
      if (pollId) clearInterval(pollId);
      if (cleanup) cleanup();
    };
  }, [providerRef]);

  if (users.length === 0) return null;

  const visible = users.slice(0, MAX_VISIBLE);
  const overflow = users.length - visible.length;
  const totalLabel = `${users.length} other ${users.length === 1 ? "user" : "users"} editing`;

  return (
    <div className="connected-users" aria-label={totalLabel}>
      {visible.map((u) => (
        <Tooltip
          key={u.key}
          title={u.count > 1 ? `${u.name} (${u.count} open sessions)` : u.name}
        >
          <span
            className={`connected-users__avatar${u.count > 1 ? " connected-users__avatar--multi" : ""}`}
            style={{ borderColor: u.color }}
          >
            {u.avatarUrl ? (
              <img src={u.avatarUrl} alt="" referrerPolicy="no-referrer" />
            ) : (
              <span
                className="connected-users__initials"
                style={{ background: u.color }}
              >
                {u.name.slice(0, 1).toUpperCase()}
              </span>
            )}
            {u.count > 1 && (
              <span className="connected-users__count" aria-hidden="true">
                {u.count}
              </span>
            )}
          </span>
        </Tooltip>
      ))}
      {overflow > 0 && (
        <Tooltip
          title={users
            .slice(MAX_VISIBLE)
            .map((u) => u.name)
            .join(", ")}
        >
          <span className="connected-users__more">+{overflow}</span>
        </Tooltip>
      )}
    </div>
  );
}
