<script lang="ts">
  import type { Snippet } from 'svelte';
  import Header from './Header.svelte';
  import { navigate } from '../lib/router.svelte';

  // The app-shell layout (all screens): the nav bar grows; the ad strip, content and
  // optional tab bar pin to the bottom (ad directly above the content). Pass `scroll`
  // false for screens that own their vertical fit (the game board).
  let {
    title,
    back,
    tabbar,
    children,
    scroll = true,
    growNav = false,
    column = false,
    selfInset = false,
  }: {
    title: string;
    back?: string;
    tabbar?: Snippet;
    children?: Snippet;
    scroll?: boolean;
    growNav?: boolean;
    // selfInset: the content paints the bottom home-indicator inset itself (a tab-bar-less screen
    // whose own bottom element owns the strip, e.g. the landscape game's left-panel controls), so
    // the shell does not add the detached .content padding-bottom below it.
    selfInset?: boolean;
    // column lays the content out as a flex column so a child can own the vertical fit
    // (the game makes only its board scroll while the score/rack/tab bar stay put).
    column?: boolean;
  } = $props();

  // Edge-swipe back: a rightward drag begun in the left band returns to `back`, the standard
  // mobile gesture (instant on release — the route slide plays the animation). Listened at the
  // window in the CAPTURE phase so the board's own pointer handlers (which capture/stop the
  // event) can never swallow it; touch/pen only. The band is the left half of the viewport
  // width (EDGE_FRACTION — widen/narrow there). A hit-test keeps the wide band clear of the
  // gestures it would otherwise hijack: the rack (tile lift/reorder), a draggable pending tile,
  // a zoomed-in board (it pans), and text inputs.
  const EDGE_FRACTION = 0.5;
  const SWIPE_SKIP = '[data-rack], .cell.pending, .viewport.zoomed, input, textarea, select';
  // A pinch-zoom on the board is two fingers; its second finger lands after the gesture began
  // (the board is not yet .zoomed at the first touch, so the hit-test above can't skip it), and
  // releasing it would otherwise read as a horizontal back-swipe. Track the live pointer count
  // (in the capture phase, so we see every touch even if the board stops propagation) and cancel
  // the swipe the moment a second finger joins. The swipe fires only for a lone finger.
  $effect(() => {
    let startX = 0;
    let startY = 0;
    let active = 0; // pointers currently down
    let tracking = false; // a candidate single-finger edge swipe is in progress
    let pinched = false; // a second finger joined — not a back-swipe

    function onDown(e: PointerEvent) {
      active++;
      if (active > 1) {
        pinched = true;
        return;
      }
      tracking = false;
      pinched = false;
      if (!back || e.pointerType === 'mouse' || e.clientX > window.innerWidth * EDGE_FRACTION) return;
      if (document.elementFromPoint(e.clientX, e.clientY)?.closest(SWIPE_SKIP)) return;
      startX = e.clientX;
      startY = e.clientY;
      tracking = true;
    }

    function onUp(e: PointerEvent) {
      if (active > 0) active--;
      if (active > 0) return; // wait until every finger has lifted
      if (tracking && !pinched && back) {
        const dx = e.clientX - startX;
        const dy = e.clientY - startY;
        if (dx > 64 && Math.abs(dx) > Math.abs(dy) * 1.4) navigate(back);
      }
      tracking = false;
      pinched = false;
    }

    window.addEventListener('pointerdown', onDown, true);
    window.addEventListener('pointerup', onUp, true);
    window.addEventListener('pointercancel', onUp, true);
    return () => {
      window.removeEventListener('pointerdown', onDown, true);
      window.removeEventListener('pointerup', onUp, true);
      window.removeEventListener('pointercancel', onUp, true);
    };
  });
</script>

<div class="screen">
  <Header {title} {back} grow={growNav} />
  <main class="content" class:scroll class:fill={!growNav} class:column class:selfinset={selfInset}>{@render children?.()}</main>
  {#if tabbar}
    <nav class="tabbar">{@render tabbar()}</nav>
  {/if}
</div>

<style>
  .screen {
    display: flex;
    flex-direction: column;
    /* Fit the visible viewport (set from visualViewport, app.svelte.ts) so a screen with a
       bottom input — chat, word-check — stays above an open soft keyboard without the page
       scrolling; falls back to the full height where the var is unset. */
    height: var(--vvh, 100%);
    /* Clear the landscape notch sides inside Telegram (0 elsewhere). The top inset is owned by the
       header; the home-indicator (bottom) inset is owned by the bottom bar — the .tabbar paints its
       own chrome into it, and a screen with no tab bar pads its content (.content:last-child) — so
       the strip takes the bar's colour rather than the detached content background. */
    padding-left: var(--tg-safe-left, 0px);
    padding-right: var(--tg-safe-right, 0px);
  }
  .content {
    flex: 0 1 auto;
    min-height: 0;
  }
  .content.fill {
    flex: 1 1 auto;
  }
  .content.scroll {
    overflow-y: auto;
  }
  .content.column {
    display: flex;
    flex-direction: column;
  }
  /* No tab bar → the content is the bottom-most element: pad it by the device home-indicator inset
     so it clears the cut-out, the strip taking the content's own background. With a tab bar the
     .tabbar owns that inset instead (and content is not the last child, so this does not apply). */
  .content:last-child {
    padding-bottom: var(--tg-safe-bottom, 0px);
  }
  /* selfInset: the content's own bottom element paints the home-indicator strip (the landscape
     game's left-panel controls), so the shell does not add a detached padding strip below it. */
  .content.selfinset:last-child {
    padding-bottom: 0;
  }
  .tabbar {
    flex: 0 0 auto;
    /* Extend the bottom bar's chrome (the TabBar's --bg-elev) under the device home indicator
       inside Telegram (the inset is 0 elsewhere), so the safe-area strip reads as part of the bar
       instead of the content background showing through. */
    background: var(--bg-elev);
    padding-bottom: var(--tg-safe-bottom, 0px);
  }
</style>
