/* Sidebar + Top Bar + Market Summary Bar */
const { Icon, Sparkline, fmt, fmtVol, sign, PctBadge, MANUAL_REFRESH_EVENT } = window.UI;

const Sidebar = ({ page, setPage, sidebarW, collapsed: extCollapsed, onToggle }) => {
  const items = [
    { id: "overview", label: "總覽", icon: "dashboard" },
    { id: "watchlists", label: "觀察清單", icon: "list" },
    { id: "heatmap", label: "產業熱力圖", icon: "grid" },
    { id: "alerts", label: "通知規則", icon: "bell" },
    { id: "backtests", label: "回測", icon: "beaker" },
    { id: "settings", label: "設定", icon: "settings" },
  ];
  const collapsed = extCollapsed !== undefined ? extCollapsed : sidebarW < 90;
  const effectiveW = collapsed ? 64 : sidebarW;
  return (
    <aside className="sidebar" style={{ width: effectiveW }}>
      <div className="brand">
        <button className="btn ghost icon-btn sidebar-toggle"
          onClick={onToggle}
          title={collapsed ? "展開選單" : "收合選單"}>
          <Icon name="menu" size={16}/>
        </button>
        {!collapsed && (
          <div className="brand-text">
            <div className="brand-name">台美股</div>
            <div className="brand-sub">觀察工作台</div>
          </div>
        )}
      </div>
      <nav className="nav">
        {items.map(it => (
          <button key={it.id}
            className={"nav-item" + (page === it.id ? " active" : "")}
            onClick={() => setPage(it.id)}
            title={collapsed ? it.label : ""}>
            <Icon name={it.icon} size={16}/>
            {!collapsed && <span>{it.label}</span>}
          </button>
        ))}
      </nav>
      <div className="sidebar-footer">
        <div className={"deploy-mode " + (collapsed ? "collapsed" : "")}>
          {!collapsed && <span className="dim" style={{fontSize:"var(--fs-xs)"}}>部署模式</span>}
          <span className="pill accent dot">{collapsed ? "MVP" : "免費 MVP · CF"}</span>
        </div>
      </div>
    </aside>
  );
};

const TopBar = ({ theme, setTheme, user, onSignOut, goChart }) => {
  const [menuOpen, setMenuOpen] = React.useState(false);
  const [refreshing, setRefreshing] = React.useState(false);
  const refreshTimerRef = React.useRef(null);
  React.useEffect(() => {
    if (!menuOpen) return;
    const close = () => setMenuOpen(false);
    document.addEventListener("click", close);
    return () => document.removeEventListener("click", close);
  }, [menuOpen]);
  React.useEffect(() => () => {
    if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
  }, []);
  const [now, setNow] = React.useState(() => new Date());
  React.useEffect(() => {
    const id = setInterval(() => setNow(new Date()), 1000);
    return () => clearInterval(id);
  }, []);

  /* 搜尋 */
  const [q, setQ] = React.useState("");
  const [searchOpen, setSearchOpen] = React.useState(false);
  const [activeIdx, setActiveIdx] = React.useState(0);
  const [twUniverse, setTwUniverse] = React.useState([]);
  const searchRef = React.useRef(null);
  const inputRef = React.useRef(null);

  /* 非阻塞載入全台股 universe（首次有 token 才會成功，24h 快取）*/
  React.useEffect(() => {
    if (!window.FinMind || !window.FinMind.getTaiwanUniverse) return;
    window.FinMind.getTaiwanUniverse()
      .then((list) => setTwUniverse(list || []))
      .catch(() => {});
  }, []);

  const universe = React.useMemo(() => {
    const map = new Map();
    (window.MOCK?.WATCHLIST || []).forEach((s) => map.set(s.sym, s));
    twUniverse.forEach((s) => { if (!map.has(s.sym)) map.set(s.sym, s); });
    return Array.from(map.values());
  }, [twUniverse]);

  const candidates = React.useMemo(() => {
    const qq = q.trim().toLowerCase();
    if (!qq) return [];
    const matched = universe
      .filter((s) => s.sym.toLowerCase().includes(qq) || (s.name || "").toLowerCase().includes(qq))
      .slice(0, 8);
    const raw = q.trim();
    const isUsTicker = /^[A-Za-z]{1,5}$/.test(raw);
    const isTwTicker = /^\d{4,6}$/.test(raw);
    if (matched.length === 0 && (isUsTicker || isTwTicker)) {
      const sym = isUsTicker ? raw.toUpperCase() : raw + ".TW";
      return [{ sym, name: "未在清單，直接開圖", market: isUsTicker ? "US" : "TW", synthetic: true }];
    }
    return matched;
  }, [q, universe]);

  const resolveSym = (raw) => {
    const s = (raw || "").trim();
    if (!s) return null;
    if (s.includes(".")) return s.toUpperCase();
    if (/^\d+$/.test(s)) return s + ".TW";
    return s.toUpperCase();
  };

  const submitSearch = (override) => {
    if (!goChart) return;
    if (override) { goChart(override); }
    else if (candidates[activeIdx]) { goChart(candidates[activeIdx].sym); }
    else {
      const sym = resolveSym(q);
      if (sym) goChart(sym);
    }
    setQ("");
    setSearchOpen(false);
    setActiveIdx(0);
    inputRef.current && inputRef.current.blur();
  };

  React.useEffect(() => { setActiveIdx(0); }, [q]);

  React.useEffect(() => {
    if (!searchOpen) return;
    const close = (e) => {
      if (!searchRef.current) return;
      if (!searchRef.current.contains(e.target)) setSearchOpen(false);
    };
    document.addEventListener("mousedown", close);
    return () => document.removeEventListener("mousedown", close);
  }, [searchOpen]);

  /* ⌘K / Ctrl+K 聚焦搜尋 */
  React.useEffect(() => {
    const handler = (e) => {
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
        e.preventDefault();
        inputRef.current && inputRef.current.focus();
        setSearchOpen(true);
      }
    };
    document.addEventListener("keydown", handler);
    return () => document.removeEventListener("keydown", handler);
  }, []);

  const fmtHm = (tz) => now.toLocaleTimeString("zh-TW", { hour12: false, hour: "2-digit", minute: "2-digit", timeZone: tz });
  const fmtHms = (tz) => now.toLocaleTimeString("zh-TW", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit", timeZone: tz });

  const twTrading = window.Intraday?.isTradingHoursTW?.() ?? false;
  const usTrading = window.Intraday?.isTradingHoursUS?.() ?? false;
  const triggerRefresh = () => {
    if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
    setRefreshing(true);
    window.dispatchEvent(new CustomEvent(MANUAL_REFRESH_EVENT));
    refreshTimerRef.current = setTimeout(() => setRefreshing(false), 900);
  };

  return (
    <header className="topbar">
      <div className="search-wrap" ref={searchRef} style={{ position: "relative" }}>
        <div className="search">
          <Icon name="search" size={14}/>
          <input ref={inputRef}
            className="search-input"
            placeholder="搜尋代號或名稱 — 2330 / NVDA / 台積電"
            value={q}
            onChange={(e) => { setQ(e.target.value); setSearchOpen(true); }}
            onFocus={() => setSearchOpen(true)}
            onKeyDown={(e) => {
              if (e.key === "Enter") { e.preventDefault(); submitSearch(); }
              else if (e.key === "Escape") { setSearchOpen(false); inputRef.current.blur(); }
              else if (e.key === "ArrowDown") { e.preventDefault(); setActiveIdx((i) => Math.min(candidates.length - 1, i + 1)); }
              else if (e.key === "ArrowUp") { e.preventDefault(); setActiveIdx((i) => Math.max(0, i - 1)); }
            }}/>
          <kbd>⌘K</kbd>
        </div>
        {searchOpen && q.trim() && (
          <div className="search-dropdown surface" style={{
            position: "absolute", top: "calc(100% + 6px)", left: 0, right: 0,
            zIndex: 50, padding: 4,
            border: "1px solid var(--border)", borderRadius: "var(--radius)",
            background: "var(--bg-elevated)", boxShadow: "0 10px 28px rgba(0,0,0,0.4)",
          }}>
            {candidates.length > 0 ? candidates.map((s, i) => (
              <button key={s.sym}
                onMouseDown={(e) => { e.preventDefault(); submitSearch(s.sym); }}
                onMouseEnter={() => setActiveIdx(i)}
                data-color-mode={s.market === "US" ? "us" : "tw"}
                style={{
                  display: "flex", alignItems: "center", gap: 10, width: "100%",
                  padding: "8px 10px", border: 0, borderRadius: 4,
                  background: i === activeIdx ? "var(--bg-app)" : "transparent",
                  color: "var(--text-1)", cursor: "pointer", textAlign: "left",
                  fontFamily: "inherit", fontSize: "var(--fs-sm)",
                }}>
                <span className="mono" style={{ fontWeight: 600, minWidth: 72 }}>{s.sym}</span>
                <span style={{ flex: 1 }}>{s.name}</span>
                <span className="pill" style={{ fontSize: 9 }}>{s.market === "TW" ? "TWSE" : "NASDAQ"}</span>
                {typeof s.pct === "number" && (
                  <span className={"num mono " + (s.pct >= 0 ? "up" : "down")} style={{ minWidth: 56, textAlign: "right" }}>
                    {s.pct >= 0 ? "+" : ""}{s.pct.toFixed(2)}%
                  </span>
                )}
              </button>
            )) : (
              <button onMouseDown={(e) => { e.preventDefault(); submitSearch(); }}
                style={{
                  display: "flex", alignItems: "center", gap: 10, width: "100%",
                  padding: "8px 10px", border: 0, borderRadius: 4,
                  background: "var(--bg-app)", color: "var(--text-1)", cursor: "pointer",
                  textAlign: "left", fontFamily: "inherit", fontSize: "var(--fs-sm)",
                }}>
                <Icon name="external" size={12}/>
                <span style={{ flex: 1 }}>直接查 <strong className="mono">{resolveSym(q)}</strong></span>
                <kbd style={{ fontSize: 9 }}>Enter</kbd>
              </button>
            )}
          </div>
        )}
      </div>
      <div className="topbar-spacer"/>
      <div className="topbar-status">
        <span className="status-item">
          <span className={"dot " + (twTrading ? "ok" : "off")}/>
          {twTrading ? `台股盤中 · ${fmtHm("Asia/Taipei")}` : "台股休市"}
        </span>
        <span className="status-item">
          <span className={"dot " + (usTrading ? "ok" : "off")}/>
          {usTrading ? `美股盤中 · ${fmtHm("America/New_York")} ET` : "美股休市"}
        </span>
        <span className="status-item dim">現在 {fmtHms("Asia/Taipei")}</span>
      </div>
      <div className="topbar-actions">
        <button className="btn ghost icon-btn" onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
          title="Toggle theme">
          <Icon name={theme === "dark" ? "sun" : "moon"} size={15}/>
        </button>
        <button
          className={"btn ghost icon-btn refresh-btn" + (refreshing ? " refreshing" : "")}
          onClick={triggerRefresh}
          disabled={refreshing}
          title={refreshing ? "更新中" : "刷新即時報價"}
          aria-label="刷新即時報價">
          <Icon name="refresh" size={15}/>
        </button>
        {user && (
          <div className="user-menu" onClick={(e) => e.stopPropagation()}>
            <button className="user-avatar-btn" title={user.email || user.displayName || ""}
              onClick={() => setMenuOpen((o) => !o)}>
              {user.photoURL
                ? <img src={user.photoURL} alt="" className="user-avatar"/>
                : <div className="user-avatar fallback">{(user.displayName || user.email || "?")[0].toUpperCase()}</div>}
            </button>
            {menuOpen && (
              <div className="user-menu-pop surface">
                <div className="user-menu-head">
                  <div className="user-menu-name">{user.displayName || "(無名)"}</div>
                  <div className="user-menu-email dim mono">{user.email}</div>
                </div>
                <button className="user-menu-item" onClick={() => { setMenuOpen(false); onSignOut(); }}>
                  <Icon name="external" size={12}/> 登出
                </button>
              </div>
            )}
          </div>
        )}
      </div>
    </header>
  );
};

/* 大盤指數透過 worker /quote 反代 Yahoo（^ 符號 Yahoo 支援）。
 * 閉市時 Yahoo 仍會回最後一筆收盤值與 regularMarketTime，所以「沒即時就抓最新一筆」由 Yahoo 端自然處理。 */
const MARKET_INDEX_SYMS = [
  { sym: "^TWII", region: "TW", name: "台灣加權" },
  { sym: "^GSPC", region: "US", name: "S&P 500" },
  { sym: "^IXIC", region: "US", name: "Nasdaq" },
  { sym: "^DJI",  region: "US", name: "Dow 30" },
];

const fmtAsOf = (ts, region) => {
  if (!ts) return "";
  const d = new Date(ts);
  const tz = region === "US" ? "America/New_York" : "Asia/Taipei";
  const date = d.toLocaleDateString("zh-TW", { timeZone: tz, month: "2-digit", day: "2-digit" });
  const time = d.toLocaleTimeString("zh-TW", { timeZone: tz, hour: "2-digit", minute: "2-digit", hour12: false });
  return `${date} ${time}`;
};

const MarketSummaryBar = ({ data }) => {
  const explicit = Array.isArray(data) ? data : null;
  const [auto, setAuto] = React.useState({ items: [], error: null, loading: true });

  React.useEffect(() => {
    if (explicit) return;
    if (!window.Intraday || !window.Intraday.isEnabled()) {
      setAuto({ items: [], error: "尚未設定即時報價 Worker（設定 → Worker URL）", loading: false });
      return;
    }
    let cancelled = false;
    let inFlight = false;
    const syms = MARKET_INDEX_SYMS.map((x) => x.sym);

    const refresh = async () => {
      if (inFlight) return;
      inFlight = true;
      setAuto((prev) => ({ ...prev, error: null, loading: true }));
      try {
        const { quotes } = await window.Intraday.getQuotes(syms);
        if (cancelled) return;
        const items = [];
        for (const def of MARKET_INDEX_SYMS) {
          const q = quotes.get(def.sym);
          if (!q || q.price == null || q.prevClose == null) continue;
          const chg = q.price - q.prevClose;
          const pct = q.prevClose ? (chg / q.prevClose) * 100 : 0;
          items.push({ ...def, price: q.price, chg, pct, ts: q.ts });
        }
        setAuto({
          items,
          error: items.length === 0 ? "大盤指數抓取失敗（Yahoo 無回應）" : null,
          loading: false,
        });
      } catch (e) {
        if (!cancelled) setAuto({ items: [], error: e.message, loading: false });
      } finally {
        inFlight = false;
      }
    };

    refresh();
    window.addEventListener(MANUAL_REFRESH_EVENT, refresh);
    const tw = window.Intraday.isTradingHoursTW?.();
    const us = window.Intraday.isTradingHoursUS?.();
    const timer = (tw || us) ? setInterval(refresh, 30000) : null;
    return () => {
      cancelled = true;
      window.removeEventListener(MANUAL_REFRESH_EVENT, refresh);
      if (timer) clearInterval(timer);
    };
  }, [explicit]);

  const items = explicit || auto.items;
  if (items.length === 0) {
    const msg = auto.loading
      ? "— 載入大盤指數中…"
      : `— ${auto.error || "大盤指數資料源未就緒"}`;
    return (
      <div className="market-bar empty" style={{padding:"14px 16px", color:"var(--text-3)", fontSize:"var(--fs-sm)"}}>
        {msg}
      </div>
    );
  }
  return (
    <div className="market-bar">
      {items.map((m, i) => {
        const mode = m.region === "TW" ? "tw" : (m.region === "US" ? "us" : "tw");
        const cls = m.pct >= 0 ? "up" : "down";
        return (
          <div className="market-tile" key={i} data-color-mode={mode}>
            <div className="market-tile-head">
              <span className="market-tile-name">{m.name}</span>
              <span className="market-tile-region dim">{m.region}</span>
            </div>
            <div className="market-tile-row">
              <span className="num market-tile-price">{fmt(m.price, 2)}</span>
            </div>
            <div className="market-tile-row sub">
              <span className={"num " + cls}>{m.chg >= 0 ? "+" : ""}{fmt(m.chg, 2)}</span>
              <span className={"num " + cls}>{m.pct >= 0 ? "+" : ""}{fmt(m.pct, 2)}%</span>
            </div>
            {m.ts && (
              <div className="market-tile-asof dim mono">截至 {fmtAsOf(m.ts, m.region)}</div>
            )}
          </div>
        );
      })}
    </div>
  );
};

window.Shell = { Sidebar, TopBar, MarketSummaryBar };
