// Chart component — hand-built SVG, dual-axis line chart
// Renders forecast/actual/inventory/requisitions with confidence band + festival markers

// Palette for confidence-band fills. Color slot N is assigned in the order
// groups are formed by `groupBandSelection` — paired (same level) groups get
// one slot; unpaired uppers/lowers each get their own.
const BAND_PALETTE = [
  '#3b82f6', // blue
  '#7c3aed', // violet
  '#ea580c', // orange
  '#16a34a', // green
  '#0891b2', // cyan
  '#db2777', // pink
  '#ca8a04', // yellow-amber
  '#475569', // slate
];

// Convert an array of selection keys into render groups. Same-level upper+lower
// pairs collapse into a single group ('pair') drawn as one polygon between
// upper and lower bounds. Unpaired keys become their own group ('upper' or
// 'lower') drawn as a half-band between the forecast line and the bound.
function groupBandSelection(selection) {
  const byLevel = {};
  (selection || []).forEach(s => {
    const m = /^(upper|lower)_(\d+)$/.exec(s);
    if (!m) return;
    const lvl = m[2];
    byLevel[lvl] = byLevel[lvl] || {};
    byLevel[lvl][m[1]] = true;
  });
  const groups = [];
  Object.keys(byLevel)
    .map(n => parseInt(n, 10))
    .sort((a, b) => b - a)
    .forEach(lvl => {
      const g = byLevel[String(lvl)];
      if (g.upper && g.lower) {
        groups.push({ kind: 'pair', level: lvl, label: `${lvl}% band` });
      } else if (g.upper) {
        groups.push({ kind: 'upper', level: lvl, label: `Upper ${lvl}%` });
      } else {
        groups.push({ kind: 'lower', level: lvl, label: `Lower ${lvl}%` });
      }
    });
  return groups;
}

const Chart = ({
  data,           // [{date, forecast, actual, inventory, requisitions, bands: {upper_80, lower_80, ...}}]
  series,         // {forecast, actual, inventory, requisitions}
  bandSelection,  // string[] — selected band keys e.g. ["upper_80","lower_60"]
  festivals,      // [{date, name}] within range
  width = 900,
  height = 380,
  onHoverChange,
}) => {
  const [hoverIdx, setHoverIdx] = React.useState(null);
  const svgRef = React.useRef(null);

  const padL = 64, padR = 64, padT = 36, padB = 56;
  const w = width, h = height;
  const innerW = w - padL - padR;
  const innerH = h - padT - padB;

  // Compute scales
  // Primary axis: forecast/actual/requisitions/selected confidence bounds
  // Secondary axis: inventory
  const selection = bandSelection || [];
  let primaryMax = 0, secondaryMax = 0;
  data.forEach(d => {
    if (series.forecast && d.forecast != null) primaryMax = Math.max(primaryMax, d.forecast);
    if (series.forecast && d.bands) {
      selection.forEach(k => {
        const v = d.bands[k];
        if (v != null) primaryMax = Math.max(primaryMax, v);
      });
    }
    if (series.actual && d.actual != null) primaryMax = Math.max(primaryMax, d.actual);
    if (series.requisitions && d.requisitions != null) primaryMax = Math.max(primaryMax, d.requisitions);
    if (series.inventory && d.inventory != null) secondaryMax = Math.max(secondaryMax, d.inventory);
  });
  primaryMax = Math.ceil((primaryMax || 100) * 1.15 / 100) * 100;
  secondaryMax = Math.ceil((secondaryMax || 100) * 1.15 / 100) * 100;

  const xFor = (i) => padL + (data.length <= 1 ? innerW / 2 : (i / (data.length - 1)) * innerW);
  const yPrimary = (v) => padT + innerH - (v / primaryMax) * innerH;
  const ySecondary = (v) => padT + innerH - (v / secondaryMax) * innerH;

  // Path builder
  const buildPath = (key, scale) => {
    const pts = [];
    data.forEach((d, i) => {
      if (d[key] != null) pts.push(`${pts.length === 0 ? 'M' : 'L'}${xFor(i).toFixed(1)},${scale(d[key]).toFixed(1)}`);
    });
    return pts.join(' ');
  };

  // Build one filled polygon per render group with an assigned palette color.
  // - 'pair'   → polygon from lower_N up to upper_N (full N% interval).
  // - 'upper'  → half-band from forecast line up to upper_N.
  // - 'lower'  → half-band from forecast line down to lower_N.
  // Empty paths (no data on any point) are dropped before render.
  const bandPaths = React.useMemo(() => {
    if (!series.forecast) return [];
    const groups = groupBandSelection(selection);
    return groups.map((g, i) => {
      const color = BAND_PALETTE[i % BAND_PALETTE.length];
      const top = [], bot = [];
      const upperKey = `upper_${g.level}`;
      const lowerKey = `lower_${g.level}`;
      data.forEach((d, idx) => {
        const f = d.forecast;
        const u = d.bands && d.bands[upperKey];
        const l = d.bands && d.bands[lowerKey];
        if (g.kind === 'pair') {
          if (u != null && l != null) {
            top.push(`${top.length === 0 ? 'M' : 'L'}${xFor(idx).toFixed(1)},${yPrimary(u).toFixed(1)}`);
            bot.push(`L${xFor(idx).toFixed(1)},${yPrimary(l).toFixed(1)}`);
          }
        } else if (g.kind === 'upper') {
          if (u != null && f != null) {
            top.push(`${top.length === 0 ? 'M' : 'L'}${xFor(idx).toFixed(1)},${yPrimary(u).toFixed(1)}`);
            bot.push(`L${xFor(idx).toFixed(1)},${yPrimary(f).toFixed(1)}`);
          }
        } else {
          if (l != null && f != null) {
            top.push(`${top.length === 0 ? 'M' : 'L'}${xFor(idx).toFixed(1)},${yPrimary(f).toFixed(1)}`);
            bot.push(`L${xFor(idx).toFixed(1)},${yPrimary(l).toFixed(1)}`);
          }
        }
      });
      if (top.length === 0) return null;
      return {
        d: top.join(' ') + ' ' + bot.reverse().join(' ') + ' Z',
        color,
        label: g.label,
      };
    }).filter(Boolean);
  }, [data, selection.join('|'), series.forecast, primaryMax]);

  // Y ticks
  const yTicks = 5;
  const ticks = Array.from({ length: yTicks + 1 }, (_, i) => i * (primaryMax / yTicks));
  const ticksSec = Array.from({ length: yTicks + 1 }, (_, i) => i * (secondaryMax / yTicks));

  // X ticks — show every Nth date
  const xTickEvery = Math.max(1, Math.ceil(data.length / 8));

  // run_date marker index (the "TODAY" badge on the chart)
  const run_date_idx = data.findIndex(d => d.is_run_date);

  // Festival markers
  const festByDate = React.useMemo(() => {
    const m = {};
    festivals.forEach(f => m[f.date] = f.name);
    return m;
  }, [festivals]);

  const handleMouseMove = (e) => {
    const rect = svgRef.current.getBoundingClientRect();
    const x = ((e.clientX - rect.left) / rect.width) * w;
    const rel = (x - padL) / innerW;
    const idx = Math.round(rel * (data.length - 1));
    if (idx >= 0 && idx < data.length) {
      setHoverIdx(idx);
      onHoverChange && onHoverChange(idx);
    }
  };
  const handleMouseLeave = () => {
    setHoverIdx(null);
    onHoverChange && onHoverChange(null);
  };

  const fmtShort = (dStr) => {
    const d = new Date(dStr);
    return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
  };

  return (
    <div className="relative w-full">
      <svg
        ref={svgRef}
        viewBox={`0 0 ${w} ${h}`}
        className="w-full h-auto"
        onMouseMove={handleMouseMove}
        onMouseLeave={handleMouseLeave}
        style={{ userSelect: 'none' }}
      >
        {/* Grid */}
        {ticks.map((t, i) => (
          <line key={`g${i}`} x1={padL} x2={w - padR} y1={yPrimary(t)} y2={yPrimary(t)}
            stroke="#f1f5f9" strokeWidth="1" />
        ))}

        {/* run_date divider — badge auto-flips to whichever side has room.
            The right axis ("INVENTORY" label + tick values) starts at w-padR+8,
            so a centered badge at the rightmost x position would overlap it. */}
        {run_date_idx >= 0 && (() => {
          const tx = xFor(run_date_idx);
          const flipLeft  = tx > w - padR - 22;   // not enough room on the right
          const flipRight = tx < padL + 22;       // not enough room on the left
          // Default: centered. Otherwise anchor entirely on the side that fits.
          let rectX, textX, textAnchor;
          if (flipLeft) {
            rectX = tx - 38; textX = tx - 4; textAnchor = 'end';
          } else if (flipRight) {
            rectX = tx + 2;  textX = tx + 4; textAnchor = 'start';
          } else {
            rectX = tx - 18; textX = tx;     textAnchor = 'middle';
          }
          return (
            <g>
              <line x1={tx} x2={tx} y1={padT} y2={h - padB}
                stroke="#94a3b8" strokeWidth="1" strokeDasharray="2 3" opacity="0.6" />
              <rect x={rectX} y={padT - 4} width="36" height="16" rx="3"
                fill="#1e293b" />
              <text x={textX} y={padT + 7} fontSize="10" fill="#fff"
                textAnchor={textAnchor} fontWeight="600">TODAY</text>
            </g>
          );
        })()}

        {/* Festival vertical markers */}
        {festivals.map((f, i) => {
          const idx = data.findIndex(d => d.date === f.date);
          if (idx < 0) return null;
          return (
            <g key={`f${i}`} className="festival-marker">
              <line x1={xFor(idx)} x2={xFor(idx)} y1={padT} y2={h - padB}
                stroke="#dc2626" strokeWidth="1" strokeDasharray="3 4" opacity="0.55" />
              <circle cx={xFor(idx)} cy={padT + 2} r="3" fill="#dc2626" />
            </g>
          );
        })}

        {/* Confidence shades — one per render group, colored from palette */}
        {bandPaths.map((p, i) => (
          <path key={`band-${i}`} d={p.d} fill={p.color} fillOpacity="0.18" stroke="none" />
        ))}

        {/* Series */}
        {series.forecast && (
          <path d={buildPath('forecast', yPrimary)} fill="none"
            stroke="#3b82f6" strokeWidth="2" strokeLinejoin="round" strokeLinecap="round"
            strokeDasharray={data.some(d => !d.isPast) ? "0" : "0"} />
        )}
        {series.actual && (
          <path d={buildPath('actual', yPrimary)} fill="none"
            stroke="#16a34a" strokeWidth="2" strokeLinejoin="round" strokeLinecap="round" />
        )}
        {series.requisitions && (
          <path d={buildPath('requisitions', yPrimary)} fill="none"
            stroke="#7c3aed" strokeWidth="2" strokeLinejoin="round" strokeLinecap="round" />
        )}
        {series.inventory && (
          <path d={buildPath('inventory', ySecondary)} fill="none"
            stroke="#f59e0b" strokeWidth="2" strokeLinejoin="round" strokeLinecap="round" />
        )}

        {/* Hover crosshair */}
        {hoverIdx != null && (
          <line x1={xFor(hoverIdx)} x2={xFor(hoverIdx)} y1={padT} y2={h - padB}
            stroke="#0f172a" strokeWidth="1" opacity="0.2" />
        )}

        {/* Hover dots */}
        {hoverIdx != null && (
          <g>
            {series.forecast && data[hoverIdx].forecast != null && (
              <circle cx={xFor(hoverIdx)} cy={yPrimary(data[hoverIdx].forecast)} r="4"
                fill="#fff" stroke="#3b82f6" strokeWidth="2" />
            )}
            {series.actual && data[hoverIdx].actual != null && (
              <circle cx={xFor(hoverIdx)} cy={yPrimary(data[hoverIdx].actual)} r="4"
                fill="#fff" stroke="#16a34a" strokeWidth="2" />
            )}
            {series.requisitions && data[hoverIdx].requisitions != null && (
              <circle cx={xFor(hoverIdx)} cy={yPrimary(data[hoverIdx].requisitions)} r="4"
                fill="#fff" stroke="#7c3aed" strokeWidth="2" />
            )}
            {series.inventory && data[hoverIdx].inventory != null && (
              <circle cx={xFor(hoverIdx)} cy={ySecondary(data[hoverIdx].inventory)} r="4"
                fill="#fff" stroke="#f59e0b" strokeWidth="2" />
            )}
          </g>
        )}

        {/* Y axis primary (left) */}
        {ticks.map((t, i) => (
          <text key={`yt${i}`} x={padL - 8} y={yPrimary(t) + 4}
            fontSize="11" fill="#64748b" textAnchor="end">{Math.round(t)}</text>
        ))}
        {/* Y axis secondary (right, inventory) */}
        {ticksSec.map((t, i) => (
          <text key={`ys${i}`} x={w - padR + 8} y={ySecondary(t) + 4}
            fontSize="11" fill="#b45309" textAnchor="start">{Math.round(t)}</text>
        ))}
        <text x={padL - 8} y={padT - 16} fontSize="11" fill="#475569" textAnchor="end" fontWeight="600">UNITS</text>
        <text x={w - 4} y={padT - 16} fontSize="11" fill="#b45309" textAnchor="end" fontWeight="600">INVENTORY</text>

        {/* X axis — month-day on every shown tick; year on the first tick and
            whenever the year changes vs. the previous shown tick. Keeps labels
            uncluttered while still visually separating year boundaries. */}
        <line x1={padL} x2={w - padR} y1={h - padB} y2={h - padB} stroke="#cbd5e1" strokeWidth="1" />
        {(() => {
          let lastShownYear = null;
          return data.map((d, i) => {
            if (i % xTickEvery !== 0 && i !== data.length - 1) return null;
            const yr = new Date(d.date).getFullYear();
            const showYear = yr !== lastShownYear;
            lastShownYear = yr;
            return (
              <g key={`xt${i}`}>
                <line x1={xFor(i)} x2={xFor(i)} y1={h - padB} y2={h - padB + 4} stroke="#cbd5e1" />
                <text x={xFor(i)} y={h - padB + 18} fontSize="11" fill="#64748b" textAnchor="middle">
                  {fmtShort(d.date)}
                </text>
                {showYear && (
                  <text x={xFor(i)} y={h - padB + 32} fontSize="10" fill="#94a3b8"
                    textAnchor="middle" fontWeight="600">
                    {yr}
                  </text>
                )}
              </g>
            );
          });
        })()}
      </svg>

      {/* Tooltip */}
      {hoverIdx != null && (
        <ChartTooltip
          d={data[hoverIdx]}
          series={series}
          selection={selection}
          festName={festByDate[data[hoverIdx].date]}
          xPct={(xFor(hoverIdx) / w) * 100}
        />
      )}

      {/* Legend */}
      <div className="flex flex-wrap gap-4 mt-2 px-2 text-xs text-slate-600">
        {series.forecast && <LegendDot color="#3b82f6" label="Forecast" />}
        {series.actual && <LegendDot color="#16a34a" label="Actual Sales" />}
        {series.requisitions && <LegendDot color="#7c3aed" label="Requisitions" />}
        {series.inventory && <LegendDot color="#f59e0b" label="Inventory (right axis)" />}
        {bandPaths.map((p, i) => (
          <LegendDot key={`legend-${i}`} color={p.color} label={p.label} faded />
        ))}
        {festivals.length > 0 && (
          <span className="inline-flex items-center gap-1.5">
            <span className="inline-block w-3 border-t-2 border-dashed" style={{borderColor: '#dc2626'}} />
            <span>Festival</span>
          </span>
        )}
      </div>
    </div>
  );
};

const LegendDot = ({ color, label, faded }) => (
  <span className="inline-flex items-center gap-1.5">
    <span className={`inline-block w-3 h-3 rounded-sm ${faded ? 'opacity-25' : ''}`} style={{ background: color }} />
    <span>{label}</span>
  </span>
);

const ChartTooltip = ({ d, series, selection, festName, xPct }) => {
  const date = new Date(d.date).toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' });
  const left = xPct > 70 ? 'auto' : `${xPct}%`;
  const right = xPct > 70 ? `${100 - xPct}%` : 'auto';
  // Build a band sub-label that lists each currently-selected bound that
  // actually has a value at this date. Order matches selection order.
  const bandParts = (selection || [])
    .map(k => {
      const v = d.bands && d.bands[k];
      if (v == null) return null;
      const m = /^(upper|lower)_(\d+)$/.exec(k);
      const arrow = m && m[1] === 'upper' ? '↑' : '↓';
      const lvl = m ? `${m[2]}%` : k;
      return `${arrow}${lvl} ${v}`;
    })
    .filter(Boolean);
  const bandSub = bandParts.length ? bandParts.join(' · ') : null;
  return (
    <div
      className="absolute pointer-events-none bg-white border border-slate-200 rounded-lg shadow-lg px-3 py-2 text-xs"
      style={{ top: 8, left, right, transform: xPct > 70 ? 'none' : 'translateX(-50%)', minWidth: 180, zIndex: 5 }}
    >
      <div className="font-semibold text-slate-900 mb-1.5">{date}</div>
      {festName && (
        <div className="flex items-center gap-1.5 mb-1.5 -mx-1 px-1.5 py-0.5 bg-red-50 text-red-700 rounded text-[11px] font-medium">
          <span className="w-1.5 h-1.5 rounded-full bg-red-600" />{festName}
        </div>
      )}
      <div className="space-y-1">
        {series.forecast && d.forecast != null && (
          <TooltipRow color="#3b82f6" label="Forecast" value={d.forecast}
            sub={bandSub} />
        )}
        {series.actual && d.actual != null && (
          <TooltipRow color="#16a34a" label="Actual" value={d.actual} />
        )}
        {series.requisitions && d.requisitions != null && (
          <TooltipRow color="#7c3aed" label="Requisitions" value={d.requisitions} />
        )}
        {series.inventory && d.inventory != null && (
          <TooltipRow color="#f59e0b" label="Inventory" value={d.inventory} />
        )}
      </div>
    </div>
  );
};

const TooltipRow = ({ color, label, value, sub }) => (
  <div className="flex items-center justify-between gap-3">
    <span className="inline-flex items-center gap-1.5 text-slate-600">
      <span className="w-2 h-2 rounded-full" style={{ background: color }} />
      {label}
    </span>
    <span className="text-slate-900 tabular-nums font-medium">
      {value.toLocaleString()}
      {sub && <span className="text-slate-400 font-normal ml-1">({sub})</span>}
    </span>
  </div>
);

window.Chart = Chart;
