'use client';

/**
 * Live event control for hosts (and platform admins acting for a venue). One Socket.IO connection
 * with a host token; every message carries the complete HostState, so this renders from state and
 * never from message order. Commands are acknowledged by the server; errors are shown verbatim.
 */
import { useCallback, useEffect, useMemo, useState, type ReactNode } from 'react';
import type { HostState, Standing } from '@pintrush/engine';
import { useCountdown, useGameConnection, type GameViewState } from '@/lib/game';
import { Badge } from '@/components/ui/Badge';
import { Button, ButtonLink } from '@/components/ui/Button';
import { PlayIcon, TvIcon } from '@/components/ui/icons';
import { cn } from '@/lib/cn';
import { ConfirmDialog } from '../client';
import { formatClock, formatNumber } from '../format';
import { Notice, Panel, StatCard, StatusBadge } from '../ui';
import { CONTROL_STATUS_COPY, emptyTeamsText, nowPlaying, PHASE_LABELS, tableSummary, type ControlStatus } from './labels';
import { PlayersTable, type ModerateFn } from './PlayersTable';
import { PlayerStandings, TeamStandings } from './Standings';
import { useHostView } from './useHostView';

export interface LiveControlEvent {
  id: string;
  name: string;
  kind: 'ALWAYS_ON' | 'CHAMPIONSHIP';
  /** Stored Event.status at page load (the live session wins once connected). */
  status: 'SCHEDULED' | 'LIVE' | 'PAUSED' | 'COMPLETED' | 'CANCELLED';
  whenLabel: string;
  roomName: string;
  planLength: number | null;
}

type HostCommand = 'event.open' | 'event.start' | 'event.pause' | 'event.resume' | 'event.end' | 'round.advance';

const COMMAND_DONE: Record<HostCommand, string> = {
  'event.open': 'Lobby is open — guests can join now.',
  'event.start': 'The show has started.',
  'event.pause': 'Paused. Timers are frozen on every screen.',
  'event.resume': 'Resumed.',
  'event.end': 'The event has ended.',
  'round.advance': 'Skipped ahead.',
};

const CONNECTION_COPY: Record<GameViewState['status'], { label: string; tone: 'lime' | 'amber' | 'danger' | 'cyan' }> = {
  connecting: { label: 'Connecting', tone: 'cyan' },
  connected: { label: 'Connected', tone: 'lime' },
  reconnecting: { label: 'Reconnecting', tone: 'amber' },
  offline: { label: 'Offline', tone: 'danger' },
  denied: { label: 'Access denied', tone: 'danger' },
};

export function LiveControl({
  event,
  hostToken,
  tvUrl,
  editHref,
  finalStandings,
}: {
  event: LiveControlEvent;
  hostToken: string;
  tvUrl: string;
  editHref?: string;
  finalStandings: Standing[] | null;
}) {
  const { conn, state } = useGameConnection({ auth: () => ({ hostToken, eventId: event.id }) }, event.id);
  const [busy, setBusy] = useState<HostCommand | null>(null);
  const [message, setMessage] = useState<{ tone: 'success' | 'error'; text: string } | null>(null);
  const [confirmEnd, setConfirmEnd] = useState(false);

  // Before this event's session exists the server shows the room's active session to host sockets,
  // so only trust views that belong to THIS event.
  const { host, sync } = useHostView(conn, state, event.id);
  const pub = host?.public ?? (state.public && state.public.eventId === event.id ? state.public : null);
  const tables = useMemo(() => tableSummary(host?.teams ?? []), [host?.teams]);

  const status: ControlStatus = useMemo(() => {
    if (event.status === 'CANCELLED') return 'CANCELLED';
    if (pub) return pub.status;
    if (event.status === 'COMPLETED') return 'COMPLETED';
    if (state.status === 'denied') return 'NOT_OPEN';
    return state.snapshot === null ? 'CONNECTING' : 'NOT_OPEN';
  }, [event.status, pub, state.snapshot, state.status]);

  const connected = state.status === 'connected';
  const can: Record<HostCommand, boolean> = {
    'event.open': connected && status === 'NOT_OPEN',
    'event.start': connected && (status === 'NOT_OPEN' || status === 'WAITING'),
    'event.pause': connected && status === 'RUNNING',
    'event.resume': connected && status === 'PAUSED',
    'round.advance': connected && status === 'RUNNING',
    'event.end': connected && (status === 'WAITING' || status === 'RUNNING' || status === 'PAUSED'),
  };

  const run = useCallback(
    async (command: HostCommand) => {
      if (!conn) return;
      setBusy(command);
      setMessage(null);
      // One key per click, reused by emitWithRetry, so a retried command is applied at most once.
      const res = await conn.emitWithRetry(command, { eventId: event.id, idempotencyKey: crypto.randomUUID() });
      setBusy(null);
      setMessage(res.ok ? { tone: 'success', text: COMMAND_DONE[command] } : { tone: 'error', text: res.error.message });
      if (res.ok && (command === 'event.open' || command === 'event.start')) {
        // The room switches to this session; make sure our view follows even if a broadcast was missed.
        void conn.requestState();
        void sync();
      }
    },
    [conn, event.id, sync],
  );

  const moderate: ModerateFn = useCallback(
    async (player, action, nickname) => {
      if (!conn) return { ok: false, message: 'Not connected to the game server.' };
      const payload = action === 'rename' ? { eventId: event.id, playerSessionId: player.playerId, action, nickname, idempotencyKey: crypto.randomUUID() } : { eventId: event.id, playerSessionId: player.playerId, action, idempotencyKey: crypto.randomUUID() };
      const res = await conn.emitWithRetry<{ nickname: string }>('nickname.moderate', payload);
      if (!res.ok) return { ok: false, message: res.error.message };
      return { ok: true, nickname: res.data?.nickname ?? nickname ?? player.nickname };
    },
    [conn, event.id],
  );

  // Success notes fade after a few seconds so they never describe an old state; errors stay put.
  useEffect(() => {
    if (message?.tone !== 'success') return;
    const t = setTimeout(() => setMessage((m) => (m === message ? null : m)), 6000);
    return () => clearTimeout(t);
  }, [message]);

  const copy = CONTROL_STATUS_COPY[status];
  const connection = CONNECTION_COPY[state.status];
  const standings = pub?.leaderboard ?? (status === 'COMPLETED' ? (finalStandings ?? []) : []);

  return (
    <div className="flex flex-col gap-6">
      <header className="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
        <div className="min-w-0">
          <p className="text-xs font-bold tracking-[0.2em] text-cyan uppercase">
            {event.kind === 'CHAMPIONSHIP' ? 'Championship' : 'Always-on night'} · {event.roomName}
          </p>
          <h1 className="mt-1 font-display text-2xl font-bold tracking-[-0.02em] sm:text-3xl">{event.name}</h1>
          <p className="mt-2 flex flex-wrap items-center gap-2 text-sm text-muted">
            <StatusBadge status={copy.badge} label={copy.title} />
            <span>{event.whenLabel}</span>
            <Badge tone={connection.tone} className="ml-1">
              <span className={cn('size-2 rounded-full bg-current', state.status === 'connected' && 'motion-safe:animate-glow-pulse')} aria-hidden="true" />
              {connection.label}
            </Badge>
          </p>
        </div>
        <div className="flex flex-wrap gap-2">
          {editHref && status === 'NOT_OPEN' && event.status === 'SCHEDULED' && (
            <ButtonLink href={editHref} variant="secondary" size="sm">
              Edit details
            </ButtonLink>
          )}
          <ButtonLink href={tvUrl} target="_blank" rel="noopener noreferrer" prefetch={false} variant="accent" size="sm">
            <TvIcon /> TV screen<span className="sr-only"> (opens in a new tab)</span>
          </ButtonLink>
        </div>
      </header>

      {state.status === 'denied' && (
        <Notice tone="error" title="The game server refused this control session">
          {state.error?.message ?? 'Your session may have expired.'}{' '}
          <button type="button" className="font-semibold text-fg underline underline-offset-4" onClick={() => window.location.reload()}>
            Reload the page
          </button>{' '}
          to sign in again.
        </Notice>
      )}
      {(state.status === 'reconnecting' || state.status === 'offline') && (
        <Notice tone="warning" title="Connection lost — reconnecting…">The game keeps running on the server. Controls come back as soon as the connection does.</Notice>
      )}

      <div className="grid gap-6 xl:grid-cols-[1.25fr_1fr]">
        <StatePanel status={status} copy={copy} host={host} conn={conn} planLength={event.planLength} kind={event.kind} />

        <Panel title="Controls" description={copy.detail}>
          <div className="grid grid-cols-2 gap-3">
            {event.kind === 'CHAMPIONSHIP' && (
              <ControlButton label="Open lobby" busy={busy === 'event.open'} enabled={can['event.open']} onClick={() => void run('event.open')} variant="secondary" />
            )}
            <ControlButton
              label={event.kind === 'CHAMPIONSHIP' ? 'Start show' : 'Start'}
              busy={busy === 'event.start'}
              enabled={can['event.start']}
              onClick={() => void run('event.start')}
              variant="primary"
              icon={<PlayIcon />}
              className={event.kind === 'CHAMPIONSHIP' ? undefined : 'col-span-2'}
            />
            {status === 'PAUSED' ? (
              <ControlButton label="Resume" busy={busy === 'event.resume'} enabled={can['event.resume']} onClick={() => void run('event.resume')} variant="accent" />
            ) : (
              <ControlButton label="Pause" busy={busy === 'event.pause'} enabled={can['event.pause']} onClick={() => void run('event.pause')} variant="secondary" />
            )}
            <ControlButton
              label="Skip ahead"
              hint="Ends the current phase or break now"
              busy={busy === 'round.advance'}
              enabled={can['round.advance']}
              onClick={() => void run('round.advance')}
              variant="secondary"
            />
            <ControlButton
              label="End event"
              busy={busy === 'event.end'}
              enabled={can['event.end']}
              onClick={() => setConfirmEnd(true)}
              variant="danger"
              className="col-span-2"
            />
          </div>
          <div className="mt-4" aria-live="polite" role="status">
            {message && <Notice tone={message.tone}>{message.text}</Notice>}
          </div>
        </Panel>
      </div>

      <dl className="grid grid-cols-2 gap-3 md:grid-cols-4">
        <StatCard label="Players" value={formatNumber(pub?.playerCount ?? 0)} tone="violet" />
        <StatCard
          label="Tables"
          value={formatNumber(tables.tables)}
          hint={tables.waiting.length > 0 ? `+${tables.waiting.length} waiting for tablemates` : 'teams of 2 or more'}
          tone="cyan"
        />
        <StatCard
          label="Rounds completed"
          value={host ? (host.planLength ? `${host.roundsCompleted} / ${host.planLength}` : formatNumber(host.roundsCompleted)) : '0'}
          tone="lime"
        />
        <StatCard
          label="Answered"
          value={pub?.round && (pub.round.phase === 'OPEN' || pub.round.phase === 'LOCKED') ? `${pub.answeredCount} / ${pub.participantCount}` : '—'}
          hint="this question"
          tone="amber"
        />
      </dl>

      <div className="grid gap-6 lg:grid-cols-2">
        <Panel title={status === 'COMPLETED' ? 'Final standings' : 'Leaderboard'} description={status === 'COMPLETED' ? undefined : 'Top 10, live.'}>
          <PlayerStandings standings={standings} emptyText={status === 'COMPLETED' ? 'No scores were recorded.' : 'Scores appear after the first answers are revealed.'} />
        </Panel>
        <Panel title="Teams" description={pub?.soloLeaderboard ? 'Teams of two or more; solo players rank on their own board.' : 'Team score = average points of members who answered.'}>
          <div className="flex flex-col gap-5">
            <TeamStandings teams={pub?.teamLeaderboard ?? []} emptyText={emptyTeamsText(tables.waiting)} />
            {pub?.soloLeaderboard && pub.soloLeaderboard.length > 0 && (
              <div>
                <h3 className="mb-2 text-sm font-semibold text-muted">Solo board</h3>
                <TeamStandings teams={pub.soloLeaderboard} emptyText="" />
              </div>
            )}
          </div>
        </Panel>
      </div>

      <Panel title="Players" description="Rename offensive nicknames or remove disruptive players. Changes show on every screen immediately.">
        <PlayersTable players={host?.players ?? []} onModerate={moderate} disabled={!connected || status === 'COMPLETED'} />
      </Panel>

      <ConfirmDialog
        open={confirmEnd}
        onClose={() => setConfirmEnd(false)}
        title={`End “${event.name}” now?`}
        description={
          event.kind === 'CHAMPIONSHIP'
            ? 'The current round stops, the standings are final, and the TV shows the results. This can’t be undone.'
            : 'Tonight’s always-on game stops for the rest of the night and the nightly standings are final. This can’t be undone.'
        }
        confirmLabel="End event"
        busy={busy === 'event.end'}
        onConfirm={async () => {
          await run('event.end');
          setConfirmEnd(false);
        }}
      />
    </div>
  );
}

function ControlButton({
  label,
  hint,
  busy,
  enabled,
  onClick,
  variant,
  icon,
  className,
}: {
  label: string;
  hint?: string;
  busy: boolean;
  enabled: boolean;
  onClick: () => void;
  variant: 'primary' | 'secondary' | 'accent' | 'danger';
  icon?: ReactNode;
  className?: string;
}) {
  return (
    <Button size="lg" variant={variant} disabled={!enabled || busy} aria-busy={busy || undefined} onClick={onClick} className={cn('flex-col gap-0 whitespace-normal', className)}>
      <span className="inline-flex items-center gap-2">
        {icon}
        {busy ? 'Working…' : label}
      </span>
      {hint && <span className="text-xs font-normal opacity-80">{hint}</span>}
    </Button>
  );
}

function StatePanel({
  status,
  copy,
  host,
  conn,
  planLength,
  kind,
}: {
  status: ControlStatus;
  copy: { title: string; detail: string };
  host: HostState | null;
  conn: ReturnType<typeof useGameConnection>['conn'];
  planLength: number | null;
  kind: LiveControlEvent['kind'];
}) {
  const pub = host?.public ?? null;
  const round = pub?.round ?? null;
  const question = pub?.question ?? null;
  const endsAt = round ? round.phaseEndsAt : (pub?.nextRoundAt ?? null);
  const remaining = useCountdown(conn, endsAt, 250);
  // While paused, timers are frozen: show the time that was left when the host paused.
  const frozen = status === 'PAUSED' && pub?.pausedAt && endsAt ? Math.max(0, endsAt - pub.pausedAt) : null;
  const shownMs = frozen ?? remaining;
  const playing = pub ? nowPlaying(pub) : null;
  const total = host?.planLength ?? planLength;
  // Between rounds the plan points at the next round; once the show is over there is no current round.
  const roundNumber = round ? round.index + 1 : pub?.plan && status !== 'COMPLETED' ? Math.min(pub.plan.index + 1, pub.plan.total) : null;

  return (
    <section
      aria-labelledby="state-title"
      className="relative overflow-hidden rounded-card border border-violet/50 bg-[linear-gradient(160deg,rgb(124_92_255/0.18),rgb(13_20_51/0.95)_45%)] p-5 shadow-glow-violet sm:p-6"
    >
      <h2 id="state-title" className="sr-only">
        Game state
      </h2>
      <div className="flex flex-wrap items-start justify-between gap-4">
        <div className="min-w-0">
          <p className="text-xs font-bold tracking-[0.2em] text-violet-soft uppercase">{copy.title}</p>
          <p className="mt-2 font-display text-2xl font-bold sm:text-3xl">{playing?.headline ?? (status === 'COMPLETED' ? 'Show over' : copy.title)}</p>
          {playing?.sub && <p className="mt-1 text-muted">{playing.sub}</p>}
        </div>
        {endsAt && status !== 'COMPLETED' && (status === 'RUNNING' || status === 'PAUSED') && (
          <div className="text-right">
            <p className="text-xs font-semibold tracking-[0.14em] text-subtle uppercase">{status === 'PAUSED' ? 'Frozen at' : round ? 'Phase ends in' : 'Next round in'}</p>
            <p className={cn('num font-display text-4xl font-bold sm:text-5xl', status === 'PAUSED' ? 'text-amber' : shownMs < 5000 ? 'text-cyan' : 'text-fg')} aria-live="off">
              {formatClock(shownMs)}
            </p>
          </div>
        )}
      </div>

      <dl className="mt-6 grid grid-cols-2 gap-x-6 gap-y-4 sm:grid-cols-4">
        <div>
          <dt className="text-xs text-subtle">Round</dt>
          <dd className="num mt-0.5 font-semibold">
            {roundNumber ? `${roundNumber}${total ? ` of ${total}` : ''}` : '—'}
            {round && <span className="block text-sm font-normal text-muted">{round.label}</span>}
          </dd>
        </div>
        <div>
          <dt className="text-xs text-subtle">Question</dt>
          <dd className="num mt-0.5 font-semibold">{question && round ? `${question.index + 1} / ${round.questionCount}` : '—'}</dd>
        </div>
        <div>
          <dt className="text-xs text-subtle">Phase</dt>
          <dd className="mt-0.5 font-semibold">{round ? PHASE_LABELS[round.phase] : pub?.status === 'RUNNING' ? 'Break' : '—'}</dd>
        </div>
        <div>
          <dt className="text-xs text-subtle">Answered</dt>
          <dd className="num mt-0.5 font-semibold">{round && (round.phase === 'OPEN' || round.phase === 'LOCKED' || round.phase === 'REVEALED') ? `${pub!.answeredCount} / ${pub!.participantCount}` : '—'}</dd>
        </div>
      </dl>

      {question?.prompt && (
        <div className="mt-6 rounded-2xl border border-line bg-night/50 p-4">
          <p className="text-xs font-semibold tracking-[0.14em] text-subtle uppercase">On screen now</p>
          <p className="mt-1 text-lg font-semibold">{question.prompt}</p>
          {question.options && (
            <ul className="mt-3 grid gap-2 sm:grid-cols-2">
              {question.options.map((o) => {
                const correct = pub?.reveal?.correctOptionId === o.id;
                const picks = pub?.reveal?.distribution[o.id];
                return (
                  <li
                    key={o.id}
                    className={cn('flex items-center justify-between gap-2 rounded-xl border px-3 py-2 text-sm', correct ? 'border-lime/60 bg-lime/10 text-lime-soft' : 'border-line/70 text-muted')}
                  >
                    <span>
                      {o.text}
                      {correct && <span className="sr-only"> (correct answer)</span>}
                    </span>
                    {picks !== undefined && <span className="num text-xs">{picks}</span>}
                  </li>
                );
              })}
            </ul>
          )}
          {pub?.reveal && (
            <p className="mt-3 text-sm text-muted">
              {pub.reveal.correctCount} of {pub.reveal.answeredCount} got it right
              {pub.reveal.fastest[0] && ` · fastest: ${pub.reveal.fastest[0].nickname} (${(pub.reveal.fastest[0].elapsedMs / 1000).toFixed(1)} s)`}
            </p>
          )}
        </div>
      )}

      {pub?.status === 'RUNNING' && !round && pub.lastRound && (
        <p className="mt-6 text-sm text-muted">
          Last round: <span className="font-semibold text-fg">{pub.lastRound.label}</span>
          {pub.lastRound.podium[0] && ` — won by ${pub.lastRound.podium[0].nickname} with ${formatNumber(pub.lastRound.podium[0].points)} points`}
        </p>
      )}
      {status === 'WAITING' && pub && (
        <p className="mt-6 text-sm text-muted">
          {pub.playerCount} {pub.playerCount === 1 ? 'player has' : 'players have'} joined.
          {pub.recentJoins.length > 0 && ` Latest: ${pub.recentJoins.slice(-4).join(', ')}.`}
        </p>
      )}
      {status === 'NOT_OPEN' && (
        <p className="mt-6 text-sm text-muted">
          {kind === 'CHAMPIONSHIP'
            ? 'Guests in the room keep playing tonight’s always-on game until you open the lobby.'
            : 'Tonight’s always-on game isn’t running. It starts by itself during opening hours, or press Start.'}
        </p>
      )}
    </section>
  );
}
