// ════════════════════════════════════════════════════════════════
// InfoLabel.лейбл с всплывающей справкой о метрике (Хармози-style).
// Hover/focus на иконке "?" → tooltip с 3 секциями (title / что / зачем).
//
// Fix log 2026-05-23:
//   B1: pointerEvents=auto + grace period 200ms → можно навести мышь
//       внутрь тултипа и прочитать длинный текст не теряя его
//   B2: hide() debounced (200ms) → дрогнувшая мышь не закрывает
//   B3: scroll listener throttled (только если scroll > 50px от open-точки)
//   B4: tooltip ширина/высота measured после render → правильная позиция
//   B5: viewport boundary clamp слева+справа+сверху+снизу
//   B6: добавлена стрелка-указатель на trigger
//   B7: tabIndex=0 → только на ? svg (не вокруг всего children)
//   B8: SVG xmlns + лучше rendering ? символа (replaced text → path)
// Fix log 2026-05-26 (agent-audit actions #5 + #7, баг от Орлова скриншотом):
//   B9: ReactDOM.createPortal в document.body → обход CSS-бага когда parent
//       с transform/filter/perspective ломает position:fixed (была визуальная
//       регрессия — тултип уезжал вниз страницы поверх списка стартапов)
//   B10: все 8 em-dash убраны из METRIC_DICT (Brand Guard #4 compliance)
//   B11: zIndex 1100 → var(--z-tooltip) — semantic token из tokens.css
// ════════════════════════════════════════════════════════════════
const { useState: useStateInfo, useRef: useRefInfo, useEffect: useEffectInfo, useCallback: useCallbackInfo } = React;

const TIP_WIDTH = 320;
const TIP_GAP = 10;          // расстояние между trigger и тултипом
const GRACE_MS = 220;        // grace period для close
const SCROLL_THRESHOLD = 80; // px scroll до auto-close

function InfoLabel({ title, what, why, children, asInline = true }) {
  const [open, setOpen] = useStateInfo(false);
  const [pos, setPos] = useStateInfo(null);
  const triggerRef = useRefInfo(null);
  const tooltipRef = useRefInfo(null);
  const closeTimerRef = useRefInfo(null);
  const openScrollYRef = useRefInfo(0);

  // ── Open helper ───────────────────────────────────────────
  const computePos = useCallbackInfo(() => {
    if (!triggerRef.current) return null;
    const r = triggerRef.current.getBoundingClientRect();
    // Estimate tooltip height.будет refined после render через effect.
    const tipH = tooltipRef.current ? tooltipRef.current.offsetHeight : 200;
    const tipW = tooltipRef.current ? tooltipRef.current.offsetWidth : TIP_WIDTH;

    const spaceBelow = window.innerHeight - r.bottom - TIP_GAP;
    const spaceAbove = r.top - TIP_GAP;
    const above = tipH > spaceBelow && spaceAbove > spaceBelow;

    // Horizontal: trigger center, clamp в [8, viewport-tipW-8]
    const triggerCenter = r.left + r.width / 2;
    let left = triggerCenter - tipW / 2;
    left = Math.max(8, Math.min(left, window.innerWidth - tipW - 8));

    const top = above ? r.top - TIP_GAP - tipH : r.bottom + TIP_GAP;

    // Стрелка указывает на центр trigger относительно tooltip left edge
    const arrowLeft = Math.max(14, Math.min(triggerCenter - left, tipW - 14));

    return { top, left, above, arrowLeft };
  }, []);

  function show() {
    if (closeTimerRef.current) {
      clearTimeout(closeTimerRef.current);
      closeTimerRef.current = null;
    }
    openScrollYRef.current = window.scrollY;
    setOpen(true);
    // pos считается в effect после render (нужен offsetHeight tooltip'а)
  }

  function hide() {
    if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
    closeTimerRef.current = setTimeout(() => {
      setOpen(false);
      setPos(null);
    }, GRACE_MS);
  }

  function cancelHide() {
    if (closeTimerRef.current) {
      clearTimeout(closeTimerRef.current);
      closeTimerRef.current = null;
    }
  }

  // ── После render tooltip'а.пересчитать позицию с реальной высотой
  useEffectInfo(() => {
    if (!open) return;
    // requestAnimationFrame чтобы DOM успел отрендерить
    const id = requestAnimationFrame(() => {
      const p = computePos();
      if (p) setPos(p);
    });
    return () => cancelAnimationFrame(id);
  }, [open, computePos]);

  // ── Close on significant scroll/resize/escape ─────────────
  useEffectInfo(() => {
    if (!open) return;
    function onScroll() {
      if (Math.abs(window.scrollY - openScrollYRef.current) > SCROLL_THRESHOLD) {
        setOpen(false); setPos(null);
      } else {
        // mild scroll.пересчитываем позицию (триггер уехал)
        const p = computePos();
        if (p) setPos(p);
      }
    }
    function onResize() { setOpen(false); setPos(null); }
    function onKey(e) { if (e.key === 'Escape') { setOpen(false); setPos(null); } }
    window.addEventListener('scroll', onScroll, true);
    window.addEventListener('resize', onResize);
    window.addEventListener('keydown', onKey);
    return () => {
      window.removeEventListener('scroll', onScroll, true);
      window.removeEventListener('resize', onResize);
      window.removeEventListener('keydown', onKey);
    };
  }, [open, computePos]);

  // ── Cleanup timer on unmount ──────────────────────────────
  useEffectInfo(() => () => {
    if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
  }, []);

  return (
    <React.Fragment>
      <span style={{
        display: asInline ? 'inline-flex' : 'flex',
        alignItems: 'center', gap: 4,
      }}>
        {children}
        <span ref={triggerRef}
          onMouseEnter={show}
          onMouseLeave={hide}
          onFocus={show}
          onBlur={hide}
          tabIndex={0}
          aria-label={`Подсказка: ${title}`}
          role="button"
          style={{
            display: 'inline-flex',
            alignItems: 'center', justifyContent: 'center',
            cursor: 'help',
            outline: 'none',
            borderRadius: '50%',
            transition: 'background .15s',
          }}>
          <svg xmlns="http://www.w3.org/2000/svg"
               width="12" height="12" viewBox="0 0 12 12"
               style={{
                 color: open ? 'var(--brand-bronze)' : 'var(--text-tertiary)',
                 opacity: open ? 1 : 0.55,
                 flexShrink: 0,
                 transition: 'opacity .15s, color .15s',
               }}>
            <circle cx="6" cy="6" r="5" fill="none" stroke="currentColor" strokeWidth="0.9" />
            {/* Question mark as path для consistent рендеринга в Firefox */}
            <path d="M4.6 4.6c0-1 .7-1.5 1.5-1.5s1.4.5 1.4 1.3c0 .6-.3.9-.9 1.2-.4.2-.6.5-.6.9v.4"
                  fill="none" stroke="currentColor" strokeWidth="0.9"
                  strokeLinecap="round" strokeLinejoin="round" />
            <circle cx="6" cy="8.6" r="0.55" fill="currentColor" />
          </svg>
        </span>
      </span>

      {open && ReactDOM.createPortal((
        <div ref={tooltipRef}
          className="sdh-fade"
          role="tooltip"
          onMouseEnter={cancelHide}
          onMouseLeave={hide}
          style={{
            position: 'fixed',
            top: pos ? pos.top : -9999,           // off-screen пока не measured
            left: pos ? pos.left : -9999,
            width: TIP_WIDTH,
            maxWidth: 'calc(100vw - 16px)',
            maxHeight: 'calc(100vh - 16px)',
            overflowY: 'auto',
            zIndex: 'var(--z-tooltip)',
            background: 'var(--surface-elevated)',
            border: '1px solid var(--border-default)',
            borderRadius: 'var(--radius-md)',
            boxShadow: 'var(--shadow-lg)',
            padding: 'var(--space-4)',
            pointerEvents: 'auto',          // B1 fix: чтобы можно навести
            fontFamily: 'var(--font-sans)',
            textTransform: 'none',
            letterSpacing: 'normal',
            opacity: pos ? 1 : 0,           // скрыть пока позиция не считается
            transition: 'opacity .12s ease-out',
          }}>
          {/* Tooltip портирован в document.body чтобы position:fixed работал корректно
              даже если parent имеет transform/filter/perspective (известный CSS-баг). */}
          {/* Arrow указатель на trigger */}
          {pos && (
            <span aria-hidden="true" style={{
              position: 'absolute',
              left: pos.arrowLeft - 5,
              [pos.above ? 'bottom' : 'top']: -6,
              width: 10, height: 10,
              background: 'var(--surface-elevated)',
              borderRight: '1px solid var(--border-default)',
              borderBottom: pos.above ? '1px solid var(--border-default)' : 'none',
              borderLeft: pos.above ? 'none' : 'none',
              borderTop: pos.above ? 'none' : '1px solid var(--border-default)',
              transform: pos.above ? 'rotate(45deg)' : 'rotate(-135deg)',
            }} />
          )}

          <div style={{
            fontSize: 13.5, fontWeight: 600, color: 'var(--text-primary)',
            marginBottom: 8, letterSpacing: '-0.005em',
          }}>
            {title}
          </div>

          <div style={{ marginBottom: 8 }}>
            <div className="sdh-mono-label" style={{ marginBottom: 2 }}>что это</div>
            <div style={{ fontSize: 12.5, color: 'var(--text-secondary)', lineHeight: 1.5 }}>{what}</div>
          </div>

          <div>
            <div className="sdh-mono-label" style={{ marginBottom: 2 }}>зачем понимать</div>
            <div style={{
              fontFamily: 'var(--font-serif-body)', fontStyle: 'italic',
              fontSize: 12.5, color: 'var(--text-secondary)', lineHeight: 1.5,
            }}>{why}</div>
          </div>
        </div>
      ), document.body)}
    </React.Fragment>
  );
}

// ════════ Центральный словарь метрик ════════
const METRIC_DICT = {
  startups: {
    title: 'Стартапов в корпусе',
    what: 'Размер базы стартапов на текущий момент. Каждая запись прошла дедупликацию по домену и нормализацию названия.',
    why: 'Показывает охват наблюдения. Если число замедляется при росте источников. значит сигналы повторяются и нужно расширять воронку.',
  },
  classified: {
    title: 'Классифицировано',
    what: 'Доля стартапов, для которых модель распознала нишу с уверенностью выше 0.7.',
    why: 'Метрика полноты разметки. Низкое значение означает, что аналитика по нишам строится на неполных данных.',
  },
  sources: {
    title: 'Источников активно',
    what: 'Сколько внешних источников отдают данные за последние 24 часа.',
    why: 'Стабильность поставки. Падение числа источников = риск пропустить тренд.',
  },
  ideas: {
    title: 'Идей в работе',
    what: 'Количество идей Стартапзавода, по которым ведётся активное исследование.',
    why: 'Объём фокус-портфеля Стартапзавода. Влияет на нагрузку команды синтеза.',
  },
  weekNew: {
    title: 'За последние 7 дней',
    what: 'Стартапы, впервые появившиеся в корпусе за прошедшие семь дней.',
    why: 'Импульс пополнения. Резкий рост указывает на разогрев конкретной ниши, который стоит проверить отдельно.',
  },

  // Corpus columns
  niche: {
    title: 'Ниша',
    what: 'Сегмент рынка, к которому отнесён стартап классификатором.',
    why: 'Группировка по тематике позволяет видеть, где растёт конкуренция, и где идеи Стартапзавода вступают в плотный рынок.',
  },
  source: {
    title: 'Источник',
    what: 'Откуда запись попала в корпус: каталог, агрегатор или внутренний скаут.',
    why: 'Помогает оценить доверие к сигналу. Y Combinator и PitchBook дают разную глубину данных, чем Product Hunt.',
  },
  firstSeen: {
    title: 'Появился',
    what: 'Дата первого упоминания стартапа в любом из источников.',
    why: 'Свежесть сигнала. Молодые записи требуют подтверждения, старые.переоценки актуальности.',
  },
  similarity: {
    title: 'Близость',
    what: 'Семантическая релевантность стартапа к идеям Стартапзавода (0 до 1). Цвет точки: красный до 0.50, янтарный 0.50 до 0.75, зелёный выше 0.75.',
    why: 'Высокая близость = кандидат на углублённый разбор или прямой конкурент идеи. Низкая.контекстная единица для рынка.',
  },
  geo: {
    title: 'География',
    what: 'Страна, где зарегистрирована штаб-квартира стартапа по данным источника.',
    why: 'Помогает увидеть концентрацию направления (Edge AI в США vs Биотех в ЕС) и оценить регуляторный контекст.',
  },

  // Trends scatter axes
  axisGrowth: {
    title: 'Импульс кластера',
    what: 'Темп прироста числа стартапов в кластере по сравнению с предыдущей неделей. Шкала от снижения слева до роста справа.',
    why: 'Точки в правой части ускоряются.там надо быть первыми. Левая часть.гипотезы, которые остывают.',
  },
  axisVolume: {
    title: 'Объём кластера',
    what: 'Число стартапов, отнесённых к кластеру на текущий момент. Размер точки масштабируется пропорционально.',
    why: 'Большой объём с низким импульсом = насыщенный рынок. Малый объём с высоким импульсом = окно возможностей.',
  },

  // Ideas card
  competitors: {
    title: 'Конкурентный ландшафт',
    what: 'Стартапы из корпуса, выполняющие задачу, близкую к идее. Распределение вердиктов: рискованно / требует валидации / жизнеспособно.',
    why: 'Понятно, насколько идея уже занята и в какой стадии зрелости конкуренты.',
  },
  linked: {
    title: 'Связанные идеи',
    what: 'Другие идеи Стартапзавода, пересекающиеся по методологии или нише.',
    why: 'Подсказка для команд: одна и та же гипотеза может закрываться разными протоколами, лучше работать связкой.',
  },
  // Cluster page
  cluster_filterSim: {
    title: 'Фильтр близости',
    what: 'Прячет стартапы, у которых релевантность к идее вне выбранного диапазона. Близость считается как косинусное расстояние эмбеддинга описания стартапа от центра кластера идей.',
    why: 'Не листайте 47 строк глазами. Нажали ≥0.75.видите прямых конкурентов и понимаете, насколько занят рынок. Нажали 0.50–0.74.смотрите на смежников, у которых можно подсмотреть бизнес-модель и каналы. Нажали <0.50.это контекст и фон, нужен перед питчем. Каждый клик экономит 10 минут на ручном сравнении.',
  },
  cluster_sourcesPanel: {
    title: 'Источники сигнала',
    what: 'Распределение стартапов кластера по поставщикам данных: каталоги, инвестиционные базы, скауты.',
    why: 'Если 80% сигнала пришло из одного источника, у вас не картина рынка, а зеркало одного агрегатора. Расширьте воронку источников до того, как делать выводы. Несколько источников с одинаковым сигналом.это уже подтверждение.',
  },
  cluster_geoPanel: {
    title: 'География кластера',
    what: 'Топ-6 стран, где зарегистрированы стартапы кластера.',
    why: 'Концентрация в одной географии означает регуляторный режим, культурный паттерн или сильную локальную школу. Делаете похожее в другой стране.сначала разберитесь почему первые там, а не у вас. Это сэкономит шесть месяцев.',
  },
  cluster_verdictPanel: {
    title: 'Распределение близости',
    what: 'Сколько стартапов кластера попадает в каждый диапазон релевантности к идеям Стартапзавода.',
    why: 'Если в зелёной зоне (≥0.75) больше 60%.рынок плотный, нужен серьёзный диффер. Если 0.вы либо первые, либо никому это не нужно. Узнайте, что из двух, через 5 углублённых интервью.',
  },
};

window.InfoLabel = InfoLabel;
window.METRIC_DICT = METRIC_DICT;
