<script lang="ts">
  import { onMount } from 'svelte';
  import Modal from '../components/Modal.svelte';
  import { app, handleError, refreshNotifications, showToast } from '../lib/app.svelte';
  import { connection } from '../lib/connection.svelte';
  import { gateway } from '../lib/gateway';
  import { GatewayError } from '../lib/client';
  import { t } from '../lib/i18n/index.svelte';
  import { friendCodeParam, shareLink, vkShareLink } from '../lib/deeplink';
  import { insideTelegram, shareTelegramLink, telegramDialogsAvailable, telegramShowConfirm } from '../lib/telegram';
  import { insideVK, vkCopyText, vkShare } from '../lib/vk';
  import type { AccountRef, FriendCode, RobotBlockEntry } from '../lib/model';

  let friends = $state<AccountRef[]>([]);
  let incoming = $state<AccountRef[]>([]);
  let blocked = $state<AccountRef[]>([]);
  // Per-game disguised-robot blocks, shown as distinct entries alongside blocked humans.
  let robotBlocks = $state<RobotBlockEntry[]>([]);
  let code = $state<FriendCode | null>(null);
  let redeemInput = $state('');
  // The friend row whose kebab actions are slid open, like the lobby list.
  let revealedId = $state<string | null>(null);
  // Pending confirmation targets: the friend account awaiting a block / unfriend confirm.
  let blockTarget = $state<AccountRef | null>(null);
  let unfriendTarget = $state<AccountRef | null>(null);

  async function load() {
    try {
      const [fl, inc, bl] = await Promise.all([
        gateway.friendsList(),
        gateway.friendsIncoming(),
        gateway.blocksList(),
      ]);
      friends = fl;
      incoming = inc;
      blocked = bl.blocked;
      robotBlocks = bl.robots;
    } catch (e) {
      handleError(e);
    }
  }

  onMount(() => {
    if (!app.profile?.isGuest) void load();
  });

  async function act(fn: () => Promise<unknown>) {
    try {
      await fn();
      await load();
      void refreshNotifications();
    } catch (e) {
      handleError(e);
    }
  }

  const respond = (id: string, accept: boolean) => act(() => gateway.friendRespond(id, accept));
  const remove = (id: string) => act(() => gateway.unfriend(id));
  const blockUser = (id: string) => act(() => gateway.block(id));
  const unblock = (id: string) => act(() => gateway.unblock(id));

  // toggleReveal slides one friend row open (closing any other), exposing its
  // block / unfriend icon actions; tapping the same kebab again closes it.
  function toggleReveal(id: string): void {
    revealedId = revealedId === id ? null : id;
  }

  // confirmBlock / confirmUnfriend run the pending action once its modal is
  // accepted, then clear the target and the revealed row.
  function confirmBlock(target = blockTarget): void {
    blockTarget = null;
    revealedId = null;
    if (target) void blockUser(target.accountId);
  }

  function confirmUnfriend(target = unfriendTarget): void {
    unfriendTarget = null;
    revealedId = null;
    if (target) void remove(target.accountId);
  }

  // onBlockClick / onUnfriendClick: inside the Mini App (and online) confirm with Telegram's native
  // dialog and act on accept; otherwise open the in-app confirm modal (the offline / web path).
  async function onBlockClick(f: AccountRef): Promise<void> {
    if (connection.online && insideTelegram() && telegramDialogsAvailable()) {
      if (await telegramShowConfirm(`${t('friends.blockConfirm')}\n${f.displayName}`)) confirmBlock(f);
      return;
    }
    blockTarget = f;
  }

  async function onUnfriendClick(f: AccountRef): Promise<void> {
    if (connection.online && insideTelegram() && telegramDialogsAvailable()) {
      if (await telegramShowConfirm(`${t('friends.unfriendConfirm')}\n${f.displayName}`)) confirmUnfriend(f);
      return;
    }
    unfriendTarget = f;
  }

  // While a friend row is slid open, a tap anywhere outside its action buttons
  // closes it again. Taps on a kebab are skipped so its own toggle stays in charge.
  $effect(() => {
    if (revealedId === null) return;
    function onDown(e: PointerEvent) {
      const el = e.target as Element | null;
      if (el?.closest('.acts') || el?.closest('.kebab')) return;
      revealedId = null;
    }
    window.addEventListener('pointerdown', onDown, true);
    return () => window.removeEventListener('pointerdown', onDown, true);
  });

  async function getCode() {
    try {
      code = await gateway.friendCodeIssue();
    } catch (e) {
      handleError(e);
    }
  }

  async function redeem() {
    const c = redeemInput.trim();
    if (!c) return;
    try {
      const friend = await gateway.friendCodeRedeem(c);
      redeemInput = '';
      showToast(t('friends.added', { name: friend.displayName }));
      await load();
    } catch (e) {
      if (e instanceof GatewayError && e.code === 'self_relation') {
        showToast(t('friends.selfInvite')); // redeeming your own code: a friendly, non-error note
      } else {
        handleError(e);
      }
    }
  }

  function codeTime(unix: number): string {
    return new Date(unix * 1000).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
  }

  async function copyCode() {
    if (!code) return;
    // Inside the VK iframe navigator.clipboard is blocked, so copy via VK Bridge there.
    if (insideVK()) {
      if (await vkCopyText(code.code)) showToast(t('friends.codeCopied'));
      return;
    }
    try {
      await navigator.clipboard.writeText(code.code);
      showToast(t('friends.codeCopied'));
    } catch {
      // Clipboard may be unavailable (insecure context); leave the code on screen.
    }
  }

  // shareInvite shares the friend-code deep link: inside Telegram via the native
  // "share to chat" picker; on the web via the system share sheet; failing both, it
  // copies the link to the clipboard.
  async function shareInvite(url: string) {
    const text = t('friends.inviteText');
    // Inside VK navigator.share is absent (desktop iframe) and the clipboard is blocked: share via VK
    // Bridge, falling back to a VK-Bridge clipboard copy if the share is dismissed or unavailable.
    if (insideVK()) {
      if (await vkShare(url)) return;
      if (await vkCopyText(url)) showToast(t('friends.linkCopied'));
      return;
    }
    if (shareTelegramLink(url, text)) return;
    if (typeof navigator !== 'undefined' && navigator.share) {
      try {
        await navigator.share({ text, url });
        return;
      } catch {
        return; // the user dismissed the share sheet
      }
    }
    try {
      await navigator.clipboard.writeText(url);
      showToast(t('friends.linkCopied'));
    } catch {
      // Clipboard unavailable (insecure context); nothing more to do.
    }
  }
</script>

<div class="page">
  {#if app.profile?.isGuest}
    <p class="muted">{t('profile.guestLocked')}</p>
  {:else}
    <section>
      <h3>{t('friends.add')}</h3>
      <div class="addrow">
        <input
          class="codein"
          bind:value={redeemInput}
          placeholder={t('friends.codePlaceholder')}
          inputmode="numeric"
          maxlength="6"
        />
        <button class="btn" onclick={redeem} disabled={!connection.online}>{t('friends.redeem')}</button>
      </div>
      {#if code}
        {@const shareUrl = insideVK() ? vkShareLink() : shareLink(friendCodeParam(code.code))}
        <div class="code" data-testid="friend-code">
          <div class="coderow">
            <button class="codeval" onclick={copyCode}>{code.code}</button>
            <button class="copy" onclick={copyCode} aria-label={t('friends.copy')}>📋</button>
          </div>
          <span class="codehint">
            {t('friends.codeHint')} · {t('friends.codeExpires', { time: codeTime(code.expiresAtUnix) })}
          </span>
          {#if shareUrl}
            <button type="button" class="link" onclick={() => shareInvite(shareUrl)}>{t('friends.shareTelegram')}</button>
          {/if}
        </div>
      {:else}
        <button class="link" onclick={getCode} disabled={!connection.online}>{t('friends.getCode')}</button>
      {/if}
    </section>

    {#if incoming.length}
      <section>
        <h3>{t('friends.incoming')}</h3>
        <div class="list">
          {#each incoming as r (r.accountId)}
            <div class="rowwrap">
              <div class="row">
                <span class="who">{r.displayName}</span>
                <span class="btns">
                  <button class="btn" onclick={() => respond(r.accountId, true)} disabled={!connection.online}>{t('friends.accept')}</button>
                  <button class="ghost" onclick={() => respond(r.accountId, false)} disabled={!connection.online}>{t('friends.decline')}</button>
                </span>
              </div>
            </div>
          {/each}
        </div>
      </section>
    {/if}

    <section>
      <h3>{t('friends.yours')}</h3>
      {#if friends.length}
        <div class="list">
          {#each friends as f (f.accountId)}
            <div class="rowwrap" class:revealed={revealedId === f.accountId}>
              <div class="acts">
                <button class="iconbtn" onclick={() => onBlockClick(f)} disabled={!connection.online} aria-label={t('friends.block')}>🚫</button>
                <button class="iconbtn" onclick={() => onUnfriendClick(f)} disabled={!connection.online} aria-label={t('friends.unfriend')}>✖️</button>
              </div>
              <div class="row">
                <span class="who">{f.displayName}</span>
                <button class="kebab" onclick={() => toggleReveal(f.accountId)} aria-label={t('friends.actions')}>⋮</button>
              </div>
            </div>
          {/each}
        </div>
      {:else}
        <p class="muted">{t('friends.none')}</p>
      {/if}
    </section>

    {#if blocked.length || robotBlocks.length}
      <section>
        <h3>{t('friends.blockedList')}</h3>
        <div class="list">
          {#each blocked as b (b.accountId)}
            <div class="rowwrap">
              <div class="row">
                <span class="who">{b.displayName}</span>
                <span class="btns">
                  <button class="ghost" onclick={() => unblock(b.accountId)} disabled={!connection.online}>{t('friends.unblock')}</button>
                </span>
              </div>
            </div>
          {/each}
          {#each robotBlocks as r (r.id)}
            <div class="rowwrap">
              <div class="row">
                <span class="who">{r.displayName}</span>
                <span class="btns">
                  <button class="ghost" onclick={() => unblock(r.id)} disabled={!connection.online}>{t('friends.unblock')}</button>
                </span>
              </div>
            </div>
          {/each}
        </div>
      </section>
    {/if}

    {#if blockTarget}
      <Modal title={t('friends.blockConfirm')} onclose={() => (blockTarget = null)}>
        <p class="confirm-name">{blockTarget.displayName}</p>
        <div class="confirm-row">
          <button class="cancel" onclick={() => (blockTarget = null)}>{t('common.cancel')}</button>
          <button class="danger" onclick={() => confirmBlock()} disabled={!connection.online}>{t('friends.block')}</button>
        </div>
      </Modal>
    {/if}
    {#if unfriendTarget}
      <Modal title={t('friends.unfriendConfirm')} onclose={() => (unfriendTarget = null)}>
        <p class="confirm-name">{unfriendTarget.displayName}</p>
        <div class="confirm-row">
          <button class="cancel" onclick={() => (unfriendTarget = null)}>{t('common.cancel')}</button>
          <button class="danger" onclick={() => confirmUnfriend()} disabled={!connection.online}>{t('friends.unfriend')}</button>
        </div>
      </Modal>
    {/if}
  {/if}
</div>

<style>
  .page {
    padding: var(--pad);
    display: flex;
    flex-direction: column;
    gap: 22px;
  }
  h3 {
    margin: 0 0 10px;
    font-size: 0.95rem;
    color: var(--text-muted);
  }
  .muted {
    color: var(--text-muted);
    margin: 0;
  }
  .addrow {
    display: flex;
    gap: 8px;
  }
  .codein {
    flex: 1;
    min-width: 0;
    padding: 10px 12px;
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--text);
    border-radius: var(--radius-sm);
    letter-spacing: 0.3em;
    font-size: 1.1rem;
  }
  .code {
    margin-top: 10px;
    padding: 12px 14px;
    border: 1px dashed var(--border);
    border-radius: var(--radius);
    display: flex;
    flex-direction: column;
    gap: 4px;
  }
  .coderow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
  }
  .codeval {
    font-size: 1.8rem;
    font-weight: 700;
    letter-spacing: 0.3em;
    background: none;
    border: none;
    color: var(--text);
    padding: 0;
    cursor: pointer;
    text-align: left;
    font-family: inherit;
  }
  .copy {
    flex: 0 0 auto;
    background: none;
    border: none;
    font-size: 1.4rem;
    padding: 4px;
    cursor: pointer;
  }
  .codehint {
    font-size: 0.8rem;
    color: var(--text-muted);
  }
  .link {
    margin-top: 10px;
    background: none;
    border: none;
    color: var(--accent);
    padding: 4px 0;
    text-align: left;
  }
  .list {
    display: flex;
    flex-direction: column;
  }
  /* One-line rows split by hairlines, mirroring the lobby list. */
  .rowwrap {
    position: relative;
    overflow: hidden;
  }
  .rowwrap + .rowwrap {
    border-top: 1px solid var(--border);
  }
  /* Block / unfriend icon actions sit behind the friend row, exposed when it slides left. */
  .acts {
    position: absolute;
    inset: 0 0 0 auto;
    display: flex;
    align-items: stretch;
  }
  .iconbtn {
    flex: 0 0 auto;
    width: 48px;
    border: none;
    background: var(--bg-elev);
    color: var(--text);
    font-size: 1.1rem;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .iconbtn + .iconbtn {
    border-left: 1px solid var(--border); /* the vertical divider between 🚫 and ✖️ */
  }
  .row {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 10px 12px;
    background: var(--bg);
    transform: translateX(0);
    transition: transform 0.18s ease;
  }
  .rowwrap.revealed .row {
    transform: translateX(-96px); /* 2 × 48px icon buttons */
  }
  .kebab {
    flex: 0 0 auto;
    width: 30px;
    padding: 6px 0;
    border: none;
    background: none;
    color: var(--text-muted);
    font-size: 1.4rem;
    line-height: 1;
  }
  .btns {
    display: flex;
    gap: 8px;
    flex: 0 0 auto;
  }
  .who {
    flex: 1;
    min-width: 0;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .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);
  }
  .confirm-name {
    margin: 0 0 12px;
    font-weight: 600;
    overflow-wrap: anywhere; /* a long display name wraps instead of stretching the sheet */
  }
  .confirm-row {
    display: flex;
    gap: 8px;
  }
  .confirm-row button {
    flex: 1;
    padding: 11px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--text);
    font-weight: 600;
  }
  .confirm-row .danger {
    background: var(--danger);
    color: #fff;
    border-color: var(--danger);
  }
</style>
