import { style } from '@vanilla-extract/css';
import { recipe } from '@vanilla-extract/recipes';
import { color, config, toRem } from 'folds';
import {
  CURTAIN_RADIUS_PX,
  CURTAIN_SNAP_EASING,
  CURTAIN_SNAP_MS,
  HANDLE_HEIGHT_PX,
  MASCOT_CARD_PX,
  MASCOT_VIDEO_PX,
  PULSE_CENTER_Y_PX,
  PULSE_EDGE_PX,
  PULSE_SPAN_PX,
  TABS_ROW_PX,
  WEB_TABS_ROW_PX,
} from './geometry';

// Stage. Position-relative anchor. The header itself paints the
// light-blue backdrop; the curtain is layered ABOVE it via z-index.
//
// In pager mode the bg collapses to transparent so the pager's static
// header (sitting behind the strip in DOM order) shows through every
// pixel the curtain isn't covering. See
// `mobile-tabs-pager/style.css.ts::pagerStaticHeader` for the full
// curtain-overlay contract. The strip is tagged
// `data-pager-pane="true"` in MobileTabsPager.tsx, which gates this
// selector.
export const stage = style({
  position: 'relative',
  flex: 1,
  minHeight: 0,
  display: 'flex',
  flexDirection: 'column',
  backgroundColor: color.SurfaceVariant.Container,
  selectors: {
    '[data-pager-pane="true"] &': {
      backgroundColor: 'transparent',
    },
  },
});

// Header — always-rendered strip carrying tabs row + (optional)
// mascot-refresh card + (optional) active form. The curtain slides on
// top of the area BELOW the tabs row to cover/reveal those children.
//
// In pager mode the bg collapses to transparent for the same reason as
// `stage` above — let the static pager header show through where the
// curtain isn't. The mascot card carries its own `SurfaceVariant`
// backdrop and the inline form is composed of folds-styled inputs with
// their own backgrounds, so the refresh/form snaps stay visually opaque
// without this layer.
export const header = style({
  position: 'absolute',
  top: 0,
  left: 0,
  right: 0,
  display: 'flex',
  flexDirection: 'column',
  // Higher than `stage`, lower than `curtain` so the curtain occludes
  // everything below the tabs row when raised.
  zIndex: 1,
  backgroundColor: color.SurfaceVariant.Container,
  selectors: {
    '[data-pager-pane="true"] &': {
      backgroundColor: 'transparent',
    },
  },
});

// Tabs row. Stays fully visible regardless of curtain position
// because the curtain's `top` floor equals `TABS_ROW_PX` on native
// (`WEB_TABS_ROW_PX` on web — see `geometry.ts::WEB_TABS_ROW_PX`).
//
// Web variant: shrink to `WEB_TABS_ROW_PX` (= 54 px = folds Header
// `size="600"`) so the row reads at the same height as the right-pane
// room `PageHeader`, AND own the 1 px divider rule as a
// `border-bottom`. Putting the rule on `tabsRow` (not on the curtain
// as a `border-top`) is load-bearing for pixel alignment: with the
// global `* { box-sizing: border-box }` reset (`src/index.css`),
// `tabsRow`'s 1 px bottom border lands at y=53→54 inside the 54 px
// box — exactly where PageHeader's outlined border-bottom paints. If
// the rule lived on the curtain's `border-top` at `top: 54`, it would
// paint at y=54→55, off-by-one against the right pane.
export const tabsRow = style({
  flexShrink: 0,
  height: toRem(TABS_ROW_PX),
  display: 'flex',
  alignItems: 'center',
  // Horizontal padding is tuned for the narrowest target: Android 360 px
  // viewport (Pixel/Galaxy base) must fit 3 RU segments (Личные/Каналы/
  // Роботы) + Plus + Search without iconsCluster being pushed past the
  // right edge. Reducing this from 8 → 6 saves 4 px across the row;
  // combined with the tightened Segment / cluster gaps below, the floor
  // drops from ≈378 px to ≈350 px. See sidebarWidth.ts for the desktop
  // resize floor that mirrors this measurement.
  padding: `0 ${toRem(6)}`,
  selectors: {
    '[data-platform="web"] &': {
      height: toRem(WEB_TABS_ROW_PX),
      borderBottom: `${config.borderWidth.B300} solid ${color.Surface.ContainerLine}`,
    },
  },
});

export const tabsCluster = style({
  display: 'flex',
  alignItems: 'center',
  // Tightened from 4 → 2 px so the 3 RU segments + 2 action icons fit
  // inside a 360 px viewport (standard Android base width). See the
  // `tabsRow` padding comment for the full row-fit budget.
  gap: toRem(2),
  alignSelf: 'stretch',
});

export const iconsCluster = style({
  display: 'flex',
  alignItems: 'center',
  gap: toRem(2),
  flexShrink: 0,
});

// Curtain. Layered above the header (z-index higher). Its top edge
// moves with the snap state (and live finger drag); its bottom edge
// is anchored to the stage bottom so the curtain's `bottomPinned`
// child (DirectSelfRow / WorkspaceFooter) stays glued to the visible
// viewport bottom regardless of where the curtain's top is.
//
// On native, only the TOP corners are rounded: the bottom is meant
// to read as continuous with the always-visible bottomPinned row
// (DirectSelfRow is the curtain's last flex child) — adding
// `borderBottomRadius` would crop the row's corners against the
// curtain's `overflow: hidden`, which visually reads as «a light-
// blue strip cuts into the row».
//
// Live finger tracking and snap commits both flow through React state
// updates to `top` so the transition is always coordinated with the
// rendered position — disabled during drag, restored on commit.
//
// Web variant (`[data-platform="web"]` on `stage`, set by
// StreamHeader.tsx when `!isNativePlatform()`): there is no pin/refresh
// gesture, so the curtain is a purely static slab under the tabs row.
// Drop ONLY the «card» rounding (top corners flat). The divider rule
// at the seam is owned by `tabsRow.borderBottom` under the same
// selector — that placement keeps the rule pixel-aligned with the
// right-pane `PageHeader`'s outlined border (see `tabsRow` comment
// above). The curtain bg stays `Background.Container` so the chat-
// row rows (`NavItem variant="Background"`) keep blending into one
// continuous list surface — if we made the curtain transparent the
// rows would paint as dark cards over the lighter
// `SurfaceVariant.Container` stage.
export const curtain = style({
  position: 'absolute',
  left: 0,
  right: 0,
  bottom: 0,
  zIndex: 2,
  display: 'flex',
  flexDirection: 'column',
  minHeight: 0,
  overflow: 'hidden',
  backgroundColor: color.Background.Container,
  borderTopLeftRadius: toRem(CURTAIN_RADIUS_PX),
  borderTopRightRadius: toRem(CURTAIN_RADIUS_PX),
  transition: `top ${CURTAIN_SNAP_MS}ms ${CURTAIN_SNAP_EASING}`,
  // NB: `top` is a LAYOUT property — its animation runs on the main
  // thread regardless of this hint (will-change of a non-compositable
  // property does not promote a layer). Kept as a mild
  // style-invalidation hint; the curtain being layout-driven (not
  // transform-driven) is itself deliberate — `bottomPinned` must stay
  // glued to the viewport bottom while the top edge moves, which a
  // translateY of the whole curtain would break.
  willChange: 'top',
  selectors: {
    '[data-platform="web"] &': {
      borderTopLeftRadius: 0,
      borderTopRightRadius: 0,
    },
  },
});

// Drag handle at the top of the curtain. Dedicated touch surface for
// the pin / unpin gesture so it doesn't compete with the chat list's
// vertical scroll. `touchAction: none` keeps the browser from claiming
// the gesture for native scroll heuristics — our `touchmove` listener
// in `useCurtainHandleGesture` drives every pixel of motion.
//
// Sits as the first flex child of the curtain so the list (or
// DirectEmpty / equivalent placeholder) takes the remaining space
// below it. `flexShrink: 0` locks the height so a long list doesn't
// squash the hit-zone.
export const handle = style({
  flexShrink: 0,
  height: toRem(HANDLE_HEIGHT_PX),
  display: 'flex',
  alignItems: 'center',
  justifyContent: 'center',
  touchAction: 'none',
});

// Visual «grabber» pill centred inside `handle`. Semi-transparent
// foreground so the affordance reads as «draggable» without competing
// with content beneath. Pure decoration — the parent `handle` div
// captures the touch.
//
// State machine mirrors `PageNavResizeHandle` on desktop: a subtle
// resting state, a more prominent «being dragged» state, and an even
// more prominent «threshold reached, release to commit» state. The
// state-driving `data-dragging` / `data-at-commit` attributes live on
// the parent `handle` div (set by StreamHeader.tsx from the gesture
// hook). Transition durations match the desktop handle (140ms ease)
// so the two affordances feel related.
export const handleBar = style({
  width: toRem(40),
  height: toRem(4),
  borderRadius: toRem(2),
  backgroundColor: color.Background.OnContainer,
  opacity: 0.25,
  pointerEvents: 'none',
  transition:
    'opacity 140ms ease, width 140ms ease, height 140ms ease, background-color 140ms ease',
  selectors: {
    // Dragging but threshold not yet reached: highlight, slight grow.
    '[data-dragging="true"] &': {
      opacity: 0.55,
      width: toRem(48),
      backgroundColor: color.Primary.Main,
    },
    // Threshold reached during drag: full stretch + opacity. Releasing
    // here commits pin (or unpin). Reads as «yes, you're there».
    '[data-dragging="true"][data-at-commit="true"] &': {
      opacity: 0.9,
      width: toRem(64),
      height: toRem(5),
      backgroundColor: color.Primary.Main,
    },
  },
});

// Wrapper around `bottomPinned` inside the curtain. Anchored to the
// curtain's flex-bottom by virtue of being the last child. The TSX
// collapses this slot to `{ height: 0, overflow: hidden }` when the
// on-screen keyboard rises (via `VisualViewport.height` shrink) so
// the row neither paints nor claims flex space above the keyboard.
// Without this compensation, `interactive-widget=resizes-content`
// (global viewport meta — load-bearing for the room composer)
// shrinks the layout viewport, dragging every `bottom: 0` element
// up over the inline form. The DirectSelfRow ending up immediately
// above the keyboard would block the user's view of the form they're
// typing into.
export const bottomPinnedSlot = style({
  flexShrink: 0,
});

// Segment button (Direct / Channels / Bots).
//
// Horizontal padding (8 px) and dot-to-label gap (6 px) are the tightest
// values that still keep the tap target comfortable on touch. They're
// load-bearing for the row-fit budget on 360 px Android viewports — see
// `tabsRow` above. Vertical padding (8 px) is kept full for hit-target
// height; the row is height-fixed by `TABS_ROW_PX` so trimming vertical
// padding wouldn't buy any width.
export const segment = recipe({
  base: {
    appearance: 'none',
    border: 'none',
    background: 'transparent',
    color: color.Background.OnContainer,
    cursor: 'pointer',
    padding: toRem(8),
    borderRadius: toRem(8),
    font: 'inherit',
    fontSize: toRem(14),
    lineHeight: 1.2,
    display: 'inline-flex',
    alignItems: 'center',
    gap: toRem(6),
    whiteSpace: 'nowrap',
    fontWeight: 500,
    WebkitAppearance: 'none',
  },
  variants: {
    active: {
      true: { fontWeight: 600 },
    },
    disabled: {
      true: { opacity: 0.45, cursor: 'default' },
    },
  },
});

// Active-state dot inside each segment.
export const segmentDot = recipe({
  base: {
    width: toRem(6),
    height: toRem(6),
    borderRadius: '50%',
    flexShrink: 0,
  },
  variants: {
    active: {
      true: { backgroundColor: color.Primary.Main },
      false: { backgroundColor: 'transparent' },
    },
  },
});

// Dancing-mascot pull-to-refresh card. Revealed below the tabs row when
// the user drags the curtain down to the `refresh` snap. Its fixed
// height equals `MASCOT_CARD_PX`, which the curtain's refresh rest-top
// (`TABS_ROW_PX + MASCOT_CARD_PX`) lands exactly on — so the card edge
// sits at the curtain's rounded top with the content centred above it.
// Own opaque backdrop so the refresh reveal stays solid in pager mode,
// where the `header` bg collapses to transparent (the transparent
// mascot video would otherwise show the static pager header through it).
//
// Two hosts: the in-stage header slot on NON-pager native surfaces, and
// the strip-hosted PagerRefreshSingleton in pager mode
// (mobile-tabs-pager/style.css.ts::pagerMascotSlot positions it at the
// same screen Y), where it stays put while the panes slide. The opaque
// SurfaceVariant backdrop is correct in both.
export const mascotCard = style({
  flexShrink: 0,
  height: toRem(MASCOT_CARD_PX),
  display: 'flex',
  flexDirection: 'column',
  alignItems: 'center',
  justifyContent: 'center',
  gap: toRem(2),
  backgroundColor: color.SurfaceVariant.Container,
});

// Invisible stand-in for the mascot card in PAGER mode. The visible
// mascot lives in the strip-hosted singleton (PagerRefreshSingleton),
// but that singleton is `pointer-events: none` behind the panes'
// hit-test stack, so the tap-to-poke easter egg would die on the pane's
// transparent header band. This catcher occupies the exact slot the
// in-stage card would (same flex position + height) and proxies taps to
// the singleton via `mobilePagerMascotApiAtom`. At the closed snap it's
// z-covered by the curtain exactly like the card, so it's only
// reachable while the refresh band is actually revealed.
export const mascotPokeCatcher = style({
  flexShrink: 0,
  height: toRem(MASCOT_CARD_PX),
});

// Radar-pulse overlay — pink concentric rings rippling across the light-blue
// surface behind the curtain. Rendered as ONE transparent 2D <canvas>
// (RadarPulse.tsx) — NOT CSS-animated DOM rings. History of this surface,
// each step device-measured: 10 animated <span>s blew the WebView tile
// budget (572 «tile memory limits exceeded» warnings); one <svg> of 10
// animated <circle>s did NOT fix it — a running transform/opacity animation
// is a direct compositing reason on each SVG CHILD, so every circle still
// got its own cc layer rastered at peak scale (~110 MB; full corrected
// root-cause in RadarPulse.tsx). The canvas is one untiled TextureLayer
// whose buffers never touch the tile budget at all.
//
// (The historical «full-screen 2D canvas composites as an OPAQUE WHITE
// surface» incident that long banned canvases here is explained and
// avoided: that's the `desynchronized: true` / SurfaceLayerBridge path.
// A plain `{alpha: true}` canvas composites as a normal transparent
// TextureLayer in the page's paint order — see the context contract in
// RadarPulse.tsx.)
//
// This layer spans the visible surface — up into the safe-top (negative
// `top`, so its origin is the SCREEN top like the pager singleton host's)
// and down to the stage bottom. `zIndex: 1` is over the header chrome but
// under the curtain (`zIndex: 2`), which occludes whatever's below its top
// edge. `pointerEvents: none` lets taps fall through. Rendered only while
// revealed + animated (StreamHeader `showPulse`).
//
// Rendered only on NON-pager native surfaces (StreamHeader gates
// `showPulse` on `!inPagerMode`); in pager mode the rings render inside
// the strip-hosted PagerRefreshSingleton instead — see
// mobile-tabs-pager/style.css.ts::pagerRefreshSingleton for the full
// hosting / tile-memory contract. NB the `-safe-top` top offset here is
// STAGE-relative (the stage starts at y = safe-top) and must NOT be
// reused for the singleton host, whose y-origin is already the screen
// top.
export const stagePulseLayer = style({
  position: 'absolute',
  top: 'calc(-1 * var(--vojo-safe-top, 0px))',
  left: 0,
  right: 0,
  bottom: 0,
  overflow: 'hidden',
  pointerEvents: 'none',
  zIndex: 1,
});

// The ring-field canvas. Spans the host's full width from the screen top
// down to the field's bottom edge: safe-top + PULSE_CENTER_Y_PX (ring
// centre) + PULSE_SPAN_PX/2 (largest ring) + PULSE_EDGE_PX (stroke + AA
// headroom). RadarPulse recovers the draw centre from the box itself —
// cx = width/2, cy = height − (PULSE_SPAN_PX/2 + PULSE_EDGE_PX) — which
// lands at exactly safe-top + PULSE_CENTER_Y_PX from the screen top, the
// same centre the old 760² svg box had. Both hosting layers
// (`stagePulseLayer` here, the pager singleton host) start at the top of
// the SCREEN, so this one safe-top-relative height works in either.
// Rings wider than the screen simply draw past the canvas edges — the
// clipping the old svg needed its host's overflow:hidden for is now
// inherent to the backing store. Keep the canvas VIEWPORT-width (not
// 760px wide): the backing buffers are ~7 MB × 2–3 in flight at dpr 3,
// and width is the lever that keeps them there.
export const stagePulseCanvas = style({
  position: 'absolute',
  top: 0,
  left: 0,
  width: '100%',
  height: `calc(var(--vojo-safe-top, 0px) + ${toRem(
    PULSE_CENTER_Y_PX + PULSE_SPAN_PX / 2 + PULSE_EDGE_PX
  )})`,
  pointerEvents: 'none',
});

// The looping mascot video (or its still poster under reduced-motion),
// `object-fit: contain` so the whole dance frame fits the box. The box is
// SMALLER than the card height (geometry.ts), so it's centred with
// breathing room and never clipped. `pointerEvents: none` so a drag that
// starts on the card flows to the curtain body gesture rather than being
// eaten by the media element.
export const mascotMedia = style({
  width: toRem(MASCOT_VIDEO_PX),
  height: toRem(MASCOT_VIDEO_PX),
  objectFit: 'contain',
  pointerEvents: 'none',
  userSelect: 'none',
});

// Caption under the mascot («Обновляем…»). Muted so the dancing mascot
// stays the focus.
export const mascotCaption = style({
  opacity: 0.7,
  whiteSpace: 'nowrap',
});

// Active form area in the header. Outer is `position: relative`; the
// inner mounted form fills it with `top: 0` so the form's input bar
// sits flush just below the tabs row.
export const formArea = style({
  position: 'relative',
  flexShrink: 0,
  overflow: 'hidden',
});

export const formInner = style({
  position: 'absolute',
  top: 0,
  left: 0,
  right: 0,
  // 12px side padding insets the search bar a little from the column edge.
  //
  // Bottom padding is 0 (not 8 like the top): the form's last child is
  // the scrollable result list, and it must run flush into the curtain's
  // top edge. Any bottom padding here would reintroduce a light-blue strip
  // that clips the search results before they reach the curtain. The 8px
  // top padding stays: it's the gap below the tabs row.
  padding: `${toRem(8)} ${toRem(12)} 0`,
});
