'use client';

import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { useEffect, useId, useRef, useState, type FormEvent } from 'react';
import { MAX_NICKNAME_LENGTH, MAX_TEAM_NAME_LENGTH, validateNickname, validateTeamName } from '@pintrush/engine';
import type { RoomJoinResult } from '@pintrush/protocol';
import { Button } from '@/components/ui/Button';
import { AlertIcon, ArrowRightIcon, ChevronDownIcon, MoonIcon, UsersIcon } from '@/components/ui/icons';
import { cn } from '@/lib/cn';
import { gameServerUrl, getDeviceId, loadTokens, saveToken, clearTokens, useGameConnection, type StoredToken } from '@/lib/game';
import { recallNickname, rememberNickname, waitForConnected } from '@/lib/play/device';
import { useSettledKey } from '@/lib/play/hooks';
import { friendlyError } from '@/lib/play/errors';

interface RoomStatus {
  active: boolean;
  kind: 'ALWAYS_ON' | 'CHAMPIONSHIP' | null;
  name: string | null;
}

const STATUS_POLL_MS = 20_000;

/** Client-side copy for the engine's name rules (the server re-validates everything). */
function nameMessage(reason: string, what: 'nickname' | 'team'): string {
  const noun = what === 'nickname' ? 'Nickname' : 'Team name';
  switch (reason) {
    case 'TOO_SHORT':
      return `${noun} needs at least 2 characters.`;
    case 'TOO_LONG':
      return `${noun} can be at most ${what === 'nickname' ? MAX_NICKNAME_LENGTH : MAX_TEAM_NAME_LENGTH} characters.`;
    case 'INVALID_CHARACTERS':
      return 'Use letters, numbers, spaces and . _ - ’ ! only.';
    case 'URL':
      return 'No web addresses, please.';
    case 'RESERVED':
      return 'That name is reserved — pick another.';
    case 'PROFANITY':
      return 'Let’s keep it friendly — try another name.';
    default:
      return `That ${what === 'nickname' ? 'nickname' : 'team name'} won’t work — try another.`;
  }
}

function Spinner() {
  return <span aria-hidden="true" className="size-5 animate-spin rounded-full border-2 border-white/40 border-t-white motion-reduce:animate-none" />;
}

export function JoinForm({ venueCode, venueName, roomId, embed }: { venueCode: string; venueName: string; roomId: string; embed: boolean }) {
  const router = useRouter();
  const ids = useId();
  const nickId = `nick${ids}`;
  const teamId = `team${ids}`;
  const nickInput = useRef<HTMLInputElement>(null);
  const teamInput = useRef<HTMLInputElement>(null);
  const joinKey = useRef<string | null>(null);

  const [nickname, setNickname] = useState('');
  const [teamOpen, setTeamOpen] = useState(false);
  const [teamName, setTeamName] = useState('');
  const [existing, setExisting] = useState<StoredToken | null>(null);
  const [busy, setBusy] = useState(false);
  const [errors, setErrors] = useState<{ nickname?: string; team?: string; form?: string }>({});
  const [noGame, setNoGame] = useState(false);
  const [roomStatus, setRoomStatus] = useState<RoomStatus | null>(null);

  // An anonymous socket, ready by the time the guest taps Join (room.join turns it into the player).
  const { conn } = useGameConnection({ auth: () => ({}) }, useSettledKey(`join:${roomId}`));

  const playHref = (id: string) => `/play/${id}${embed ? '?embed=1' : ''}`;

  // Browser-only state (tokens, last nickname) is read after mount to keep hydration stable.
  useEffect(() => {
    const token = loadTokens(roomId)[0] ?? null;
    setExisting(token);
    const last = recallNickname(roomId);
    if (last) setNickname(last.slice(0, MAX_NICKNAME_LENGTH));
  }, [roomId]);

  // Live / idle status from the game server, refreshed while the page is open.
  useEffect(() => {
    let cancelled = false;
    const check = async () => {
      try {
        const res = await fetch(`${gameServerUrl()}/rooms/${roomId}/public`, { cache: 'no-store' });
        if (!res.ok) return;
        const body = (await res.json()) as { active?: unknown; activeEventKind?: unknown; activeEventName?: unknown };
        if (cancelled) return;
        const kind = body.activeEventKind === 'ALWAYS_ON' || body.activeEventKind === 'CHAMPIONSHIP' ? body.activeEventKind : null;
        const active = body.active === true;
        setRoomStatus({ active, kind, name: typeof body.activeEventName === 'string' ? body.activeEventName : null });
        if (active) setNoGame(false);
      } catch {
        // Unknown status (offline, server down): leave the form usable; the join ack has the final say.
      }
    };
    void check();
    const timer = setInterval(check, STATUS_POLL_MS);
    return () => {
      cancelled = true;
      clearInterval(timer);
    };
  }, [roomId]);

  const idle = noGame || roomStatus?.active === false;

  async function onSubmit(e: FormEvent<HTMLFormElement>) {
    e.preventDefault();
    if (busy) return;
    const nick = validateNickname(nickname);
    let team: string | null = null;
    const nextErrors: typeof errors = {};
    if (!nick.ok) nextErrors.nickname = nameMessage(nick.reason, 'nickname');
    if (teamOpen && teamName.trim()) {
      const t = validateTeamName(teamName);
      if (t.ok) team = t.nickname;
      else nextErrors.team = nameMessage(t.reason, 'team');
    }
    setErrors(nextErrors);
    if (nextErrors.nickname) return nickInput.current?.focus();
    if (nextErrors.team) return teamInput.current?.focus();
    if (!nick.ok || !conn) return;

    setBusy(true);
    const live = await waitForConnected(conn, 10_000);
    if (!live) {
      setErrors({ form: 'Can’t reach the game right now. Check your Wi-Fi and try again.' });
      setBusy(false);
      return;
    }
    joinKey.current ??= crypto.randomUUID();
    const res = await conn.emitWithRetry<RoomJoinResult>('room.join', {
      venueCode,
      roomId,
      nickname: nick.nickname,
      teamName: team,
      deviceId: getDeviceId(),
      idempotencyKey: joinKey.current,
    });
    if (res.ok && res.data) {
      const d = res.data;
      saveToken(d.roomId, {
        token: d.token,
        gameSessionId: d.gameSessionId,
        playerSessionId: d.playerSessionId,
        expiresAt: d.expiresAt,
        nickname: d.nickname,
      });
      rememberNickname(d.roomId, d.nickname);
      router.push(playHref(d.roomId));
      return; // stay busy while the play screen loads
    }
    setBusy(false);
    if (res.ok) {
      setErrors({ form: 'Something went wrong. Try again.' });
      return;
    }
    const { code } = res.error;
    if (code === 'NICKNAME_PROFANE' || code === 'NICKNAME_INVALID') {
      setErrors({ nickname: res.error.message });
      nickInput.current?.focus();
    } else if (code === 'TEAM_NAME_INVALID') {
      setTeamOpen(true);
      setErrors({ team: res.error.message });
      teamInput.current?.focus();
    } else if (code === 'NO_ACTIVE_SESSION') {
      setNoGame(true);
    } else if (code === 'NOT_FOUND') {
      setErrors({ form: 'We couldn’t find this bar’s game. Check the code on the TV.' });
    } else {
      setErrors({ form: friendlyError(res.error) });
    }
  }

  function startOver() {
    clearTokens(roomId);
    setExisting(null);
    requestAnimationFrame(() => nickInput.current?.focus());
  }

  const count = Array.from(nickname).length;

  return (
    <div className={cn('flex flex-1 flex-col', embed ? 'mt-5' : 'mt-6')}>
      <LiveStatus status={roomStatus} idle={idle} />

      {existing ? (
        <div className="mt-6 flex flex-1 flex-col">
          <div className="rounded-card border border-violet/50 bg-[linear-gradient(180deg,rgb(124_92_255/0.18),rgb(13_20_51/0.9)_60%)] p-5 text-center shadow-glow-violet">
            <p className="text-sm text-muted">Welcome back</p>
            <p className="mt-1 truncate font-display text-2xl font-bold">{existing.nickname ?? 'Your player'}</p>
            <p className="mt-2 text-sm text-muted">Your score and board are saved on this phone.</p>
          </div>
          <div className="mt-auto flex flex-col gap-3 pt-6">
            <Link
              href={playHref(roomId)}
              className="relative inline-flex min-h-16 w-full items-center justify-center gap-2 rounded-xl bg-[linear-gradient(135deg,var(--color-violet-strong),var(--color-violet-deep))] px-8 font-display text-lg font-bold text-white shadow-[inset_0_1px_0_rgb(255_255_255/0.25),0_8px_28px_-8px_rgb(124_92_255/0.75)] transition-transform motion-safe:active:scale-[0.98]"
            >
              <span className="truncate">{existing.nickname ? `Continue as ${existing.nickname}` : 'Continue playing'}</span>
              <ArrowRightIcon className="size-5 shrink-0" />
            </Link>
            <button
              type="button"
              onClick={startOver}
              className="mx-auto min-h-12 rounded-lg px-4 text-sm font-semibold text-violet-soft hover:text-fg"
            >
              Not you? Start over
            </button>
          </div>
        </div>
      ) : (
        <form onSubmit={onSubmit} noValidate className="mt-6 flex flex-1 flex-col" aria-describedby={errors.form ? `${nickId}-form` : undefined}>
          <div className="flex flex-col gap-2">
            <div className="flex items-baseline justify-between gap-3">
              <label htmlFor={nickId} className="text-base font-semibold">
                Your nickname
              </label>
              <span aria-hidden="true" className={cn('num text-sm', count > MAX_NICKNAME_LENGTH - 3 ? 'text-amber-soft' : 'text-subtle')}>
                {count}/{MAX_NICKNAME_LENGTH}
              </span>
            </div>
            <input
              ref={nickInput}
              id={nickId}
              name="nickname"
              value={nickname}
              onChange={(e) => {
                setNickname(e.target.value);
                if (errors.nickname) setErrors((x) => ({ ...x, nickname: undefined }));
              }}
              // The whole point of the page: the keyboard should be up the moment the QR code opens it.
              // Inside the /demo iframe, autofocus would steal focus and scroll the host page.
              autoFocus={!embed}
              required
              maxLength={MAX_NICKNAME_LENGTH}
              autoComplete="nickname"
              autoCapitalize="words"
              autoCorrect="off"
              spellCheck={false}
              enterKeyHint="go"
              placeholder="e.g. Quizzly Bear"
              aria-invalid={errors.nickname ? true : undefined}
              aria-describedby={`${nickId}-hint${errors.nickname ? ` ${nickId}-error` : ''}`}
              className={cn(
                'block min-h-16 w-full rounded-2xl border-2 bg-night/70 px-5 font-display text-xl font-semibold text-fg placeholder:font-sans placeholder:text-base placeholder:font-normal placeholder:text-subtle',
                'transition-colors focus-visible:border-cyan focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-cyan/60',
                errors.nickname ? 'border-danger' : 'border-line-strong',
              )}
            />
            <p id={`${nickId}-hint`} className="sr-only">
              {`${count} of ${MAX_NICKNAME_LENGTH} characters. Shown on the TV leaderboard.`}
            </p>
            {errors.nickname ? (
              <p id={`${nickId}-error`} role="alert" className="flex items-start gap-1.5 text-sm font-medium text-danger-soft">
                <AlertIcon className="mt-0.5 size-4 shrink-0" />
                {errors.nickname}
              </p>
            ) : (
              <p aria-hidden="true" className="text-sm text-subtle">
                Shown on the TV leaderboard. Keep it friendly.
              </p>
            )}
          </div>

          <div className="mt-5">
            <button
              type="button"
              aria-expanded={teamOpen}
              aria-controls={`${teamId}-panel`}
              onClick={() => {
                setTeamOpen((o) => !o);
                if (!teamOpen) requestAnimationFrame(() => teamInput.current?.focus());
              }}
              className="flex min-h-12 w-full items-center gap-3 rounded-2xl border border-line bg-surface/70 px-4 text-left text-sm font-semibold text-fg hover:border-violet-soft"
            >
              <UsersIcon className="size-5 shrink-0 text-cyan" />
              <span className="flex-1">
                Playing with your table? <span className="text-violet-soft">Add a team</span>
              </span>
              <ChevronDownIcon className={cn('size-5 shrink-0 text-muted transition-transform', teamOpen && 'rotate-180')} />
            </button>
            <div id={`${teamId}-panel`} hidden={!teamOpen} className="mt-3 flex flex-col gap-2">
              <label htmlFor={teamId} className="text-sm font-semibold">
                Team name <span className="font-normal text-subtle">(optional)</span>
              </label>
              <input
                ref={teamInput}
                id={teamId}
                name="team"
                value={teamName}
                onChange={(e) => {
                  setTeamName(e.target.value);
                  if (errors.team) setErrors((x) => ({ ...x, team: undefined }));
                }}
                maxLength={MAX_TEAM_NAME_LENGTH}
                autoComplete="off"
                autoCapitalize="words"
                spellCheck={false}
                enterKeyHint="go"
                placeholder="e.g. The Quizzly Bears"
                aria-invalid={errors.team ? true : undefined}
                aria-describedby={`${teamId}-hint${errors.team ? ` ${teamId}-error` : ''}`}
                className={cn(
                  'block min-h-14 w-full rounded-2xl border-2 bg-night/70 px-4 text-lg text-fg placeholder:text-base placeholder:text-subtle',
                  'focus-visible:border-cyan focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-cyan/60',
                  errors.team ? 'border-danger' : 'border-line-strong',
                )}
              />
              <p id={`${teamId}-hint`} className="text-sm text-subtle">
                Everyone at your table types the same name. Teams score the average of their players.
              </p>
              {errors.team && (
                <p id={`${teamId}-error`} role="alert" className="flex items-start gap-1.5 text-sm font-medium text-danger-soft">
                  <AlertIcon className="mt-0.5 size-4 shrink-0" />
                  {errors.team}
                </p>
              )}
            </div>
          </div>

          <div className="mt-auto flex flex-col gap-3 pt-6">
            {errors.form && (
              <p
                id={`${nickId}-form`}
                role="alert"
                className="flex items-start gap-2 rounded-xl border border-danger/50 bg-danger/10 px-4 py-3 text-sm font-medium text-danger-soft"
              >
                <AlertIcon className="mt-0.5 size-4 shrink-0" />
                {errors.form}
              </p>
            )}
            <Button type="submit" size="xl" fullWidth disabled={busy || idle} className="font-display">
              {busy ? (
                <>
                  <Spinner /> Joining…
                </>
              ) : idle ? (
                'Waiting for the next game…'
              ) : (
                <>
                  Join the game <ArrowRightIcon />
                </>
              )}
            </Button>
            <p className="text-center text-sm text-subtle">
              Nickname only. No account, no app.
              {!embed && (
                <>
                  {' '}
                  <Link href="/legal/privacy" className="font-semibold text-violet-soft underline-offset-4 hover:underline">
                    Privacy
                  </Link>
                </>
              )}
            </p>
          </div>
        </form>
      )}
      <p className="sr-only" aria-live="polite">
        {busy ? `Joining ${venueName}…` : ''}
      </p>
    </div>
  );
}

/** "Live now" / "No game right now" strip above the form. */
function LiveStatus({ status, idle }: { status: RoomStatus | null; idle: boolean }) {
  if (idle) {
    return (
      <div role="status" className="flex items-start gap-3 rounded-2xl border border-amber/45 bg-amber/10 p-4">
        <MoonIcon className="mt-0.5 size-6 shrink-0 text-amber" />
        <div>
          <p className="font-semibold text-amber-soft">No game running right now</p>
          <p className="mt-1 text-sm text-muted">
            Rounds start automatically while the bar is open. Keep this page open — the Join button wakes up as soon as a game starts.
          </p>
        </div>
      </div>
    );
  }
  if (!status?.active) return null;
  return (
    <p
      role="status"
      className="mx-auto inline-flex items-center gap-2 rounded-full border border-lime/40 bg-lime/10 px-4 py-1.5 text-sm font-semibold text-lime-soft"
    >
      <span aria-hidden="true" className="size-2 rounded-full bg-lime motion-safe:animate-glow-pulse" />
      {status.kind === 'CHAMPIONSHIP' ? `Championship live${status.name ? `: ${status.name}` : ''}` : 'Live trivia right now'}
    </p>
  );
}
