// Parquet Viewer — Row-level data explorer with anomaly detection
// Light theme matching Signals page design language

function ParquetViewerPage() {
  const [mode, setMode] = React.useState('NIFTY');
  const [date, setDate] = React.useState(() => {
    const d = new Date();
    while (d.getDay() === 0 || d.getDay() === 6) d.setDate(d.getDate() - 1);
    if (d.toDateString() === new Date().toDateString()) d.setDate(d.getDate() - 1);
    while (d.getDay() === 0 || d.getDay() === 6) d.setDate(d.getDate() - 1);
    return d.toISOString().slice(0, 10);
  });
  const [hourIst, setHourIst] = React.useState(14);
  const [symbol, setSymbol] = React.useState('');
  const [loading, setLoading] = React.useState(false);
  const [data, setData] = React.useState(null);
  const [error, setError] = React.useState(null);
  const [activeView, setActiveView] = React.useState('time-slice');
  const [files, setFiles] = React.useState(null);
  const [selectedFile, setSelectedFile] = React.useState(null);
  const [sortCol, setSortCol] = React.useState('exchFeedTime');
  const [sortAsc, setSortAsc] = React.useState(true);
  const [colFilter, setColFilter] = React.useState('core');
  const [expandedRow, setExpandedRow] = React.useState(null);
  const [limit, setLimit] = React.useState(300);
  const [anomalies, setAnomalies] = React.useState([]);
  const [insightsOpen, setInsightsOpen] = React.useState(false);
  const el = React.createElement;

  const istToUtcHours = (istH) => {
    const base = istH - 5.5;
    const h1 = Math.floor(base < 0 ? base + 24 : base);
    const h2 = Math.ceil(base < 0 ? base + 24 : base);
    return h1 === h2 ? [h1] : [h1, h2];
  };

  // ── Anomaly Detection Engine ──
  const detectAnomalies = (rows, columns) => {
    if (!rows || rows.length === 0) return [];
    const found = [];
    const numCols = columns.filter(c => c !== 'tradingSymbol' && c !== 'depth' && c !== 'schemaVersion' && c !== '_sourceFile' && c !== 'capturedAtUtc' && c !== 'exchFeedTime');
    const stats = {};
    numCols.forEach(col => {
      const vals = rows.map(r => r[col]).filter(v => v != null && typeof v === 'number' && isFinite(v));
      if (vals.length < 3) return;
      const sorted = [...vals].sort((a, b) => a - b);
      const q1 = sorted[Math.floor(sorted.length * 0.25)];
      const q3 = sorted[Math.floor(sorted.length * 0.75)];
      const iqr = q3 - q1;
      const median = sorted[Math.floor(sorted.length * 0.5)];
      const mean = vals.reduce((a, b) => a + b, 0) / vals.length;
      const nullCount = rows.filter(r => r[col] == null).length;
      stats[col] = { q1, q3, iqr, median, mean, min: sorted[0], max: sorted[sorted.length - 1], nullCount, total: rows.length };
    });

    rows.forEach((row, ri) => {
      numCols.forEach(col => {
        const val = row[col];
        const st = stats[col];
        if (val == null || st == null) return;
        if (st.iqr > 0) {
          const lower = st.q1 - 3 * st.iqr;
          const upper = st.q3 + 3 * st.iqr;
          if (val < lower || val > upper) {
            found.push({ row: ri, col, value: val, severity: 'critical',
              message: `${col} = ${val.toFixed?.(4) || val} \u2014 extreme outlier (IQR range: ${st.q1.toFixed(2)} to ${st.q3.toFixed(2)})`,
              description: getAnomalyDescription(col, val, st, row)
            });
            return;
          }
          const wLower = st.q1 - 1.5 * st.iqr;
          const wUpper = st.q3 + 1.5 * st.iqr;
          if (val < wLower || val > wUpper) {
            found.push({ row: ri, col, value: val, severity: 'warning',
              message: `${col} = ${val.toFixed?.(4) || val} \u2014 outside normal range (${st.q1.toFixed(2)} to ${st.q3.toFixed(2)})`,
              description: getAnomalyDescription(col, val, st, row)
            });
            return;
          }
        }
        if (col === 'spread' && val < 0) {
          found.push({ row: ri, col, value: val, severity: 'warning',
            message: `Negative spread: ${val.toFixed(2)} on ${row.tradingSymbol || ''}`,
                description: 'Negative bid-ask spread indicates crossed or stale quotes.'
                });
              }
            });
      if (row.totBuyQuan != null && row.totSellQuan != null && row.totSellQuan > 0) {
        const ratio = row.totBuyQuan / row.totSellQuan;
        if (ratio > 20) {
          found.push({ row: ri, col: 'totBuyQuan', value: row.totBuyQuan, severity: 'info',
            message: `Buy/Sell ratio = ${ratio.toFixed(0)}x \u2014 massive buy-side pressure on ${row.tradingSymbol}`,
            description: 'Significantly more buy orders than sell orders. Could indicate institutional accumulation.'
          });
        }
      }
      if (row.delta != null && row.gamma != null && Math.abs(row.delta) > 0.9 && row.gamma < 0.0001) {
        found.push({ row: ri, col: 'delta', value: row.delta, severity: 'info',
          message: `Deep ITM: delta=${row.delta.toFixed(3)}, gamma\u22480 \u2014 behaves like the underlying`,
          description: 'This option is deep in-the-money and moves nearly 1:1 with the underlying index.'
        });
      }
    });

    numCols.forEach(col => {
      const st = stats[col];
      if (st && st.nullCount > st.total * 0.5) {
        found.push({ row: -1, col, value: null, severity: 'warning',
          message: `${col}: ${Math.round(st.nullCount * 100 / st.total)}% null values (${st.nullCount}/${st.total})`,
          description: `More than half the rows have no data for "${col}". The indicator may need more warmup time.`
        });
      }
    });

    const seen = new Set();
    return found.filter(a => {
      const key = `${a.row}:${a.col}`;
      if (seen.has(key)) return false;
      seen.add(key);
      return true;
    }).sort((a, b) => (a.severity === 'critical' ? 0 : a.severity === 'warning' ? 1 : 2) - (b.severity === 'critical' ? 0 : b.severity === 'warning' ? 1 : 2));
  };

  const getAnomalyDescription = (col, val, st, row) => {
    if (col === 'spread') return `The bid-ask spread is abnormally ${val > 0 ? 'wide' : 'negative'}. Negative spreads can occur from stale quotes. Very wide spreads indicate poor liquidity.`;
    if (col === 'theta') return `Theta is unusually high. Options close to expiry lose value rapidly.`;
    if (col === 'vega') return `Vega sensitivity is abnormal \u2014 unusually sensitive to volatility changes.`;
    if (col === 'totBuyQuan' || col === 'totSellQuan') return `Order book quantity is unusually large \u2014 could indicate institutional positioning.`;
    return `Value ${val?.toFixed?.(2) || val} deviates significantly from the typical range (${st.min.toFixed(2)} to ${st.max.toFixed(2)}, median: ${st.median.toFixed(2)}).`;
  };

  React.useEffect(() => {
    if (data && data.rows && data.rows.length > 0) {
      setAnomalies(detectAnomalies(data.rows, data.columns || []));
    } else {
      setAnomalies([]);
    }
  }, [data]);

  const doSearch = async () => {
    const utcHours = istToUtcHours(hourIst).join(',');
    setLoading(true); setError(null);
    if (activeView === 'file-browse') {
      setFiles(null);
      try { setFiles(await API.get(`/api/parquet-audit/browse?mode=${mode}&date=${date}&hours=${utcHours}`)); }
      catch (err) { setError(err.message || 'Browse failed'); }
      finally { setLoading(false); }
    } else {
      setData(null);
      try {
        const params = new URLSearchParams({ mode, date, hours: utcHours, limit });
        if (symbol.trim()) params.set('symbol', symbol.trim());
        setData(await API.get(`/api/parquet-audit/time-slice?${params}`));
      } catch (err) { setError(err.message || 'Search failed'); }
      finally { setLoading(false); }
    }
  };

  const loadFileRows = async (fileName) => {
    setLoading(true); setError(null); setSelectedFile(fileName);
    try {
      const params = new URLSearchParams({ mode, file: fileName, limit });
      if (symbol.trim()) params.set('symbol', symbol.trim());
      setData(await API.get(`/api/parquet-audit/rows?${params}`));
    } catch (err) { setError(err.message || 'Failed to read file'); }
    finally { setLoading(false); }
  };

  const quickLoad = (m, d, h) => {
    setMode(m); setDate(d); setHourIst(h); setActiveView('time-slice');
    setLoading(true); setError(null); setData(null);
    const params = new URLSearchParams({ mode: m, date: d, hours: istToUtcHours(h).join(','), limit });
    if (symbol.trim()) params.set('symbol', symbol.trim());
    API.get(`/api/parquet-audit/time-slice?${params}`)
      .then(res => setData(res)).catch(err => setError(err.message || 'Failed'))
      .finally(() => setLoading(false));
  };

  const colGroups = {
    core: ['exchFeedTime','tradingSymbol','ltp','moneyness','timeToExpiryDays','spread','totBuyQuan','totSellQuan'],
    smc: ['exchFeedTime','tradingSymbol','underlyingLtp','underlyingMomentum','breakOfStructure','pricePositionInRange','rangeCompression'],
    greeks: ['exchFeedTime','tradingSymbol','ltp','delta','gamma','theta','vega','ivRank','putCallRatio'],
    derived: ['exchFeedTime','tradingSymbol','ltp','oiDelta','volumeSpike','orderFlowImbalance','liquidityScore','minutesSinceOpen','rollingStd5','rollingRange10'],
    volume: ['exchFeedTime','tradingSymbol','ltp','tradeVolume_x','opnInterest','totBuyQuan','totSellQuan','depth']
  };

  const getVisibleCols = () => {
    if (!data || !data.columns) return [];
    if (colFilter === 'all') {
      const pin = ['exchFeedTime', 'tradingSymbol'];
      const rest = data.columns.filter(c => c !== '_sourceFile' && !pin.includes(c));
      return [...pin.filter(c => data.columns.includes(c)), ...rest];
    }
    return (colGroups[colFilter] || []).filter(c => data.columns.includes(c));
  };

  const getSortedRows = () => {
    if (!data || !data.rows) return [];
    return [...data.rows].sort((a, b) => {
      const av = a[sortCol], bv = b[sortCol];
      if (av == null && bv == null) return 0;
      if (av == null) return 1;
      if (bv == null) return -1;
      return (sortAsc ? 1 : -1) * (typeof av === 'string' ? av.localeCompare(bv) : av - bv);
    });
  };

  const handleSort = (col) => {
    if (sortCol === col) setSortAsc(!sortAsc);
    else { setSortCol(col); setSortAsc(true); }
  };

  const fmtIst = (val) => {
    if (!val) return '\u2014';
    try {
      const d = new Date(val);
      const ist = new Date(d.getTime() + 5.5 * 3600000);
      return String(ist.getUTCHours()).padStart(2, '0') + ':' + String(ist.getUTCMinutes()).padStart(2, '0') + ':' + String(ist.getUTCSeconds()).padStart(2, '0');
    } catch { return '\u2014'; }
  };

  const fmtIstFull = (val) => {
    if (!val) return '\u2014';
    try {
      const d = new Date(val);
      const ist = new Date(d.getTime() + 5.5 * 3600000);
      return ist.toISOString().replace('T', ' ').slice(0, 19) + ' IST';
    } catch { return val; }
  };

  const fmtExchIst = (val) => {
    if (!val) return '\u2014';
    try {
      const d = new Date(val);
      if (isNaN(d.getTime())) return val;
      const ist = new Date(d.getTime() + 5.5 * 3600000);
      return ist.getUTCFullYear() + '-' +
        String(ist.getUTCMonth() + 1).padStart(2, '0') + '-' +
        String(ist.getUTCDate()).padStart(2, '0') + ' ' +
        String(ist.getUTCHours()).padStart(2, '0') + ':' +
        String(ist.getUTCMinutes()).padStart(2, '0') + ':' +
        String(ist.getUTCSeconds()).padStart(2, '0');
    } catch { return val; }
  };

  const fmt = (val, col) => {
    if (val === null || val === undefined) return '\u2014';
    if (col === 'exchFeedTime') return fmtExchIst(val);
    if (col === 'capturedAtUtc') return fmtIst(val);
    if (col === 'depth') return '\u2026';
    if (typeof val === 'number') {
      if (['ltp','spread'].includes(col)) return val.toFixed(2);
      if (col === 'moneyness') return val.toFixed(4);
      if (col === 'timeToExpiryDays') return val.toFixed(3);
      if (['putCallRatio','ivRank','delta','gamma','theta','vega',
           'underlyingMomentum','breakOfStructure','pricePositionInRange','rangeCompression',
           'oiDelta','volumeSpike','orderFlowImbalance','liquidityScore',
           'rollingStd5','rollingRange10','minutesSinceOpen'].includes(col))
        return val.toFixed(4);
      if (Number.isInteger(val)) return val.toLocaleString();
      return val.toFixed(2);
    }
    return String(val);
  };

  const getRowAnomalies = (ri) => anomalies.filter(a => a.row === ri);
  const getCellAnomaly = (ri, col) => anomalies.find(a => a.row === ri && a.col === col);

  const cellBg = (val, col, ri) => {
    const anom = getCellAnomaly(ri, col);
    if (anom) return anom.severity === 'critical' ? 'rgba(220,38,38,0.06)' : anom.severity === 'warning' ? 'rgba(217,119,6,0.06)' : '';
    if (val === null || val === undefined) return 'rgba(148,163,184,0.04)';
    return '';
  };

  const sevColor = (anom) => anom ? (anom.severity === 'critical' ? '#dc2626' : anom.severity === 'warning' ? '#d97706' : '#2563eb') : '#e2e8f0';

  // ── Shared styles (matching signals.jsx) ──
  const labelStyle = { display: 'block', fontSize: '0.72rem', fontWeight: '600', color: '#64748b', textTransform: 'uppercase', letterSpacing: '0.5px', marginBottom: '4px' };
  const inputStyle = { width: '100%', padding: '7px 10px', borderRadius: '8px', border: '1px solid #e2e8f0', fontSize: '0.82rem', background: '#fff', outline: 'none', transition: 'border-color 0.2s' };

  // ── Quick links ──
  const lastFri = (() => { const d = new Date(); while (d.getDay() !== 5) d.setDate(d.getDate() - 1); return d.toISOString().slice(0, 10); })();
  const lastThu = (() => { const d = new Date(); while (d.getDay() !== 4) d.setDate(d.getDate() - 1); return d.toISOString().slice(0, 10); })();

  const quickLinks = el('div', { style: { display: 'flex', gap: '6px', flexWrap: 'wrap', alignItems: 'center' } },
    el('span', { style: { fontSize: '0.75rem', color: '#94a3b8', marginRight: '2px' } }, 'Quick:'),
    ...[
      { label: `NIFTY ${lastFri.slice(5)} 14h`, m: 'NIFTY', d: lastFri, h: 14 },
      { label: `NIFTY ${lastThu.slice(5)} 14h`, m: 'NIFTY', d: lastThu, h: 14 },
      { label: `SENSEX ${lastFri.slice(5)} 14h`, m: 'SENSEX', d: lastFri, h: 14 },
      { label: 'NIFTY Apr 9 14h', m: 'NIFTY', d: '2025-04-09', h: 14 },
      { label: 'NIFTY Apr 9 15h', m: 'NIFTY', d: '2025-04-09', h: 15 },
    ].map((q, i) =>
      el('button', { key: i, onClick: () => quickLoad(q.m, q.d, q.h),
        style: {
          padding: '3px 10px', background: '#fff', color: '#2563eb', border: '1px solid #e2e8f0',
          borderRadius: '6px', cursor: 'pointer', fontSize: '0.72rem', fontWeight: '500',
          transition: 'all 0.15s'
        },
        onMouseEnter: e => { e.currentTarget.style.borderColor = '#2563eb'; e.currentTarget.style.background = '#eff6ff'; },
        onMouseLeave: e => { e.currentTarget.style.borderColor = '#e2e8f0'; e.currentTarget.style.background = '#fff'; }
      }, q.label)
    )
  );

  // ── Controls (filter card, matching signals layout) ──
  const controls = el('div', {
    className: 'card',
    style: { padding: '16px', marginBottom: '16px' }
  },
    quickLinks,
    el('div', {
      style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(160px, 1fr))', gap: '12px', alignItems: 'end', marginTop: '12px' }
    },
      el('div', null,
        el('label', { style: labelStyle }, 'Index'),
        el('select', { value: mode, onChange: e => setMode(e.target.value), style: inputStyle },
          el('option', { value: 'NIFTY' }, 'NIFTY'), el('option', { value: 'SENSEX' }, 'SENSEX'))),
      el('div', null,
        el('label', { style: labelStyle }, 'Date'),
        el('input', { type: 'date', value: date, onChange: e => setDate(e.target.value), style: inputStyle })),
      el('div', null,
        el('label', { style: labelStyle }, 'Hour (IST)'),
        el('select', { value: hourIst, onChange: e => setHourIst(+e.target.value), style: inputStyle },
          ...[9, 10, 11, 12, 13, 14, 15].map(h =>
            el('option', { key: h, value: h }, `${h}:00\u2013${h}:59 (UTC ${istToUtcHours(h).join(',')})`)
          ))),
      el('div', null,
        el('label', { style: labelStyle }, 'Symbol'),
        el('input', { type: 'text', placeholder: 'e.g. NIFTY24APR22000CE', value: symbol,
          onChange: e => setSymbol(e.target.value), onKeyDown: e => e.key === 'Enter' && doSearch(),
          style: inputStyle })),
      el('div', null,
        el('label', { style: labelStyle }, 'Rows'),
        el('select', { value: limit, onChange: e => setLimit(+e.target.value), style: inputStyle },
          ...[100, 300, 500, 1000].map(n => el('option', { key: n, value: n }, n)))),
      el('div', null,
        el('label', { style: labelStyle }, 'View'),
        el('div', { className: 'flex gap-2' },
          el('button', {
            className: `filter-chip ${activeView === 'time-slice' ? 'active' : ''}`,
            onClick: () => setActiveView('time-slice')
          }, 'Time Slice'),
          el('button', {
            className: `filter-chip ${activeView === 'file-browse' ? 'active' : ''}`,
            onClick: () => setActiveView('file-browse')
          }, 'Files')
        )),
      el('div', null,
        el('label', { style: { ...labelStyle, visibility: 'hidden' } }, '.'),
        el('button', {
          className: 'btn btn-primary',
          onClick: () => doSearch(),
          disabled: loading,
          style: { width: '100%' }
        }, loading ? 'Searching\u2026' : '\uD83D\uDD0D Search'))
    )
  );

  // ── Summary bar ──
  const critCount = anomalies.filter(a => a.severity === 'critical').length;
  const warnCount = anomalies.filter(a => a.severity === 'warning').length;

  const summaryBar = data ? el('div', {
    style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '12px', padding: '0 4px' }
  },
    el('span', { style: { fontSize: '0.82rem', color: '#64748b', fontWeight: '500' } },
      `${(data.totalRows || 0).toLocaleString()} rows \u00b7 ${(data.columns || []).filter(c => c !== '_sourceFile').length} columns`,
      data.totalFiles != null ? ` \u00b7 ${data.filesRead || 0}/${data.totalFiles} files scanned` : ''),
    anomalies.length > 0 ? el('button', {
      onClick: () => setInsightsOpen(!insightsOpen),
      style: {
        background: 'transparent', border: 'none', cursor: 'pointer', padding: '4px 10px',
        fontSize: '0.8rem', fontWeight: '500', borderRadius: '6px',
        color: critCount > 0 ? '#dc2626' : warnCount > 0 ? '#d97706' : '#2563eb',
        display: 'flex', alignItems: 'center', gap: '6px'
      }
    },
      el('span', {
        style: {
          display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
          width: '20px', height: '20px', borderRadius: '50%', fontSize: '0.7rem', fontWeight: '700',
          background: critCount > 0 ? '#fee2e2' : warnCount > 0 ? '#fef3c7' : '#dbeafe',
          color: critCount > 0 ? '#dc2626' : warnCount > 0 ? '#d97706' : '#2563eb'
        }
      }, anomalies.length),
      `${critCount} anomalies, ${warnCount} warnings${insightsOpen ? ' \u25B2' : ' \u25BC'}`
    ) : (data.rows && data.rows.length > 0 ? el('span', { style: { fontSize: '0.8rem', color: '#16a34a', fontWeight: '500' } }, '\u2713 No anomalies') : null)
  ) : null;

  // ── Column filter pills (using filter-chip class) ──
  const colPills = data && data.columns && data.columns.length > 0 ? el('div', {
    style: { display: 'flex', gap: '6px', marginBottom: '12px', flexWrap: 'wrap' }
  },
    ...['all', 'core', 'indicators', 'greeks', 'underlying', 'volume'].map(tab => {
      const active = colFilter === tab;
      const count = tab === 'all' ? data.columns.filter(c => c !== '_sourceFile').length : (colGroups[tab] || []).length;
      return el('button', {
        key: tab,
        className: `filter-chip ${active ? 'active' : ''}`,
        onClick: () => setColFilter(tab)
      }, `${tab.charAt(0).toUpperCase() + tab.slice(1)} (${count})`);
    })
  ) : null;

  // ── Insights panel (collapsible, card style) ──
  const insightsPanel = (insightsOpen && anomalies.length > 0) ? el('div', {
    className: 'card',
    style: { padding: '12px 16px', marginBottom: '12px' }
  },
    el('div', { style: { maxHeight: '240px', overflow: 'auto' } },
      ...anomalies.slice(0, 25).map((a, i) => {
        const sc = a.severity === 'critical' ? '#dc2626' : a.severity === 'warning' ? '#d97706' : '#2563eb';
        const bg = a.severity === 'critical' ? '#fee2e2' : a.severity === 'warning' ? '#fef3c7' : '#eff6ff';
        return el('div', { key: i, style: {
          padding: '8px 12px', marginBottom: '4px', borderRadius: '8px',
          borderLeft: `3px solid ${sc}`, background: bg, fontSize: '0.78rem'
        } },
          el('div', { style: { display: 'flex', gap: '8px', alignItems: 'baseline' } },
            a.row >= 0 ? el('span', { style: { color: '#94a3b8', fontSize: '0.7rem', minWidth: '42px' } }, `Row ${a.row + 1}`) : null,
            el('span', { style: { color: '#1e293b', fontWeight: '500' } }, a.message)),
          a.description ? el('div', { style: { color: '#64748b', fontSize: '0.72rem', marginTop: '3px', paddingLeft: a.row >= 0 ? '50px' : '0', lineHeight: '1.5' } }, a.description) : null
        );
      }),
      anomalies.length > 25 ? el('div', { style: { color: '#94a3b8', fontSize: '0.75rem', padding: '6px 12px' } }, `\u2026and ${anomalies.length - 25} more`) : null
    )
  ) : null;

  // ── File browser ──
  const fileBrowser = (activeView === 'file-browse' && files && !loading) ? el('div', {
    className: 'card', style: { overflow: 'hidden', marginBottom: '12px' }
  },
    el('div', {
      style: { padding: '10px 16px', borderBottom: '1px solid #e2e8f0', display: 'flex', justifyContent: 'space-between', alignItems: 'center', background: '#f8fafc' }
    },
      el('span', { style: { fontSize: '0.82rem', fontWeight: '600', color: '#1e293b' } },
        `${files.fileCount} files \u2014 ${date} (UTC ${(files.hoursSearched || []).join(', ')})`)),
    files.fileCount === 0
      ? el(EmptyState, { icon: '\uD83D\uDCC2', title: 'No files found', text: 'Market may have been closed on this date.' })
      : el('div', { style: { maxHeight: '260px', overflow: 'auto' } },
        ...(files.files || []).map((f, i) =>
          el('div', { key: i, onClick: () => loadFileRows(f.name),
            style: {
              padding: '8px 16px', cursor: 'pointer', display: 'flex', justifyContent: 'space-between',
              borderBottom: '1px solid #f1f5f9',
              background: selectedFile === f.name ? '#eff6ff' : i % 2 === 0 ? '#fff' : '#fafbfc'
            },
            onMouseEnter: e => { if (selectedFile !== f.name) e.currentTarget.style.background = '#eff6ff'; },
            onMouseLeave: e => { if (selectedFile !== f.name) e.currentTarget.style.background = i % 2 === 0 ? '#fff' : '#fafbfc'; }
          },
            el('span', { style: { fontFamily: 'var(--mono, monospace)', fontSize: '0.78rem', color: '#1e293b' } }, f.name.split('/').pop()),
            el('span', { style: { fontSize: '0.72rem', color: '#94a3b8' } }, `${f.sizeKb} KB`))
        ))
  ) : null;

  // ── Sortable table header ──
  const thCell = (label, col, extraStyle) => {
    const isSorted = sortCol === col;
    const arrow = isSorted ? (sortAsc ? ' \u25B2' : ' \u25BC') : '';
    return el('th', {
      key: label,
      style: Object.assign({
        padding: '10px 8px', textAlign: 'left', fontWeight: '600',
        fontSize: '0.68rem', textTransform: 'uppercase', letterSpacing: '0.5px',
        color: isSorted ? '#1e40af' : '#6b7280', whiteSpace: 'nowrap',
        cursor: col ? 'pointer' : 'default', userSelect: 'none',
        borderBottom: isSorted ? '2px solid #2563eb' : '2px solid #e2e8f0',
        background: '#f8fafc',
        position: 'sticky', top: 0, zIndex: 10,
        minWidth: col === 'tradingSymbol' ? '140px' : col === 'capturedAtUtc' || col === 'exchFeedTime' ? '80px' : '58px'
      }, extraStyle || {}),
      onClick: col ? () => handleSort(col) : undefined
    }, label + arrow);
  };

  // ── Data table ──
  const visCols = getVisibleCols();
  const sortedRows = getSortedRows();

  const dataTable = (data && sortedRows.length > 0 && !loading) ? el('div', {
    className: 'card', style: { overflow: 'hidden' }
  },
    el('div', { style: { overflowX: 'auto', maxHeight: 'calc(100vh - 340px)' } },
      el('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: '0.78rem' } },
        el('thead', null,
          el('tr', null,
            thCell('#', null, { textAlign: 'center', minWidth: '28px', padding: '10px 4px' }),
            ...visCols.map(col =>
              thCell(
                col === 'exchFeedTime' ? 'Time (IST)' : col === 'capturedAtUtc' ? 'Captured' : col.replace(/([A-Z])/g, '\u200B$1'),
                col
              )
            )
          )
        ),
        el('tbody', null,
          ...sortedRows.map((row, ri) => {
            const rowAnoms = getRowAnomalies(ri);
            const hasAnomaly = rowAnoms.length > 0;
            const hasCritical = rowAnoms.some(a => a.severity === 'critical');
            const rowBg = ri % 2 === 0 ? '#fff' : '#fafbfc';
            return [
              el('tr', { key: ri,
                onClick: () => setExpandedRow(expandedRow === ri ? null : ri),
                style: {
                  cursor: 'pointer',
                  background: expandedRow === ri ? '#eff6ff' : rowBg,
                  borderBottom: '1px solid #f1f5f9',
                  borderLeft: hasCritical ? '3px solid #dc2626' : hasAnomaly ? '3px solid #d97706' : '3px solid transparent',
                  transition: 'background 0.15s'
                },
                onMouseEnter: e => { if (expandedRow !== ri) e.currentTarget.style.background = '#f0f4ff'; },
                onMouseLeave: e => { if (expandedRow !== ri) e.currentTarget.style.background = rowBg; }
              },
                el('td', { style: { padding: '6px 4px', color: '#94a3b8', fontSize: '0.68rem', textAlign: 'center' } },
                  hasAnomaly
                    ? el('span', {
                        title: rowAnoms.map(a => a.message).join('\n'),
                        style: {
                          display: 'inline-block', width: '8px', height: '8px', borderRadius: '50%',
                          background: hasCritical ? '#dc2626' : '#d97706'
                        }
                      })
                    : (ri + 1)),
                ...visCols.map(col =>
                  el('td', { key: col, title: getCellAnomaly(ri, col)?.message || '',
                    style: {
                      padding: '6px 8px', color: '#1e293b', whiteSpace: 'nowrap',
                      background: cellBg(row[col], col, ri),
                      fontFamily: col === 'tradingSymbol' ? 'inherit' : 'var(--mono, monospace)',
                      fontWeight: col === 'tradingSymbol' || col === 'ltp' || col === 'capturedAtUtc' ? '500' : '400',
                      fontSize: '0.76rem'
                    }
                  }, fmt(row[col], col))
                )
              ),
              expandedRow === ri ? el('tr', { key: `${ri}-x` },
                el('td', { colSpan: visCols.length + 1, style: { padding: '14px 16px', background: '#f8fafc', borderBottom: '1px solid #e2e8f0' } },
                  rowAnoms.length > 0 ? el('div', { style: { marginBottom: '12px' } },
                    ...rowAnoms.map((a, ai) => {
                      const ac = a.severity === 'critical' ? '#dc2626' : a.severity === 'warning' ? '#d97706' : '#2563eb';
                      const abg = a.severity === 'critical' ? '#fee2e2' : a.severity === 'warning' ? '#fef3c7' : '#eff6ff';
                      return el('div', { key: ai, style: {
                        padding: '6px 12px', marginBottom: '4px', borderRadius: '6px',
                        borderLeft: `3px solid ${ac}`, background: abg, fontSize: '0.78rem'
                      } },
                        el('div', { style: { color: '#1e293b', fontWeight: '500' } }, a.message),
                        a.description ? el('div', { style: { color: '#64748b', fontSize: '0.72rem', marginTop: '3px' } }, a.description) : null);
                    })
                  ) : null,
                  el('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: '6px' } },
                    ...(data.columns || []).filter(c => c !== '_sourceFile').map(col =>
                      el('div', { key: col, style: {
                        padding: '6px 10px', background: '#fff', borderRadius: '8px',
                        border: `1px solid ${sevColor(getCellAnomaly(ri, col))}`,
                        display: 'flex', justifyContent: 'space-between', gap: '8px'
                      } },
                        el('span', { style: { color: '#64748b', fontSize: '0.72rem', flexShrink: 0 } },
                          col === 'exchFeedTime' ? 'Time (IST)' : col === 'capturedAtUtc' ? 'Captured' : col),
                        el('span', { style: {
                          color: row[col] == null ? '#94a3b8' : '#1e293b',
                          fontFamily: 'var(--mono, monospace)', fontSize: '0.74rem',
                          overflow: 'hidden', textOverflow: 'ellipsis', textAlign: 'right'
                        } },
                          col === 'exchFeedTime' ? fmtExchIst(row[col]) + ' IST'
                            : col === 'capturedAtUtc' ? fmtIstFull(row[col])
                            : fmt(row[col], col)))
                    ),
                    row._sourceFile ? el('div', { key: '_sf', style: {
                      padding: '6px 10px', background: '#fff', borderRadius: '8px',
                      border: '1px solid #e2e8f0', gridColumn: '1 / -1'
                    } },
                      el('span', { style: { color: '#64748b', fontSize: '0.72rem' } }, 'File: '),
                      el('span', { style: { color: '#94a3b8', fontFamily: 'var(--mono, monospace)', fontSize: '0.72rem' } }, row._sourceFile)) : null
                  )
                )
              ) : null
            ];
          }).flat().filter(Boolean)
        )
      )
    )
  ) : null;

  const noDataMsg = (data && !loading && (!data.rows || data.rows.length === 0))
    ? el(EmptyState, {
        icon: '\uD83D\uDCC2',
        title: 'No data found',
        text: `No Parquet rows for ${mode} on ${date} at ${hourIst}:00 IST. Check if the market was open.`
      })
    : null;

  const spinner = loading ? el(Loading, { text: 'Loading Parquet data\u2026' }) : null;

  const errorBanner = error ? el('div', {
    style: {
      background: '#fee2e2', color: '#dc2626', padding: '10px 16px', borderRadius: '8px',
      border: '1px solid #fca5a5', marginBottom: '12px', fontSize: '0.82rem', fontWeight: '500'
    }
  }, '\u274C ' + error) : null;

  return el('div', { className: 'page-content' },
    el('div', { className: 'page-header', style: { marginBottom: '12px' } },
      el('h1', { className: 'page-title' }, '\uD83D\uDDC2\uFE0F Parquet Data Viewer'),
      el('p', { className: 'page-subtitle' },
        'Browse raw feature data \u2014 inspect rows, investigate market events, validate technical indicators')
    ),
    controls, errorBanner, spinner,
    summaryBar, colPills, insightsPanel,
    fileBrowser, dataTable, noDataMsg,
    el(SpreadRepairTool, null)
  );
}

// ── Hidden Spread Repair Tool (triple-click page title to reveal) ──
function SpreadRepairTool() {
  const el = React.createElement;
  const [visible, setVisible] = React.useState(false);
  const [repairMode, setRepairMode] = React.useState('NIFTY');
  const [startDate, setStartDate] = React.useState('2026-04-21');
  const [endDate, setEndDate] = React.useState(() => new Date().toISOString().slice(0, 10));
  const [dryRun, setDryRun] = React.useState(true);
  const [loading, setLoading] = React.useState(false);
  const [result, setResult] = React.useState(null);
  const [error, setError] = React.useState(null);
  const clickCount = React.useRef(0);
  const clickTimer = React.useRef(null);

  // Triple-click the title area to reveal
  React.useEffect(() => {
    const handler = (e) => {
      if (e.target.closest('.page-title')) {
        clickCount.current++;
        clearTimeout(clickTimer.current);
        clickTimer.current = setTimeout(() => { clickCount.current = 0; }, 600);
        if (clickCount.current >= 3) {
          setVisible(v => !v);
          clickCount.current = 0;
        }
      }
    };
    document.addEventListener('click', handler);
    return () => document.removeEventListener('click', handler);
  }, []);

  if (!visible) return null;

  const runRepair = async () => {
    setLoading(true);
    setError(null);
    setResult(null);
    try {
      const params = `mode=${repairMode}&startDate=${startDate}&endDate=${endDate}&dryRun=${dryRun}`;
      const res = await API.post(`/api/parquet-audit/repair-spread?${params}`);
      setResult(res);
    } catch (ex) {
      setError(ex.message || 'Repair failed');
    } finally {
      setLoading(false);
    }
  };

  const s = {
    panel: { marginTop: '24px', padding: '20px', background: '#fffbeb', border: '2px dashed #f59e0b', borderRadius: '12px' },
    title: { fontSize: '1rem', fontWeight: '700', color: '#92400e', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '8px' },
    row: { display: 'flex', gap: '12px', alignItems: 'center', flexWrap: 'wrap', marginBottom: '12px' },
    label: { fontSize: '0.8rem', fontWeight: '600', color: '#78716c', minWidth: '60px' },
    input: { padding: '6px 10px', border: '1px solid #d6d3d1', borderRadius: '6px', fontSize: '0.82rem' },
    btn: { padding: '8px 20px', borderRadius: '8px', border: 'none', fontWeight: '600', fontSize: '0.82rem', cursor: 'pointer' },
    dryBtn: { background: '#3b82f6', color: '#fff' },
    liveBtn: { background: '#dc2626', color: '#fff' },
    resultBox: { marginTop: '16px', padding: '14px', background: '#fff', borderRadius: '8px', border: '1px solid #e7e5e4', fontSize: '0.8rem', maxHeight: '400px', overflow: 'auto' },
    stat: { display: 'inline-block', padding: '4px 10px', borderRadius: '6px', background: '#f3f4f6', marginRight: '8px', fontSize: '0.78rem', fontWeight: '600' },
    warn: { color: '#dc2626', fontWeight: '600', fontSize: '0.8rem', marginTop: '8px' }
  };

  return el('div', { style: s.panel },
    el('div', { style: s.title },
      '\uD83D\uDD27 Spread Repair Tool',
      el('span', { style: { fontSize: '0.7rem', fontWeight: '400', color: '#a8a29e' } }, '(admin \u2022 hidden)')
    ),
    el('p', { style: { fontSize: '0.78rem', color: '#78716c', marginBottom: '14px' } },
      'Recalculates corrupted spread values from the depth JSON column in existing parquet files. ',
      'Always dry-run first to preview.'
    ),

    // Controls
    el('div', { style: s.row },
      el('span', { style: s.label }, 'Index'),
      el('select', { value: repairMode, onChange: e => setRepairMode(e.target.value), style: s.input },
        el('option', { value: 'NIFTY' }, 'NIFTY'),
        el('option', { value: 'SENSEX' }, 'SENSEX')
      ),
      el('span', { style: s.label }, 'From'),
      el('input', { type: 'date', value: startDate, onChange: e => setStartDate(e.target.value), style: s.input }),
      el('span', { style: s.label }, 'To'),
      el('input', { type: 'date', value: endDate, onChange: e => setEndDate(e.target.value), style: s.input })
    ),
    el('div', { style: s.row },
      el('label', { style: { fontSize: '0.8rem', display: 'flex', alignItems: 'center', gap: '6px', cursor: 'pointer' } },
        el('input', { type: 'checkbox', checked: dryRun, onChange: e => setDryRun(e.target.checked) }),
        'Dry Run (preview only, no writes)'
      ),
      el('button', {
        onClick: runRepair,
        disabled: loading,
        style: { ...s.btn, ...(dryRun ? s.dryBtn : s.liveBtn) }
      }, loading ? '\u23F3 Running...' : dryRun ? '\uD83D\uDD0D Dry Run' : '\u26A0\uFE0F Apply Fix')
    ),
    !dryRun ? el('div', { style: s.warn }, '\u26A0 Live mode — files will be overwritten in blob storage!') : null,

    // Error
    error ? el('div', { style: { ...s.resultBox, background: '#fee2e2', borderColor: '#fca5a5', color: '#dc2626' } }, error) : null,

    // Results
    result ? el('div', { style: s.resultBox },
      el('div', { style: { marginBottom: '10px' } },
        el('span', { style: { ...s.stat, background: '#dbeafe' } }, `Files scanned: ${result.totalFilesScanned}`),
        el('span', { style: { ...s.stat, background: result.filesWithCorruptSpread > 0 ? '#fef3c7' : '#d1fae5' } },
          `Corrupt files: ${result.filesWithCorruptSpread}`),
        el('span', { style: { ...s.stat, background: result.totalRowsFixed > 0 ? '#fecaca' : '#d1fae5' } },
          `Rows fixed: ${result.totalRowsFixed}`),
        el('span', { style: { ...s.stat, background: '#e0e7ff' } }, `Estimated: ${result.totalRowsEstimated || 0}`),
        el('span', { style: s.stat }, `No depth: ${result.totalRowsNoDepth}`),
        el('span', { style: s.stat }, `${result.elapsedMs}ms`)
      ),
      result.repairMethod ? el('div', { style: { fontSize: '0.72rem', color: '#6b7280', marginBottom: '6px', fontStyle: 'italic' } },
        '\uD83D\uDCA1 ' + result.repairMethod
      ) : null,
      result.dryRun ? el('div', { style: { color: '#2563eb', fontWeight: '600', fontSize: '0.78rem', marginBottom: '8px' } },
        '\u2139\uFE0F Dry run — no files were modified. Uncheck "Dry Run" to apply.'
      ) : el('div', { style: { color: '#16a34a', fontWeight: '600', fontSize: '0.78rem', marginBottom: '8px' } },
        '\u2705 Repair applied — files overwritten with corrected spread values.'
      ),
      result.files && result.files.length > 0 ? el('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: '0.75rem' } },
        el('thead', null, el('tr', null,
          ['File', 'Rows', 'Fixed', 'Est.', 'Before', 'After', 'Written'].map(h =>
            el('th', { key: h, style: { textAlign: 'left', padding: '4px 6px', borderBottom: '1px solid #e5e7eb', fontWeight: '600', color: '#6b7280' } }, h)
          )
        )),
        el('tbody', null, result.files.map((f, i) =>
          f.error
            ? el('tr', { key: i }, el('td', { colSpan: 7, style: { padding: '4px 6px', color: '#dc2626' } }, `${f.file}: ${f.error}`))
            : el('tr', { key: i, style: { background: i % 2 === 0 ? '#fafaf9' : '#fff' } },
                el('td', { style: { padding: '4px 6px', maxWidth: '300px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, f.file),
                el('td', { style: { padding: '4px 6px' } }, f.totalRows),
                el('td', { style: { padding: '4px 6px', color: '#dc2626', fontWeight: '600' } }, f.rowsFixed),
                el('td', { style: { padding: '4px 6px', color: '#7c3aed', fontWeight: '600' } }, f.rowsEstimated || 0),
                el('td', { style: { padding: '4px 6px', fontFamily: 'monospace', color: '#dc2626' } }, f.sampleBefore?.toExponential(2) ?? '-'),
                el('td', { style: { padding: '4px 6px', fontFamily: 'monospace', color: '#16a34a' } }, f.sampleAfter != null ? f.sampleAfter.toFixed(2) : 'null'),
                el('td', { style: { padding: '4px 6px' } }, f.written ? '\u2705' : '\u2014')
              )
        ))
      ) : el('div', { style: { color: '#6b7280', fontStyle: 'italic' } }, 'No files with corrupt spread found in range \u2014 data is clean.')
    ) : null
  );
}
