// ov2-parts.jsx — UI building blocks for Overview v2

const { useState, useRef, useEffect, useCallback } = React;

/* ============== Tooltip ============== */
const TTContext = React.createContext(null);
const TTProvider = ({ children }) => {
  const [tt, setTt] = useState(null);
  return (
    <TTContext.Provider value={setTt}>
      {children}
      {tt && (
        <div className="tt" style={{ left: tt.x, top: tt.y, transform: 'translate(-50%, calc(-100% - 12px))' }}>
          {tt.content}
        </div>
      )}
    </TTContext.Provider>
  );
};
const useTT = () => React.useContext(TTContext);
const ttHandlers = (setTt, content) => ({
  onMouseEnter: (e) => {
    const r = e.currentTarget.getBoundingClientRect();
    setTt({ x: r.left + r.width / 2, y: r.top, content });
  },
  onMouseMove: (e) => {
    setTt(t => t ? { ...t, x: e.clientX, y: e.clientY - 4 } : t);
  },
  onMouseLeave: () => setTt(null),
});

/* ============== Delta ============== */
const Delta = ({ sign, children, withCmp, t }) => {
  const arrow = sign === 'pos' ? '▲' : sign === 'neg' ? '▼' : sign === 'warn' ? '▼' : '–';
  const cls = sign === 'flat' ? 'flat' : sign;
  return (
    <span className="flex center-y gap-8">
      <span className={`delta ${cls}`}>
        {sign !== 'flat' && <span style={{ fontSize: 9 }}>{arrow}</span>}
        {children}
      </span>
      {withCmp && <span className="delta-cmp">vs prev.</span>}
    </span>
  );
};

/* ============== Sparkline ============== */
const Sparkline = ({ data, color = 'var(--chart-1)', height = 24, fill = false }) => {
  if (!data || !data.length) return null;
  const w = 100;
  const min = Math.min(...data), max = Math.max(...data);
  const span = max - min || 1;
  const pts = data.map((v, i) => [
    (i / (data.length - 1)) * w,
    height - ((v - min) / span) * (height - 4) - 2
  ]);
  const d = pts.map((p, i) => `${i === 0 ? 'M' : 'L'}${p[0].toFixed(2)},${p[1].toFixed(2)}`).join(' ');
  const area = `${d} L${w},${height} L0,${height} Z`;
  return (
    <svg className="hero-spark" viewBox={`0 0 ${w} ${height}`} preserveAspectRatio="none">
      {fill && <path d={area} fill={color} opacity="0.10" />}
      <path d={d} stroke={color} strokeWidth="1.2" fill="none" vectorEffect="non-scaling-stroke" />
    </svg>
  );
};

/* ============== Stacked Area Chart ============== */
const StackedArea = ({ data, padelMuted, t, hidden = {} }) => {
  const setTt = useTT();
  const [hover, setHover] = useState(null);
  const W = 720, H = 220, padL = 36, padR = 8, padT = 8, padB = 28;
  const innerW = W - padL - padR;
  const innerH = H - padT - padB;
  const days = data.length;

  // streams in stacking order (bottom to top)
  const streams = [
    { key: 'rooms', color: 'var(--chart-1)', label: t.chart.legend.rooms },
    { key: 'spa', color: 'var(--chart-2)', label: t.chart.legend.spa },
    { key: 'fnb', color: 'var(--chart-3)', label: t.chart.legend.fnb },
    { key: 'other', color: 'var(--chart-4)', label: t.chart.legend.other },
    { key: 'padel', color: 'var(--chart-5)', label: t.chart.legend.padel, muted: true },
  ];

  // build cumulative stacks
  const stacks = data.map(d => {
    let acc = 0;
    return streams.map(s => {
      const v = hidden[s.key] ? 0 : (d[s.key] || 0);
      const out = { y0: acc, y1: acc + v, raw: d[s.key] || 0 };
      acc += v;
      return out;
    });
  });

  const maxY = Math.max(1500, ...stacks.map(d => d[d.length - 1].y1));
  const xAt = (i) => padL + (i / (days - 1)) * innerW;
  const yAt = (v) => padT + innerH - (v / maxY) * innerH;

  // Areas
  const areas = streams.map((s, sIdx) => {
    const top = stacks.map((d, i) => `${i === 0 ? 'M' : 'L'}${xAt(i).toFixed(1)},${yAt(d[sIdx].y1).toFixed(1)}`).join(' ');
    const bottom = stacks.slice().reverse().map((d, i) => {
      const realI = stacks.length - 1 - i;
      return `L${xAt(realI).toFixed(1)},${yAt(d[sIdx].y0).toFixed(1)}`;
    }).join(' ');
    return { ...s, d: top + ' ' + bottom + ' Z' };
  });

  // Y ticks
  const ticks = [0, 0.25, 0.5, 0.75, 1].map(f => Math.round(maxY * f));

  const xLabels = data.map((_, i) => i).filter(i => i % 5 === 0 || i === days - 1);

  const onMove = (e) => {
    const r = e.currentTarget.getBoundingClientRect();
    const x = e.clientX - r.left;
    const ratio = (x - padL * (r.width / W)) / (innerW * (r.width / W));
    const idx = Math.max(0, Math.min(days - 1, Math.round(ratio * (days - 1))));
    const d = data[idx];
    const total = (d.rooms || 0) + (d.spa || 0) + (d.fnb || 0) + (d.other || 0);
    setHover({ idx });
    setTt({
      x: e.clientX, y: e.clientY,
      content: (
        <div>
          <div className="tt-title">День {idx + 1} · €{total.toLocaleString('en-US')}</div>
          <div className="tt-row"><span className="lbl">{t.chart.legend.rooms}</span><span>€{d.rooms.toLocaleString('en-US')}</span></div>
          <div className="tt-row"><span className="lbl">{t.chart.legend.spa}</span><span>€{d.spa.toLocaleString('en-US')}</span></div>
          <div className="tt-row"><span className="lbl">{t.chart.legend.fnb}</span><span>€{d.fnb.toLocaleString('en-US')}</span></div>
          <div className="tt-row"><span className="lbl">{t.chart.legend.other}</span><span>€{d.other.toLocaleString('en-US')}</span></div>
          <div className="tt-row" style={{ opacity: 0.5 }}><span className="lbl">{t.chart.legend.padel}</span><span>—</span></div>
        </div>
      ),
    });
  };
  const onLeave = () => { setHover(null); setTt(null); };

  return (
    <svg className="chart-svg" viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="none" onMouseMove={onMove} onMouseLeave={onLeave} style={{ height: 220 }}>
      {/* Y ticks */}
      {ticks.map((tk, i) => (
        <g key={i}>
          <line x1={padL} x2={W - padR} y1={yAt(tk)} y2={yAt(tk)} stroke="var(--border)" strokeDasharray="2 3" />
          <text x={padL - 6} y={yAt(tk) + 3} textAnchor="end" fontSize="10" fill="var(--text-3)" fontFamily="JetBrains Mono">€{tk}</text>
        </g>
      ))}
      {/* Areas */}
      {areas.map(a => (
        <path key={a.key} d={a.d} fill={a.color} opacity={a.muted ? 0.18 : 0.85} />
      ))}
      {/* Padel diagonal hatch over its band (very thin since 0) — skip if 0 */}
      {/* Hover line + dot */}
      {hover && (
        <g>
          <line x1={xAt(hover.idx)} x2={xAt(hover.idx)} y1={padT} y2={H - padB} stroke="var(--text)" strokeWidth="1" opacity="0.4" />
          <circle cx={xAt(hover.idx)} cy={yAt(stacks[hover.idx][3].y1)} r="3.5" fill="var(--surface)" stroke="var(--text)" strokeWidth="1.5" />
        </g>
      )}
      {/* X labels */}
      {xLabels.map(i => (
        <text key={i} x={xAt(i)} y={H - 8} textAnchor="middle" fontSize="10" fill="var(--text-3)" fontFamily="JetBrains Mono">{i + 1}</text>
      ))}
    </svg>
  );
};

/* ============== Heatmap ============== */
const hmFmt = (tpl, vars = {}) => String(tpl || '').replace(/\{(\w+)\}/g, (_, key) => (
  vars[key] === 0 || vars[key] ? vars[key] : ''
));

const hmInitials = (name) => String(name || '')
  .replace(/[.,]/g, ' ')
  .split(/\s+/)
  .filter((part) => part && /[A-Za-zА-Яа-яЁё]/.test(part[0]))
  .slice(0, 2)
  .map((part) => part[0].toUpperCase())
  .join('');

const hmDateObj = (iso) => {
  const [y, m, d] = String(iso || '').split('-').map(Number);
  return new Date(y || 2026, (m || 1) - 1, d || 1, 12, 0, 0);
};

const hmDate = (iso, lang, opts = {}) => {
  const locale = lang === 'en' ? 'en-US' : 'ru-RU';
  const date = hmDateObj(iso);
  if (opts.weekday) {
    return date.toLocaleDateString(locale, { day: 'numeric', month: 'long', weekday: 'long' });
  }
  return date.toLocaleDateString(locale, { day: 'numeric', month: 'short' }).replace('.', '');
};

const hmRange = (startIso, endIso, lang) => {
  const start = hmDateObj(startIso);
  const end = hmDateObj(endIso);
  const locale = lang === 'en' ? 'en-US' : 'ru-RU';
  const sameMonth = start.getMonth() === end.getMonth();
  const startLabel = sameMonth
    ? String(start.getDate())
    : start.toLocaleDateString(locale, { day: 'numeric', month: 'short' }).replace('.', '');
  const endLabel = end.toLocaleDateString(locale, { day: 'numeric', month: 'short' }).replace('.', '');
  return `${startLabel}–${endLabel}`;
};

const hmMonthAbbr = (iso, lang) => {
  const locale = lang === 'en' ? 'en-US' : 'ru-RU';
  return hmDateObj(iso).toLocaleDateString(locale, { month: 'short' }).replace('.', '');
};

const hmMoney = (value) => `€${Math.round(value || 0).toLocaleString('en-US')}`;
const hmPaymentLabel = (t, status) => ((t.heatmap && t.heatmap.payment && t.heatmap.payment[status]) || status || '-');
const hmStatusLabel = (status, lang) => {
  const ru = { empty: 'Свободно', blocked: 'Заблокировано', confirmed: 'Бронь', in_house: 'In-house', checked_out: 'Выезд завершен' };
  const en = { empty: 'Free', blocked: 'Blocked', confirmed: 'Booked', in_house: 'In-house', checked_out: 'Checked out' };
  return (lang === 'en' ? en : ru)[status] || status || '-';
};

const hmNormalize = (data, t) => {
  if (data && data.cells) return data;
  const cabins = (window.OV2 && window.OV2.cabins) || ['Bali', 'Cyprus', 'Portofino', 'Latvia', 'Maldives'];
  const days = 30;
  const cells = (Array.isArray(data) ? data : []).map((row, ci) => (
    row.map((cell, di) => ({
      date: (window.OV2 && window.OV2.today) || '2026-04-14',
      status: cell.state === 'inhouse' ? 'in_house' : cell.state === 'booked' ? 'confirmed' : 'empty',
      cabin: cabins[ci],
      day: di,
      isArrival: !!cell.arrival,
      isDeparture: !!cell.departure,
      nightRate: cell.state !== 'free' ? 220 + ci * 20 : null,
      guestInitials: cell.state !== 'free' ? ['ИА', 'ПО', 'SJ', 'ГМ', 'КД'][ci] : '',
      guestName: cell.state !== 'free' ? ['Иванов Александр', 'Петрова Ольга', 'Smith John', 'Гарсия Мария', 'Котов Дмитрий'][ci] : '',
      guestNameMasked: cell.state !== 'free' ? ['И. А.', 'П. О.', 'S. J.', 'Г. М.', 'К. Д.'][ci] : '',
      composition: '2A',
      flag: '🇱🇻',
      countryCode: 'LV',
      channel: 'Direct',
      totalRevenue: 440,
      repeatVisitCount: 1,
      discovery: 'Google search',
      discoveryConfidence: 'ga4',
      guestEmail: 'guest@example.com',
      guestPhone: '+371 20 000 000',
      guestSegment: 'New guest',
      language: 'EN',
      paymentStatus: cell.state !== 'free' ? 'partial' : null,
      paymentMethod: 'Card',
      amountPaid: 220,
      balanceDue: 220,
      paymentDueDate: (window.OV2 && window.OV2.today) || '2026-04-14',
    }))
  ));
  const dayAggregates = Array.from({ length: days }, (_, day) => ({
    date: (window.OV2 && window.OV2.today) || '2026-04-14',
    dayOfWeek: day % 7,
    dayOfMonth: day + 1,
    monthAbbr: 'Apr',
    isWeekend: day % 7 === 0 || day % 7 === 6,
    isToday: day === 0,
    adrMedian: 240,
    occupiedCount: cells.filter((row) => row[day] && row[day].status !== 'empty').length,
    totalCabins: cabins.length,
  }));
  return {
    cabins,
    days,
    todayIndex: 0,
    cells,
    dayAggregates,
    weeks: [],
    summary: { occupied: 87, total: 150, pct: 58, revenue: 18400 },
  };
};

const HeatmapLegend = ({ t, header }) => {
  const legend = t.heatmap.legend || {};
  return (
    <div className={`hm-legend ${header ? 'hm-legend-header' : ''}`}>
      <span className="hm-legend-item"><span className="hm-legend-swatch empty"></span>{legend.empty || legend.free}</span>
      <span className="hm-legend-item"><span className="hm-legend-swatch booked"></span>{legend.booked}</span>
      <span className="hm-legend-item"><span className="hm-legend-swatch in-house"></span>{legend.inHouse || legend.inhouse}</span>
      <span className="hm-legend-item"><span className="hm-legend-swatch marker"></span>{legend.arrival}</span>
      <span className="hm-legend-item"><span className="hm-legend-swatch payment"></span>{legend.payment || 'payment'}</span>
    </div>
  );
};

const Heatmap = ({ data, t, lang = 'ru', privacyMode, onCellClick }) => {
  const hm = hmNormalize(data, t);
  const copy = t.heatmap || {};
  const tooltipCopy = copy.tooltip || {};
  const hoverTimer = useRef(null);
  const activeCell = useRef(null);
  const [tooltip, setTooltip] = useState(null);

  useEffect(() => () => window.clearTimeout(hoverTimer.current), []);

  const getPayload = (target) => {
    if (!target) return null;
    const ci = Number(target.dataset.ci);
    const di = Number(target.dataset.di);
    const cell = hm.cells[ci] && hm.cells[ci][di];
    if (!cell) return null;
    return { ...cell, cabinIndex: ci, dayIndex: di, day: hm.dayAggregates[di] };
  };

  const hideTooltip = () => {
    window.clearTimeout(hoverTimer.current);
    activeCell.current = null;
    setTooltip(null);
  };

  const renderTooltip = (payload) => {
    const cell = payload;
    const day = payload.day || {};
    const guestName = privacyMode ? (cell.guestNameMasked || cell.guestName) : cell.guestName;
    const dateLabel = hmDate(cell.date || day.date, lang, { weekday: true });
    if (cell.status === 'empty') {
      const pct = day.totalCabins ? Math.round((day.occupiedCount / day.totalCabins) * 100) : 0;
      return (
        <div>
          <div className="hm-tooltip-title">{cell.cabin} · {dateLabel}</div>
          <div className="hm-tooltip-sep"></div>
          <div>{tooltipCopy.free}</div>
          <div className="hm-tooltip-muted">{hmFmt(tooltipCopy.medianADR, { value: day.adrMedian })}</div>
          <div className="hm-tooltip-muted">{hmFmt(tooltipCopy.dayLoad, { date: hmDate(cell.date, lang), pct, n: day.occupiedCount, total: day.totalCabins })}</div>
        </div>
      );
    }
    if (cell.status === 'blocked') {
      return (
        <div>
          <div className="hm-tooltip-title">{cell.cabin} · {dateLabel}</div>
          <div className="hm-tooltip-sep"></div>
          <div>{hmFmt(tooltipCopy.blocked, { reason: cell.blockReason || '-' })}</div>
          <div className="hm-tooltip-muted">{hmFmt(tooltipCopy.medianADR, { value: day.adrMedian })}</div>
        </div>
      );
    }
    return (
      <div>
        <div className="hm-tooltip-title">{cell.cabin} · {dateLabel}</div>
        <div className="hm-tooltip-sep"></div>
        <div>{guestName}</div>
        <div className="hm-tooltip-muted">{cell.composition} · {cell.flag} {cell.countryCode}</div>
        <div className="hm-tooltip-muted">{hmDate(cell.arrivalDate, lang)} → {hmDate(cell.departureDate, lang)} · {cell.nights}N</div>
        <div className="hm-tooltip-muted">{hmFmt(tooltipCopy.channelLabel, { channel: cell.channel })} · {hmMoney(cell.totalRevenue)} total</div>
        {cell.discovery && <div className="hm-tooltip-muted">{hmFmt(tooltipCopy.discoveryLabel, { discovery: cell.discovery })} · {cell.discoveryConfidence || 'unknown'}</div>}
        {cell.paymentStatus && (
          <div className={`hm-tooltip-payment ${cell.paymentStatus}`}>
            {hmFmt(tooltipCopy.paymentLabel, { status: hmPaymentLabel(t, cell.paymentStatus) })}
            {cell.balanceDue > 0 ? ` · ${hmFmt(tooltipCopy.balanceLabel, { value: cell.balanceDue })}` : ''}
          </div>
        )}
        {cell.repeatVisitCount > 1 && <div className="hm-tooltip-muted">{hmFmt(tooltipCopy.repeatLabel, { n: cell.repeatVisitCount })}</div>}
        {cell.isArrival && <div className="hm-tooltip-event">{hmFmt(tooltipCopy.arrivalLabel, { guest: guestName, time: cell.arrivalTime || '15:00' })}</div>}
        {cell.isDeparture && <div className="hm-tooltip-event">{hmFmt(tooltipCopy.departureLabel, { guest: guestName, time: cell.departureTime || '12:00' })}</div>}
        {cell.note && <div className="hm-tooltip-note">"{cell.note}"</div>}
      </div>
    );
  };

  const scheduleTooltip = (target) => {
    const payload = getPayload(target);
    if (!payload) return;
    const key = `${payload.cabinIndex}-${payload.dayIndex}`;
    if (activeCell.current === key && tooltip) return;
    window.clearTimeout(hoverTimer.current);
    activeCell.current = key;
    const rect = target.getBoundingClientRect();
    const below = rect.top < 180;
    hoverTimer.current = window.setTimeout(() => {
      setTooltip({
        key,
        x: rect.left + rect.width / 2,
        y: below ? rect.bottom + 12 : rect.top - 12,
        below,
        content: renderTooltip(payload),
      });
    }, 300);
  };

  const onGridMouseOver = (e) => {
    const target = e.target.closest && e.target.closest('.hm-cell');
    if (!target || !e.currentTarget.contains(target)) return;
    scheduleTooltip(target);
  };

  const onGridMouseOut = (e) => {
    const target = e.target.closest && e.target.closest('.hm-cell');
    if (!target) return;
    const next = e.relatedTarget && e.relatedTarget.closest && e.relatedTarget.closest('.hm-cell');
    if (next === target) return;
    hideTooltip();
  };

  const onGridClick = (e) => {
    const target = e.target.closest && e.target.closest('.hm-cell');
    if (!target || !e.currentTarget.contains(target)) return;
    const payload = getPayload(target);
    if (payload && onCellClick) onCellClick(payload);
  };

  const dayLetters = copy.daysOfWeek || ['S', 'M', 'T', 'W', 'T', 'F', 'S'];

  return (
    <div className="hm-wrap">
      <div className="hm-grid-wrap">
        <div className="hm-grid-panel">
          <div className="hm-grid" onMouseOver={onGridMouseOver} onMouseOut={onGridMouseOut} onClick={onGridClick}>
            <div className="hm-row-label hm-header-label"></div>
            {hm.dayAggregates.map((day, di) => (
              <div key={`dow-${day.date}`} className={`hm-header-cell hm-dow ${day.isWeekend ? 'weekend' : ''} ${day.isToday ? 'today' : ''}`}>
                {dayLetters[day.dayOfWeek] || ''}
              </div>
            ))}
            <div className="hm-row-label hm-header-label"></div>
            {hm.dayAggregates.map((day, di) => (
              <div key={`num-${day.date}`} className={`hm-header-cell hm-day-num ${day.isWeekend ? 'weekend' : ''} ${day.isToday ? 'today' : ''}`}>
                <span>{day.dayOfMonth}{(day.dayOfMonth === 1 || di === 0) && <small> {hmMonthAbbr(day.date, lang)}</small>}</span>
              </div>
            ))}
            {hm.cells.map((row, ci) => (
              <React.Fragment key={hm.cabins[ci]}>
                <div className="hm-row-label">{hm.cabins[ci]}</div>
                {row.map((cell, di) => {
                  const day = hm.dayAggregates[di] || {};
                  const status = cell.status || 'empty';
                  const initials = status === 'empty' || status === 'blocked' ? '' : hmInitials(privacyMode ? (cell.guestNameMasked || cell.guestName) : (cell.guestName || cell.guestInitials));
                  const cls = [
                    'hm-cell',
                    status,
                    ['confirmed', 'in_house', 'checked_out'].includes(status) ? 'occupied' : '',
                    day.isWeekend ? 'weekend' : '',
                    day.isToday ? 'today' : '',
                  ].filter(Boolean).join(' ');
                  return (
                    <div
                      key={`${cell.cabin}-${di}`}
                      className={cls}
                      data-ci={ci}
                      data-di={di}
                      title=""
                    >
                      {cell.isArrival && <span className="hm-arrival-marker"></span>}
                      {cell.isDeparture && <span className="hm-departure-marker"></span>}
                      {status === 'blocked' ? (
                        <span className="hm-cell-blocked">—</span>
                      ) : (
                        <>
                          {initials && <span className="hm-cell-initials">{initials}</span>}
                          {cell.nightRate && <span className="hm-cell-rate">€{cell.nightRate}</span>}
                          {cell.paymentStatus && <span className={`hm-payment-dot ${cell.paymentStatus}`} aria-label={hmPaymentLabel(t, cell.paymentStatus)}></span>}
                        </>
                      )}
                    </div>
                  );
                })}
              </React.Fragment>
            ))}
            {Number.isFinite(hm.todayIndex) && (
              <div className="hm-today-divider" style={{ left: `calc(var(--hm-label-width) + 3px + ${hm.todayIndex * 45}px)` }}></div>
            )}
          </div>
          {tooltip && (
            <div className={`hm-tooltip visible ${tooltip.below ? 'below' : ''}`} style={{ left: tooltip.x, top: tooltip.y }}>
              {tooltip.content}
              <span className="hm-tooltip-arrow"></span>
            </div>
          )}
        </div>
        <HeatmapLegend t={t} />
      </div>
    </div>
  );
};

/* ============== Pace row ============== */
const PaceRow = ({ row, help }) => {
  const cmpCls = row.cmpKind === 'pos' ? 'pos' : row.cmpKind === 'neg' ? 'neg' : 'flat';
  return (
    <div className="pace-row">
      <div className="pace-label" style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span>{row.label}</span>
        <HelpIcon help={help} />
      </div>
      <div className="pace-bar-wrap">
        <div className="pace-bar"><div className={`pace-bar-fill ${row.barKind}`} style={{ width: row.pct + '%' }}></div></div>
        <div className="pace-num"><strong>{row.num}</strong> · {row.pct}%</div>
      </div>
      <div className="pace-cmp"><Delta sign={cmpCls}>{row.cmp}</Delta></div>
    </div>
  );
};

/* ============== Help icon ============== */
const HelpIcon = ({ help }) => {
  const setTt = useTT();
  if (!help) return null;
  const showTooltip = (e) => {
    const r = e.currentTarget.getBoundingClientRect();
    setTt({
      x: r.left + r.width / 2, y: r.top,
      content: (
        <div>
          <div className="tt-title">{help.title}</div>
          <div style={{ fontSize: 12, lineHeight: 1.45, opacity: 0.92 }}>{help.body}</div>
        </div>
      ),
    });
  };
  return (
    <button
      type="button"
      className="help-ico"
      aria-label={help.title}
      onClick={(e) => e.stopPropagation()}
      onMouseEnter={showTooltip}
      onMouseLeave={() => setTt(null)}
      onFocus={showTooltip}
      onBlur={() => setTt(null)}
    >?</button>
  );
};

const HelpTitle = ({ children, help, className = '' }) => (
  <span className={`help-title ${className}`.trim()}>
    <span>{children}</span>
    <HelpIcon help={help} />
  </span>
);

/* ============== Hero card ============== */
const HeroCard = ({ lbl, num, sub, deltaText, deltaSign, alert, spark, sparkColor, onClick, help, cmpText }) => {
  const sign = deltaSign;
  return (
    <div className="card hero clickable" onClick={onClick}>
      {alert && <span className="hero-alert-dot" style={{ right: 32 }}></span>}
      <div className="hero-head">
        <div className="hero-lbl">{lbl}</div>
        <HelpIcon help={help} />
      </div>
      <div className="hero-num">{num}{sub && <span className="hero-num-sub">{sub}</span>}</div>
      {deltaText && (
        <div className="hero-foot">
          <Delta sign={sign}>{deltaText}</Delta>
          <span className="delta-cmp">{cmpText || 'vs prev.'}</span>
        </div>
      )}
      {spark && <Sparkline data={spark} color={sparkColor || 'var(--chart-1)'} fill />}
    </div>
  );
};

/* ============== Dropdown ============== */
const Dropdown = ({ value, options = [], onChange, icon }) => {
  const [open, setOpen] = useState(false);
  const [activeIdx, setActiveIdx] = useState(0);
  const wrapRef = useRef(null);
  const selected = options.find((opt) => opt.value === value) || options.find((opt) => !opt.disabled) || options[0];
  const selectable = options.filter((opt) => !opt.disabled);

  const openMenu = () => {
    const idx = Math.max(0, options.findIndex((opt) => opt.value === value));
    setActiveIdx(idx);
    setOpen(true);
  };

  useEffect(() => {
    if (!open) return undefined;
    const onDown = (e) => {
      if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
    };
    document.addEventListener('mousedown', onDown);
    return () => document.removeEventListener('mousedown', onDown);
  }, [open]);

  const move = (dir) => {
    if (!selectable.length) return;
    const currentValue = options[activeIdx] && !options[activeIdx].disabled ? options[activeIdx].value : selected && selected.value;
    const currentSelectable = Math.max(0, selectable.findIndex((opt) => opt.value === currentValue));
    const next = selectable[(currentSelectable + dir + selectable.length) % selectable.length];
    setActiveIdx(options.findIndex((opt) => opt.value === next.value));
  };

  const choose = (opt) => {
    if (!opt || opt.disabled) return;
    if (onChange) onChange(opt.value);
    setOpen(false);
  };

  const onKeyDown = (e) => {
    if (e.key === 'Escape') {
      setOpen(false);
      return;
    }
    if (!open && (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown')) {
      e.preventDefault();
      openMenu();
      return;
    }
    if (!open) return;
    if (e.key === 'ArrowDown') {
      e.preventDefault();
      move(1);
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();
      move(-1);
    } else if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      choose(options[activeIdx]);
    }
  };

  return (
    <div className="dropdown-wrap" ref={wrapRef} onKeyDown={onKeyDown}>
      <button type="button" className="tb-select" aria-haspopup="menu" aria-expanded={open} onClick={() => open ? setOpen(false) : openMenu()}>
        {icon && <I name={icon} size={14}/>}
        <span>{selected ? selected.label : ''}</span>
        <I name="chevron-down" size={14}/>
      </button>
      {open && (
        <div className="dropdown-menu" role="menu">
          {options.map((opt, idx) => (
            <React.Fragment key={opt.value}>
              <button
                type="button"
                role="menuitemradio"
                aria-checked={value === opt.value}
                className={`dropdown-item ${value === opt.value ? 'selected' : ''} ${opt.disabled ? 'disabled' : ''} ${idx === activeIdx ? 'active' : ''}`}
                onMouseEnter={() => setActiveIdx(idx)}
                onClick={() => choose(opt)}
                disabled={!!opt.disabled}
              >
                <span>{opt.label}</span>
                {opt.disabled && opt.hint ? <span className="dropdown-hint">{opt.hint}</span> : value === opt.value ? <span className="dropdown-check"><I name="check" size={14}/></span> : <span className="dropdown-check empty"></span>}
              </button>
              {opt.divider && <div className="dropdown-divider"></div>}
            </React.Fragment>
          ))}
        </div>
      )}
    </div>
  );
};

/* ============== Top bar ============== */
const TopBar = ({ t, theme, onTheme, lang, onLang, density, onDensity, onState, state, tab, onTab, period, compare, onPeriodChange, onCompareChange }) => {
  return (
    <div className="topbar" data-screen-label="00 TopBar">
      <div className="tb-logo">{t.appName}</div>
      <div className="tb-divider"></div>
      <div className="tb-tabs">
        {t.tabs.map((label, i) => {
          const key = ['overview', 'revenue', 'marketing', 'booking', 'traffic', 'customers'][i];
          return (
            <button
              key={label}
              className={`tb-tab ${tab === key ? 'active' : ''}`}
              onClick={() => onTab && onTab(key)}
            >{label}</button>
          );
        })}
      </div>
      <div className="tb-right">
        <Dropdown value={period} options={t.period.options || []} onChange={onPeriodChange} icon="calendar" lang={lang} />
        <Dropdown value={compare} options={t.compare.options || []} onChange={onCompareChange} lang={lang} />
        <div className="tb-refresh"><I name="refresh" size={14}/>{t.updated}</div>
        <div className="tb-avatar">МК</div>
      </div>
    </div>
  );
};

/* ============== Pulse strip ============== */
const Pulse = ({ t, alertMode, help, onOpenDrawer }) => {
  const h = help || {};
  const activate = (key) => {
    if (onOpenDrawer) onOpenDrawer(key);
  };
  const onKey = (e, key) => {
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      activate(key);
    }
  };
  const itemProps = (key) => ({
    role: 'button',
    tabIndex: 0,
    onClick: () => activate(key),
    onKeyDown: (e) => onKey(e, key),
  });
  return (
    <div className="pulse" data-screen-label="01 Pulse">
      <div className="pulse-item pulse-strip-element-clickable" {...itemProps('pulse_inhouse')}>
        <span className="pulse-icon"><I name="home" size={16}/></span>
        <span className="pulse-lbl">{t.pulse.inhouse.lbl}</span>
        <span className="pulse-val">{t.pulse.inhouse.val}</span>
        <HelpIcon help={h.inhouse} />
      </div>
      <div className="pulse-item pulse-strip-element-clickable" {...itemProps('pulse_arrivals')}>
        <span className="pulse-icon"><I name="log-in" size={16}/></span>
        <span className="pulse-lbl">{t.pulse.arrivals.lbl}</span>
        <span className="pulse-val">{t.pulse.arrivals.val}</span>
        <HelpIcon help={h.arrivals} />
      </div>
      <div className="pulse-item pulse-strip-element-clickable" {...itemProps('pulse_departures')}>
        <span className="pulse-icon"><I name="log-out" size={16}/></span>
        <span className="pulse-lbl">{t.pulse.departures.lbl}</span>
        <span className="pulse-val">{t.pulse.departures.val}</span>
        <HelpIcon help={h.departures} />
      </div>
      <div className="pulse-item pulse-strip-element-clickable" {...itemProps('pulse_spa')}>
        <span className="pulse-icon"><I name="sparkles" size={16}/></span>
        <span className="pulse-lbl">{t.pulse.spa.lbl}</span>
        <span className="pulse-val">{t.pulse.spa.val}</span>
        <HelpIcon help={h.spa} />
      </div>
      <div className="pulse-item pulse-strip-element-clickable" style={{ marginLeft: 'auto' }} {...itemProps('pulse_messages')}>
        <span className="pulse-icon"><I name="message" size={16}/></span>
        <span className="pulse-lbl">{t.pulse.msg.lbl}</span>
        <span className="pulse-val">{t.pulse.msg.val}</span>
        <span className="pulse-badge">{alertMode ? '7' : '3'}</span>
        <HelpIcon help={h.messages} />
      </div>
    </div>
  );
};

const pulseFmt = (tpl, vars = {}) => String(tpl || '').replace(/\{(\w+)\}/g, (_, key) => (
  vars[key] == null ? '' : vars[key]
));

const pulseDate = (iso, lang) => {
  if (!iso) return '';
  const locale = lang === 'en' ? 'en-US' : 'ru-RU';
  return new Date(`${iso}T12:00:00`).toLocaleDateString(locale, { day: 'numeric', month: 'short' }).replace('.', '');
};

const pulseDaysAgo = (iso, todayIso) => {
  if (!iso || !todayIso) return 0;
  const a = new Date(`${iso}T12:00:00`);
  const b = new Date(`${todayIso}T12:00:00`);
  return Math.max(0, Math.round((b - a) / 86400000));
};

const pulseGuestName = (item, privacyMode, next) => {
  if (!item) return '';
  if (next) return privacyMode ? (item.nextArrivalGuestMasked || item.nextArrivalGuest) : item.nextArrivalGuest;
  return privacyMode ? (item.guestNameMasked || item.guestName) : item.guestName;
};

const pulseGuestCount = (composition) => {
  if (!composition) return 0;
  return composition.split('·').reduce((sum, part) => {
    const m = part.match(/(\d+)/);
    return sum + (m ? Number(m[1]) : 0);
  }, 0);
};

const pulsePlatform = (platform) => {
  const map = {
    whatsapp: { label: 'WhatsApp', icon: '💬' },
    instagram: { label: 'Instagram DM', icon: '◎' },
    messenger: { label: 'Messenger', icon: '◌' },
  };
  return map[platform] || { label: platform || 'Message', icon: '💬' };
};

const pulseStub = (label) => {
  console.log(`[Pulse mock] ${label}`);
};

const pulseOpenKey = (e, fn) => {
  if (e.key === 'Enter' || e.key === ' ') {
    e.preventDefault();
    fn();
  }
};

const PulseChecklist = ({ items, lang }) => (
  <div className="pulse-drawer-checklist">
    {(items || []).map((item) => (
      <div className="pulse-drawer-check" key={typeof item.label === 'object' ? item.label.ru : item.label}>
        <span className={item.done ? 'done' : ''}>{item.done ? '✓' : '○'}</span>
        <span>{typeof item.label === 'object' ? (item.label[lang] || item.label.ru) : item.label}</span>
      </div>
    ))}
  </div>
);

const PulseDrawerView = ({
  d,
  lang,
  privacyMode,
  onClose,
  onOpenDrawer,
  onNavigateBooking,
  handledMessageIds,
  fadingMessageIds,
  onHandleMessage,
}) => {
  const pulse = window.OV2.pulse || {};
  const i18n = window.I18N_OV2[lang] || window.I18N_OV2.ru;
  const copy = (i18n.pulse.drawers || {})[d.pulseKind] || {};
  const todayStr = pulse.todayStr && (pulse.todayStr[lang] || pulse.todayStr.ru);
  const openBooking = () => onOpenDrawer && onOpenDrawer('booking');
  const goBooking = () => onNavigateBooking && onNavigateBooking();
  const cabins = pulse.cabins || [];
  const arrivals = (pulse.arrivals || []).slice().sort((a, b) => a.time.localeCompare(b.time));
  const departures = (pulse.departures || []).slice().sort((a, b) => a.deadline.localeCompare(b.deadline));
  const visibleMessages = (pulse.messages || [])
    .filter((m) => handledMessageIds.indexOf(m.id) === -1)
    .sort((a, b) => b.ageMinutes - a.ageMinutes);

  const summary = (() => {
    if (d.pulseKind === 'inhouse') {
      const occupied = cabins.filter((c) => c.status === 'occupied');
      return pulseFmt(copy.summary, {
        cabins: occupied.length,
        guests: occupied.reduce((sum, c) => sum + pulseGuestCount(c.composition), 0),
        empty: cabins.length - occupied.length,
      });
    }
    if (d.pulseKind === 'arrivals') {
      return pulseFmt(copy.summary, {
        arrivals: arrivals.length,
        guests: arrivals.reduce((sum, a) => sum + pulseGuestCount(a.composition), 0),
      });
    }
    if (d.pulseKind === 'departures') {
      return pulseFmt(copy.summary, {
        departures: departures.length,
        guests: departures.reduce((sum, item) => sum + pulseGuestCount(item.composition), 0),
      });
    }
    if (d.pulseKind === 'spa') {
      const summary = pulse.spaSummary || {};
      return pulseFmt(copy.summary, {
        sessions: summary.occupiedSlots || 0,
        guests: summary.guestCount || 5,
        free: (summary.freeSlots || []).length,
      });
    }
    if (d.pulseKind === 'messages') {
      return pulseFmt(copy.summary, {
        messages: visibleMessages.length,
        old: visibleMessages.filter((m) => m.ageMinutes > 30).length,
      });
    }
    return '';
  })();

  const renderInhouse = () => (
    <>
      <div className="pulse-drawer-list">
        {cabins.map((cabin) => {
          const occupied = cabin.status === 'occupied';
          if (!occupied) {
            return (
              <div className="pulse-drawer-card empty" key={cabin.name}>
                <div className="pulse-drawer-card-head">
                  <div className="pulse-drawer-titleline">
                    <span className="pulse-drawer-dot neutral"></span>
                    <strong>{cabin.name}</strong>
                  </div>
                </div>
                <div className="pulse-drawer-muted">{copy.free}</div>
                <div className="pulse-drawer-subline">
                  {pulseFmt(copy.nextArrival, {
                    date: pulseDate(cabin.nextArrivalDate, lang),
                    guest: pulseGuestName(cabin, privacyMode, true),
                  })}
                </div>
              </div>
            );
          }
          return (
            <div
              className="pulse-drawer-card clickable"
              key={cabin.name}
              role="button"
              tabIndex={0}
              onClick={openBooking}
              onKeyDown={(e) => pulseOpenKey(e, openBooking)}
            >
              <div className="pulse-drawer-card-head">
                <div className="pulse-drawer-titleline">
                  <span className="pulse-drawer-dot"></span>
                  <strong>{cabin.name}</strong>
                </div>
                <strong className="pulse-drawer-money">€{cabin.revenue}</strong>
              </div>
              <div className="pulse-drawer-subline">
                {pulseGuestName(cabin, privacyMode)} · {cabin.composition} · {cabin.flag} {cabin.countryCode}
              </div>
              <div className="pulse-drawer-divider"></div>
              <div className="pulse-drawer-muted">
                {pulseFmt(copy.checkedIn, {
                  date: pulseDate(cabin.arrivalDate, lang),
                  days: pulseDaysAgo(cabin.arrivalDate, pulse.todayIso),
                })}
              </div>
              <div className="pulse-drawer-muted">
                {pulseFmt(copy.departure, {
                  nights: cabin.nightsRemaining,
                  date: pulseDate(cabin.departureDate, lang),
                })}
              </div>
              {cabin.note && <div className="pulse-drawer-note">“{cabin.note}”</div>}
              <span className="pulse-drawer-inline-action">{copy.openBooking}</span>
            </div>
          );
        })}
      </div>
      <button type="button" className="pulse-drawer-footer" onClick={goBooking}>{copy.footerCTA}</button>
    </>
  );

  const renderArrivals = () => {
    if (!arrivals.length) {
      const next = cabins.find((c) => c.nextArrivalDate);
      return (
        <>
          <div className="pulse-drawer-empty">
            {pulseFmt(copy.empty, {
              date: next ? pulseDate(next.nextArrivalDate, lang) : todayStr,
              cabin: next ? next.name : '',
              guest: next ? pulseGuestName(next, privacyMode, true) : '',
            })}
          </div>
          <button type="button" className="pulse-drawer-footer" onClick={goBooking}>{copy.footerCTA}</button>
        </>
      );
    }
    return (
      <>
        <div className="pulse-drawer-list">
          {arrivals.map((arrival) => (
            <div className="pulse-drawer-card" key={`${arrival.time}-${arrival.cabin}`}>
              <div className="pulse-drawer-time">🕐 {arrival.time}</div>
              <div className="pulse-drawer-divider"></div>
              <div className="pulse-drawer-main">{arrival.cabin} · {pulseGuestName(arrival, privacyMode)}</div>
              <div className="pulse-drawer-subline">
                {arrival.composition} · {arrival.flag} {arrival.countryCode} · {pulseFmt(copy.nights, { nights: arrival.nights })}
              </div>
              <div className="pulse-drawer-subline">
                {copy.channel}: {arrival.channel} · <span className="pulse-drawer-pill">{arrival.repeatVisitCount > 1 ? pulseFmt(copy.repeatNTimes, { count: arrival.repeatVisitCount }) : copy.newGuest}</span>
              </div>
              {arrival.note && <div className="pulse-drawer-note">“{arrival.note}”</div>}
              <div className="pulse-drawer-section-title">{copy.checklistTitle}</div>
              <PulseChecklist items={arrival.checklist} lang={lang} />
              <div className="pulse-drawer-actions">
                <button type="button" className="tb-select" onClick={() => pulseStub('Send WhatsApp')}>{copy.sendWhatsApp}</button>
                <button type="button" className="tb-select" onClick={openBooking}>{copy.openBooking}</button>
              </div>
            </div>
          ))}
        </div>
        <button type="button" className="pulse-drawer-footer" onClick={goBooking}>{copy.footerCTA}</button>
      </>
    );
  };

  const renderDepartures = () => {
    if (!departures.length) {
      return (
        <>
          <div className="pulse-drawer-empty">{copy.empty}</div>
          <button type="button" className="pulse-drawer-footer" onClick={goBooking}>{copy.footerCTA}</button>
        </>
      );
    }
    return (
      <>
        <div className="pulse-drawer-list">
          {departures.map((departure) => (
            <div className="pulse-drawer-card" key={`${departure.deadline}-${departure.cabin}`}>
              <div className="pulse-drawer-time">🕐 {pulseFmt(copy.deadlineUntil, { time: departure.deadline })}</div>
              <div className="pulse-drawer-divider"></div>
              <div className="pulse-drawer-main">{departure.cabin} · {pulseGuestName(departure, privacyMode)}</div>
              <div className="pulse-drawer-subline">
                {departure.composition} · {departure.flag} {departure.countryCode} · {pulseFmt(copy.nights || '{nights} ночей', { nights: departure.nights })}
              </div>
              <div className="pulse-drawer-subline">
                {pulseFmt(copy.totalSpent, { amount: departure.totalSpent })} · <span className="pulse-drawer-pill">{departure.isFirstVisit ? copy.firstVisit : pulseFmt(copy.repeatGuest, { count: departure.repeatVisitCount || 2 })}</span>
              </div>
              <div className="pulse-drawer-section-title">{copy.checklistTitle}</div>
              <PulseChecklist items={departure.checklist} lang={lang} />
              <div className="pulse-drawer-actions">
                <button type="button" className="tb-select" onClick={() => pulseStub('Send thank-you')}>{copy.sendThankYou}</button>
                <button type="button" className="tb-select" onClick={openBooking}>{copy.openBooking}</button>
              </div>
            </div>
          ))}
        </div>
        <button type="button" className="pulse-drawer-footer" onClick={goBooking}>{copy.footerCTA}</button>
      </>
    );
  };

  const renderSpa = () => {
    const summary = pulse.spaSummary || {};
    const pct = summary.totalSlots ? Math.round((summary.occupiedSlots / summary.totalSlots) * 100) : 0;
    return (
      <>
        <div className="pulse-drawer-spa-list">
          {(pulse.spaSchedule || []).map((slot) => (
            <div className={`pulse-drawer-spa-slot ${slot.free ? 'free' : ''}`} key={slot.time}>
              <div className="pulse-drawer-spa-time">{slot.time}</div>
              <div>
                {slot.free ? copy.freeSlot : (
                  <>
                    <strong>{slot.treatment}</strong> · {pulseFmt(copy.duration, { minutes: slot.durationMin })} · {privacyMode ? (slot.guestNameMasked || slot.guestName) : slot.guestName} ({slot.cabin}) · €{slot.price}
                  </>
                )}
              </div>
            </div>
          ))}
        </div>
        <div className="pulse-drawer-spa-summary">
          <div>{pulseFmt(copy.summaryLoad, { used: summary.occupiedSlots, total: summary.totalSlots, pct })}</div>
          <div>{pulseFmt(copy.summaryRevenue, { amount: summary.revenue })}</div>
          <div>{pulseFmt(copy.summaryFree, { list: (summary.freeSlots || []).join(', ') })}</div>
        </div>
        <div className="pulse-drawer-cross-sell-hint">
          {pulseFmt(copy.crossSellHint, {
            free: (summary.freeSlots || []).length,
            guests: privacyMode ? (summary.crossSellHintMasked || summary.crossSellHint) : summary.crossSellHint,
          })}
        </div>
        <button type="button" className="pulse-drawer-footer" onClick={() => pulseStub('Open SPA calendar')}>{copy.footerCTA}</button>
      </>
    );
  };

  const renderMessages = () => {
    if (!visibleMessages.length) {
      return (
        <>
          <div className="pulse-drawer-empty success">{copy.empty}</div>
          <button type="button" className="pulse-drawer-footer" onClick={() => pulseStub('Open unified inbox')}>{copy.footerCTA}</button>
        </>
      );
    }
    return (
      <>
        <div className="pulse-drawer-list">
          {visibleMessages.map((message) => {
            const platform = pulsePlatform(message.platform);
            const urgency = message.ageMinutes > 120 ? 'urgent-critical' : message.ageMinutes > 30 ? 'urgent-warn' : '';
            const warn = message.ageMinutes > 120 ? '⚠⚠' : message.ageMinutes > 30 ? '⚠' : '';
            return (
              <div className={`pulse-drawer-card message ${urgency} ${fadingMessageIds.indexOf(message.id) > -1 ? 'is-fading' : ''}`} key={message.id}>
                <div className="pulse-drawer-time">{platform.icon} {platform.label} · {message.timeAgo[lang] || message.timeAgo.ru} {warn}</div>
                <div className="pulse-drawer-divider"></div>
                <div className="pulse-drawer-main">{copy.from}: {privacyMode ? (message.fromMasked || message.from) : message.from}</div>
                <div className="pulse-drawer-subline">
                  <span className="pulse-drawer-pill">{message.fromIsExistingGuest ? copy.existingGuest : copy.newContact}</span>
                </div>
                <div className="pulse-drawer-preview">“{message.preview}”</div>
                <div className="pulse-drawer-actions">
                  <button type="button" className="tb-select" onClick={() => pulseStub(`Open ${platform.label}`)}>{pulseFmt(copy.openPlatform, { platform: platform.label })}</button>
                  <button type="button" className="tb-select" onClick={() => onHandleMessage(message.id)}>{copy.markHandled}</button>
                </div>
              </div>
            );
          })}
        </div>
        <button type="button" className="pulse-drawer-footer" onClick={() => pulseStub('Open unified inbox')}>{copy.footerCTA}</button>
      </>
    );
  };

  const body = d.pulseKind === 'inhouse' ? renderInhouse()
    : d.pulseKind === 'arrivals' ? renderArrivals()
    : d.pulseKind === 'departures' ? renderDepartures()
    : d.pulseKind === 'spa' ? renderSpa()
    : renderMessages();

  return (
    <>
      <div className="drawer-overlay" onClick={onClose}></div>
      <div className="drawer">
        <div className="drawer-head">
          <div>
            <div className="drawer-title">{copy.title}</div>
            <div className="text-3" style={{ fontSize: 12 }}>{todayStr} · {summary}</div>
          </div>
          <button className="drawer-close" onClick={onClose}><I name="x" size={16}/></button>
        </div>
        <div className="drawer-body pulse-drawer-body">
          {body}
        </div>
      </div>
    </>
  );
};

const CellDrawerSection = ({ title, rows, children }) => (
  <div className="hm-drawer-section">
    <div className="uppercase-lbl">{title}</div>
    {children || rows.map((row) => (
      <div className="hm-drawer-row" key={row.lbl}>
        <span>{row.lbl}</span>
        <strong>{row.val}</strong>
      </div>
    ))}
  </div>
);

const CellDrawerView = ({ d, payload, lang, privacyMode, onClose }) => {
  const cell = payload || {};
  const labels = d.labels || {};
  const day = cell.day || {};
  const dateLabel = hmDate(cell.date || day.date, lang, { weekday: true });
  const guestName = privacyMode ? (cell.guestNameMasked || cell.guestName) : cell.guestName;
  const status = cell.status || 'empty';
  const isBooking = ['confirmed', 'in_house', 'checked_out'].includes(status);
  const isBlocked = status === 'blocked';
  const pct = day.totalCabins ? Math.round(((day.occupiedCount || 0) / day.totalCabins) * 100) : 0;
  const paymentLabel = d.payment && d.payment[cell.paymentStatus] ? d.payment[cell.paymentStatus] : hmPaymentLabel({ heatmap: { payment: d.payment || {} } }, cell.paymentStatus);
  const confidence = d.confidence && d.confidence[cell.discoveryConfidence] ? d.confidence[cell.discoveryConfidence] : (cell.discoveryConfidence || 'unknown');
  const title = isBooking
    ? hmFmt(d.titleBooking, { id: cell.bookingId || '-', cabin: cell.cabin || '-' })
    : isBlocked
      ? d.titleBlocked
      : d.titleFree;
  const opsText = isBlocked
    ? d.opsCopy.blocked
    : !isBooking
      ? d.opsCopy.empty
      : cell.isArrival
        ? d.opsCopy.arrival
        : cell.isDeparture
          ? d.opsCopy.departure
          : status === 'checked_out'
            ? d.opsCopy.past
            : status === 'in_house'
              ? d.opsCopy.stay
              : d.opsCopy.future;
  const freeRows = [
    { lbl: labels.dayStatus, val: isBlocked ? labels.blocked : labels.free },
    { lbl: lang === 'en' ? 'Cabin' : 'Домик', val: cell.cabin || '-' },
    { lbl: lang === 'en' ? 'Date' : 'Дата', val: dateLabel },
    { lbl: lang === 'en' ? 'Day load' : 'Загрузка дня', val: `${day.occupiedCount || 0}/${day.totalCabins || 5} · ${pct}%` },
    { lbl: 'Median ADR', val: hmMoney(day.adrMedian || 240) },
    { lbl: labels.suggestedPrice, val: hmMoney((day.adrMedian || 240) + (day.isWeekend ? 35 : 10)) },
  ];

  return (
    <>
      <div className="drawer-overlay" onClick={onClose}></div>
      <div className="drawer">
        <div className="drawer-head">
          <div>
            <div className="drawer-title">{title}</div>
            <div className="text-3" style={{ fontSize: 12 }}>{dateLabel} · {hmStatusLabel(status, lang)}</div>
          </div>
          <button className="drawer-close" onClick={onClose}><I name="x" size={16}/></button>
        </div>
        <div className="drawer-body hm-drawer-body">
          {isBooking ? (
            <>
              <div className="hm-drawer-stats">
                <div className="hm-drawer-stat">
                  <div className="uppercase-lbl">{lang === 'en' ? 'Night rate' : 'Цена ночи'}</div>
                  <strong>{hmMoney(cell.nightRate)}</strong>
                </div>
                <div className="hm-drawer-stat">
                  <div className="uppercase-lbl">{lang === 'en' ? 'Total' : 'Итого'}</div>
                  <strong>{hmMoney(cell.totalRevenue)}</strong>
                </div>
                <div className={`hm-drawer-stat payment ${cell.paymentStatus || ''}`}>
                  <div className="uppercase-lbl">{labels.payment}</div>
                  <strong>{paymentLabel}</strong>
                </div>
              </div>

              <CellDrawerSection title={labels.guest} rows={[
                { lbl: lang === 'en' ? 'Name' : 'Имя', val: guestName || labels.noGuest },
                { lbl: lang === 'en' ? 'Country' : 'Страна', val: `${cell.flag || ''} ${cell.countryCode || '-'}` },
                { lbl: lang === 'en' ? 'Guests' : 'Состав', val: cell.composition || '-' },
                { lbl: lang === 'en' ? 'Segment' : 'Сегмент', val: cell.guestSegment || '-' },
                { lbl: lang === 'en' ? 'Repeat' : 'Повторы', val: cell.repeatVisitCount > 1 ? `Repeat ${cell.repeatVisitCount}x` : (lang === 'en' ? 'New guest' : 'Новый гость') },
                { lbl: 'Email', val: privacyMode ? '***@***' : (cell.guestEmail || '-') },
                { lbl: lang === 'en' ? 'Phone' : 'Телефон', val: privacyMode ? '+*** ***' : (cell.guestPhone || '-') },
                { lbl: lang === 'en' ? 'Language' : 'Язык', val: cell.language || '-' },
              ]} />

              <CellDrawerSection title={labels.booking} rows={[
                { lbl: 'ID', val: cell.bookingId || '-' },
                { lbl: lang === 'en' ? 'Cabin' : 'Домик', val: cell.cabin || '-' },
                { lbl: lang === 'en' ? 'Dates' : 'Даты', val: `${hmDate(cell.arrivalDate, lang)} → ${hmDate(cell.departureDate, lang)} · ${cell.nights || 0}N` },
                { lbl: lang === 'en' ? 'Booking date' : 'Дата брони', val: hmDate(cell.bookingDate, lang) },
                { lbl: 'Lead time', val: `${cell.leadTimeDays || 0}d` },
                { lbl: lang === 'en' ? 'Arrival/departure' : 'Заезд/выезд', val: `${cell.arrivalTime || '15:00'} / ${cell.departureTime || '12:00'}` },
              ]} />

              <CellDrawerSection title={labels.source} rows={[
                { lbl: lang === 'en' ? 'Channel' : 'Канал', val: cell.channel || '-' },
                { lbl: 'Discovery', val: cell.discovery || '-' },
                { lbl: lang === 'en' ? 'Confidence' : 'Достоверность', val: confidence },
                { lbl: lang === 'en' ? 'CRM use' : 'Для CRM', val: lang === 'en' ? 'Use channel + discovery for cohort and retargeting' : 'Использовать channel + discovery для когорт и ретаргета' },
              ]} />

              <CellDrawerSection title={labels.payment} rows={[
                { lbl: lang === 'en' ? 'Status' : 'Статус', val: paymentLabel },
                { lbl: lang === 'en' ? 'Method' : 'Метод', val: cell.paymentMethod || '-' },
                { lbl: lang === 'en' ? 'Paid' : 'Оплачено', val: hmMoney(cell.amountPaid) },
                { lbl: lang === 'en' ? 'Balance due' : 'Остаток', val: hmMoney(cell.balanceDue) },
                { lbl: lang === 'en' ? 'Due date' : 'Срок оплаты', val: cell.paymentDueDate ? hmDate(cell.paymentDueDate, lang) : '-' },
              ]} />

              <CellDrawerSection title={labels.operations}>
                <div className="hm-drawer-note">{opsText}</div>
                {cell.note && <div className="hm-drawer-note muted">"{cell.note}"</div>}
              </CellDrawerSection>

              <div className="hm-drawer-actions">
                <button type="button" className="tb-select">{labels.openCloudbeds}</button>
                <button type="button" className="tb-select">{labels.sendWhatsApp}</button>
                {cell.balanceDue > 0 && <button type="button" className="tb-select">{labels.markPaid}</button>}
              </div>
            </>
          ) : (
            <>
              <div className="hm-drawer-empty-state">
                <div className="hm-drawer-empty-title">{isBlocked ? labels.blocked : labels.free}</div>
                <div>{opsText}</div>
              </div>
              <CellDrawerSection title={isBlocked ? labels.dayStatus : labels.opportunity} rows={freeRows} />
              {isBlocked && (
                <CellDrawerSection title={labels.operations} rows={[
                  { lbl: lang === 'en' ? 'Reason' : 'Причина', val: cell.blockReason || '-' },
                  { lbl: labels.salesAction, val: lang === 'en' ? 'Review if this date can return to sale' : 'Проверить, можно ли вернуть день в продажу' },
                ]} />
              )}
            </>
          )}
        </div>
      </div>
    </>
  );
};

/* ============== Drawer ============== */
const Drawer = ({ open, onClose, metricKey, drawerPayload, lang, privacyMode, onOpenDrawer, onNavigateBooking }) => {
  const [handledMessageIds, setHandledMessageIds] = useState([]);
  const [fadingMessageIds, setFadingMessageIds] = useState([]);
  useEffect(() => {
    if (!open) return undefined;
    const onKey = (e) => {
      if (e.key === 'Escape') onClose();
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [open, onClose]);

  const handleMessage = (id) => {
    if (handledMessageIds.indexOf(id) > -1 || fadingMessageIds.indexOf(id) > -1) return;
    setFadingMessageIds((ids) => ids.concat(id));
    window.setTimeout(() => {
      setHandledMessageIds((ids) => ids.indexOf(id) > -1 ? ids : ids.concat(id));
      setFadingMessageIds((ids) => ids.filter((item) => item !== id));
    }, 200);
  };

  if (!open) return null;
  const drawers = window.DRAWERS_OV2[lang] || window.DRAWERS_OV2.ru;
  const d = drawers[metricKey] || drawers.gop;
  if (d.kind === 'cell') {
    return <CellDrawerView d={d} payload={drawerPayload} lang={lang} privacyMode={privacyMode} onClose={onClose} />;
  }
  if (d.kind === 'pulse') {
    return (
      <PulseDrawerView
        d={d}
        lang={lang}
        privacyMode={privacyMode}
        onClose={onClose}
        onOpenDrawer={onOpenDrawer}
        onNavigateBooking={onNavigateBooking}
        handledMessageIds={handledMessageIds}
        fadingMessageIds={fadingMessageIds}
        onHandleMessage={handleMessage}
      />
    );
  }
  if (d.kind === 'bookingsList') {
    return (
      <>
        <div className="drawer-overlay" onClick={onClose}></div>
        <div className="drawer">
          <div className="drawer-head">
            <div>
              <div className="drawer-title">{d.title}</div>
              <div className="text-3" style={{ fontSize: 12 }}>{d.period}</div>
            </div>
            <button className="drawer-close" onClick={onClose}><I name="x" size={16}/></button>
          </div>
          <div className="drawer-body">
            <div className="bk-table-drawer-empty">{d.body}</div>
          </div>
        </div>
      </>
    );
  }
  if (d.kind === 'booking') {
    return (
      <>
        <div className="drawer-overlay" onClick={onClose}></div>
        <div className="drawer">
          <div className="drawer-head">
            <div>
              <div className="drawer-title">{d.title}</div>
              <div className="text-3" style={{ fontSize: 12 }}>{d.period}</div>
            </div>
            <button className="drawer-close" onClick={onClose}><I name="x" size={16}/></button>
          </div>
          <div className="drawer-body">
            <div className="bk-table-drawer-stats">
              {d.stats.map((stat) => (
                <div className="bk-table-drawer-stat" key={stat.lbl}>
                  <div className="uppercase-lbl">{stat.lbl}</div>
                  <div>{stat.val}</div>
                </div>
              ))}
            </div>

            {d.sections.map((section) => (
              <div className="bk-table-drawer-section" key={section.title}>
                <div className="uppercase-lbl" style={{ marginBottom: 10 }}>{section.title}</div>
                {section.rows.map((row) => (
                  <div className={`bk-table-drawer-row ${row.total ? 'total' : ''}`} key={`${section.title}-${row.lbl}`}>
                    <span>{row.lbl}</span>
                    <strong>{row.val}</strong>
                  </div>
                ))}
              </div>
            ))}

            <div className="bk-table-drawer-actions">
              <button className="tb-select">{d.actions.primary}</button>
              <button className="tb-select">{d.actions.secondary}</button>
            </div>
          </div>
        </div>
      </>
    );
  }
  return (
    <>
      <div className="drawer-overlay" onClick={onClose}></div>
      <div className="drawer">
        <div className="drawer-head">
          <div>
            <div className="drawer-title">{d.title}</div>
            <div className="text-3" style={{ fontSize: 12 }}>{d.period}</div>
          </div>
          <button className="drawer-close" onClick={onClose}><I name="x" size={16}/></button>
        </div>
        <div className="drawer-body">
          <div style={{ marginBottom: 24 }}>
            <div className="uppercase-lbl" style={{ marginBottom: 6 }}>{d.headline.lbl}</div>
            <div style={{ display: 'flex', alignItems: 'baseline', gap: 12 }}>
              <div style={{ fontSize: 32, fontWeight: 500, fontFeatureSettings: '"tnum"', letterSpacing: '-0.02em' }}>{d.headline.val}</div>
              {d.headline.delta && <Delta sign={d.headline.deltaSign}>{d.headline.delta}</Delta>}
            </div>
            {d.headline.sub && <div className="text-2" style={{ fontSize: 13, marginTop: 4 }}>{d.headline.sub}</div>}
          </div>

          <div className="uppercase-lbl" style={{ marginBottom: 12 }}>{d.sectionsTitle}</div>
          <div>
            {d.pl.map((row, i) => (
              <div key={i} className={`pl-row ${row.total ? 'total' : ''}`}>
                <div className="pl-lbl" style={row.total ? { fontWeight: 500 } : {}}>{row.lbl}</div>
                <div className={`pl-num ${row.total ? '' : 'muted'}`}>
                  {row.neg ? '−' : ''}{row.val}
                </div>
                <div className="pl-num" style={{ color: row.deltaPct ? 'var(--pos)' : 'transparent', minWidth: 60, textAlign: 'right' }}>
                  {row.deltaPct || '·'}
                </div>
              </div>
            ))}
          </div>

          <div className="uppercase-lbl" style={{ marginTop: 28, marginBottom: 12 }}>{d.sparkLabel}</div>
          <div className="card" style={{ padding: 14 }}>
            <Sparkline data={Array.from({ length: 90 }, (_, i) => 28 + Math.sin((i + (metricKey || 'x').length) / 10) * 4 + i / 25)} height={80} color="var(--chart-1)" fill />
          </div>
        </div>
      </div>
    </>
  );
};

/* ============== Skeleton block ============== */
const Sk = ({ w = '100%', h = 16, r = 4, style = {} }) => (
  <div className="sk" style={{ width: w, height: h, borderRadius: r, ...style }}></div>
);

const SkeletonOverview = () => (
  <div className="page" data-screen-label="04 Skeleton">
    <Sk h={48} r={8} style={{ marginTop: 16 }} />
    <div className="row-4 section">
      {[0,1,2,3].map(i => (
        <div className="card hero" key={i}>
          <Sk w={70} h={10} />
          <Sk w={140} h={28} style={{ marginTop: 8 }} />
          <Sk w={90} h={12} style={{ marginTop: 6 }} />
          <div style={{ flex: 1 }}></div>
          <Sk w="100%" h={24} />
        </div>
      ))}
    </div>
    <div className="row-2-60-40 section">
      <div className="card" style={{ minHeight: 280 }}>
        <Sk w={180} h={14} />
        <Sk w="100%" h={210} style={{ marginTop: 16 }} />
      </div>
      <div className="card" style={{ minHeight: 280 }}>
        <Sk w={140} h={14} />
        <div style={{ display: 'grid', gap: 4, marginTop: 16 }}>
          {[0,1,2,3,4].map(i => <Sk key={i} w="100%" h={28} />)}
        </div>
      </div>
    </div>
    <div className="row-4 section">
      {[0,1,2,3].map(i => (
        <div className="card mk-card" key={i}>
          <Sk w={70} h={10} />
          <Sk w={100} h={22} style={{ marginTop: 6 }} />
          <Sk w={120} h={10} style={{ marginTop: 6 }} />
        </div>
      ))}
    </div>
    <div className="row-2-60-40 section">
      <div className="card" style={{ minHeight: 220 }}>
        <Sk w={200} h={14} />
        {[0,1,2,3].map(i => <Sk key={i} w="100%" h={32} style={{ marginTop: 12 }} />)}
      </div>
      <div className="card" style={{ minHeight: 220 }}>
        <Sk w={150} h={14} />
        {[0,1,2].map(i => <Sk key={i} w="100%" h={48} style={{ marginTop: 14 }} />)}
      </div>
    </div>
  </div>
);

window.OV2_UI = { TTProvider, useTT, Delta, Sparkline, StackedArea, Heatmap, PaceRow, HeroCard, TopBar, Dropdown, Pulse, Drawer, Sk, SkeletonOverview, HelpIcon, HelpTitle };
