// ============================================================
// API4ATKA mobile — core: icons, atoms, chrome, swipe hook, sheet
// All shared symbols exported to window at bottom.
// ============================================================
const { useState, useEffect, useRef, useMemo, useCallback, useLayoutEffect } = React;

// ---------- haptics ----------
const buzz = (ms = 10) => { try { navigator.vibrate && navigator.vibrate(ms); } catch (e) {} };

// ---------- formatting ----------
const money = (n, dp = 2) => {
  if (n == null || isNaN(n)) return "—";
  return (n < 0 ? "-" : "") + Math.abs(n).toLocaleString("en-US", { minimumFractionDigits: dp, maximumFractionDigits: dp });
};
const signed = (n, dp = 2) => (n >= 0 ? "+" : "") + money(n, dp);
const pct = (n, dp = 1) => (n >= 0 ? "+" : "") + n.toFixed(dp) + "%";
const price = (n) => n >= 1000 ? n.toLocaleString("en-US", { maximumFractionDigits: 1 }) : n >= 1 ? n.toFixed(2) : n.toFixed(4);

// ---------- icons (lucide-style; crisp across Yandex/Chrome/Safari, unlike emoji) ----------
const MIcon = ({ name, size = 20, color = "currentColor", sw = 2, style }) => {
  const P = {
    home:   <><path d="M3 10.5 12 3l9 7.5"/><path d="M5 9.5V21h14V9.5"/></>,
    journal:<><path d="M4 4h12a2 2 0 0 1 2 2v14l-4-2-4 2-4-2-2 1V6a2 2 0 0 1 2-2Z"/><path d="M8 8h6"/><path d="M8 12h6"/></>,
    signal: <><path d="M4.9 19.1a10 10 0 0 1 0-14.2"/><path d="M7.8 16.2a6 6 0 0 1 0-8.4"/><circle cx="12" cy="12" r="2"/><path d="M16.2 7.8a6 6 0 0 1 0 8.4"/><path d="M19.1 4.9a10 10 0 0 1 0 14.2"/></>,
    gear:   <><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 0 1-4 0v-.1a1.7 1.7 0 0 0-1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 0 1 0-4h.1a1.7 1.7 0 0 0 1.5-1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 0 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 0 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1Z"/></>,
    bell:   <><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></>,
    back:   <><path d="M15 18l-6-6 6-6"/></>,
    x:      <><path d="M18 6 6 18M6 6l12 12"/></>,
    check:  <><path d="M20 6 9 17l-5-5"/></>,
    search: <><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></>,
    chevR:  <><path d="m9 18 6-6-6-6"/></>,
    arrowR: <><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></>,
    up:     <><path d="m6 15 6-6 6 6"/></>,
    down:   <><path d="m6 9 6 6 6-6"/></>,
    trendUp:<><path d="m22 7-8.5 8.5-5-5L2 17"/><path d="M16 7h6v6"/></>,
    wallet: <><path d="M3 7a2 2 0 0 1 2-2h14v4"/><path d="M3 7v10a2 2 0 0 0 2 2h16V9H5a2 2 0 0 1-2-2Z"/><circle cx="17" cy="14" r="1.4"/></>,
    target: <><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="1.4"/></>,
    stop:   <><circle cx="12" cy="12" r="9"/><path d="M9 9h6v6H9z"/></>,
    hand:   <><path d="M9 11V6a1.5 1.5 0 0 1 3 0v5"/><path d="M12 11V5a1.5 1.5 0 0 1 3 0v6"/><path d="M15 11V7a1.5 1.5 0 0 1 3 0v8a6 6 0 0 1-6 6h-1a6 6 0 0 1-5.2-3l-2.3-4a1.5 1.5 0 0 1 2.6-1.5L9 15"/></>,
    refresh:<><path d="M21 12a9 9 0 1 1-3-6.7L21 8"/><path d="M21 3v5h-5"/></>,
    mail:   <><rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 7 9 6 9-6"/></>,
    lock:   <><rect x="4" y="11" width="16" height="9" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></>,
    logout: <><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><path d="m16 17 5-5-5-5"/><path d="M21 12H9"/></>,
    plane:  <><path d="M22 2 11 13"/><path d="M22 2 15 22l-4-9-9-4Z"/></>,
    key:    <><circle cx="7.5" cy="15.5" r="4.5"/><path d="m10.5 12.5 8-8"/><path d="m17 7 2 2"/><path d="m15 9 2 2"/></>,
    bot:    <><rect x="4" y="8" width="16" height="12" rx="3"/><path d="M12 4v4"/><circle cx="9" cy="14" r="1"/><circle cx="15" cy="14" r="1"/><path d="M2 13v3M22 13v3"/></>,
    plus:   <><path d="M12 5v14M5 12h14"/></>,
    briefcase: <><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M3 12h18"/></>,
    layers: <><path d="m12 3 9 5-9 5-9-5 9-5Z"/><path d="m3 12 9 5 9-5"/></>,
    sliders:<><path d="M4 6h10M18 6h2M4 12h2M10 12h10M4 18h8M16 18h4"/><circle cx="16" cy="6" r="2"/><circle cx="8" cy="12" r="2"/><circle cx="14" cy="18" r="2"/></>,
    coffee: <><path d="M4 8h13v5a5 5 0 0 1-5 5H9a5 5 0 0 1-5-5V8Z"/><path d="M17 9h2a2 2 0 0 1 0 4h-2"/><path d="M7 3v2M11 3v2M15 3v2"/></>,
    inbox:  <><path d="M3 12h5l2 3h4l2-3h5"/><path d="M5 6h14l2 6v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-6Z"/></>,
    clock:  <><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></>,
    sun:    <><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></>,
    globe:  <><circle cx="12" cy="12" r="9"/><path d="M2 12h20"/><path d="M12 2a14 14 0 0 1 0 20M12 2a14 14 0 0 0 0 20"/></>,
  };
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={color}
      strokeWidth={sw} strokeLinecap="round" strokeLinejoin="round" style={style}>
      {P[name]}
    </svg>
  );
};

// ---------- coin badge ----------
const Coin = ({ sym, size }) => {
  const bg = window.M_DATA.COIN_COLORS[sym] || "#555";
  return <span className={"coin" + (size === "sm" ? " sm" : "")} style={{ background: bg }}>{sym.slice(0, 1)}</span>;
};

// ---------- side pill ----------
const Side = ({ side }) => <span className={"m-pill " + (side === "LONG" ? "long" : "short")}>{side}</span>;

// ---------- sparkline ----------
const Spark = ({ data, color, w = 200, h = 56 }) => {
  if (!data || !data.length) return null;
  const mn = Math.min(...data), mx = Math.max(...data), r = mx - mn || 1, step = w / (data.length - 1);
  const pts = data.map((v, i) => [i * step, h - ((v - mn) / r) * h]);
  const d = pts.map(([x, y], i) => (i ? "L" : "M") + x.toFixed(1) + " " + y.toFixed(1)).join(" ");
  const id = "sp" + Math.random().toString(36).slice(2, 7);
  return (
    <svg viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none" style={{ width: "100%", height: "100%" }}>
      <defs><linearGradient id={id} x1="0" y1="0" x2="0" y2="1">
        <stop offset="0%" stopColor={color} stopOpacity="0.4"/><stop offset="100%" stopColor={color} stopOpacity="0"/>
      </linearGradient></defs>
      <path d={d + ` L ${w} ${h} L 0 ${h} Z`} fill={`url(#${id})`}/>
      <path d={d} fill="none" stroke={color} strokeWidth="1.6"/>
    </svg>
  );
};

// ---------- pull-to-refresh hook (reusable для любого scroll-контейнера) ----------
// Возвращает { ptr, refreshing, scrollRef, handlers, indicatorStyle }.
// Использует touch-события вместо pointer для надёжности на Android Yandex/Chrome
// (PointerEvents иногда конфликтуют со встроенными жестами браузера).
const usePullToRefresh = (onRefresh) => {
  const [ptr, setPtr] = useState(0);
  const [refreshing, setRefreshing] = useState(false);
  const scrollRef = useRef(null);
  const st = useRef({ y: 0, pulling: false });
  const ptrRef = useRef(0);

  const handleStart = (clientY) => {
    if (scrollRef.current && scrollRef.current.scrollTop <= 0) {
      st.current = { y: clientY, pulling: true };
    }
  };
  const handleMove = (clientY, e) => {
    if (!st.current.pulling) return;
    const d = clientY - st.current.y;
    if (d > 0 && scrollRef.current && scrollRef.current.scrollTop <= 0) {
      // Чем дальше — тем сильнее сопротивление (нелинейное)
      const next = Math.min(80, Math.sqrt(d) * 5);
      setPtr(next);
      ptrRef.current = next;
      // Блокируем штатный скролл браузера если уже тянем
      if (e && e.cancelable) e.preventDefault();
    } else if (d < 0) {
      // Тянем вверх — закрываем индикатор
      st.current.pulling = false;
      setPtr(0);
      ptrRef.current = 0;
    }
  };
  const handleEnd = async () => {
    if (!st.current.pulling) return;
    st.current.pulling = false;
    const currentPtr = ptrRef.current;
    if (currentPtr > 50) {
      setRefreshing(true);
      try { buzz(); } catch (e) {}
      try { if (onRefresh) await onRefresh(); } catch (e) {}
      setRefreshing(false);
    }
    setPtr(0);
    ptrRef.current = 0;
  };

  return {
    ptr, refreshing, scrollRef,
    handlers: {
      // Touch — основной канал на мобиле
      onTouchStart: (e) => handleStart(e.touches[0].clientY),
      onTouchMove: (e) => handleMove(e.touches[0].clientY, e),
      onTouchEnd: handleEnd,
      onTouchCancel: handleEnd,
      // Pointer/mouse — для desktop тестирования
      onMouseDown: (e) => handleStart(e.clientY),
      onMouseMove: (e) => {
        if (e.buttons === 1) handleMove(e.clientY, null);
      },
      onMouseUp: handleEnd,
    },
  };
};

// Компонент-индикатор для pull-to-refresh
const PullIndicator = ({ ptr, refreshing }) => (
  <div className="ptr" style={{ height: refreshing ? 36 : ptr }}>
    <span className={refreshing ? "spin" : ""} style={{ display: "inline-flex" }}>
      <MIcon name="refresh" size={16}/>
    </span>
    {refreshing
      ? (window.M_I18N ? window.M_I18N.t("common.refreshing", "Обновление…") : "Обновление…")
      : ptr > 48
      ? (window.M_I18N ? window.M_I18N.t("common.releaseToRefresh", "Отпустите для обновления") : "Отпустите для обновления")
      : (window.M_I18N ? window.M_I18N.t("common.pullToRefresh", "Потяните вниз") : "Потяните вниз")}
  </div>
);

// ---------- pulse-on-change hook ----------
const usePulse = (val) => {
  const [c, setC] = useState("");
  const prev = useRef(val);
  useEffect(() => {
    if (val === prev.current) return;
    setC(val > prev.current ? "pulse-up" : "pulse-down");
    prev.current = val;
    const t = setTimeout(() => setC(""), 220);
    return () => clearTimeout(t);
  }, [val]);
  return c;
};

// ---------- swipe hook (pointer events, cross-platform) ----------
// onLeft/onRight fire when dragged past threshold and released.
const useSwipe = ({ onLeft, onRight, threshold = 0.4, enabled = true } = {}) => {
  const [dx, setDx] = useState(0);
  const [active, setActive] = useState(false);
  const st = useRef({ x: 0, y: 0, w: 1, locked: null, id: null });

  const down = (e) => {
    if (!enabled) return;
    const el = e.currentTarget;
    st.current = { x: e.clientX, y: e.clientY, w: el.offsetWidth, locked: null, id: e.pointerId };
  };
  const move = (e) => {
    if (!enabled || st.current.id !== e.pointerId) return;
    const ddx = e.clientX - st.current.x;
    const ddy = e.clientY - st.current.y;
    if (st.current.locked === null) {
      if (Math.abs(ddx) < 6 && Math.abs(ddy) < 6) return;
      st.current.locked = Math.abs(ddx) > Math.abs(ddy) ? "x" : "y";
    }
    if (st.current.locked !== "x") return;
    setActive(true);
    // rubber-band resistance
    setDx(ddx);
  };
  const up = (e) => {
    if (st.current.id !== e.pointerId) { return; }
    const ratio = dx / st.current.w;
    if (ratio <= -threshold && onLeft) { buzz(12); onLeft(); }
    else if (ratio >= threshold && onRight) { buzz(12); onRight(); }
    setDx(0); setActive(false);
    st.current.id = null;
  };
  const handlers = { onPointerDown: down, onPointerMove: move, onPointerUp: up, onPointerCancel: up };
  return { dx, active, handlers, reset: () => { setDx(0); setActive(false); } };
};

// ============================================================
// Top app bar
// ============================================================
const TopBar = ({ variant, title, onBack, exchange = "WEEX", online = true, notif = 0, onBell, right }) => {
  if (variant === "back") {
    return (
      <header className="m-top-bar">
        <button className="m-back" onClick={() => { buzz(); onBack && onBack(); }}><MIcon name="back" size={22}/></button>
        <span className="m-top-title">{title}</span>
        <span className="m-top-spacer"></span>
        {right}
      </header>
    );
  }
  if (variant === "title") {
    return (
      <header className="m-top-bar">
        <span className="m-top-title">{title}</span>
        <span className="m-top-spacer"></span>
        {right}
      </header>
    );
  }
  return (
    <header className="m-top-bar">
      <span className="m-brand">API<span className="m-brand-dim">4</span>ATKA</span>
      <span className="m-top-spacer"></span>
      <span className="m-exch-chip"><span className={"dot" + (online ? "" : " off")}></span>{exchange} · {online ? "online" : "offline"}</span>
      <span className="m-top-spacer"></span>
      <button className="m-bell" onClick={() => { buzz(); onBell && onBell(); }} aria-label="Notifications">
        <MIcon name="bell" size={20}/>
        {notif > 0 && <span className="badge">{notif}</span>}
      </button>
    </header>
  );
};

// ============================================================
// Bottom navigation
// ============================================================
const NAV = [
  { id: "home",     label: T("tabs.home", "Главная"),   icon: "home" },
  { id: "journal",  label: T("tabs.journal", "Журнал"),    icon: "journal" },
  { id: "signals",  label: T("topbar.signals", "Сигналы"),   icon: "signal" },
  { id: "settings", label: T("topbar.settings", "Настройки"), icon: "gear" },
];
const BottomNav = ({ active, onChange, badges = {} }) => (
  <nav className="m-bottom-nav">
    {NAV.map(n => (
      <button key={n.id} className={"m-nav-item" + (active === n.id ? " active" : "")}
        onClick={() => { buzz(); onChange(n.id); }}>
        <span className="ni-icon"><MIcon name={n.icon} size={22} sw={active === n.id ? 2.2 : 1.9}/></span>
        <span>{n.label}</span>
        {badges[n.id] > 0 && <span className="ni-badge">{badges[n.id]}</span>}
      </button>
    ))}
  </nav>
);

// ============================================================
// FAB — swipe up to arm "close all"
// ============================================================
// Fab: тап → onTrigger (закрыть всё); long-press 600мс → onLongPress (шторм-режим).
const Fab = ({ onTrigger, onLongPress }) => {
  const [armed, setArmed] = useState(false);
  const st = useRef({ y: 0, id: null, lpTimer: null });
  const down = (e) => {
    st.current = { y: e.clientY, id: e.pointerId, lpTimer: null };
    e.currentTarget.setPointerCapture?.(e.pointerId);
    if (onLongPress) {
      st.current.lpTimer = setTimeout(() => {
        buzz(40);
        st.current.lpTimer = "fired";
        onLongPress();
      }, 600);
    }
  };
  const move = (e) => {
    if (st.current.id !== e.pointerId) return;
    if (Math.abs(e.clientY - st.current.y) > 6 && st.current.lpTimer && typeof st.current.lpTimer !== "string") {
      clearTimeout(st.current.lpTimer); st.current.lpTimer = null;
    }
    if (st.current.y - e.clientY > 28) setArmed(true); else setArmed(false);
  };
  const up = (e) => {
    if (st.current.id !== e.pointerId) return;
    if (st.current.lpTimer && typeof st.current.lpTimer !== "string") {
      clearTimeout(st.current.lpTimer);
    }
    if (st.current.lpTimer === "fired") {
      // long-press уже сработал — обычный тап не выполняем
    } else if (armed) {
      buzz(20); onTrigger();
    }
    setArmed(false); st.current.id = null; st.current.lpTimer = null;
  };
  return (
    <button className={"m-fab" + (armed ? " armed" : "")}
      onPointerDown={down} onPointerMove={move} onPointerUp={up} onPointerCancel={up}
      onClick={() => {
        if (st.current.lpTimer === "fired") return;
        if (!armed) { buzz(); onTrigger(); }
      }}>
      <span className="fab-hint">{T("home.closeHoldStorm", "↑ Закрыть · удерж. = шторм")}</span>
      <MIcon name="briefcase" size={24}/>
    </button>
  );
};

// ============================================================
// Bottom sheet (drag-down to dismiss)
// ============================================================
const BottomSheet = ({ title, onClose, children, foot, maxH = "86vh" }) => {
  const sheetRef = useRef(null);
  const [drag, setDrag] = useState(0);
  const st = useRef({ y: 0, id: null, fromGrab: false });

  useEffect(() => {
    const h = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", h);
    return () => window.removeEventListener("keydown", h);
  }, [onClose]);

  const down = (e) => { st.current = { y: e.clientY, id: e.pointerId }; };
  const move = (e) => {
    if (st.current.id !== e.pointerId) return;
    const d = e.clientY - st.current.y;
    if (d > 0) setDrag(d);
  };
  const up = (e) => {
    if (st.current.id !== e.pointerId) return;
    if (drag > 110) { buzz(); onClose(); } else setDrag(0);
    st.current.id = null;
  };

  return (
    <>
      <div className="m-backdrop" onClick={() => { buzz(); onClose(); }}></div>
      <div className="m-sheet" ref={sheetRef}
        style={{ maxHeight: maxH, transform: drag ? `translateY(${drag}px)` : undefined, transition: drag ? "none" : undefined }}>
        <div className="grabber" onPointerDown={down} onPointerMove={move} onPointerUp={up} onPointerCancel={up}
          style={{ touchAction: "none", padding: "12px 0", margin: "-8px auto 0", width: 80, cursor: "grab" }}>
          <div style={{ width: 38, height: 4, borderRadius: 999, background: "var(--border-3)", margin: "0 auto" }}></div>
        </div>
        {title && (
          <div className="sheet-head">
            <h3>{title}</h3>
            <button className="sheet-close" onClick={() => { buzz(); onClose(); }}><MIcon name="x" size={18}/></button>
          </div>
        )}
        <div className="sheet-body">{children}</div>
        {foot && <div className="sheet-foot">{foot}</div>}
      </div>
    </>
  );
};

// ============================================================
// Toast (auto-dismiss 5s, optional action)
// ============================================================
const Toast = ({ msg, action, onAction, onDone, err }) => {
  useEffect(() => { const t = setTimeout(() => onDone && onDone(), 5000); return () => clearTimeout(t); }, []);
  return (
    <div className={"m-toast" + (err ? " err" : "")}>
      <span className="tt">{msg}</span>
      {action && <button className="ta" onClick={() => { buzz(); onAction && onAction(); }}>{action}</button>}
      <span className="tprog"></span>
    </div>
  );
};

// ---------- empty state ----------
const EmptyState = ({ icon, title, msg, art }) => (
  <div className="m-empty">
    <div className="art">{art || <MIcon name={icon || "inbox"} size={56} sw={1.2}/>}</div>
    <div className="et">{title}</div>
    <div className="em">{msg}</div>
  </div>
);

// ---------- skeleton card ----------
const SkelCard = () => (
  <div className="m-card" style={{ marginBottom: 10 }}>
    <div style={{ display: "flex", gap: 10, alignItems: "center" }}>
      <div className="skel" style={{ width: 26, height: 26, borderRadius: "50%" }}></div>
      <div className="skel" style={{ width: 100, height: 14 }}></div>
      <div className="skel" style={{ width: 50, height: 18, marginLeft: "auto", borderRadius: 6 }}></div>
    </div>
    <div className="skel" style={{ width: "70%", height: 12, marginTop: 14 }}></div>
    <div className="skel" style={{ width: "40%", height: 16, marginTop: 12 }}></div>
  </div>
);

Object.assign(window, {
  buzz, money, signed, pct, price,
  MIcon, Coin, Side, Spark, usePulse, useSwipe,
  usePullToRefresh, PullIndicator,
  TopBar, BottomNav, NAV, Fab, BottomSheet, Toast, EmptyState, SkelCard,
});
