<script lang="ts">
  import { onMount } from 'svelte';
  import Modal from '../components/Modal.svelte';
  import { app, handleError, showToast } from '../lib/app.svelte';
  import { gateway } from '../lib/gateway';
  import { normalizeSubtype } from '../lib/platform';
  import { t, i18n, type MessageKey } from '../lib/i18n/index.svelte';
  import { executionContext, formatAmount, needsWebSpendWarning, type SpendContext } from '../lib/wallet';
  import { isGooglePlayBuild } from '../lib/distribution';
  import { onExternalLinkClick, openExternalUrl } from '../lib/links';
  import { vkPlatform, vkShowOrderBox } from '../lib/vk';
  import { telegramOpenInvoice } from '../lib/telegram';
  import type { Wallet, Catalog, CatalogProduct } from '../lib/model';

  // The Wallet section: the context-visible chip balances, the active benefits, and the storefront.
  // Values are bought with chips (they work once the player has any); chip packs are bought with
  // money (the purchase itself arrives with payment intake — here they are shown priced only). On
  // the Google Play build the money purchases are hidden behind a RuStore stub (store policy), while
  // spending already-earned chips still works.

  let wallet = $state<Wallet | null>(null);
  let catalog = $state<Catalog | null>(null);
  let loading = $state(true);
  // The product whose purchase is in flight, or null. It both guards against a concurrent purchase
  // (busy) and scopes the pressed/dimmed visual to the tapped button — not every buy button.
  let busyId = $state<string | null>(null);
  const busy = $derived(busyId !== null);
  // The value awaiting a web-spend warning confirmation (a spend that would draw vk/tg chips).
  let warnProduct = $state<CatalogProduct | null>(null);

  const context: SpendContext = executionContext();
  const gpBuild = isGooglePlayBuild();
  // Money purchases are not permitted inside the VK iOS app (Apple ToS); the pack CTA is shown
  // muted and taps explain why, rather than hiding the storefront. VK's device family is not on the
  // client platformSubtype (server-derived) — read it from the signed vk_platform launch param.
  const purchaseBlocked = context === 'vk' && normalizeSubtype(vkPlatform()) === 'ios';
  // The "other version" link points at this deployment's own site root (the landing lists every
  // platform), so it follows prod vs the contour without a hardcoded host.
  const siteRoot = typeof location !== 'undefined' ? `${location.origin}/` : '/';

  const values = $derived(catalog?.products.filter((p) => p.kind === 'value') ?? []);
  const packs = $derived(catalog?.products.filter((p) => p.kind === 'pack') ?? []);

  async function load() {
    try {
      const [w, c] = await Promise.all([gateway.wallet(), gateway.catalog()]);
      wallet = w;
      catalog = c;
    } catch (e) {
      handleError(e);
    } finally {
      loading = false;
    }
  }
  onMount(() => {
    void load();
    // Fallback to the payment push: re-fetch when the tab regains focus, i.e. the user returned
    // from the provider's payment window.
    const onVisible = () => {
      if (!document.hidden) void load();
    };
    document.addEventListener('visibilitychange', onVisible);
    return () => document.removeEventListener('visibilitychange', onVisible);
  });

  // Main path: a payment-intake push bumps app.walletRefresh; re-fetch in place when it changes
  // (a credit landed while the screen is open).
  let seenRefresh = app.walletRefresh;
  $effect(() => {
    if (app.walletRefresh !== seenRefresh) {
      seenRefresh = app.walletRefresh;
      void load();
    }
  });

  function sourceLabel(source: string): string {
    return t(`wallet.source.${source}` as MessageKey);
  }

  function currencyLabel(currency: string): string {
    return t(`wallet.cur.${currency}` as MessageKey);
  }

  function adsLine(): string {
    if (!wallet) return '';
    if (wallet.adsForever) return t('wallet.noAdsForever');
    if (wallet.adsPaidUntilMs > Date.now()) {
      const date = new Date(wallet.adsPaidUntilMs).toLocaleDateString(i18n.locale === 'ru' ? 'ru-RU' : 'en-US');
      return t('wallet.noAdsUntil', { date });
    }
    return t('wallet.adsOn');
  }

  function onBuy(p: CatalogProduct) {
    if (needsWebSpendWarning(context, wallet?.segments ?? [], p.chips)) {
      warnProduct = p;
      return;
    }
    void doBuy(p);
  }

  async function doBuy(p: CatalogProduct) {
    warnProduct = null;
    if (busy) return;
    busyId = p.productId;
    try {
      wallet = await gateway.walletBuy(p.productId);
    } catch (e) {
      handleError(e);
    } finally {
      busyId = null;
    }
  }

  // onOrder funds a chip pack with money: it opens a pending order and sends the player to the
  // provider's hosted-payment page. The chips are credited later, by the verified server callback;
  // paying accepts the public offer (linked below the packs).
  async function onOrder(p: CatalogProduct) {
    if (busy) return;
    busyId = p.productId;
    try {
      const order = await gateway.walletOrder(p.productId);
      if (context === 'vk') {
        // VK settles the payment in-app via the bridge; the chips arrive on the server callback.
        await vkShowOrderBox(order.orderId);
      } else if (context === 'telegram') {
        // Telegram Stars: the bot minted an invoice link (returned as redirectUrl); open it in-app.
        // The chips are credited server-side by the forwarded payment and arrive on the live push;
        // refresh on 'paid' as a fallback. Outside a Stars-capable client, fall back to the link.
        const opened = telegramOpenInvoice(order.redirectUrl, (status) => {
          if (status === 'paid') void load();
        });
        if (!opened) openExternalUrl(order.redirectUrl);
      } else {
        openExternalUrl(order.redirectUrl);
      }
    } catch (e) {
      handleError(e);
    } finally {
      busyId = null;
    }
  }
</script>

<div class="wallet" data-testid="wallet">
  <section>
    <h3>{t('wallet.balance')}</h3>
    {#if wallet && wallet.segments.length > 0}
      {#each wallet.segments as s (s.source)}
        <div class="row">
          <span class="name">{sourceLabel(s.source)}</span>
          <span class="value"
            >🪙 {s.chips}{#if !s.spendable}&nbsp;<span class="muted">({t('wallet.viewOnly')})</span>{/if}</span
          >
        </div>
      {/each}
    {:else if !loading}
      <p class="empty">{t('wallet.noChips')}</p>
    {/if}
  </section>

  <section>
    <h3>{t('wallet.benefits')}</h3>
    <div class="row"><span class="name">{adsLine()}</span></div>
    <div class="row"><span class="name">{t('wallet.hints', { n: wallet?.hints ?? 0 })}</span></div>
  </section>

  <section>
    <h3>{t('wallet.store')}</h3>
    {#if purchaseBlocked}
      <!-- prettier-ignore -->
      <p class="ios-note" data-testid="ios-note">{t('wallet.iosBlockedPre')}<a href={siteRoot} target="_blank" rel="noopener noreferrer" onclick={onExternalLinkClick}>{t('wallet.iosBlockedLink')}</a>{t('wallet.iosBlockedPost')}</p>
    {/if}
    {#each values as p (p.productId)}
      <div class="row product" data-testid="product" data-kind="value" data-pid={p.productId}>
        <span class="name">{p.title}</span>
        <span class="price">🪙 {p.chips}</span>
        <button class="buy" class:working={busyId === p.productId} data-testid="buy" disabled={busy} onclick={() => onBuy(p)}
          >{t('wallet.buy')}</button
        >
      </div>
    {/each}

    {#if gpBuild}
      <p class="stub" data-testid="gp-stub">{t('wallet.gpStub')}</p>
    {:else}
      {#each packs as p (p.productId)}
        <div class="row product" data-testid="product" data-kind="pack" data-pid={p.productId}>
          <span class="name">{p.title}</span>
          <span class="price">{formatAmount(p.moneyAmount, p.moneyCurrency)}&nbsp;{currencyLabel(p.moneyCurrency)}</span>
          <button
            class="buy"
            class:blocked={purchaseBlocked}
            class:working={busyId === p.productId}
            data-testid="buy-pack"
            disabled={busy}
            onclick={() => {
              if (purchaseBlocked) showToast(t('wallet.platformNoBuy'));
              else void onOrder(p);
            }}>{t('wallet.buy')}</button
          >
        </div>
      {/each}
      {#if packs.length > 0}
        <p class="offer" data-testid="offer">
          <a href="/offer/" target="_blank" rel="noopener noreferrer">{t('wallet.offer')}</a>
        </p>
      {/if}
    {/if}

    {#if !loading && values.length === 0 && (gpBuild || packs.length === 0)}
      <p class="empty">{t('wallet.empty')}</p>
    {/if}
  </section>
</div>

{#if warnProduct}
  <Modal title={t('wallet.warnTitle')} onclose={() => (warnProduct = null)}>
    <p class="warn-body" data-testid="warn">{t('wallet.warnBody')}</p>
    <div class="warn-actions">
      <button class="cancel" onclick={() => (warnProduct = null)}>{t('wallet.warnCancel')}</button>
      <button class="buy" data-testid="warn-confirm" onclick={() => warnProduct && doBuy(warnProduct)}
        >{t('wallet.warnConfirm')}</button
      >
    </div>
  </Modal>
{/if}

<style>
  .wallet {
    display: flex;
    flex-direction: column;
    gap: 18px;
    padding: var(--pad);
  }
  section {
    display: flex;
    flex-direction: column;
    gap: 8px;
  }
  h3 {
    margin: 0;
    font-size: 0.95rem;
    color: var(--text-muted);
  }
  .row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
  }
  .name {
    flex: 1 1 auto;
    min-width: 0;
  }
  .value,
  .price {
    flex: 0 0 auto;
    font-weight: 600;
    white-space: nowrap;
  }
  .muted {
    color: var(--text-muted);
    font-weight: 400;
  }
  .buy {
    flex: 0 0 auto;
    padding: 8px 14px;
    border: 1px solid var(--accent);
    background: var(--accent);
    color: var(--accent-text);
    border-radius: var(--radius-sm);
    font-weight: 600;
    cursor: pointer;
  }
  .buy:disabled {
    cursor: default;
  }
  /* The dimmed look is scoped: the tapped button while its purchase is in flight (working), or a
     blocked pack on VK iOS — never every buy button at once when one purchase is busy. */
  .buy.blocked,
  .buy.working {
    opacity: 0.5;
  }
  .ios-note {
    margin: 0 0 4px;
    padding: 8px 12px;
    color: var(--text-muted);
    font-size: 0.85rem;
    border: 1px dashed var(--border);
    border-radius: var(--radius-sm);
  }
  .ios-note a {
    color: var(--accent);
  }
  .empty,
  .stub {
    margin: 0;
    padding: 10px 12px;
    color: var(--text-muted);
  }
  .stub {
    border: 1px dashed var(--border);
    border-radius: var(--radius-sm);
  }
  .offer {
    margin: 2px 0 0;
    text-align: center;
    font-size: 0.85rem;
  }
  .offer a {
    color: var(--text-muted);
  }
  .warn-body {
    margin: 0 0 14px;
  }
  .warn-actions {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
  }
  .cancel {
    padding: 8px 14px;
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--text);
    border-radius: var(--radius-sm);
    cursor: pointer;
  }
</style>
