/* Track & Throw — Visualization (Sector & Graph views) */

// Helpers
const SECTOR_PAD = { top: 28, bottom: 22, left: 24, right: 24 };

// ---------- SectorView ----------
// Top-down view of the throwing sector. Apex at the bottom-center.
function SectorView({ throws, scaleMax, event, highlightedThrows, onPickThrow, selectedThrowId }) {
  const cfg = EVENTS[event] || EVENTS.javelin;
  const W = 360, H = 432;
  const apexX = W / 2;
  const apexY = H - SECTOR_PAD.bottom;
  const drawableH = H - SECTOR_PAD.top - SECTOR_PAD.bottom;

  // px-per-meter so scaleMax fits in drawableH
  const ppm = drawableH / scaleMax;

  // Half-angle of sector (radians)
  const halfA = (cfg.angle / 2) * Math.PI / 180;

  // Build sector polygon
  const farY = apexY - scaleMax * ppm;
  const dx = Math.tan(halfA) * scaleMax * ppm;
  // Also clamp to W bounds
  const polyPoints = [
    `${apexX},${apexY}`,
    `${apexX - dx},${farY}`,
    `${apexX + dx},${farY}`,
  ].join(' ');

  // Distance arcs (10m increments if range > 30, else nice steps)
  let step;
  if (scaleMax <= 20) step = 2;
  else if (scaleMax <= 35) step = 5;
  else step = 10;
  const arcs = [];
  for (let d = step; d <= scaleMax + 0.001; d += step) {
    arcs.push(d);
  }

  // Foul "x" pattern: place small fouls below the apex along the run-up area
  const fouls = throws.filter(t => t.foul);
  const legals = throws.filter(t => !t.foul && t.distance != null);

  // Place legal throws within the sector
  // Use hashRandom on throw id to set horizontal position within sector
  const placedLegals = legals.map(t => {
    const r = hashRandom(t.id, 7) * 2 - 1; // -1..1
    // Add a slight bias toward 0 (center) since user said they throw too central
    const biased = r * 0.85;
    const maxOffset = Math.tan(halfA) * t.distance * ppm;
    const x = apexX + biased * maxOffset;
    const y = apexY - t.distance * ppm;
    return { throw: t, x, y };
  });

  const placedFouls = fouls.map((t, i) => {
    // place foul X marks along a small arc just past the apex (run-up area)
    const r = hashRandom(t.id, 11);
    const angle = (r * 2 - 1) * (halfA * 0.7);
    const dist = 1.2 + (i % 3) * 0.8; // meters from apex visually
    const px = apexX + Math.sin(angle) * dist * ppm * 1.4;
    const py = apexY - Math.cos(angle) * dist * ppm * 0.8;
    return { throw: t, x: px, y: py };
  });

  const isHL = (t) => highlightedThrows.has(t.id);
  const isSel = (t) => selectedThrowId === t.id;

  return (
    <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="xMidYMid meet">
      <defs>
        <pattern id="diag" patternUnits="userSpaceOnUse" width="6" height="6" patternTransform="rotate(45)">
          <line x1="0" y1="0" x2="0" y2="6" stroke="var(--line)" strokeWidth="1"/>
        </pattern>
        <clipPath id="sector-clip">
          <polygon points={polyPoints}/>
        </clipPath>
      </defs>

      {/* Background "field" texture */}
      <rect x="0" y="0" width={W} height={H} fill="var(--surface-2)" />

      {/* Sector fill */}
      <polygon points={polyPoints} fill="#fff" stroke="var(--line-2)" strokeWidth="1"/>

      {/* Distance arcs, clipped */}
      <g clipPath="url(#sector-clip)">
        {arcs.map(d => {
          const r = d * ppm;
          return (
            <Fragment key={d}>
              <circle cx={apexX} cy={apexY} r={r} fill="none" stroke="var(--line)" strokeWidth="0.8" strokeDasharray={d === scaleMax ? '0' : '2 4'} />
            </Fragment>
          );
        })}
      </g>

      {/* Distance labels along left edge of sector */}
      {arcs.filter(d => d % (step * (scaleMax > 60 ? 2 : 1)) === 0 || d === scaleMax).map(d => {
        const r = d * ppm;
        const lx = apexX - Math.sin(halfA) * r - 2;
        const ly = apexY - Math.cos(halfA) * r;
        return (
          <text key={'lbl'+d} x={lx} y={ly + 3} fontSize="9" textAnchor="end" fill="var(--ink-3)" fontFamily="var(--mono)">{d}m</text>
        );
      })}

      {/* Foul / scratch line at apex */}
      <line x1={apexX - 30} x2={apexX + 30} y1={apexY} y2={apexY} stroke="var(--ink)" strokeWidth="2" strokeLinecap="round" />
      <text x={apexX} y={apexY + 14} fontSize="9" textAnchor="middle" fill="var(--ink-3)" fontFamily="var(--mono)">FOUL LINE</text>

      {/* Foul marks (small × markers behind the line, in the run-up) */}
      {placedFouls.map(({ throw: t, x, y }) => {
        const hl = isHL(t);
        const sel = isSel(t);
        const stroke = hl ? 'var(--accent)' : 'var(--foul)';
        return (
          <g key={t.id} className="foul-x" onClick={(e) => { e.stopPropagation(); onPickThrow && onPickThrow(t); }} style={{cursor:'pointer'}}>
            <circle cx={x} cy={y} r="11" fill="transparent" />
            <path d={`M${x-4},${y-4} L${x+4},${y+4} M${x+4},${y-4} L${x-4},${y+4}`} stroke={stroke} strokeWidth={sel ? 2.2 : 1.6} strokeLinecap="round"/>
            {sel && <circle cx={x} cy={y} r="9" fill="none" stroke={stroke} strokeWidth="1"/>}
          </g>
        );
      })}

      {/* Legal throws */}
      {placedLegals.map(({ throw: t, x, y }) => {
        const hl = isHL(t);
        const sel = isSel(t);
        const r = sel ? 7 : (hl ? 5.5 : 4.5);
        const fill = hl ? 'var(--accent)' : 'var(--ink)';
        return (
          <g key={t.id} onClick={(e) => { e.stopPropagation(); onPickThrow && onPickThrow(t); }} style={{cursor:'pointer'}}>
            <circle cx={x} cy={y} r="14" fill="transparent" />
            <circle cx={x} cy={y} r={r} fill={fill} stroke="#fff" strokeWidth={hl ? 1.5 : 1}>
              {sel && <animate attributeName="r" values={`${r};${r+2};${r}`} dur="1.2s" repeatCount="indefinite"/>}
            </circle>
            {sel && <circle cx={x} cy={y} r={r + 5} fill="none" stroke={fill} strokeWidth="1" opacity="0.5"/>}
          </g>
        );
      })}
    </svg>
  );
}

// ---------- GraphView ----------
// X = chronological order (date), Y = distance
function GraphView({ throws, competitions, scaleMax, highlightedThrows, onPickThrow, selectedThrowId }) {
  const W = 360, H = 432;
  const padL = 36, padR = 14, padT = 16, padB = 38;
  const innerW = W - padL - padR;
  const innerH = H - padT - padB;

  // Each throw gets an x position: based on its competition's date order, plus offset for throw index
  const compById = new Map(competitions.map(c => [c.id, c]));
  const items = throws.map(t => {
    const c = compById.get(t.competitionId);
    if (!c || !c.date) return null;
    const time = new Date(c.date).getTime();
    return { t, c, time };
  }).filter(Boolean);

  if (!items.length) {
    return (
      <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="xMidYMid meet">
        <text x={W/2} y={H/2} fontSize="13" fill="var(--ink-3)" textAnchor="middle">No throws yet</text>
      </svg>
    );
  }

  const tMin = Math.min(...items.map(i => i.time));
  const tMax = Math.max(...items.map(i => i.time));
  const tSpan = Math.max(tMax - tMin, 1);

  // Group items by comp so throws in same comp are clustered (sub-spread)
  const byComp = new Map();
  for (const it of items) {
    const list = byComp.get(it.c.id) || [];
    list.push(it);
    byComp.set(it.c.id, list);
  }

  // Map a single comp center → x, then jitter throws around it
  // Inset by half-max-spread on each side so clusters stay inside the chart
  const xInset = 14;
  const tInnerW = Math.max(1, innerW - xInset * 2);
  const compXFor = (time) => padL + xInset + ((time - tMin) / tSpan) * tInnerW;

  const yMax = scaleMax;
  const yMin = 0;
  const yFor = (d) => padT + innerH - ((d - yMin) / (yMax - yMin)) * innerH;

  // Y-axis ticks
  let step;
  if (yMax <= 20) step = 2;
  else if (yMax <= 35) step = 5;
  else step = 10;
  const yTicks = [];
  for (let y = 0; y <= yMax + 0.001; y += step) yTicks.push(y);

  // X-axis ticks: choose 3-5 dates evenly
  const datesSorted = [...new Set(competitions.filter(c => c.date).map(c => c.date))].sort();
  const sampledDates = datesSorted.length <= 5
    ? datesSorted
    : (() => {
        const k = 4;
        const out = [];
        for (let i = 0; i <= k; i++) {
          const idx = Math.round((i / k) * (datesSorted.length - 1));
          out.push(datesSorted[idx]);
        }
        return out;
      })();

  // Build per-comp clustered positions
  const points = [];
  for (const [cId, list] of byComp.entries()) {
    const baseX = compXFor(list[0].time);
    // Sort list by throw index in comp's throws array, fallback to id
    const compThrowsOrder = list[0].c.throws.map(t => t.id);
    list.sort((a, b) => compThrowsOrder.indexOf(a.t.id) - compThrowsOrder.indexOf(b.t.id));
    const spread = Math.min(22, 4 + list.length * 2.6);
    const start = -spread / 2;
    list.forEach((it, idx) => {
      const ratio = list.length > 1 ? idx / (list.length - 1) : 0.5;
      let x = baseX + (start + ratio * spread);
      // Clamp inside the chart area
      x = Math.max(padL + 2, Math.min(padL + innerW - 2, x));
      points.push({ ...it, x });
    });
  }

  // Connect throws within same competition with thin line (legal only)
  const compPaths = [];
  for (const [cId, list] of byComp.entries()) {
    const pts = points.filter(p => p.c.id === cId && !p.t.foul && p.t.distance != null);
    if (pts.length < 2) continue;
    const isHL = pts.some(p => highlightedThrows.has(p.t.id));
    const d = pts.map((p, i) => `${i ? 'L' : 'M'}${p.x.toFixed(1)},${yFor(p.t.distance).toFixed(1)}`).join(' ');
    compPaths.push({ d, hl: isHL, id: cId });
  }

  const isHL = (t) => highlightedThrows.has(t.id);
  const isSel = (t) => selectedThrowId === t.id;

  // Best-throw-per-comp (legal)
  const bestIds = new Set();
  for (const [cId, list] of byComp.entries()) {
    const c = list[0].c;
    const b = bestThrow(c.throws);
    if (b) bestIds.add(b.id);
  }

  return (
    <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="xMidYMid meet">
      {/* Background */}
      <rect x="0" y="0" width={W} height={H} fill="var(--surface-2)" />
      <rect x={padL} y={padT} width={innerW} height={innerH} fill="#fff" />

      {/* Y grid */}
      {yTicks.map(v => (
        <Fragment key={'y'+v}>
          <line x1={padL} x2={padL + innerW} y1={yFor(v)} y2={yFor(v)} stroke="var(--line)" strokeWidth="0.7" strokeDasharray={v === 0 ? '0' : '2 3'} />
          <text x={padL - 6} y={yFor(v) + 3} fontSize="9" textAnchor="end" fill="var(--ink-3)" fontFamily="var(--mono)">{v}</text>
        </Fragment>
      ))}
      <text x={padL - 26} y={padT + 4} fontSize="9" fill="var(--ink-3)" fontFamily="var(--mono)" transform={`rotate(-90 ${padL - 26} ${padT + 4})`}>METRES</text>

      {/* X axis bottom */}
      <line x1={padL} x2={padL + innerW} y1={yFor(0)} y2={yFor(0)} stroke="var(--ink-3)" strokeWidth="0.8"/>
      {sampledDates.map((d, i) => {
        const time = new Date(d).getTime();
        const x = compXFor(time);
        return (
          <Fragment key={'xt'+i+d}>
            <line x1={x} x2={x} y1={yFor(0)} y2={yFor(0) + 4} stroke="var(--ink-3)" strokeWidth="0.8"/>
            <text x={x} y={yFor(0) + 16} fontSize="9.5" fill="var(--ink-3)" textAnchor="middle" fontFamily="var(--mono)">
              {formatDate(d, { day:'numeric', month:'short' })}
            </text>
          </Fragment>
        );
      })}

      {/* Foul X marks at y=0 line */}
      {points.filter(p => p.t.foul).map(p => {
        const hl = isHL(p.t);
        const sel = isSel(p.t);
        const stroke = hl ? 'var(--accent)' : 'var(--foul)';
        const y = yFor(0) - 7;
        return (
          <g key={p.t.id} onClick={(e) => { e.stopPropagation(); onPickThrow && onPickThrow(p.t); }} style={{cursor:'pointer'}}>
            <circle cx={p.x} cy={y} r="11" fill="transparent"/>
            <path d={`M${p.x-3.5},${y-3.5} L${p.x+3.5},${y+3.5} M${p.x+3.5},${y-3.5} L${p.x-3.5},${y+3.5}`} stroke={stroke} strokeWidth={sel ? 2.2 : 1.6} strokeLinecap="round"/>
          </g>
        );
      })}

      {/* Per-comp connecting lines (legal) */}
      {compPaths.map((p, i) => (
        <path key={'pp'+i+p.id} d={p.d} fill="none" stroke={p.hl ? 'var(--accent)' : 'var(--line-2)'} strokeWidth={p.hl ? 1.4 : 1} strokeLinecap="round" strokeLinejoin="round" opacity={p.hl ? 0.9 : 0.55}/>
      ))}

      {/* Legal points */}
      {points.filter(p => !p.t.foul && p.t.distance != null).map(p => {
        const hl = isHL(p.t);
        const sel = isSel(p.t);
        const isBest = bestIds.has(p.t.id);
        const r = sel ? 6 : (isBest ? 5 : (hl ? 4.5 : 3.8));
        const fill = hl ? 'var(--accent)' : 'var(--ink)';
        const y = yFor(p.t.distance);
        return (
          <g key={p.t.id} onClick={(e) => { e.stopPropagation(); onPickThrow && onPickThrow(p.t); }} style={{cursor:'pointer'}}>
            <circle cx={p.x} cy={y} r="14" fill="transparent"/>
            {isBest && <circle cx={p.x} cy={y} r={r + 3} fill="none" stroke={fill} strokeWidth="1" opacity="0.4"/>}
            <circle cx={p.x} cy={y} r={r} fill={fill} stroke="#fff" strokeWidth={hl ? 1.4 : 0.8}>
              {sel && <animate attributeName="r" values={`${r};${r+2};${r}`} dur="1.2s" repeatCount="indefinite"/>}
            </circle>
          </g>
        );
      })}
    </svg>
  );
}

// ---------- ThrowPopover ----------
function ThrowPopover({ pick, competitions, onClose, onOpenComp }) {
  if (!pick) return null;
  const c = competitions.find(c => c.id === pick.competitionId);
  const throwIdx = c ? c.throws.findIndex(t => t.id === pick.id) + 1 : null;
  return (
    <div className="throw-pop pop-in" style={{ left: pick.popLeft, top: pick.popTop }}>
      <button className="pop-close" onClick={onClose}><Icon.close width="14" height="14"/></button>
      <div className="pop-distance">
        {pick.foul ? 'Foul' : `${formatDistance(pick.distance)}m`}
      </div>
      <div className="pop-comp">{c ? c.name : '—'}</div>
      <div className="pop-meta">
        {c && c.date ? formatDate(c.date, { day:'numeric', month:'short', year:'numeric' }) : ''}
        {throwIdx ? ` · throw ${throwIdx}` : ''}
      </div>
      {pick.videoUrl && (
        <a className="pop-link" href={pick.videoUrl} target="_blank" rel="noreferrer">
          <Icon.video width="13" height="13"/> Watch video
        </a>
      )}
      {c && (
        <div style={{marginTop:8}}>
          <button className="btn sm secondary" style={{background:'rgba(255,255,255,0.12)', color:'#fff', borderColor:'transparent'}} onClick={() => onOpenComp(c.id)}>Open competition →</button>
        </div>
      )}
    </div>
  );
}

// ---------- Stats Hero ----------
function SeasonHero({ season, stats }) {
  const { pbStart, pbEnd, progression, best, attempts, legal, comps } = stats;
  return (
    <div className="hero">
      <div className="hero-label">Season {season} · Best</div>
      <div className="hero-value">
        {best ? formatDistance(best.distance) : '—'}<span className="unit">m</span>
      </div>
      <div className="hero-sub">{best ? `${best.compName} · ${formatDate(best.date, { day:'numeric', month:'short' })}` : 'No legal throw recorded'}</div>
      <div className="hero-grid">
        <div className="hero-stat">
          <div className="k">PB · Season start</div>
          <div className="v">{pbStart != null ? formatDistance(pbStart) + 'm' : '—'}</div>
        </div>
        <div className="hero-stat">
          <div className="k">PB · Season end</div>
          <div className="v">{pbEnd != null ? formatDistance(pbEnd) + 'm' : '—'}</div>
        </div>
        <div className="hero-stat">
          <div className="k">Progression</div>
          <div className="v">
            {progression == null ? '—' : (
              <span className={progression > 0 ? 'pos' : (progression < 0 ? 'neg' : '')}>
                {progression > 0 ? '+' : ''}{progression.toFixed(2)}m
              </span>
            )}
          </div>
        </div>
        <div className="hero-stat">
          <div className="k">Comps · Throws</div>
          <div className="v">{comps} · {legal}/{attempts}</div>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { SectorView, GraphView, ThrowPopover, SeasonHero });
