/* global React */
// ============================================================
// SIGNATURE HERO CANVAS — "The data-cleaning robots"
// A faint spreadsheet of bid data. Some fields are broken (red
// #REF! / NULL / $?? errors). A FLEET of small stylized robots
// patrols the grid; each claims its own broken field, selects it
// like an Excel active cell, beams it, and repairs the value with a
// mint "fixed" flash — then moves to the next. No two robots target
// the same cell. Entropy keeps seeding new errors, so when many
// break at once the whole fleet swarms. On load the sheet is full
// of errors and the fleet cleans it up — the entrance.
// Pure canvas 2D. All motion eased.
// ============================================================
function HeroCanvas({ intensity = 1 }) {
  const canvasRef = React.useRef(null);
  const wrapRef = React.useRef(null);

  React.useEffect(() => {
    const canvas = canvasRef.current;
    const wrap = wrapRef.current;
    if (!canvas || !wrap) return;
    const ctx = canvas.getContext('2d');
    const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    const TAU = Math.PI * 2;

    let W = 0, H = 0, dpr = Math.min(window.devicePixelRatio || 1, 2);
    let raf = 0, lastT = 0, nextErr = 0;

    const rand = (s) => Math.abs(Math.sin(s * 99991.137) * 43758.5453) % 1;

    const SCHEMA = [
      { key: 'TENDER ID', w: 1.6, type: 'code' },
      { key: 'DEPT',      w: 1.0, type: 'dept' },
      { key: 'GSIN',      w: 0.8, type: 'gsin' },
      { key: 'VALUE',     w: 1.2, type: 'money' },
      { key: 'WIN',       w: 0.6, type: 'pct' },
      { key: 'STATUS',    w: 1.1, type: 'status' },
    ];
    const DEPTS = ['PWGSC', 'DND', 'CRA', 'TBS', 'ISED', 'PSPC'];
    const GSINS = ['N7030', 'R0199', 'D0302', 'D0306', 'J0105', 'N5805'];
    const STATS = ['Verified', 'Matched', 'Scored', 'Ranked', 'Indexed'];
    const ERRORS = ['#REF!', 'NULL', '#N/A', '$ ??', '#ERR', '######', '#VALUE!'];

    function genVal(type, r, c) {
      const s = rand(r * 3.7 + c * 11.3 + 1.1);
      switch (type) {
        case 'code':  return `${DEPTS[(rand(r*2.1+c)*DEPTS.length)|0]}-${['IT','EN','DA','SE'][(s*4)|0]}-0${(800 + ((r*37+c*13)%180))}`;
        case 'dept':  return DEPTS[(s * DEPTS.length) | 0];
        case 'gsin':  return GSINS[(s * GSINS.length) | 0];
        case 'money': return '$' + (0.4 + s * 4.2).toFixed(2) + 'M';
        case 'pct':   return (28 + ((s * 64) | 0)) + '%';
        case 'status':return STATS[(s * STATS.length) | 0];
        default: return '...';
      }
    }

    let cols = [];
    let rows = 0, rh = 30, headerH = 30;
    let cells = [];
    let robots = [];
    let brokenList = [];

    function layout() {
      const rect = wrap.getBoundingClientRect();
      W = rect.width; H = rect.height;
      canvas.width = W * dpr; canvas.height = H * dpr;
      canvas.style.width = W + 'px'; canvas.style.height = H + 'px';
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);

      rh = Math.max(26, Math.min(38, Math.round(32 / Math.sqrt(intensity))));
      headerH = rh;
      const unit = 150 / intensity;
      const tileW = SCHEMA.reduce((a, s) => a + s.w, 0) * unit;
      const colTiles = Math.max(1, Math.ceil(W / tileW));
      cols = [];
      let x = 0, ci = 0;
      for (let t = 0; t < colTiles; t++) {
        for (const s of SCHEMA) {
          const w = s.w * unit;
          cols.push({ x, w, key: s.key, type: s.type, ci: ci++ });
          x += w;
        }
      }
      rows = Math.ceil((H - headerH) / rh) + 1;

      cells = [];
      for (let r = 0; r < rows; r++) {
        for (const col of cols) {
          cells.push({
            r, c: col.ci, type: col.type,
            x: col.x, w: col.w,
            cx: col.x + 12, cy: headerH + r * rh + rh / 2,
            selX: col.x + col.w / 2,
            val: genVal(col.type, r, col.ci),
            err: false, errTok: '', fix: 0, claimed: false,
          });
        }
      }
      brokenList = [];

      // Fleet sized to the viewport.
      const N = Math.max(2, Math.min(5, Math.round(W / 440)));
      robots = [];
      for (let i = 0; i < N; i++) {
        robots.push({
          x: W * (i + 0.5) / N, y: headerH + rh * (1 + (i % 3)),
          homeX: W * (i + 0.5) / N, homeY: headerH + rh * (1.5 + (i % 3)),
          state: 'seek', timer: 0, target: null, bob: Math.random() * TAU,
          scale: 1.04 + rand(i * 4.4) * 0.16, phase: i * 1.3,
        });
      }

      if (!reduced) {
        const n = Math.min(cells.length, 14 + ((W * H) / 80000 | 0));
        for (let i = 0; i < n; i++) breakRandom();
      }
      nextErr = performance.now() + 1300;
    }

    function breakRandom() {
      if (!cells.length) return;
      for (let tries = 0; tries < 14; tries++) {
        const cell = cells[(Math.random() * cells.length) | 0];
        if (cell.err) continue;
        cell.err = true; cell.claimed = false;
        cell.errTok = ERRORS[(Math.random() * ERRORS.length) | 0];
        brokenList.push(cell);
        return;
      }
    }

    function claimNearest(robot) {
      let best = null, bd = Infinity;
      for (const cell of brokenList) {
        if (cell.claimed) continue;
        const d = Math.abs(cell.selX - robot.x) + Math.abs(cell.cy - robot.y);
        if (d < bd) { bd = d; best = cell; }
      }
      if (best) { best.claimed = true; robot.target = best; robot.state = 'seek'; }
      return best;
    }

    function updateRobot(robot, dt, now) {
      const kMove = 1 - Math.exp(-dt * 6);
      robot.bob = Math.sin(now * 0.004 + robot.phase) * 2;

      if (!robot.target || !robot.target.err) {
        robot.target = null;
        claimNearest(robot);
      }
      const tgt = robot.target;
      if (!tgt) { // idle: drift home
        robot.x += (robot.homeX - robot.x) * kMove * 0.25;
        robot.y += (robot.homeY - robot.y) * kMove * 0.25;
        robot.state = 'seek';
        return;
      }
      const tx = tgt.selX, ty = tgt.cy;
      if (robot.state === 'seek') {
        if (Math.abs(robot.x - tx) > 2) robot.x += (tx - robot.x) * kMove;
        else { robot.x = tx; robot.y += (ty - robot.y) * kMove; }
        if (Math.abs(robot.x - tx) < 2 && Math.abs(robot.y - ty) < 2) {
          robot.x = tx; robot.y = ty; robot.state = 'repair'; robot.timer = 0;
        }
      } else if (robot.state === 'repair') {
        robot.timer += dt;
        if (robot.timer > 0.45) {
          tgt.err = false; tgt.fix = 1; tgt.claimed = false;
          tgt.val = genVal(tgt.type, tgt.r, tgt.c);
          const i = brokenList.indexOf(tgt);
          if (i >= 0) brokenList.splice(i, 1);
          robot.target = null; robot.state = 'seek';
        }
      }
    }

    function frame(now) {
      const dt = Math.min(0.05, (now - (lastT || now)) / 1000);
      lastT = now;
      ctx.clearRect(0, 0, W, H);
      const t = now * 0.001;

      if (!reduced && now > nextErr) {
        const burst = brokenList.length < 6 ? 1 + (Math.random() * 2 | 0) : 1;
        for (let i = 0; i < burst && brokenList.length < 26; i++) breakRandom();
        nextErr = now + 600 + Math.random() * 650;
      }

      // Grid lines
      ctx.lineWidth = 1;
      ctx.strokeStyle = 'rgba(240,234,218,0.035)';
      for (let r = 0; r <= rows; r++) {
        const y = headerH + r * rh + 0.5;
        ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke();
      }
      for (const col of cols) {
        ctx.beginPath(); ctx.moveTo(col.x + 0.5, 0); ctx.lineTo(col.x + 0.5, H); ctx.stroke();
      }
      // Header
      ctx.fillStyle = 'rgba(16,49,90,0.5)';
      ctx.fillRect(0, 0, W, headerH);
      ctx.strokeStyle = 'rgba(240,234,218,0.07)';
      ctx.beginPath(); ctx.moveTo(0, headerH + 0.5); ctx.lineTo(W, headerH + 0.5); ctx.stroke();
      ctx.font = "600 10px 'JetBrains Mono', monospace";
      ctx.textBaseline = 'middle';
      for (const col of cols) {
        ctx.fillStyle = 'rgba(194,178,128,0.6)';
        ctx.fillText(col.key, col.x + 12, headerH / 2);
      }

      // Cells
      ctx.font = "11px 'JetBrains Mono', monospace";
      for (const cell of cells) {
        cell.fix += (0 - cell.fix) * (1 - Math.exp(-dt * 3));
        if (cell.fix > 0.02) {
          ctx.fillStyle = `rgba(163,235,177,${cell.fix * 0.16})`;
          ctx.fillRect(cell.x + 1, cell.cy - rh / 2 + 1, cell.w - 2, rh - 2);
        }
        ctx.save();
        ctx.beginPath();
        ctx.rect(cell.x + 8, cell.cy - rh / 2, cell.w - 12, rh);
        ctx.clip();
        if (cell.err) {
          ctx.fillStyle = 'rgba(239,110,110,0.92)';
          ctx.fillText(cell.errTok, cell.cx, cell.cy);
        } else {
          const mint = cell.fix;
          const rr = 194 + (163 - 194) * mint, gg = 178 + (235 - 178) * mint, bb = 128 + (177 - 128) * mint;
          ctx.fillStyle = `rgba(${rr|0},${gg|0},${bb|0},${0.4 + mint * 0.5})`;
          ctx.fillText(cell.val, cell.cx, cell.cy);
        }
        ctx.restore();
      }

      // Robots: update
      if (!reduced) for (const robot of robots) updateRobot(robot, dt, now);

      // Active-cell selection boxes
      if (!reduced) for (const robot of robots) {
        const tgt = robot.target;
        if (!tgt) continue;
        const pulse = 0.5 + 0.5 * Math.sin(t * 5 + robot.phase);
        ctx.strokeStyle = `rgba(239,141,136,${0.5 + pulse * 0.35})`;
        ctx.lineWidth = 1.5;
        roundRect(ctx, tgt.x + 2, tgt.cy - rh / 2 + 2, tgt.w - 4, rh - 4, 4);
        ctx.stroke();
        ctx.fillStyle = 'rgba(239,141,136,0.07)';
        ctx.fill();
      }

      // Robots: draw
      if (!reduced) for (const robot of robots) drawRobot(robot, now);

      raf = requestAnimationFrame(frame);
    }

    function drawRobot(robot, now) {
      const repairing = robot.state === 'repair';
      const x = robot.x, y = robot.y + robot.bob, s = robot.scale;

      // Repair beam (absolute coords, onto the cell).
      if (repairing) {
        const beam = ctx.createLinearGradient(x, y, x, y + rh * 0.7);
        beam.addColorStop(0, 'rgba(239,141,136,0.35)');
        beam.addColorStop(1, 'rgba(239,141,136,0)');
        ctx.fillStyle = beam;
        ctx.beginPath();
        ctx.moveTo(x - 4, y + 6); ctx.lineTo(x + 4, y + 6);
        ctx.lineTo(x + 13, y + rh * 0.7); ctx.lineTo(x - 13, y + rh * 0.7);
        ctx.closePath(); ctx.fill();
        const sp = (Math.sin(now * 0.02 + robot.phase) * 0.5 + 0.5);
        ctx.fillStyle = `rgba(163,235,177,${0.5 + sp * 0.5})`;
        ctx.beginPath(); ctx.arc(x, y + rh * 0.55, 1.6 + sp, 0, TAU); ctx.fill();
      }

      ctx.save();
      ctx.translate(x, y);
      ctx.scale(s, s);

      // glow
      const glow = ctx.createRadialGradient(0, 0, 0, 0, 0, 22);
      glow.addColorStop(0, 'rgba(239,141,136,0.16)');
      glow.addColorStop(1, 'rgba(239,141,136,0)');
      ctx.fillStyle = glow;
      ctx.beginPath(); ctx.arc(0, 0, 22, 0, TAU); ctx.fill();

      // antenna
      ctx.strokeStyle = 'rgba(240,234,218,0.8)'; ctx.lineWidth = 1.2;
      ctx.beginPath(); ctx.moveTo(0, -9); ctx.lineTo(0, -14); ctx.stroke();
      const blink = 0.6 + 0.4 * Math.sin(now * 0.006 + robot.phase);
      ctx.fillStyle = `rgba(239,141,136,${blink})`;
      ctx.beginPath(); ctx.arc(0, -15.5, 2, 0, TAU); ctx.fill();

      // head
      roundRect(ctx, -13, -9, 26, 18, 6);
      ctx.fillStyle = '#F0EADA'; ctx.fill();
      ctx.lineWidth = 1.2; ctx.strokeStyle = 'rgba(13,43,82,0.55)'; ctx.stroke();
      // side bolts
      ctx.fillStyle = '#E8A08C';
      roundRect(ctx, -16, -3, 3, 6, 1.5); ctx.fill();
      roundRect(ctx, 13, -3, 3, 6, 1.5); ctx.fill();
      // visor
      roundRect(ctx, -9.5, -5.5, 19, 11, 4);
      ctx.fillStyle = '#0D2B52'; ctx.fill();
      // eyes
      ctx.fillStyle = repairing ? '#EF8D88' : '#A3EBB1';
      ctx.beginPath(); ctx.arc(-4, 0, 2, 0, TAU); ctx.fill();
      ctx.beginPath(); ctx.arc(4, 0, 2, 0, TAU); ctx.fill();
      // mouth
      ctx.strokeStyle = 'rgba(194,178,128,0.5)'; ctx.lineWidth = 1;
      ctx.beginPath(); ctx.moveTo(-3, 3.5); ctx.lineTo(3, 3.5); ctx.stroke();

      ctx.restore();
    }

    function roundRect(ctx, x, y, w, h, r) {
      r = Math.min(r, h / 2, w / 2);
      ctx.beginPath();
      ctx.moveTo(x + r, y);
      ctx.arcTo(x + w, y, x + w, y + h, r);
      ctx.arcTo(x + w, y + h, x, y + h, r);
      ctx.arcTo(x, y + h, x, y, r);
      ctx.arcTo(x, y, x + w, y, r);
      ctx.closePath();
    }

    layout();
    if (reduced) {
      ctx.clearRect(0, 0, W, H);
      frame(performance.now());
      cancelAnimationFrame(raf);
    } else {
      raf = requestAnimationFrame(frame);
    }
    const ro = new ResizeObserver(layout);
    ro.observe(wrap);
    return () => { cancelAnimationFrame(raf); ro.disconnect(); };
  }, [intensity]);

  return (
    <div ref={wrapRef} style={{
      position:'absolute', inset:0, overflow:'hidden', pointerEvents:'none',
      maskImage:'radial-gradient(125% 118% at 50% 44%, rgba(0,0,0,0.30) 0%, rgba(0,0,0,0.5) 26%, #000 64%)',
      WebkitMaskImage:'radial-gradient(125% 118% at 50% 44%, rgba(0,0,0,0.30) 0%, rgba(0,0,0,0.5) 26%, #000 64%)',
    }}>
      <canvas ref={canvasRef} style={{display:'block', width:'100%', height:'100%'}} />
    </div>
  );
}

Object.assign(window, { HeroCanvas });
