<script lang="ts">
  import { onMount } from 'svelte';
  import Screen from '../components/Screen.svelte';
  import Menu from '../components/Menu.svelte';
  import TabBar from '../components/TabBar.svelte';
  import { app, handleError, showToast } from '../lib/app.svelte';
  import { connection } from '../lib/connection.svelte';
  import { gateway } from '../lib/gateway';
  import { navigate } from '../lib/router.svelte';
  import { t, type MessageKey } from '../lib/i18n/index.svelte';
  import { resultBadge } from '../lib/result';
  import { getLobby, setLobby } from '../lib/lobbycache';
  import { groupGames } from '../lib/lobbysort';
  import type { AccountRef, GameView, Invitation } from '../lib/model';

  let games = $state<GameView[]>([]);
  let invitations = $state<Invitation[]>([]);
  let incoming = $state<AccountRef[]>([]);

  const guest = $derived(app.profile?.isGuest ?? true);

  async function load() {
    try {
      games = (await gateway.gamesList()).games;
      if (!guest) {
        [invitations, incoming] = await Promise.all([gateway.invitationsList(), gateway.friendsIncoming()]);
        app.notifications = invitations.length + incoming.length;
      }
      setLobby({ games, invitations, incoming });
    } catch (e) {
      handleError(e);
    }
  }

  onMount(() => {
    // Render instantly from the cached lists (so the screen does not "draw in" during
    // the back-slide), then refresh in the background.
    const cached = getLobby();
    if (cached) {
      games = cached.games;
      invitations = cached.invitations;
      incoming = cached.incoming;
    }
    void load();
  });
  $effect(() => {
    if (app.lastEvent) void load();
  });

  const myId = $derived(app.session?.userId ?? '');
  const groups = $derived(groupGames(games, myId));

  function opponents(g: GameView): string {
    return g.seats
      .filter((s) => s.accountId !== myId)
      .map((s) => s.displayName)
      .join(', ');
  }
  function scoreline(g: GameView): string {
    const me = g.seats.find((s) => s.accountId === myId);
    const opp = g.seats.filter((s) => s.accountId !== myId).map((s) => s.score);
    return `${me?.score ?? 0} : ${opp.join(', ')}`;
  }

  // Hiding a finished game (Stage 17). The delete action sits behind each finished row and is
  // revealed by swiping the row left (touch) or tapping its kebab (any pointer); the action is
  // per-account and irreversible. Only one row is revealed at a time.
  let revealedId = $state<string | null>(null);
  let drag: { id: string; x0: number; y0: number } | null = null;
  // A horizontal swipe must not also count as a tap that opens the game; armed on swipe,
  // consumed by the next tap, and reset on the next pointerdown so a later tap is never eaten.
  let swiped = false;

  function onRowDown(e: PointerEvent, id: string): void {
    swiped = false;
    if (e.pointerType === 'mouse') return; // desktop reveals via the kebab, not a swipe
    drag = { id, x0: e.clientX, y0: e.clientY };
  }
  function onRowUp(e: PointerEvent, finished: boolean): void {
    if (!drag) return;
    const dx = e.clientX - drag.x0;
    const dy = e.clientY - drag.y0;
    // Only a finished row reveals on a horizontal swipe; that swipe then suppresses the tap so it
    // does not also open the game. Active rows ignore swipes and stay plain tap-to-open.
    if (finished && Math.abs(dx) > 40 && Math.abs(dx) > Math.abs(dy) * 1.4) {
      swiped = true;
      revealedId = dx < 0 ? drag.id : null;
    }
    drag = null;
  }
  function openGame(g: GameView): void {
    if (swiped) {
      swiped = false;
      return;
    }
    if (revealedId === g.id) {
      revealedId = null;
      return;
    }
    navigate(`/game/${g.id}`);
  }
  function toggleReveal(id: string): void {
    revealedId = revealedId === id ? null : id;
  }
  async function hide(id: string): Promise<void> {
    revealedId = null;
    const prev = games;
    games = games.filter((g) => g.id !== id); // optimistic; the backend already filters it out
    setLobby({ games, invitations, incoming });
    try {
      await gateway.hideGame(id);
    } catch (e) {
      games = prev;
      setLobby({ games, invitations, incoming });
      handleError(e);
    }
  }

  const menuItems = $derived([
    ...(guest ? [] : [{ label: t('lobby.friends'), onclick: () => navigate('/friends'), badge: incoming.length }]),
    { label: t('lobby.profile'), onclick: () => navigate('/profile') },
    { label: t('lobby.settings'), onclick: () => navigate('/settings') },
    { label: t('lobby.about'), onclick: () => navigate('/about') },
  ]);

  async function acceptInvite(inv: Invitation) {
    try {
      const r = await gateway.invitationAccept(inv.id);
      if (r.gameId) navigate(`/game/${r.gameId}`);
      else await load();
    } catch (e) {
      handleError(e);
    }
  }
  const declineInvite = (inv: Invitation) => act(() => gateway.invitationDecline(inv.id));
  const cancelInvite = (inv: Invitation) => act(() => gateway.invitationCancel(inv.id));
  async function act(fn: () => Promise<unknown>) {
    try {
      await fn();
      await load();
    } catch (e) {
      handleError(e);
    }
  }

  const variantKey: Record<string, MessageKey> = {
    english: 'new.english',
    russian_scrabble: 'new.russian',
    erudit: 'new.erudit',
  };
</script>

<Screen title={app.profile?.displayName ?? t('app.title')}>
  {#snippet menu()}
    <Menu items={menuItems} badge={app.notifications} />
  {/snippet}

  <div class="lobby">
    {#if invitations.length}
      <section>
        <h2>{t('lobby.invitations')}</h2>
        {#each invitations as inv (inv.id)}
          <div class="invite">
            <span class="emoji">💌</span>
            <span class="info">
              {#if inv.inviter.accountId === myId}
                <span class="who">{t('invitations.with', { names: inv.invitees.map((i) => i.displayName).join(', ') })}</span>
                <span class="sub">{t('invitations.waiting')}</span>
              {:else}
                <span class="who">{t('invitations.from', { name: inv.inviter.displayName })}</span>
                <span class="sub">{t(variantKey[inv.variant] ?? 'new.english')}</span>
              {/if}
            </span>
            <span class="acts">
              {#if inv.inviter.accountId === myId}
                <button class="ghost" onclick={() => cancelInvite(inv)}>{t('invitations.cancel')}</button>
              {:else}
                <button class="btn" onclick={() => acceptInvite(inv)}>{t('invitations.accept')}</button>
                <button class="ghost" onclick={() => declineInvite(inv)}>{t('invitations.decline')}</button>
              {/if}
            </span>
          </div>
        {/each}
      </section>
    {/if}

    {#each [{ h: 'lobby.yourTurn', list: groups.yourTurn, finished: false }, { h: 'lobby.theirTurn', list: groups.theirTurn, finished: false }, { h: 'lobby.finishedGames', list: groups.finished, finished: true }] as group (group.h)}
      {#if group.list.length}
        <section>
          <h2>{t(group.h as 'lobby.yourTurn')}</h2>
          <div class="list">
            {#each group.list as g (g.id)}
              <div class="rowwrap" class:revealed={group.finished && revealedId === g.id}>
                {#if group.finished}
                  <button class="del" onclick={() => hide(g.id)} disabled={!connection.online} aria-label={t('lobby.hideGame')}>❌</button>
                {/if}
                <div class="row">
                  <button
                    class="open"
                    onpointerdown={(e) => onRowDown(e, g.id)}
                    onpointerup={(e) => onRowUp(e, group.finished)}
                    onclick={() => openGame(g)}
                  >
                    <span class="info">
                      <span class="who">{opponents(g) || '—'}</span>
                      <span class="sub">{scoreline(g)}</span>
                    </span>
                    <span class="emoji">{resultBadge(g, myId).emoji}</span>
                  </button>
                  {#if group.finished}
                    <button class="kebab" onclick={() => toggleReveal(g.id)} aria-label={t('lobby.hideGame')}>⋮</button>
                  {:else}
                    <!-- A visual duplicate of the row's tap target: opens the game on tap, but kept out
                         of the tab order / a11y tree since the .open button already does the same. -->
                    <button class="chev" onclick={() => openGame(g)} tabindex={-1} aria-hidden="true">›</button>
                  {/if}
                </div>
              </div>
            {/each}
          </div>
        </section>
      {/if}
    {/each}

    {#if !games.length && !invitations.length}
      <p class="empty">{t('lobby.noActive')}</p>
    {/if}
  </div>

  {#snippet tabbar()}
    <TabBar>
      <button class="tab" onclick={() => navigate('/new')}>
        <span class="sq">🎲</span><span class="lbl">{t('lobby.new')}</span>
      </button>
      <button class="tab" onclick={() => navigate('/stats')}>
        <span class="sq">📊</span><span class="lbl">{t('lobby.stats')}</span>
      </button>
      <button class="tab" onclick={() => showToast(t('lobby.soon'))}>
        <span class="sq">🏆</span><span class="lbl">{t('lobby.tournaments')}</span>
      </button>
    </TabBar>
  {/snippet}
</Screen>

<style>
  .lobby {
    padding: var(--pad);
    display: flex;
    flex-direction: column;
    gap: 18px;
  }
  h2 {
    font-size: 0.9rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
    margin: 0 0 8px;
  }
  .empty {
    color: var(--text-muted);
    font-size: 0.9rem;
    margin: 0;
  }
  .invite {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    width: 100%;
    text-align: left;
    padding: 12px 14px;
    margin-bottom: 8px;
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--text);
    border-radius: var(--radius);
    user-select: none;
  }
  /* Game rows are a compact, flat list: no per-card frame, a hairline divider between
     consecutive rows (Stage 17). */
  .list {
    display: flex;
    flex-direction: column;
  }
  /* Each finished row can slide left to reveal a delete action sitting behind it; the row's
     own opaque background hides that action until revealed (Stage 17). */
  .rowwrap {
    position: relative;
    overflow: hidden;
  }
  .rowwrap + .rowwrap {
    border-top: 1px solid var(--border);
  }
  .del {
    position: absolute;
    inset: 0 0 0 auto;
    width: 64px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: var(--danger);
    color: #fff;
    font-size: 1.1rem;
  }
  .row {
    position: relative;
    display: flex;
    align-items: center;
    gap: 2px;
    background: var(--bg);
    transform: translateX(0);
    transition: transform 0.18s ease;
  }
  .rowwrap.revealed .row {
    transform: translateX(-64px);
  }
  .open {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    min-width: 0;
    text-align: left;
    padding: 10px 6px;
    border: none;
    background: none;
    color: var(--text);
    user-select: none;
    touch-action: pan-y; /* keep vertical list scroll; we only read horizontal swipes */
  }
  .open:active {
    background: var(--surface-2);
  }
  .kebab {
    flex: 0 0 auto;
    width: 30px;
    padding: 10px 0;
    border: none;
    background: none;
    color: var(--text-muted);
    font-size: 1.4rem;
    line-height: 1;
  }
  .chev {
    flex: 0 0 auto;
    width: 30px;
    padding: 10px 0;
    border: none;
    background: none;
    text-align: center;
    color: var(--text-muted);
    font-size: 1.4rem;
    line-height: 1;
  }
  .info {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
  }
  .who {
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .sub {
    font-size: 0.85rem;
    color: var(--text-muted);
  }
  .emoji {
    font-size: 1.8rem;
    line-height: 1;
    flex: 0 0 auto;
  }
  .acts {
    display: flex;
    gap: 8px;
    flex: 0 0 auto;
  }
  .btn {
    padding: 8px 12px;
    border: 1px solid var(--accent);
    background: var(--accent);
    color: var(--accent-text);
    border-radius: var(--radius-sm);
  }
  .ghost {
    padding: 8px 12px;
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--text);
    border-radius: var(--radius-sm);
  }
</style>
