/* ops2-charts.jsx — переиспользуемые визуализации и хелперы (ОБИТЕЛЬ) */

/* ============================================================
   OpsWgt / OpsModal — компактный дашбордовый виджет с раскрытием
   в поп-ап (визуально паритетно с ЛК: используем те же классы
   .lk-wgt / .lk-modal из lk-shell.css). Виджет показывает суть
   (KPI/топ-N/сигнал), полный контент — в модалке.
   ============================================================ */
/* Контекст перемещаемых блоков. В опцентре этот файл грузится первым,
   поэтому создаёт его он; lk-arrange.jsx потом переиспользует. */
const OPS_ARRANGE_CTX = window.LKArrangeCtx || (window.LKArrangeCtx = React.createContext(null));
/* Активный тип графика виджета (из настроек ⋯ → Тип графика). */
const OpsChartVizCtx = React.createContext(null);
function useOpsChartViz(fallback) {
  const v = React.useContext(OpsChartVizCtx);
  return v != null ? v : fallback;
}

const CHART_VIZ_OPTS = [
  { id: 'donut', label: 'Кольцо', hint: 'Доли + среднее в центре' },
  { id: 'bar', label: 'Столбцы', hint: 'Сравнение по категориям' },
  { id: 'line', label: 'Линия', hint: 'Динамика ряда' },
  { id: 'hbar', label: 'Рейтинг', hint: 'Горизонтальные полосы' },
];

function ChartVizGlyph({ id }) {
  if (id === 'donut') return (
    <svg className="of-viz-glyph" viewBox="0 0 28 28" aria-hidden="true">
      <circle cx="14" cy="14" r="9" fill="none" stroke="currentColor" strokeWidth="5" strokeOpacity=".22" />
      <circle cx="14" cy="14" r="9" fill="none" stroke="currentColor" strokeWidth="5" strokeDasharray="18 40" strokeLinecap="butt" transform="rotate(-90 14 14)" />
    </svg>
  );
  if (id === 'bar') return (
    <svg className="of-viz-glyph" viewBox="0 0 28 28" aria-hidden="true">
      <rect x="5" y="14" width="4" height="9" rx="1.2" fill="currentColor" opacity=".45" />
      <rect x="12" y="8" width="4" height="15" rx="1.2" fill="currentColor" />
      <rect x="19" y="11" width="4" height="12" rx="1.2" fill="currentColor" opacity=".7" />
    </svg>
  );
  if (id === 'line') return (
    <svg className="of-viz-glyph" viewBox="0 0 28 28" aria-hidden="true">
      <polyline points="4,20 10,12 15,16 24,7" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
  return (
    <svg className="of-viz-glyph" viewBox="0 0 28 28" aria-hidden="true">
      <rect x="4" y="7" width="16" height="3.2" rx="1.6" fill="currentColor" />
      <rect x="4" y="12.4" width="12" height="3.2" rx="1.6" fill="currentColor" opacity=".7" />
      <rect x="4" y="17.8" width="8" height="3.2" rx="1.6" fill="currentColor" opacity=".45" />
    </svg>
  );
}

/* Ряд чисел → donut|bar|line|hbar. WorkspaceKit берём в рантайме (грузится позже). */
function SeriesChart({ labels, values, viz = 'hbar', h = 120, unit = '', base, onRowClick }) {
  const K = window.WorkspaceKit;
  const nums = (values || []).map((v) => Number(v) || 0);
  const cats = labels || nums.map((_, i) => '#' + (i + 1));
  const t = viz || 'hbar';
  const max = Math.max.apply(null, nums.concat([1]));
  const denom = base > 0 ? base : max;

  if (t === 'donut' && K && K.DonutChart) {
    const avg = nums.length ? Math.round(nums.reduce((a, b) => a + b, 0) / nums.length) : 0;
    const size = Math.min(Math.max(h - 4, 64), 132);
    return (
      <div className="ov-series-chart ov-series-chart--donut" style={{ minHeight: h }}>
        <K.DonutChart segments={nums.map((v) => ({ v }))} center={String(avg)} size={size} />
      </div>
    );
  }
  if (t === 'bar' && K && K.BarChart) {
    return (
      <div className="ov-series-chart" style={{ height: h }}>
        <K.BarChart data={nums} labels={cats} h={h} showAxis />
      </div>
    );
  }
  if (t === 'line' && K && K.LineChart) {
    return (
      <div className="ov-series-chart" style={{ height: h }}>
        <K.LineChart data={nums} labels={cats} h={h} showAxis />
      </div>
    );
  }

  /* Рейтинг (и fallback, если Kit ещё не готов) */
  return (
    <div className="ov-funnel-mini" role="img" aria-label="Рейтинг">
      {nums.map((v, i) => {
        const Row = onRowClick ? 'button' : 'div';
        const rowProps = onRowClick ? { type: 'button', onClick: () => onRowClick(i, cats[i], v) } : {};
        return (
          <Row key={i} className="ov-funnel-mini-row" {...rowProps}>
            <span className="ov-funnel-mini-l">{cats[i]}</span>
            <span className="ov-funnel-mini-track">
              <span className="ov-funnel-mini-fill" style={{ width: Math.max(6, Math.round(v / denom * 100)) + '%' }} />
            </span>
            <span className="ov-funnel-mini-v t-num">{v}{unit}</span>
            <span className="ov-funnel-mini-p t-num">{Math.round(v / denom * 100)}%</span>
          </Row>
        );
      })}
    </div>
  );
}

function OpsWgtCard({ title, badge, tone, action, expand, expandLabel, children, className, style }) {
  return (
    <section className={'lk-wgt' + (tone ? ' lk-wgt--' + tone : '') + (className ? ' ' + className : '')} style={style}>
      <div className="lk-wgt-head">
        <div className="lk-wgt-title">
          {title}
          {badge != null && <span className="lk-wgt-badge">{badge}</span>}
        </div>
        {action}
        {expand && (
          <button type="button" className="lk-wgt-exp" onClick={expand} title={expandLabel || 'Открыть полностью'} aria-label={expandLabel || 'Открыть полностью'}>
            <Ic n="arrow-up-right" s={15} />
          </button>
        )}
      </div>
      <div className="lk-wgt-body">{children}</div>
    </section>
  );
}

/* Внутри зоны ↗ уходит в общую полоску ArrangeItem (рядом с ⋯ и grip),
   чтобы не было двух плоскостей в углу. Вне зоны — как раньше, в шапке.
   chartViz / defaultViz — переключатель типа графика в настройках ⋯. */
function OpsWgt(props) {
  const arrange = React.useContext(OPS_ARRANGE_CTX);
  const title = props.title;
  const chartViz = props.chartViz;
  const defaultViz = props.defaultViz || (typeof chartViz === 'object' && chartViz.default) || 'hbar';
  /* title бывает узлом, а не строкой — тогда стабильного id нет */
  if (!arrange || typeof title !== 'string' || !window.LKArrangeItem) return <OpsWgtCard {...props} />;
  const id = title.trim().toLowerCase().replace(/\s+/g, '-');
  const cn = props.className || '';
  const gc = (props.style && props.style.gridColumn) || '';
  const sizeHint = (gc === '1 / -1' || gc === '1/-1') ? 'full'
    : (cn.indexOf('ov-wgt-wide') >= 0 ? 'wide' : 'std');
  const conf = arrange.conf(id) || {};
  const viz = chartViz ? (conf.viz || defaultViz) : null;
  return (
    <window.LKArrangeItem
      arrange={arrange}
      id={id}
      title={title}
      noExpand
      variant="wgt"
      sizeHint={sizeHint}
      expand={props.expand}
      expandLabel={props.expandLabel}
      chartViz={chartViz ? { default: defaultViz } : null}
    >
      <OpsChartVizCtx.Provider value={viz}>
        <OpsWgtCard {...props} expand={undefined} />
      </OpsChartVizCtx.Provider>
    </window.LKArrangeItem>
  );
}

function OpsModal({ title, subtitle, onClose, action, children, wide }) {
  const RD = window.ReactDOM;
  React.useEffect(() => {
    const k = (e) => { if (e.key === 'Escape') onClose(); };
    document.addEventListener('keydown', k);
    const prev = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    document.body.dataset.lkModal = '1';
    return () => {
      document.removeEventListener('keydown', k);
      document.body.style.overflow = prev;
      delete document.body.dataset.lkModal;
    };
  }, [onClose]);
  const node = (
    <React.Fragment>
      <div className="lk-modal-ov" onClick={onClose} />
      <div className={'lk-modal' + (wide ? ' lk-modal--wide' : '')} role="dialog" aria-modal="true" aria-label={title}>
        <div className="lk-modal-head">
          <div className="lk-modal-head-text">
            <div className="lk-modal-title">{title}</div>
            {subtitle && <div className="lk-modal-sub">{subtitle}</div>}
          </div>
          {action}
          <button type="button" className="lk-modal-x" onClick={onClose} aria-label="Закрыть"><Ic n="x" s={18} /></button>
        </div>
        <div className="lk-modal-body">{children}</div>
      </div>
    </React.Fragment>
  );
  /* portal в body — иначе of-modal (transform) ловит stacking context → «двойной попап» */
  return RD && RD.createPortal ? RD.createPortal(node, document.body) : node;
}

/* ---- Lucide icon helper ---- */
function Ic({ n, s = 18, c, sw = 2, style }) {
  const r = React.useRef();
  React.useEffect(() => {
    if (!r.current) return;
    r.current.innerHTML = '';
    const e = document.createElement('i');
    e.setAttribute('data-lucide', n);
    r.current.appendChild(e);
    if (window.lucide) window.lucide.createIcons({ attrs: { width: s, height: s, 'stroke-width': sw }, nameAttr: 'data-lucide' });
  });
  return <span ref={r} style={{ display: 'inline-flex', color: c || 'currentColor', ...style }} />;
}

/* ---- formatting ---- */
const fmtMoney = (n) => n == null ? '—' : Math.round(n).toLocaleString('ru-RU');
const fmtCompact = (n) => {
  if (n == null) return '—';
  const a = Math.abs(n);
  if (a >= 1_000_000) return (n / 1_000_000).toLocaleString('ru-RU', { maximumFractionDigits: 2 }) + ' млн';
  if (a >= 1_000) return Math.round(n / 1000) + ' тыс';
  return String(n);
};
const fmtPct = (n) => (n > 0 ? '+' : '') + n + '%';

/* ---- card section header (shared across zones) ---- */
function CardHead({ title, sub, action }) {
  return (
    <div className="card-head card-head--compact">
      <div className="card-head-main">
        <div className="card-head-title">{title}</div>
        {sub && <div className="card-head-sub">{sub}</div>}
      </div>
      {action}
    </div>);
}

/* ---- delta chip ---- */
function Delta({ value, suffix, tone }) {
  const t = tone || (value > 0 ? 'up' : value < 0 ? 'down' : 'flat');
  const col = t === 'up' ? 'var(--foreground)' : t === 'down' ? 'var(--destructive)' : 'var(--text-tertiary)';
  const ico = t === 'up' ? 'trending-up' : t === 'down' ? 'trending-down' : 'minus';
  return (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 13, fontWeight: 700, color: col }} className="t-num">
      <Ic n={ico} s={14} sw={2.4} />{typeof value === 'number' ? fmtPct(value) : value}{suffix ? ' ' + suffix : ''}
    </span>);

}

/* ============================================================
   ВОРОНКА ПРОДАЖ — пропорциональная полоса (ops)
   ============================================================ */
const FUNNEL_DEFS = [
  { key: 'leads', label: 'Входящие лиды' },
  { key: 'meetings', label: 'Встречи / замеры' },
  { key: 'estimates', label: 'Сметы' },
  { key: 'contracts', label: 'Договоры' }];

function SalesFunnel({ data, onDrill }) {
  const stages = FUNNEL_DEFS.map((def, i) => {
    const val = data[def.key] ?? 0;
    const prev = i > 0 ? data[FUNNEL_DEFS[i - 1].key] : null;
    const conv = prev && val != null && prev > 0 ? `${Math.round(val / prev * 100)}%` : null;
    return { key: def.key, label: def.label, value: val, conv, weight: Math.max(+val || 0, 1) };
  });

  return (
    <div className="ov-funnel ov-funnel--v2" role="group" aria-label="Воронка продаж: лиды, встречи, сметы, договоры">
      <div className="ov-funnel-track">
        {stages.map(function (st, i) {
          const isGoal = i === stages.length - 1;
          const StageTag = onDrill ? 'button' : 'div';
          const stageProps = onDrill ? {
            type: 'button',
            onClick: function () { onDrill(st.key); },
            onKeyDown: function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onDrill(st.key); } },
          } : {};
          return (
            <React.Fragment key={st.key}>
              <StageTag
                className={'ov-funnel-stage' + (isGoal ? ' ov-funnel-stage--goal' : '') + (' ov-funnel-stage--n' + (i + 1))}
                style={{ flex: st.weight + ' 1 0' }}
                {...stageProps}>
                <span className="ov-funnel-stage-label">{st.label}</span>
                <span className="ov-funnel-stage-val t-num">{st.value}</span>
              </StageTag>
              {i < stages.length - 1 && stages[i + 1].conv != null &&
              <div className="ov-funnel-conv" aria-label={'Конверсия ' + stages[i + 1].conv}>
                <span className="ov-funnel-conv-chevron" aria-hidden="true">›</span>
                <span className="ov-funnel-conv-val t-num">{stages[i + 1].conv}</span>
              </div>
              }
            </React.Fragment>
          );
        })}
      </div>
    </div>);
}

/* ============================================================
   DONUT — SVG-кольцо со слайсами + центр
   ============================================================ */
function Donut({ slices, size = 168, stroke = 26, centerTop, centerBottom }) {
  const total = slices.reduce((s, x) => s + x.value, 0) || 1;
  const r = (size - stroke) / 2;
  const C = 2 * Math.PI * r;
  let acc = 0;
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 22, flexWrap: 'wrap' }}>
      <svg width={size} height={size} style={{ flexShrink: 0, transform: 'rotate(-90deg)' }}>
        {slices.map((sl, i) => {
          const frac = sl.value / total;
          const dash = frac * C;
          const seg =
          <circle key={i} cx={size / 2} cy={size / 2} r={r} fill="none" stroke={sl.color} strokeWidth={stroke}
          strokeDasharray={`${dash} ${C - dash}`} strokeDashoffset={-acc * C} />;

          acc += frac;
          return seg;
        })}
        <circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke="rgba(0,0,0,0.04)" strokeWidth={stroke} style={{ mixBlendMode: 'multiply' }} opacity="0" />
      </svg>
      <div style={{ flex: 1, minWidth: 150 }}>
        {centerTop != null &&
        <div style={{ marginBottom: 12 }}>
            <div className="t-num" style={{ fontSize: 26, fontWeight: 800, letterSpacing: '-0.02em', color: 'var(--foreground)' }}>{centerTop}</div>
            {centerBottom && <div style={{ fontSize: 12.5, color: 'var(--text-tertiary)' }}>{centerBottom}</div>}
          </div>
        }
        <div style={{ display: 'flex', flexDirection: 'column', gap: 9 }}>
          {slices.map((sl, i) =>
          <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 9 }}>
              <span style={{ width: 10, height: 10, borderRadius: 3, background: sl.color, flexShrink: 0 }} />
              <span style={{ fontSize: 13, color: 'var(--text-secondary)', flex: 1 }}>{sl.label}</span>
              <span className="t-num" style={{ fontSize: 13, fontWeight: 700, color: 'var(--foreground)' }}>{Math.round(sl.value / total * 100)}%</span>
            </div>
          )}
        </div>
      </div>
    </div>);

}

/* ============================================================
   STACK BAR — горизонтальный стек (дебиторка по корзинам)
   ============================================================ */
function StackBar({ slices, height = 16 }) {
  const total = slices.reduce((s, x) => s + x.value, 0) || 1;
  return (
    <div>
      <div style={{ display: 'flex', height, borderRadius: 999, overflow: 'hidden', background: 'var(--muted)' }}>
        {slices.map((sl, i) =>
        <div key={i} style={{ width: `${sl.value / total * 100}%`, background: sl.color }} title={sl.label} />
        )}
      </div>
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px 16px', marginTop: 12 }}>
        {slices.map((sl, i) =>
        <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
            <span style={{ width: 9, height: 9, borderRadius: 3, background: sl.color }} />
            <span style={{ fontSize: 12.5, color: 'var(--text-secondary)' }}>{sl.label}</span>
            <span className="t-num" style={{ fontSize: 12.5, fontWeight: 700, color: 'var(--foreground)' }}>{fmtCompact(sl.value)} ₽</span>
          </div>
        )}
      </div>
    </div>);

}

/* ============================================================
   PROGRESS BAR
   ============================================================ */
function Bar({ pct, color, height = 7, track = 'var(--muted)' }) {
  return (
    <div style={{ height, borderRadius: 999, background: track, overflow: 'hidden' }}>
      <div style={{ width: `${Math.max(0, Math.min(100, pct))}%`, height: '100%', borderRadius: 999, background: color || 'var(--primary)', transition: 'width .5s var(--ease-out)' }} />
    </div>);

}

/* ============================================================
   PAYMENT CALENDAR — сетка месяца, нетто приход/расход
   ============================================================ */
function PayCalendar({ data, onDay }) {
  const dows = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
  const blanks = data.firstDow - 1;
  const cells = [];
  for (let i = 0; i < blanks; i++) cells.push(<div key={'b' + i} />);
  for (let d = 1; d <= data.days; d++) {
    const c = data.cells[d];
    const net = c ? c.in - c.out : 0;
    const gap = c && c.in === 0 && c.out > 0; // кассовый разрыв (только расход)
    const isToday = d === data.today;
    let bg = 'transparent',fg = 'var(--text-tertiary)';
    if (c) {
      if (gap) {bg = '#fdecec';fg = 'var(--destructive)';} else
      if (net > 0) {bg = 'var(--success-soft)';fg = 'var(--success-strong)';} else
      {bg = 'var(--warning-soft)';fg = 'var(--warning-strong)';}
    }
    cells.push(
      <button key={d} className="cal-cell" onClick={() => c && onDay && onDay(d, c)}
      style={{ background: bg, cursor: c ? 'pointer' : 'default', boxShadow: isToday ? 'inset 0 0 0 2px var(--primary)' : 'none' }}>
        <span className="t-num" style={{ fontSize: 11, fontWeight: 700, color: isToday ? 'var(--primary)' : 'var(--text-secondary)' }}>{d}</span>
        {c && <span className="t-num" style={{ fontSize: 10.5, fontWeight: 700, color: fg, lineHeight: 1.1 }}>{net > 0 ? '+' : ''}{fmtCompact(net)}</span>}
      </button>
    );
  }
  return (
    <div>
      <div className="cal-grid cal-head">{dows.map((d) => <div key={d} style={{ fontSize: 10.5, fontWeight: 700, color: 'var(--text-tertiary)', textAlign: 'center', textTransform: 'uppercase', letterSpacing: '0.04em' }}>{d}</div>)}</div>
      <div className="cal-grid">{cells}</div>
    </div>);

}

/* ---- load dot ---- */
function LoadDot({ load }) {
  const L = window.OPS.LOAD[load];
  return (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 7, fontSize: 13, fontWeight: 500, color: 'var(--text-secondary)' }}>
      <span style={{ width: 9, height: 9, borderRadius: '50%', background: L.c, flexShrink: 0 }} />{L.l}
    </span>);

}

/* ---- star rating ---- */
function Stars({ n }) {
  if (!n) return null;
  return <span style={{ color: 'var(--chart-5)', fontSize: 11, letterSpacing: '1px' }}>{'★'.repeat(n)}</span>;
}

Object.assign(window, { Ic, fmtMoney, fmtCompact, fmtPct, Delta, CardHead, SalesFunnel, Donut, StackBar, Bar, PayCalendar, LoadDot, Stars, OpsWgt, OpsModal, SeriesChart, useOpsChartViz, CHART_VIZ_OPTS, ChartVizGlyph, OpsChartVizCtx });