/* Board.jsx — interactive nonogram grid (mono + colored). Exports window.Board */
const { useRef, useState, useEffect, useMemo, useCallback } = React;

function Board({
  cells, rowClues, colClues, onChange, readOnly,
  animMarks, wrongKey, colorGrid, colored, palette, activeColor, sweep, tapMode,
}) {
  const rows = cells.length, cols = (cells[0] && cells[0].length) || 0;
  const containerRef = useRef(null);
  const [cellSize, setCellSize] = useState(30);
  const dragRef = useRef(null);
  const lastClickRef = useRef(null);
  const touchTime = useRef(0);
  const [shiftStart, setShiftStart] = useState(null);
  const [hover, setHover] = useState(null);
  const [mouse, setMouse] = useState(null);

  const maxRowClues = useMemo(() => Math.max(1, ...rowClues.map((x) => x.length)), [rowClues]);
  const maxColClues = useMemo(() => Math.max(1, ...colClues.map((x) => x.length)), [colClues]);

  const lineVals = useCallback((arr) => arr.map((v) => (v > 0 ? (colored ? v : 1) : 0)), [colored]);
  const rowDone = useMemo(() => rowClues.map((cl, r) => {
    if (colored) return Engine.coloredLineSatisfied(cells[r], cl);
    return Engine.lineSatisfied(cells[r], cl);
  }), [cells, rowClues, colored]);
  const colDone = useMemo(() => colClues.map((cl, c) => {
    const line = cells.map((row) => row[c]);
    if (colored) return Engine.coloredLineSatisfied(line, cl);
    return Engine.lineSatisfied(line, cl);
  }), [cells, colClues, colored]);

  useEffect(() => {
    const el = containerRef.current;
    if (!el) return;
    const calc = () => {
      const w = el.clientWidth, h = el.clientHeight;
      if (!w || !h || !cols || !rows) return;
      let s = 34;
      const cf = window.innerWidth < 768 ? 0.5 : 0.62; // tighter clue gutters on phones
      for (let i = 0; i < 12; i++) {
        const clueW = maxRowClues * (s * cf) + 10;
        const clueH = maxColClues * (s * cf) + 10;
        const byW = (w - clueW) / cols;
        const byH = (h - clueH) / rows;
        const minS = window.innerWidth < 768 ? 14 : 12;
        const ns = Math.max(minS, Math.floor(Math.min(byW, byH)));
        if (Math.abs(ns - s) < 1) { s = ns; break; }
        s = ns;
      }
      setCellSize(s);
    };
    const ro = new ResizeObserver(calc);
    ro.observe(el);
    calc();
    return () => ro.disconnect();
  }, [rows, cols, maxRowClues, maxColClues]);

  useEffect(() => {
    const up = () => { dragRef.current = null; setShiftStart(null); };
    window.addEventListener("mouseup", up);
    window.addEventListener("touchend", up);
    window.addEventListener("blur", up);
    return () => { window.removeEventListener("mouseup", up); window.removeEventListener("touchend", up); window.removeEventListener("blur", up); };
  }, []);

  const clueUnit = Math.round(cellSize * (typeof window !== "undefined" && window.innerWidth < 768 ? 0.5 : 0.62));
  const leftW = maxRowClues * clueUnit + 8;
  const topH = maxColClues * clueUnit + 8;
  const numFont = Math.max(9, Math.round(cellSize * 0.4));

  const paint = useCallback((r, c, v) => { if (!readOnly) onChange && onChange(r, c, v); }, [readOnly, onChange]);

  // value a left-click should produce
  const fillVal = colored ? (activeColor || 1) : 1;
  const newFillFor = (r, c) => (cells[r][c] === fillVal ? 0 : fillVal);
  // value a touch (tap/drag) should produce, honoring the mobile Fill/Mark toggle
  const touchVal = (r, c) => (tapMode === "mark" ? (cells[r][c] === -1 ? 0 : -1) : newFillFor(r, c));

  // paint while dragging, locking to the row or column the drag commits to
  // Paint one cell through the drag's live mirror so a single fast sample stays
  // correct: skip cells already ✕ (incl. auto-✕), and after a fill completes a
  // line, mirror that line's auto-✕ locally so later cells in the same sweep are
  // skipped too — exactly as they would be across separate events.
  const paintCellDrag = (d, pr, pc) => {
    if (pr < 0 || pc < 0 || pr >= rows || pc >= cols) return;
    const live = d.live;
    if (d.v > 0 && live[pr][pc] === -1) return; // respect ✕ marks
    if (live[pr][pc] === d.v) return;
    live[pr][pc] = d.v;
    paint(pr, pc, d.v);
    if (d.v > 0 && typeof window !== "undefined" && window.Engine) {
      const E = window.Engine;
      const rowSat = colored ? E.coloredLineSatisfied(live[pr], rowClues[pr]) : E.lineSatisfied(live[pr], rowClues[pr]);
      if (rowSat) for (let x = 0; x < cols; x++) if (live[pr][x] === 0) live[pr][x] = -1;
      const colArr = live.map((row) => row[pc]);
      const colSat = colored ? E.coloredLineSatisfied(colArr, colClues[pc]) : E.lineSatisfied(colArr, colClues[pc]);
      if (colSat) for (let y = 0; y < rows; y++) if (live[y][pc] === 0) live[y][pc] = -1;
    }
  };

  const dragTo = (r, c) => {
    const d = dragRef.current;
    if (!d) return;
    if (!d.live) d.live = cells.map((row) => row.slice());
    if (d.axis == null && (r !== d.r0 || c !== d.c0)) {
      d.axis = Math.abs(c - d.c0) >= Math.abs(r - d.r0) ? "h" : "v";
    }
    const rr = d.axis === "h" ? d.r0 : r;
    const cc = d.axis === "v" ? d.c0 : c;
    // Interpolate from the last painted cell so a fast swipe (sparse touchmove
    // samples) doesn't skip the cells between samples.
    const lr = d.lastR == null ? rr : d.lastR;
    const lc = d.lastC == null ? cc : d.lastC;
    if (d.axis === "h") { const step = cc >= lc ? 1 : -1; for (let x = lc; x !== cc + step; x += step) paintCellDrag(d, rr, x); }
    else if (d.axis === "v") { const step = rr >= lr ? 1 : -1; for (let y = lr; y !== rr + step; y += step) paintCellDrag(d, y, cc); }
    else paintCellDrag(d, rr, cc);
    d.lastR = rr; d.lastC = cc;
  };

  const onCellDown = (e, r, c) => {
    if (readOnly) return;
    // Ignore the synthetic mousedown the browser fires right after a touch tap.
    if (Date.now() - touchTime.current < 600) return;
    e.preventDefault();
    const right = e.button === 2;
    const newV = right ? -1 : newFillFor(r, c);
    if (e.shiftKey && lastClickRef.current) {
      const { r: r0, c: c0 } = lastClickRef.current;
      if (r0 === r) { const [a, b] = c0 <= c ? [c0, c] : [c, c0]; for (let cc = a; cc <= b; cc++) paint(r, cc, newV); dragRef.current = { v: newV, r0: r, c0: c, axis: "h", lastR: r, lastC: c }; setShiftStart(null); return; }
      if (c0 === c) { const [a, b] = r0 <= r ? [r0, r] : [r, r0]; for (let rr = a; rr <= b; rr++) paint(rr, c, newV); dragRef.current = { v: newV, r0: r, c0: c, axis: "v", lastR: r, lastC: c }; setShiftStart(null); return; }
    }
    lastClickRef.current = { r, c };
    if (e.shiftKey) { setShiftStart({ r, c }); return; }
    dragRef.current = { v: newV, r0: r, c0: c, axis: null, lastR: r, lastC: c };
    paint(r, c, newV);
  };
  const onCellEnter = (e, r, c) => {
    setHover({ r, c });
    if (e && e.clientX != null) setMouse({ x: e.clientX, y: e.clientY });
    if (dragRef.current) dragTo(r, c);
  };

  const shiftCount = (() => {
    if (!shiftStart || !hover) return 0;
    if (shiftStart.r === hover.r) return Math.abs(hover.c - shiftStart.c) + 1;
    if (shiftStart.c === hover.c) return Math.abs(hover.r - shiftStart.r) + 1;
    return 0;
  })();

  const clueColor = (ci) => (palette && palette[ci - 1]) || "var(--ink)";

  function renderClueNums(list, axis) {
    if (colored) {
      return list.map((cl, i) => (
        <span key={i} className="clue-num" style={{
          fontSize: numFont,
          color: clueColor(cl.ci),
          lineHeight: axis === "col" ? clueUnit + "px" : cellSize + "px",
          minHeight: axis === "col" ? clueUnit : undefined,
          minWidth: axis === "row" ? clueUnit : undefined,
          height: axis === "row" ? cellSize : undefined,
        }}>{cl.n}</span>
      ));
    }
    return list.map((n, i) => (
      <span key={i} className="clue-num" style={{
        fontSize: numFont,
        lineHeight: axis === "col" ? clueUnit + "px" : cellSize + "px",
        minHeight: axis === "col" ? clueUnit : undefined,
        minWidth: axis === "row" ? clueUnit : undefined,
        height: axis === "row" ? cellSize : undefined,
      }}>{n}</span>
    ));
  }

  return (
    <div ref={containerRef} className="board-stage" onMouseLeave={() => setHover(null)} onContextMenu={(e) => e.preventDefault()}>
      {shiftStart && shiftCount > 0 && mouse && (
        <div className="shift-pill" style={{ left: mouse.x + 12, top: mouse.y - 34 }}>{shiftCount}</div>
      )}
      <div className="board-frame" style={{ paddingLeft: leftW, paddingTop: topH }}>
        <div className="board-corner" style={{ width: leftW, height: topH }} />
        <div className="clues-top" style={{ left: leftW, height: topH, gridTemplateColumns: `repeat(${cols}, ${cellSize}px)` }}>
          {colClues.map((col, ci) => (
            <div key={ci} className={"clue-col" + (colDone[ci] ? " done" : "") + (hover && hover.c === ci ? " hot" : "")} style={{ width: cellSize, height: topH }}>
              <div className="clue-col-inner">{renderClueNums(col, "col")}</div>
            </div>
          ))}
        </div>
        <div className="clues-left" style={{ top: topH, width: leftW, gridTemplateRows: `repeat(${rows}, ${cellSize}px)` }}>
          {rowClues.map((row, ri) => (
            <div key={ri} className={"clue-row" + (rowDone[ri] ? " done" : "") + (hover && hover.r === ri ? " hot" : "")} style={{ height: cellSize }}>
              <div className="clue-row-inner">{renderClueNums(row, "row")}</div>
            </div>
          ))}
        </div>
        <div className="cells-grid" style={{ width: cols * cellSize, height: rows * cellSize, gridTemplateColumns: `repeat(${cols}, ${cellSize}px)` }}>
          {cells.map((row, r) =>
            row.map((v, c) => {
              const key = r + "-" + c;
              const order = animMarks && animMarks[key];
              const blk = (Math.floor(r / 5) + Math.floor(c / 5)) % 2 === 1;
              const thickR = (c + 1) % 5 === 0 && c !== cols - 1;
              const thickD = (r + 1) % 5 === 0 && r !== rows - 1;
              const hot = hover && (hover.r === r || hover.c === c);
              const reveal = colorGrid && colorGrid[r] && colorGrid[r][c];
              let fillColor = "var(--fill)";
              if (reveal && reveal !== "transparent") fillColor = reveal;
              else if (colored && v > 0) fillColor = (palette && palette[v - 1]) || "var(--fill)";
              const filled = v > 0;
              const cls = ["cell", blk ? "blk" : "", filled ? "fill" : v === -1 ? "mark" : "empty", thickR ? "tr" : "", thickD ? "td" : "", hot && v === 0 ? "hot" : "", wrongKey === key ? "shake" : ""].join(" ");
              const fillStyle = { background: fillColor };
              if (reveal && reveal !== "transparent") {
                fillStyle.transition = "background .3s steps(2)";
                if (sweep) fillStyle.transitionDelay = ((r + c) * 28) + "ms";
              }
              return (
                <div key={key} className={cls} style={{ width: cellSize, height: cellSize }}
                  onMouseDown={(e) => onCellDown(e, r, c)}
                  onMouseEnter={(e) => onCellEnter(e, r, c)}
                  onTouchStart={(e) => { if (readOnly) return; touchTime.current = Date.now(); const nv = touchVal(r, c); lastClickRef.current = { r, c }; dragRef.current = { v: nv, r0: r, c0: c, axis: null, lastR: r, lastC: c }; paint(r, c, nv); }}
                  onTouchMove={(e) => { touchTime.current = Date.now(); const t = e.touches[0]; const el = document.elementFromPoint(t.clientX, t.clientY); if (el && el.dataset && el.dataset.rc) { const [rr, cc] = el.dataset.rc.split("-").map(Number); if (dragRef.current) dragTo(rr, cc); } }}
                  data-rc={key}>
                  {filled && <span className="fill-ink" style={fillStyle} />}
                  {v === -1 && <span className="mark-x" style={order != null ? { animationDelay: Math.min(0.5, order * 0.035) + "s" } : { animation: "none" }}>✕</span>}
                </div>
              );
            })
          )}
        </div>
      </div>
    </div>
  );
}

window.Board = Board;
