import { useState, useEffect, useRef, useMemo, useCallback } from 'react';
import { Link, useSearchParams } from 'react-router';
import { useClient } from 'hooks/useClient';
import { toast } from 'sonner';
import { Hash, Users, LogOut, Send, Plus, RefreshCw } from 'lucide-react';
import { formatClock, soulseekRoomPath, ROOM_PARAM, type SlskdRoom, type SlskdRoomInfo } from './shared';

// Rooms panel — join Soulseek chat rooms and talk in them. GET /rooms/joined lists the room names we're
// in; GET /rooms/joined/{name} inlines that room's users + messages (polled while it's open); GET
// /rooms/available lists everything else to join. Messages/joins POST a bare JSON string body, matching
// slskd's [FromBody] string endpoints. Basic version: a room rail on the left, transcript + composer on
// the right.

const POLL_MS = 2500;
const AVAILABLE_LIMIT = 250;

export const SoulseekRooms = () => {
  const client = useClient();
  const [joined, setJoined] = useState<string[]>([]);
  const [available, setAvailable] = useState<SlskdRoomInfo[]>([]);
  const [params, setParams] = useSearchParams();
  const selected = params.get(ROOM_PARAM)?.trim() || null;
  const [room, setRoom] = useState<SlskdRoom | null>(null);
  const [draft, setDraft] = useState('');
  const [joinName, setJoinName] = useState('');
  const [busy, setBusy] = useState(false);
  const scrollRef = useRef<HTMLDivElement>(null);

  const loadJoined = useCallback(async () => {
    try {
      setJoined(await client.get<string[]>('/slskd/api/v0/rooms/joined'));
    } catch {
      /* status panel surfaces connection errors */
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const loadAvailable = useCallback(async () => {
    try {
      setAvailable(await client.get<SlskdRoomInfo[]>('/slskd/api/v0/rooms/available'));
    } catch {
      /* ignore */
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    loadJoined();
    loadAvailable();
  }, [loadJoined, loadAvailable]);

  // Poll the selected room's transcript + user list while it's open.
  useEffect(() => {
    if (!selected) {
      setRoom(null);
      return;
    }
    let cancelled = false;
    const tick = () =>
      client
        .get<SlskdRoom>(`/slskd/api/v0/rooms/joined/${encodeURIComponent(selected)}`)
        .then((r) => !cancelled && setRoom(r))
        .catch(() => {});
    tick();
    const timer = setInterval(tick, POLL_MS);
    return () => {
      cancelled = true;
      clearInterval(timer);
    };
    // useClient() is a fresh object each render — re-poll only when the selected room changes.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [selected]);

  const messages = room?.messages ?? [];
  // Stick to the bottom as new messages arrive / when switching rooms.
  useEffect(() => {
    const el = scrollRef.current;
    if (el) el.scrollTop = el.scrollHeight;
  }, [messages.length, selected]);

  const notJoined = useMemo(() => {
    const have = new Set(joined);
    return available
      .filter((r) => !have.has(r.name))
      .sort((a, b) => b.userCount - a.userCount)
      .slice(0, AVAILABLE_LIMIT);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [available, joined]);

  const join = async (name: string) => {
    const target = name.trim();
    if (!target || busy) return;
    setBusy(true);
    try {
      await client.post('/slskd/api/v0/rooms/joined', target);
      setJoinName('');
      setJoined((prev) => (prev.includes(target) ? prev : [...prev, target]));
      // Joins the room *and then* opens it, so this stays a button — a link cannot express the "and then".
      setParams((prev) => {
        const next = new URLSearchParams(prev);
        next.set(ROOM_PARAM, target);
        return next;
      });
      toast.success(`Joined ${target}`);
    } catch (err) {
      toast.error(`Couldn't join ${target}: ${err instanceof Error ? err.message : String(err)}`);
    } finally {
      setBusy(false);
    }
  };

  const leave = async (name: string) => {
    try {
      await client.delete(`/slskd/api/v0/rooms/joined/${encodeURIComponent(name)}`);
      setJoined((prev) => prev.filter((n) => n !== name));
      // Leaving the open room leaves nothing open. `replace`, because a room you are no longer in is not
      // somewhere Back should return you to.
      if (name === selected) {
        setParams(
          (prev) => {
            const next = new URLSearchParams(prev);
            next.delete(ROOM_PARAM);
            return next;
          },
          { replace: true },
        );
      }
    } catch (err) {
      toast.error(`Couldn't leave ${name}: ${err instanceof Error ? err.message : String(err)}`);
    }
  };

  const send = async () => {
    const text = draft.trim();
    if (!text || !selected) return;
    setDraft('');
    try {
      await client.post(`/slskd/api/v0/rooms/joined/${encodeURIComponent(selected)}/messages`, text);
    } catch (err) {
      setDraft(text);
      toast.error(`Send failed: ${err instanceof Error ? err.message : String(err)}`);
    }
  };

  return (
    <div className="flex h-full overflow-hidden">
      {/* Room rail */}
      <div className="flex w-56 shrink-0 flex-col border-r bg-muted/20">
        <div className="flex items-center justify-between px-3 py-2.5 text-xs font-medium uppercase tracking-wide text-muted-foreground">
          <span>Joined</span>
          <button type="button" onClick={loadJoined} title="Refresh" className="rounded p-1 hover:bg-muted">
            <RefreshCw className="h-3 w-3" />
          </button>
        </div>
        <div className="flex flex-col gap-0.5 px-2">
          {joined.length === 0 && <p className="px-2 py-1 text-xs text-muted-foreground">No rooms joined yet.</p>}
          {joined.map((name) => {
            const active = name === selected;
            return (
              <div
                key={name}
                className={`group flex items-center gap-1.5 rounded-lg px-2 py-1.5 text-sm transition-colors ${
                  active ? 'bg-primary/10 text-primary' : 'text-muted-foreground hover:bg-muted hover:text-foreground'
                }`}
              >
                {/* The leave button is a sibling, never nested inside the anchor. */}
                <Link
                  to={soulseekRoomPath(name)}
                  className="flex min-w-0 flex-1 items-center gap-1.5 text-left"
                  aria-current={active ? 'true' : undefined}
                >
                  <Hash className="h-3.5 w-3.5 shrink-0" />
                  <span className="truncate">{name}</span>
                </Link>
                <button
                  type="button"
                  onClick={() => leave(name)}
                  title={`Leave ${name}`}
                  className="shrink-0 rounded p-0.5 text-muted-foreground opacity-0 transition group-hover:opacity-100 hover:bg-black/10 hover:text-foreground"
                >
                  <LogOut className="h-3 w-3" />
                </button>
              </div>
            );
          })}
        </div>

        {/* Join a room */}
        <div className="mt-auto border-t p-2">
          <form
            onSubmit={(ev) => {
              ev.preventDefault();
              join(joinName);
            }}
            className="flex items-center gap-1.5"
          >
            <input
              value={joinName}
              onChange={(ev) => setJoinName(ev.target.value)}
              list="slskd-available-rooms"
              placeholder="Join a room…"
              className="h-8 min-w-0 flex-1 rounded-md border bg-background px-2 text-sm outline-none focus:border-primary/50"
            />
            <datalist id="slskd-available-rooms">
              {notJoined.map((r) => (
                <option key={r.name} value={r.name}>
                  {r.userCount} users
                </option>
              ))}
            </datalist>
            <button
              type="submit"
              disabled={!joinName.trim() || busy}
              title="Join"
              className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md border text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-40"
            >
              <Plus className="h-4 w-4" />
            </button>
          </form>
        </div>
      </div>

      {/* Transcript + composer */}
      <div className="flex min-w-0 flex-1 flex-col">
        {!selected ? (
          <div className="flex flex-1 flex-col items-center justify-center gap-2 text-center text-muted-foreground">
            <Hash className="h-8 w-8" />
            <p className="text-sm">
              {joined.length === 0 ? 'Join a room to start chatting.' : 'Pick a room to read it.'}
            </p>
          </div>
        ) : (
          <>
            <div className="flex shrink-0 items-center gap-2 border-b px-4 py-2.5">
              <Hash className="h-4 w-4 shrink-0 text-muted-foreground" />
              <span className="truncate font-medium">{selected}</span>
              <span className="ml-auto inline-flex items-center gap-1 text-xs text-muted-foreground">
                <Users className="h-3.5 w-3.5" /> {room?.users?.length ?? 0}
              </span>
            </div>

            <div ref={scrollRef} className="min-h-0 flex-1 overflow-y-auto px-4 py-3">
              {messages.length === 0 ? (
                <p className="text-sm text-muted-foreground">No messages yet.</p>
              ) : (
                <div className="flex flex-col gap-1.5">
                  {messages.map((m, i) => (
                    <div key={`${m.timestamp}-${i}`} className="flex gap-2 text-sm">
                      <span className="shrink-0 pt-0.5 text-xs tabular-nums text-muted-foreground">{formatClock(m.timestamp)}</span>
                      <span className={`shrink-0 font-medium ${m.self ? 'text-primary' : 'text-foreground'}`}>{m.username}</span>
                      <span className="min-w-0 break-words text-foreground/90">{m.message}</span>
                    </div>
                  ))}
                </div>
              )}
            </div>

            <form
              onSubmit={(ev) => {
                ev.preventDefault();
                send();
              }}
              className="flex shrink-0 items-center gap-2 border-t p-3"
            >
              <input
                value={draft}
                onChange={(ev) => setDraft(ev.target.value)}
                placeholder={`Message ${selected}…`}
                className="h-9 min-w-0 flex-1 rounded-lg border bg-background px-3 text-sm outline-none focus:border-primary/50 focus:ring-2 focus:ring-primary/20"
              />
              <button
                type="submit"
                disabled={!draft.trim()}
                className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-primary text-primary-foreground transition hover:bg-primary/90 disabled:opacity-40"
              >
                <Send className="h-4 w-4" />
              </button>
            </form>
          </>
        )}
      </div>
    </div>
  );
};
