// ============================================================================
// Live positions table + Backtest summary + DB health + Ledger feed
// ============================================================================

const tableFocusClass = (ticket, activeTicket) => {
  if (activeTicket == null) return "";
  return String(ticket) === String(activeTicket) ? "tb-row-focused" : "tb-row-dimmed";
};

const PositionsTable = ({ rows, activeTicket, onHoverTicket, onLeaveTicket, onToggleTicket }) => {
  return (
    <div className="tb-table-wrap tb-positions-table-wrap">
      <table className="tb-table tb-positions-table">
        <thead>
          <tr>
            <th>티켓</th>
            <th>종목</th>
            <th>방향</th>
            <th className="r">Lot</th>
            <th className="r">진입가</th>
            <th className="r">현재가</th>
            <th className="r">손익</th>
            <th>시간</th>
            <th>청산까지</th>
            <th>소스</th>
            <th>코멘트</th>
          </tr>
        </thead>
        <tbody>
          {rows.map((p) => {
            const exit = p.exitTime || {};
            const gridParent = p.gridParent || exit.gridParent || {};
            const virtualParentMeta = gridParent.isVirtual
              ? [
                  gridParent.virtualLabel || "가상부모",
                  gridParent.registeredAt ? `등록 ${fmt.time(gridParent.registeredAt)}` : "",
                  gridParent.lifespanEndAt ? `만료 ${fmt.time(gridParent.lifespanEndAt)}` : "",
                ].filter(Boolean).join(" · ")
              : "";
            const remain = exit.remainingSec;
            const exitTone = exit.status === "unknown"
              ? "pnl-flat"
              : remain <= 0 || remain < 3600
                ? "pnl-neg"
                : remain < 4 * 3600
                  ? "pnl-flat"
                  : "pnl-pos";
            return (
              <tr
                key={p.ticket}
                className={`tb-row-interactive ${tableFocusClass(p.ticket, activeTicket)}`}
                onMouseEnter={() => onHoverTicket?.(p.ticket)}
                onMouseLeave={() => onLeaveTicket?.()}
                onClick={() => onToggleTicket?.(p.ticket)}
              >
                <td className="tb-meta" data-label="티켓">#{p.ticket}</td>
                <td className="tb-strong" data-label="종목">{p.symbol}</td>
                <td data-label="방향">
                  <Badge kind={p.direction === "long" ? "success" : "danger"} solid>
                    {p.direction === "long" ? "롱" : "숏"}
                  </Badge>
                </td>
                <td className="r" data-label="Lot">{fmt.num(p.lots, 2)}</td>
                <td className="r" data-label="진입가">{fmt.price(p.entry, p.symbol === "XAUUSD" ? 2 : 5)}</td>
                <td className="r" data-label="현재가">{fmt.price(p.current, p.symbol === "XAUUSD" ? 2 : 5)}</td>
                <td className={`r ${fmt.pnlClass(p.pnl)}`} data-label="손익">{fmt.pnl(p.pnl)}</td>
                <td className="tb-meta" data-label="시간">{fmt.time(p.openedAt)}</td>
                <td className={`tb-exit-cell ${exitTone}`} data-label="청산까지" title={exit.targetAt ? `예상 청산 ${fmt.date(exit.targetAt)}` : ""}>
                  <div className="mono">{remain == null ? "—" : fmt.duration(remain)}</div>
                  <div className="tb-meta">{exit.targetAt ? `청산 ${fmt.time(exit.targetAt)}` : "청산 —"}</div>
                  <div className="tb-meta">{exit.rule || "—"}</div>
                </td>
                <td className="tb-meta" data-label="소스">
                  <div>{p.source}</div>
                  {virtualParentMeta ? <div>{gridParent.virtualLabel}</div> : null}
                </td>
                <td className="tb-meta tb-truncate" data-label="코멘트">
                  <div>{p.comment}</div>
                  {virtualParentMeta ? <div>{virtualParentMeta}</div> : null}
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
};

const BacktestSummary = ({ data, finalLabel = "최종 자산", returnLabel = "실시간 실효 수익률" }) => {
  const { backtest, rejectReasons } = data;
  const reasons = rejectReasons || [];
  const summaryOnly = Boolean(backtest.disabled || backtest.summary_only);
  const finalEquity = backtest.final_equity;
  return (
    <div className="tb-bt-grid">
      <div className="tb-bt-stat">
        <div className="label-caps">{finalLabel}</div>
        <div className="num-lg">{fmt.abbr(finalEquity, 2)}{finalEquity != null && <span className="tb-kpi-unit">USD</span>}</div>
        {summaryOnly && <div className="tb-meta" style={{ marginTop: 4 }}>요약값</div>}
      </div>
      <div className="tb-bt-stat">
        <div className="label-caps">최대 낙폭</div>
        <div className="num-lg pnl-neg">{fmt.pct(backtest.max_drawdown_pct)}</div>
      </div>
      <div className="tb-bt-stat">
        <div className="label-caps">승인</div>
        <div className="num-lg pnl-pos">{fmt.abbr(backtest.accepted_count, 1)}</div>
      </div>
      <div className="tb-bt-stat">
        <div className="label-caps">거절</div>
        <div className="num-lg pnl-neg">{backtest.rejected_count == null ? "—" : backtest.rejected_count}</div>
      </div>
      <div className="tb-bt-stat">
        <div className="label-caps">{returnLabel}</div>
        <div className="num-lg pnl-pos">{fmt.pct(backtest.total_live_effective_return_pct)}</div>
      </div>
      <div className="tb-bt-reasons">
        <div className="label-caps" style={{ marginBottom: 8 }}>거절 사유</div>
        {summaryOnly && !reasons.length && <div className="tb-meta">상세 로딩 비활성 · report 요약만 표시</div>}
        {!summaryOnly && !reasons.length && <div className="tb-meta">선택 구간의 거절 사유 상세는 아직 집계되지 않았습니다.</div>}
        {reasons.map((r) => {
          const max = Math.max(1, ...reasons.map((x) => x.count));
          return (
            <div className="tb-bt-reason" key={r.reason}>
              <span className="mono tb-bt-reason-name">{r.reason}</span>
              <span className="tb-bt-reason-bar">
                <span style={{ width: `${(r.count / max) * 100}%` }} />
              </span>
              <span className="mono tb-bt-reason-count">{r.count}</span>
            </div>
          );
        })}
      </div>
    </div>
  );
};

// DBHealth — flat list across all bar tables (table + symbol per row)
const DBHealth = ({ rows }) => {
  // rows = { bars5m, bars15m, bars1h } object — flatten with table label
  const tables = [
    { id: "bars5m",  label: "market_bars_5m" },
    { id: "bars15m", label: "market_bars_15m" },
    { id: "bars1h",  label: "market_bars_1h" },
  ];
  // collapse to one entry per table = the worst-status symbol
  const flat = tables.map((t) => {
    const list = rows[t.id] || [];
    const worstRow = list.reduce((w, r) => {
      const rank = { ok: 0, warn: 1, fail: 2 };
      return rank[r.status] > rank[w?.status ?? "ok"] ? r : (w || r);
    }, null);
    return { table: t.label, ...worstRow };
  });

  return (
    <div className="tb-health-list">
      {flat.map((r, i) => (
        <div key={i} className={`tb-health-row tb-health-${r.status}`}>
          <span className={`tb-health-icon tone-${r.status}`}>
            <Icon name={r.status === "ok" ? "check" : r.status === "warn" ? "alert" : "x"} size={14} />
          </span>
          <span className="tb-health-name">
            <span className="mono tb-strong">{r.table}</span>
            <span className="tb-meta">  ·  {r.symbol}</span>
          </span>
          <span className="mono tb-meta" style={{ color: r.status === "fail" ? "var(--danger-fg)" : r.status === "warn" ? "var(--warn-fg)" : "var(--fg-2)" }}>
            {fmt.time(r.lastTs)}
          </span>
          <span className="tb-meta tb-health-ago">{fmt.ago(r.lastTs)}</span>
        </div>
      ))}
    </div>
  );
};

const LedgerFeed = ({ events }) => {
  return (
    <div className="tb-ledger">
      {events.map((e, i) => (
        <div key={i} className="tb-ledger-row">
          <span className="mono tb-meta tb-ledger-time">{fmt.time(e.ts)}</span>
          <span className={`tb-ledger-dot tone-${e.level === "fail" ? "fail" : e.level === "warn" ? "warn" : "info"}`}>
            <Dot status={e.level === "fail" ? "fail" : e.level === "warn" ? "warn" : "info"} />
          </span>
          <span className="tb-ledger-msg">{e.msg}</span>
          <span className="mono tb-meta tb-ledger-detail">{e.detail}</span>
        </div>
      ))}
    </div>
  );
};

const EXIT_DIAG_LABELS = {
  stale_signal_drop: "오래된 신호",
  consec_zero: "연패 lot=0",
  structural_block: "구조적 차단",
  bt_expected_exit_but_live_still_open: "현재 보유",
  bt_expected_exit_not_seen_live: "라이브 진입/청산 없음",
  live_external_overrides_bt: "외부청산 우선",
  live_external_uncomparable: "외부청산 단독",
  live_closed_without_bt_exit: "Live 청산만",
  pre_compare_window_live_exit: "비교 전 진입 청산",
  exit_reason_mismatch: "사유 다름",
  exit_count_mismatch: "개수 다름",
};

const exitDiagnosisBreakdown = (analysis) => {
  const counts = {};
  (analysis?.details || []).forEach((row) => {
    if (row?.intended) return;
    const key = row?.diagnosis || "unknown";
    counts[key] = (counts[key] || 0) + 1;
  });
  return Object.entries(counts)
    .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
    .map(([key, count]) => `${EXIT_DIAG_LABELS[key] || key} ${fmt.num(count, 0)}`)
    .join(" / ");
};

const ENTRY_DIAG_LABELS = {
  duplicate_cooldown: "중복/쿨다운",
  stale_signal_drop: "오래된 신호 탈락",
  structural_block: "구조적 차단",
  live_gate_skip: "Live gate 탈락",
  unexplained_bt_only: "미분류 BT-only",
};

const EXIT_GUARD_LABELS = {
  stale_signal_drop: "오래된 신호",
  consec_zero: "연패 lot=0",
  structural_block: "구조적 차단",
  pre_compare_window_live_exit: "비교 전 진입 청산",
};

const entryGuardBreakdown = (analysis) => {
  const details = (analysis?.details || []).filter((row) => row?.intended);
  if (!details.length) return "";
  return details.map((row) => {
    const key = (row.key || []).join(" ");
    const label = ENTRY_DIAG_LABELS[row.diagnosis] || row.diagnosis || "guard";
    const tickets = (row.openPositions || []).map((p) => p.ticket).filter(Boolean).join(",");
    const cooldown = row.cooldownUntil ? ` · until ${fmt.time(row.cooldownUntil)}` : "";
    return `${label}: ${key}${tickets ? ` · ticket ${tickets}` : ""}${cooldown}`;
  }).join(" / ");
};

const entryMismatchBreakdown = (analysis) => {
  const details = (analysis?.details || []).filter((row) => !row?.intended);
  if (!details.length) return "";
  return details.map((row) => {
    const key = (row.key || []).join(" ");
    const label = ENTRY_DIAG_LABELS[row.diagnosis] || row.diagnosis || "진입 미매칭";
    const source = row.guardDetail?.source ? ` · ${row.guardDetail.source}` : "";
    return `${label}: ${key}${source}`;
  }).join(" / ");
};

const exitGuardBreakdown = (analysis) => {
  const details = (analysis?.details || []).filter((row) => row?.intended);
  if (!details.length) return "";
  return details.map((row) => {
    const key = (row.key || []).join(" ");
    const label = EXIT_GUARD_LABELS[row.diagnosis] || row.diagnosis || "guard";
    const source = row.guardDetail?.source ? ` · ${row.guardDetail.source}` : "";
    return `${label}: ${key}${source}`;
  }).join(" / ");
};

const atomicCycleBreakdown = (atomic) => {
  if (!atomic) return "";
  const counts = atomic.counts || {};
  const parts = [`상태 ${atomic.status || "unknown"}`];
  const pendingDue = Number(atomic.pendingDueCount || 0);
  const retryWait = Number(atomic.retryScheduledCount || 0);
  const processing = Number(counts.processing || 0);
  const staleProcessing = Number(atomic.staleProcessingCount || 0);
  const failedTotal = Number(atomic.failedCount || counts.failed || 0);
  const hasRecentFailedCount = atomic.recentFailedCount != null;
  const failedRecent = Number(hasRecentFailedCount ? atomic.recentFailedCount : failedTotal);
  if (pendingDue) parts.push(`대기 ${fmt.num(pendingDue, 0)}`);
  if (retryWait) parts.push(`재시도대기 ${fmt.num(retryWait, 0)}`);
  if (processing) parts.push(`처리중 ${fmt.num(processing, 0)}`);
  if (staleProcessing) parts.push(`stale ${fmt.num(staleProcessing, 0)}`);
  if (failedRecent) parts.push(`최근실패 ${fmt.num(failedRecent, 0)}`);
  else if (!hasRecentFailedCount && failedTotal) parts.push(`실패 ${fmt.num(failedTotal, 0)}`);
  const recentError = (atomic.recentWarnFailures || [])[0]?.error
    || (failedRecent ? (atomic.recentFailures || [])[0]?.error : "");
  if (recentError) parts.push(`최근 오류 ${String(recentError).slice(0, 90)}`);
  return parts.join(" / ");
};

const splitCompareDetails = (text) => String(text || "")
  .split(" / ")
  .map((item) => item.trim())
  .filter(Boolean);

const CompareDetailRow = ({ label, text }) => {
  const items = splitCompareDetails(text);
  if (!items.length) return null;
  return (
    <div className="tb-compare-detail-row">
      <span className="label-caps tb-compare-detail-label">{label}</span>
      <ul className="tb-compare-detail-list">
        {items.map((item, idx) => <li key={`${label}-${idx}`}>{item}</li>)}
      </ul>
    </div>
  );
};

const emptyEventTotals = { bt: 0, live: 0, matched: 0, btOnly: 0, liveOnly: 0, intendedBtOnly: 0 };
const eventBtCount = (totals = emptyEventTotals) => (
  totals.comparableBt ?? totals.bt ?? 0
);
const eventLiveCount = (totals = emptyEventTotals) => (
  totals.comparableLive ?? totals.live ?? 0
);
const eventTotalDiff = (totals = emptyEventTotals) => eventLiveCount(totals) - eventBtCount(totals);
const eventMismatchCount = (totals = emptyEventTotals) => (
  (totals.btOnly || 0) + (totals.liveOnly || 0)
);

const CompareEventPair = ({ label, totals = emptyEventTotals, diagnostic = false }) => {
  const btCount = eventBtCount(totals);
  const liveCount = eventLiveCount(totals);
  const diff = eventTotalDiff(totals);
  const mismatches = eventMismatchCount(totals);
  const aligned = diff === 0 && mismatches === 0;
  const rawBt = totals.rawBt ?? totals.bt ?? 0;
  const rawLive = totals.rawLive ?? totals.live ?? 0;
  const showRaw = rawBt !== btCount || rawLive !== liveCount;
  return (
    <div>
      <div className="label-caps">{label}</div>
      <div className="tb-event-pair">
        <span>BT {fmt.num(btCount, 0)}</span>
        <span>Live {fmt.num(liveCount, 0)}</span>
      </div>
      <div className={`tb-event-diff ${aligned ? "tone-success" : diagnostic ? "" : "tone-warn"}`}>
        차이 {diff > 0 ? "+" : ""}{fmt.num(diff, 0)}
        {mismatches ? ` · ${diagnostic ? "참고차이" : "미매칭"} ${fmt.num(mismatches, 0)}` : " · 일치"}
      </div>
      {showRaw && (
        <div className="tb-event-raw">
          원천 BT {fmt.num(rawBt, 0)} / Live {fmt.num(rawLive, 0)}
        </div>
      )}
    </div>
  );
};

const CompareOrderDiagnostics = ({ order }) => {
  if (!order || !order.totals) return null;
  const totals = order.totals || {};
  const hasModifyActivity = (r) => (
    (r.modifyRequests || 0) + (r.modifySucceeded || 0) + (r.modifyRejected || 0) + (r.modifyNoop || 0) + (r.modifyDeferred || 0)
  ) > 0;
  const modifySummary = (r, prefix = "수정") => (
    `${prefix} 시도 ${fmt.num(r.modifyRequests || 0, 0)} 성공 ${fmt.num(r.modifySucceeded || 0, 0)} 실패 ${fmt.num(r.modifyRejected || 0, 0)} 무변경 ${fmt.num(r.modifyNoop || 0, 0)} 보류 ${fmt.num(r.modifyDeferred || 0, 0)}`
  );
  const formatRoleRow = (r) => {
    if (r.role === "modify") return `${r.label || r.role} ${modifySummary(r, "").trim()}`;
    const base = `${r.label || r.role} 신호 ${fmt.num(r.signals || 0, 0)} 요청 ${fmt.num(r.brokerRequests || 0, 0)} 체결 ${fmt.num(r.opened || 0, 0)} 실패 ${fmt.num((r.brokerRejected || 0) + (r.preBrokerSkipped || 0), 0)}`;
    return hasModifyActivity(r) ? `${base} ${modifySummary(r)}` : base;
  };
  const formatSymbolRow = (r) => {
    const label = r.label || r.symbol;
    const base = `${label} 신호 ${fmt.num(r.signals || 0, 0)} 요청 ${fmt.num(r.brokerRequests || 0, 0)} 체결 ${fmt.num(r.opened || 0, 0)} 실패 ${fmt.num((r.brokerRejected || 0) + (r.preBrokerSkipped || 0), 0)}`;
    return hasModifyActivity(r) ? `${base} ${modifySummary(r)}` : base;
  };
  const formatRecent = (r) => {
    const parts = [
      r.symbol,
      r.role ? `[${r.role}]` : "",
      r.event,
    ].filter(Boolean);
    if (r.lots !== null && r.lots !== undefined) parts.push(`${fmt.num(r.lots, 4)}lot`);
    if (r.requestedSl !== null && r.requestedSl !== undefined) parts.push(`요청SL ${fmt.num(r.requestedSl, 5)}`);
    if (r.attemptedSl !== null && r.attemptedSl !== undefined && r.attemptedSl !== r.requestedSl) parts.push(`시도SL ${fmt.num(r.attemptedSl, 5)}`);
    if (r.volumeMin) parts.push(`min${fmt.num(r.volumeMin, 3)}`);
    if (r.retcode) parts.push(`ret${r.retcode}`);
    if (r.reason) parts.push(r.reason);
    return parts.join(" ");
  };
  const roleRows = (order.byRole || []).filter((r) => (r.brokerRequests || r.opened || r.brokerRejected || r.preBrokerSkipped || r.signals || hasModifyActivity(r))).slice(0, 5);
  const symbolRows = (order.bySymbol || []).slice(0, 4);
  const reasons = (order.byReason || []).slice(0, 4);
  const recent = (order.recent || []).slice(0, 4);
  return (
    <div className="tb-order-diag">
      <div>
        <div className="label-caps">부모신호</div>
        <div className="tb-order-num">{fmt.num(totals.signals || 0, 0)}</div>
        {(totals.allSignals || 0) > (totals.signals || 0) && (
          <div className="tb-event-raw">전체 {fmt.num(totals.allSignals || 0, 0)}</div>
        )}
      </div>
      <div>
        <div className="label-caps">실제 요청</div>
        <div className="tb-order-num">{fmt.num(totals.brokerRequests || 0, 0)}</div>
      </div>
      <div>
        <div className="label-caps">체결</div>
        <div className="tb-order-num tone-success">{fmt.num(totals.opened || 0, 0)}</div>
      </div>
      <div>
        <div className="label-caps">리젝</div>
        <div className={`tb-order-num ${(totals.brokerRejected || 0) ? "tone-fail" : "tone-success"}`}>
          {fmt.num(totals.brokerRejected || 0, 0)}
        </div>
      </div>
      <div>
        <div className="label-caps">Lot 스킵</div>
        <div className={`tb-order-num ${(totals.preBrokerSkipped || 0) ? "tone-warn" : "tone-success"}`}>
          {fmt.num(totals.preBrokerSkipped || 0, 0)}
        </div>
        <div className="tb-event-raw">
          min {fmt.num(totals.lotRejected || 0, 0)} / zero {fmt.num(totals.lotZeroSkipped || 0, 0)}
        </div>
      </div>
      {roleRows.length > 0 && (
        <div className="tb-order-symbols tb-meta">
          <span className="label-caps">구분별</span>
          {roleRows.map((r) => (
            <span className="tb-order-pill" key={r.role || r.label}>
              {formatRoleRow(r)}
            </span>
          ))}
        </div>
      )}
      {symbolRows.length > 0 && (
        <div className="tb-order-symbols tb-meta">
          <span className="label-caps">심볼별</span>
          {symbolRows.map((r) => (
            <span className="tb-order-pill" key={r.symbol}>
              {formatSymbolRow(r)}
            </span>
          ))}
        </div>
      )}
      {(reasons.length > 0 || recent.length > 0) && (
        <div className="tb-order-detail tb-meta">
          {reasons.length > 0 && (
            <span>
              사유 {reasons.map((r) => `${r.reason}${r.retcode ? `(${r.retcode})` : ""} ${fmt.num(r.count, 0)}`).join(" · ")}
            </span>
          )}
          {recent.length > 0 && (
            <span>
              최근 {recent.map(formatRecent).join(" · ")}
            </span>
          )}
        </div>
      )}
    </div>
  );
};

const CompareCard = ({ compare, orphan }) => {
  const tone = compare.status;
  const isPhaseMetric = compare.metric === "phases";
  const isSignalMetric = compare.metric === "signals" || isPhaseMetric;
  const phases = compare.phases || null;
  const eventClassTotals = compare.eventClassTotals || {};
  const parentTotals = compare.parentTotals || eventClassTotals.parent || compare.phaseTotals || emptyEventTotals;
  const childTotals = compare.childTotals || eventClassTotals.child || compare.diagnosticTotals || emptyEventTotals;
  const referenceTotals = compare.referenceTotals || eventClassTotals.reference || emptyEventTotals;
  const phaseText = phases
    ? [
        ["부모 진입", phases.entry],
        ["부모 청산", phases.exit],
        ["자식 진입", phases.grid_entry],
        ["자식 청산", phases.grid_exit],
        ["보조 진입", phases.aux_entry],
        ["보조 청산", phases.aux_exit],
        ["Trail", phases.trailing],
      ]
        .filter(([, p]) => p)
        .map(([label, p]) => {
          // 백엔드가 무거운 trade 배열을 *Count 로 슬림화함(없으면 .length 폴백 — 구버전 호환).
          const cnt = (arrKey, cntKey) => (p[cntKey] != null ? p[cntKey] : (p[arrKey] || []).length);
          const intended = cnt("intended_bt_only", "intendedBtOnlyCount");
          return `${label}${p.diagnosticOnly ? "(참고)" : ""} M${cnt("matched", "matchedCount")} B${cnt("bt_only", "btOnlyCount")} L${cnt("live_only", "liveOnlyCount")}${intended ? ` I${intended}` : ""}`;
        })
        .join(" · ")
    : "";
  const entryAnalysis = phases?.entry?.analysis;
  const entryGuardText = entryGuardBreakdown(entryAnalysis);
  const entryMismatchText = entryMismatchBreakdown(entryAnalysis);
  const exitAnalysis = phases?.exit?.analysis;
  const exitGuardText = exitGuardBreakdown(exitAnalysis);
  const excludedLiveExits = exitAnalysis?.excludedLiveCount || 0;
  const exitAnalysisText = exitAnalysis
    ? `청산 분석 ${fmt.num(exitAnalysis.detailCount || 0, 0)}건${excludedLiveExits ? ` · 외부제외 ${fmt.num(excludedLiveExits, 0)}건` : ""}`
    : "";
  const exitBreakdownText = exitDiagnosisBreakdown(exitAnalysis);
  const atomicCycleText = atomicCycleBreakdown(compare.atomicCycle);
  const parentTrace = compare.parentChildTrace || {};
  const parentTraceText = parentTrace.parentCount
    ? `부모 ${fmt.num(parentTrace.parentCount, 0)} · 가상 ${fmt.num(parentTrace.virtualParentCount || 0, 0)} · Grid ${fmt.num(parentTrace.gridOpenCount || 0, 0)}개설/${fmt.num(parentTrace.activeGridLayerCount || 0, 0)}활성 · Split ${fmt.num(parentTrace.splitOpenCount || 0, 0)}개설/${fmt.num(parentTrace.activeSplitChildCount || 0, 0)}활성 · Trail ${fmt.num(parentTrace.trailEventCount || 0, 0)}`
    : "";
  const trailCaveat = phases?.trailing && phases.trailing.live_count > 0 && phases.trailing.bt_count === 0
    ? "Live SL_UPDATE는 있지만 BT 중간 SL_UPDATE 이벤트가 없어 Trail은 핵심 괴리 점수에서 제외"
    : "";
  const diagnosticText = compare.diagnosticDivergenceCount
    ? `참고 차이 ${fmt.num(compare.diagnosticDivergenceCount, 0)}건${compare.hardDivergenceCount ? "" : " (핵심 영향 없음)"}`
    : "";
  const parentMismatchCount = compare.hardDivergenceCount ?? eventMismatchCount(parentTotals);
  const statusText = isPhaseMetric
    ? (tone === "fail" ? "실패" : parentMismatchCount > 0 ? "핵심 경고" : "핵심 정상")
    : (tone === "ok" ? "정상" : tone === "warn" ? "경고" : "실패");
  const syncNote = compare.syncNote || (
    parentMismatchCount > 0
      ? "핵심 진입/청산 hard parity에 미매칭이 있습니다."
      : "핵심 진입/청산은 일치합니다."
  );
  return (
    <Card tone={tone === "warn" ? "warn" : tone === "fail" ? "danger" : "neutral"} indicator>
      <div className="tb-compare-head">
        <div className="label-caps">시간별 비교</div>
        <Badge kind={tone === "ok" ? "success" : tone === "warn" ? "warn" : "danger"}>
          {statusText}
        </Badge>
      </div>
      <div className="tb-compare-grid">
        {isPhaseMetric ? (
          <>
            <CompareEventPair label="부모 이벤트" totals={parentTotals} />
            <CompareEventPair label="자식 참고" totals={childTotals} diagnostic />
          </>
        ) : (
          <>
            <div>
              <div className="label-caps">{isSignalMetric ? "BT 신호" : "백테스트 PnL"}</div>
              <div className={`num-lg ${isSignalMetric ? "pnl-flat" : "pnl-pos"}`}>
                {isSignalMetric ? fmt.num(compare.btSignals, 0) : fmt.pnl(compare.backtestPnl)}
              </div>
            </div>
            <div>
              <div className="label-caps">{isSignalMetric ? "Live 진입" : "실시간 PnL"}</div>
              <div className={`num-lg ${isSignalMetric ? "pnl-flat" : "pnl-pos"}`}>
                {isSignalMetric ? fmt.num(compare.liveOpens, 0) : fmt.pnl(compare.livePnl)}
              </div>
            </div>
          </>
        )}
        <div>
          <div className="label-caps">{isPhaseMetric ? "부모 미매칭" : isSignalMetric ? "신호 괴리" : "괴리"}</div>
          <div className={`num-lg ${tone === "fail" ? "pnl-neg" : tone === "warn" ? "pnl-warn" : "pnl-flat"}`}>
            {isPhaseMetric ? `${fmt.num(parentMismatchCount, 0)}건` : fmt.pct(compare.divergencePct)}
          </div>
          {isPhaseMetric && (
            <div className="tb-event-raw">
              괴리율 {fmt.pct(compare.divergencePct)}
            </div>
          )}
        </div>
      </div>
      {isPhaseMetric && (
        <div className="tb-compare-note tb-meta">
          {syncNote} 핵심 판정은 부모 진입/청산 hard parity만 사용합니다. Grid·Scalp·NAS BB·Trail은 참고 진단이며 핵심 괴리 점수에는 넣지 않습니다. Trail 참고 {fmt.num(referenceTotals.bt || 0, 0)} / {fmt.num(referenceTotals.live || 0, 0)}건.
        </div>
      )}
      <CompareOrderDiagnostics order={compare.orderDiagnostics} />
      <div className="tb-compare-foot">
        <span className="tb-meta">
          마지막 실행 {fmt.ago(compare.lastRunAt)}
          {isSignalMetric && ` · 매칭 ${fmt.num(compare.matched, 0)} / BT-only ${fmt.num(compare.btOnly, 0)} / Live-only ${fmt.num(compare.liveOnly, 0)}`}
          {phaseText && ` · ${phaseText}`}
          {exitAnalysisText && ` · ${exitAnalysisText}`}
          {diagnosticText && ` · ${diagnosticText}`}
          {compare.note && ` · ${compare.note}`}
        </span>
        <span className="tb-compare-badges">
          {compare.metric === "phases" && <Badge kind={tone === "ok" ? "success" : "warn"}>핵심 진입/청산 기준</Badge>}
          {compare.metric === "phases" && compare.diagnosticDivergenceCount > 0 && <Badge kind="neutral">참고 진단 별도</Badge>}
          {compare.metric === "signals" && <Badge kind={tone === "ok" ? "success" : "warn"}>신호 로그 기준</Badge>}
          {compare.atomicCycle && compare.atomicCycle.status && compare.atomicCycle.status !== "ok" && (
            <Badge kind={compare.atomicCycle.status === "warn" ? "warn" : "neutral"}>Atomic {compare.atomicCycle.status}</Badge>
          )}
          {orphan > 0 && (
            <Badge kind="warn"><Icon name="alert" size={10} /> 분리된 포지션 {orphan}건</Badge>
          )}
        </span>
      </div>
      {(entryMismatchText || entryGuardText || exitGuardText || exitBreakdownText || parentTraceText || atomicCycleText || trailCaveat) && (
        <div className="tb-compare-analysis tb-meta">
          <CompareDetailRow label="진입 원인" text={entryMismatchText} />
          <CompareDetailRow label="진입 가드" text={entryGuardText} />
          <CompareDetailRow label="청산 가드" text={exitGuardText} />
          <CompareDetailRow label="청산 원인" text={exitBreakdownText} />
          <CompareDetailRow label="부모 추적" text={parentTraceText} />
          <CompareDetailRow label="AtomicCycle" text={atomicCycleText} />
          <CompareDetailRow label="Trail 참고" text={trailCaveat} />
        </div>
      )}
    </Card>
  );
};

Object.assign(window, { PositionsTable, BacktestSummary, DBHealth, LedgerFeed, CompareCard });
