import * as React from 'react';
import { Text, View } from '@react-pdf/renderer';

import type { DatasheetVoltageTable } from '../../model/types';
import { styles } from '../styles';

function clamp(n: number, min: number, max: number): number {
  return Math.max(min, Math.min(max, n));
}

function normTextForMeasure(v: unknown): string {
  return String(v ?? '')
    .replace(/\s+/g, ' ')
    .trim();
}

function textLen(v: unknown): number {
  return normTextForMeasure(v).length;
}

function distributeWithMinMax(weights: number[], total: number, minEach: number, maxEach: number): number[] {
  const n = weights.length;
  if (!n) return [];

  const mins = Array.from({ length: n }, () => minEach);
  const maxs = Array.from({ length: n }, () => maxEach);

  // If mins don't fit, scale them down proportionally.
  const minSum = mins.reduce((a, b) => a + b, 0);
  if (minSum > total) {
    const k = total / minSum;
    return mins.map(m => m * k);
  }

  const result = mins.slice();
  let remaining = total - minSum;
  let remainingIdx = Array.from({ length: n }, (_, i) => i);

  // Distribute remaining proportionally, respecting max constraints.
  // Loop is guaranteed to terminate because each iteration either:
  // - removes at least one index due to hitting max, or
  // - exhausts `remaining`.
  while (remaining > 1e-9 && remainingIdx.length) {
    const wSum = remainingIdx.reduce((acc, i) => acc + Math.max(0, weights[i] || 0), 0);
    if (wSum <= 1e-9) {
      // No meaningful weights: distribute evenly.
      const even = remaining / remainingIdx.length;
      for (const i of remainingIdx) result[i] += even;
      remaining = 0;
      break;
    }

    const nextIdx: number[] = [];
    for (const i of remainingIdx) {
      const w = Math.max(0, weights[i] || 0);
      const add = (w / wSum) * remaining;
      const capped = Math.min(result[i] + add, maxs[i]);
      const used = capped - result[i];
      result[i] = capped;
      remaining -= used;
      if (result[i] + 1e-9 < maxs[i]) nextIdx.push(i);
    }
    remainingIdx = nextIdx;
  }

  // Numerical guard: force exact sum by adjusting the last column.
  const sum = result.reduce((a, b) => a + b, 0);
  const drift = total - sum;
  if (Math.abs(drift) > 1e-9) result[result.length - 1] += drift;

  return result;
}

export function DenseTable(props: {
  table: Pick<DatasheetVoltageTable, 'columns' | 'rows'>;
  firstColLabel: string;
}): React.ReactElement {
  const cols = props.table.columns;
  const rows = props.table.rows;

  const headerText = (label: string): string => {
    // Table headers must NEVER wrap into a second line.
    // react-pdf can wrap on spaces, so we replace whitespace with NBSP.
    return String(label || '').replace(/\s+/g, '\u00A0').trim();
  };

  // Column widths: use explicit percentages (no rounding gaps) so the table always
  // consumes the full content width.
  // Goal:
  // - keep the designation column *not too wide*
  // - distribute data columns by estimated content width (header + cells)
  //   so columns better fit their data
  // Make first column denser so numeric columns get more room.
  // (Long designations can still wrap in body if needed, but table scanability
  // benefits more from wider data columns.)
  const cfgMin = 0.14;
  const cfgMax = 0.23;

  // A content-based heuristic.
  // React-PDF doesn't expose a reliable text-measurement API at render time,
  // so we approximate width by string length (compressed via sqrt to reduce outliers).
  const cfgContentLen = Math.max(textLen(props.firstColLabel), ...rows.map(r => textLen(r.configuration)), 8);
  const dataContentLens = cols.map((c, ci) => {
    const headerL = textLen(c.label);
    let cellMax = 0;
    for (const r of rows) cellMax = Math.max(cellMax, textLen(r.cells[ci]));
    // Slightly prioritize the header (scanability) over a single long cell.
    return Math.max(headerL * 1.15, cellMax, 3);
  });

  // Use mostly-linear weights so long headers get noticeably more space.
  const cfgWeight = cfgContentLen * 1.05;
  const dataWeights = dataContentLens.map(l => l);
  const dataWeightSum = dataWeights.reduce((a, b) => a + b, 0);
  const rawCfgPct = dataWeightSum > 0 ? cfgWeight / (cfgWeight + dataWeightSum) : 0.28;
  let cfgPct = clamp(rawCfgPct, cfgMin, cfgMax);

  // Ensure a minimum per-data-column width; if needed, shrink cfgPct.
  // These floors are intentionally generous. Too-narrow columns are worse than a
  // slightly narrower first column for scanability.
  const minDataPct = cols.length >= 14 ? 0.045 : cols.length >= 12 ? 0.05 : cols.length >= 10 ? 0.055 : 0.06;
  const cfgPctMaxForMinData = 1 - cols.length * minDataPct;
  if (Number.isFinite(cfgPctMaxForMinData)) cfgPct = Math.min(cfgPct, cfgPctMaxForMinData);
  cfgPct = clamp(cfgPct, cfgMin, cfgMax);

  const dataTotal = Math.max(0, 1 - cfgPct);
  const maxDataPct = Math.min(0.24, Math.max(minDataPct * 2.8, dataTotal * 0.55));
  const dataPcts = distributeWithMinMax(dataWeights, dataTotal, minDataPct, maxDataPct);

  const cfgW = `${(cfgPct * 100).toFixed(4)}%`;
  const dataWs = dataPcts.map((p, idx) => {
    // Keep the last column as the remainder so percentages sum to exactly 100%.
    if (idx === dataPcts.length - 1) {
      const used = dataPcts.slice(0, -1).reduce((a, b) => a + b, 0);
      const remainder = Math.max(0, dataTotal - used);
      return `${(remainder * 100).toFixed(4)}%`;
    }
    return `${(p * 100).toFixed(4)}%`;
  });

  const headerFontSize = cols.length >= 14 ? 5.7 : cols.length >= 12 ? 5.9 : cols.length >= 10 ? 6.2 : 6.6;

  return (
    <View style={styles.tableWrap}>
      <View style={styles.tableHeader} wrap={false}>
        <View style={{ width: cfgW }}>
          <Text
            style={[
              styles.tableHeaderCell,
              styles.tableHeaderCellCfg,
              { fontSize: headerFontSize, paddingHorizontal: 3 },
              cols.length ? styles.tableHeaderCellDivider : null,
            ]}
            wrap={false}
          >
            {headerText(props.firstColLabel)}
          </Text>
        </View>
        {cols.map((c, idx) => {
          const isLast = idx === cols.length - 1;
          return (
            <View key={c.key} style={{ width: dataWs[idx] }}>
              <Text
                style={[
                  styles.tableHeaderCell,
                  { fontSize: headerFontSize, paddingHorizontal: 3 },
                  !isLast ? styles.tableHeaderCellDivider : null,
                ]}
                wrap={false}
              >
                {headerText(c.label)}
              </Text>
            </View>
          );
        })}
      </View>

      {rows.map((r, ri) => (
        <View
          key={`${r.configuration}-${ri}`}
          style={[styles.tableRow, ri % 2 === 0 ? styles.tableRowAlt : null]}
          wrap={false}
          // If the row doesn't fit, move the whole row to the next page.
          // This prevents page breaks mid-row.
          minPresenceAhead={16}
        >
          <View style={{ width: cfgW }} wrap={false}>
            <Text
              style={[
                styles.tableCell,
                styles.tableCellCfg,
                // Denser first column: slightly smaller type + tighter padding.
                { fontSize: 6.2, paddingHorizontal: 3 },
                cols.length ? styles.tableCellDivider : null,
              ]}
              wrap={false}
            >
              {r.configuration}
            </Text>
          </View>
          {r.cells.map((cell, ci) => {
            const isLast = ci === r.cells.length - 1;
            return (
              <View key={`${cols[ci]?.key || ci}`} style={{ width: dataWs[ci] }} wrap={false}>
                <Text style={[styles.tableCell, !isLast ? styles.tableCellDivider : null]} wrap={false}>
                  {cell}
                </Text>
              </View>
            );
          })}
        </View>
      ))}
    </View>
  );
}
