/* global React */
// Word search grid with swipe-to-select.
// Exposes <WordSearch /> and helpers via window.

const normalize = (s) =>
  s.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toUpperCase();

// Build a grid given dimensions and a list of placed words.
// `placed` items: { word, row, col, dr, dc }
function buildGrid(rows, cols, placed) {
  const g = Array.from({ length: rows }, () => Array(cols).fill(null));
  for (const p of placed) {
    const w = normalize(p.word);
    for (let i = 0; i < w.length; i++) {
      g[p.row + p.dr * i][p.col + p.dc * i] = w[i];
    }
  }
  // Fill empties with random letters that don't accidentally form the words at their cells.
  // Simple approach: use a noise letter set.
  const noise = "ABCDEFGHIJKLMNOPQRSTUVWXYZÇ".split("");
  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
      if (!g[r][c]) g[r][c] = noise[Math.floor(Math.random() * noise.length)];
    }
  }
  return g;
}

// Hard-coded grid mirroring the mockup exactly (9x9, with TRÂNSITO + FRENAGEM + SEGURANÇA).
const MOCKUP_GRID = [
  ["S","E","G","U","R","T","N","Ç","S"],
  ["E","O","I","U","Y","R","T","R","R"],
  ["G","F","R","E","N","A","G","E","M"],
  ["U","K","J","H","G","N","D","S","M"],
  ["R","N","B","V","C","S","Z","Q","I"],
  ["A","Z","X","C","V","I","N","M","T"],
  ["N","W","E","R","T","T","U","I","B"],
  ["Ç","J","Ç","A","L","O","O","I","F"],
  ["A","O","W","E","T","S","X","I","S"],
];
const MOCKUP_PLACED = [
  { word: "SEGURANCA", row: 0, col: 0, dr: 1, dc: 0 }, // vertical
  { word: "FRENAGEM",  row: 2, col: 1, dr: 0, dc: 1 }, // horizontal
  { word: "TRANSITO",  row: 0, col: 5, dr: 1, dc: 0 }, // vertical
];

// Place words with mockup-style composition: two words in one orientation
// (parallel) and one word perpendicular, crossing one of the parallels at a
// shared letter. Letters keep their original glyphs (Ç, accents) — comparison
// is done via normalize().
function placeWords(rows, cols, words, _allowDiagonal) {
  if (!words.length) return null;
  // Need at least 3 words for the parallel+cross composition; fall back to
  // simple random placement otherwise.
  if (words.length < 3) return simplePlace(rows, cols, words);

  // Try several random splits: pick which 2 words are parallel, which 1 crosses.
  for (let attempt = 0; attempt < 200; attempt++) {
    const order = [...words].sort(() => Math.random() - 0.5);
    const parallels = [order[0], order[1]];
    const cross = order[2];
    const extra = order.slice(3); // additional words placed freely

    // Pick orientation for the parallel pair
    const parallelHoriz = Math.random() < 0.5;
    const placed = [];
    const grid = Array.from({ length: rows }, () => Array(cols).fill(null));

    const placeAt = (w, r, c, dr, dc) => {
      for (let i = 0; i < w.length; i++) {
        const rr = r + dr * i, cc = c + dc * i;
        if (rr < 0 || rr >= rows || cc < 0 || cc >= cols) return false;
        if (grid[rr][cc] && normalize(grid[rr][cc]) !== normalize(w[i])) return false;
      }
      for (let i = 0; i < w.length; i++) {
        grid[r + dr * i][c + dc * i] = w[i].toUpperCase();
      }
      placed.push({ word: w, row: r, col: c, dr, dc });
      return true;
    };

    // Place parallel pair on separate rows/cols
    const dr1 = parallelHoriz ? 0 : 1;
    const dc1 = parallelHoriz ? 1 : 0;
    let ok = true;
    const used = new Set();
    for (const w of parallels) {
      let placed1 = false;
      for (let t = 0; t < 80 && !placed1; t++) {
        const maxR = parallelHoriz ? rows : rows - w.length;
        const maxC = parallelHoriz ? cols - w.length : cols;
        if (maxR <= 0 || maxC <= 0) break;
        const r = parallelHoriz ? Math.floor(Math.random() * rows) : Math.floor(Math.random() * (rows - w.length + 1));
        const c = parallelHoriz ? Math.floor(Math.random() * (cols - w.length + 1)) : Math.floor(Math.random() * cols);
        // Avoid same line as previous parallel
        const lineKey = parallelHoriz ? "r" + r : "c" + c;
        if (used.has(lineKey)) continue;
        if (placeAt(w, r, c, dr1, dc1)) {
          used.add(lineKey);
          placed1 = true;
        }
      }
      if (!placed1) { ok = false; break; }
    }
    if (!ok) continue;

    // Place the cross perpendicular, must intersect one of the parallels
    const dr2 = parallelHoriz ? 1 : 0;
    const dc2 = parallelHoriz ? 0 : 1;
    let crossPlaced = false;
    const parallelPlacements = placed.filter(p => p.dr === dr1 && p.dc === dc1);
    const targets = [...parallelPlacements].sort(() => Math.random() - 0.5);
    outer:
    for (const target of targets) {
      // For every letter position in the target, and every letter in cross
      // that matches, try to place cross so it crosses at that point.
      const positions = [];
      for (let i = 0; i < target.word.length; i++) {
        const tr = target.row + target.dr * i;
        const tc = target.col + target.dc * i;
        const tLetter = normalize(target.word[i]);
        for (let j = 0; j < cross.length; j++) {
          if (normalize(cross[j]) === tLetter) {
            positions.push({ tr, tc, j });
          }
        }
      }
      positions.sort(() => Math.random() - 0.5);
      for (const p of positions) {
        const r = p.tr - dr2 * p.j;
        const c = p.tc - dc2 * p.j;
        // bounds check
        if (r < 0 || c < 0) continue;
        if (r + dr2 * (cross.length - 1) >= rows) continue;
        if (c + dc2 * (cross.length - 1) >= cols) continue;
        // Try placement; placeAt allows existing matching letters (the cross point)
        if (placeAt(cross, r, c, dr2, dc2)) {
          crossPlaced = true;
          break outer;
        }
      }
    }
    if (!crossPlaced) continue;

    // Place any extras freely
    let extrasOk = true;
    for (const w of extra) {
      let done = false;
      for (let t = 0; t < 200 && !done; t++) {
        const horiz = Math.random() < 0.5;
        const dr = horiz ? 0 : 1, dc = horiz ? 1 : 0;
        const r = horiz ? Math.floor(Math.random() * rows) : Math.floor(Math.random() * (rows - w.length + 1));
        const c = horiz ? Math.floor(Math.random() * (cols - w.length + 1)) : Math.floor(Math.random() * cols);
        if (placeAt(w, r, c, dr, dc)) done = true;
      }
      if (!done) { extrasOk = false; break; }
    }
    if (!extrasOk) continue;

    // Fill empties with noise letters (no Ç, so the only Ç on the grid is in SEGURANÇA)
    const noise = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
    for (let r = 0; r < rows; r++) {
      for (let c = 0; c < cols; c++) {
        if (!grid[r][c]) grid[r][c] = noise[Math.floor(Math.random() * noise.length)];
      }
    }
    return { grid, placed };
  }
  return null;
}

// Simple fallback (used for word lists shorter than 3)
function simplePlace(rows, cols, words) {
  const dirs = [[0,1],[1,0]];
  const placed = [];
  const grid = Array.from({ length: rows }, () => Array(cols).fill(null));
  for (const w of words) {
    let ok = false;
    for (let t = 0; t < 400 && !ok; t++) {
      const [dr, dc] = dirs[Math.floor(Math.random() * dirs.length)];
      const r = dr ? Math.floor(Math.random() * (rows - w.length + 1)) : Math.floor(Math.random() * rows);
      const c = dc ? Math.floor(Math.random() * (cols - w.length + 1)) : Math.floor(Math.random() * cols);
      let fits = true;
      for (let i = 0; i < w.length; i++) {
        const ch = grid[r + dr * i][c + dc * i];
        if (ch && normalize(ch) !== normalize(w[i])) { fits = false; break; }
      }
      if (!fits) continue;
      for (let i = 0; i < w.length; i++) {
        grid[r + dr * i][c + dc * i] = w[i].toUpperCase();
      }
      placed.push({ word: w, row: r, col: c, dr, dc });
      ok = true;
    }
    if (!ok) return null;
  }
  const noise = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
      if (!grid[r][c]) grid[r][c] = noise[Math.floor(Math.random() * noise.length)];
    }
  }
  return { grid, placed };
}

// Snap pointer drag to nearest cell-line direction (0/45/90/etc).
function snapLine(startR, startC, endR, endC, allowDiagonal) {
  const dr = endR - startR;
  const dc = endC - startC;
  if (dr === 0 && dc === 0) return { endR: startR, endC: startC, dr: 0, dc: 0, len: 1 };
  // candidate directions
  const cands = allowDiagonal
    ? [[0,1],[0,-1],[1,0],[-1,0],[1,1],[1,-1],[-1,1],[-1,-1]]
    : [[0,1],[0,-1],[1,0],[-1,0]];
  // pick by dot product / projected length
  let best = null;
  const mag = Math.hypot(dr, dc);
  for (const [pr, pc] of cands) {
    const dot = (dr * pr + dc * pc) / Math.hypot(pr, pc);
    if (dot <= 0) continue;
    const align = dot / mag;
    if (!best || align > best.align) best = { pr, pc, dot, align };
  }
  if (!best) return { endR: startR, endC: startC, dr: 0, dc: 0, len: 1 };
  // length: project (dr,dc) onto the direction and round
  const projLen = Math.round(best.dot / Math.hypot(best.pr, best.pc));
  const len = Math.max(1, projLen + 1);
  return {
    endR: startR + best.pr * (len - 1),
    endC: startC + best.pc * (len - 1),
    dr: best.pr,
    dc: best.pc,
    len,
  };
}

function WordSearch({
  rows, cols, grid, placed, words,
  onWordFound, onAllFound,
  hintTrigger, // increments to trigger a hint
}) {
  const { useRef, useState, useEffect, useMemo } = React;
  const wrapRef = useRef(null);
  const cardRef = useRef(null);
  const [found, setFound] = useState([]); // [{word, row, col, dr, dc}]
  const [active, setActive] = useState(null); // current drag {startR,startC,endR,endC,dr,dc,len}
  const [hintCell, setHintCell] = useState(null); // {r,c}
  const cellSize = useRef(0);
  const stateRef = useRef({ found: [], active: null });
  stateRef.current.found = found;
  stateRef.current.active = active;

  // recompute cell size
  useEffect(() => {
    const update = () => {
      if (!cardRef.current) return;
      const box = cardRef.current.getBoundingClientRect();
      cellSize.current = (box.width - 36) / cols; // padding 18*2
    };
    update();
    const ro = new ResizeObserver(update);
    if (cardRef.current) ro.observe(cardRef.current);
    return () => ro.disconnect();
  }, [cols]);

  // Hint handler
  useEffect(() => {
    if (!hintTrigger) return;
    const unfound = placed.filter(p => !found.some(f => f.word === p.word));
    if (!unfound.length) return;
    const pick = unfound[Math.floor(Math.random() * unfound.length)];
    setHintCell({ r: pick.row, c: pick.col });
    const t = setTimeout(() => setHintCell(null), 2400);
    return () => clearTimeout(t);
  }, [hintTrigger]);

  // pointer→cell
  const cellAt = (clientX, clientY) => {
    const box = cardRef.current.getBoundingClientRect();
    const x = clientX - box.left - 18;
    const y = clientY - box.top - 18;
    const cs = (box.width - 36) / cols;
    const c = Math.floor(x / cs);
    const r = Math.floor(y / cs);
    if (r < 0 || r >= rows || c < 0 || c >= cols) return null;
    return { r, c };
  };

  // Pointer event handlers via native DOM (more reliable across mouse+touch)
  useEffect(() => {
    const card = cardRef.current;
    if (!card) return;
    let downStart = null;

    const onDown = (e) => {
      e.preventDefault();
      const hit = cellAt(e.clientX, e.clientY);
      if (!hit) return;
      downStart = { r: hit.r, c: hit.c };
      setActive({ startR: hit.r, startC: hit.c, endR: hit.r, endC: hit.c, dr: 0, dc: 0, len: 1 });
      try { card.setPointerCapture(e.pointerId); } catch {}
    };
    const onMove = (e) => {
      if (!downStart) return;
      const hit = cellAt(e.clientX, e.clientY);
      if (!hit) return;
      const line = snapLine(downStart.r, downStart.c, hit.r, hit.c, true);
      setActive({ startR: downStart.r, startC: downStart.c, ...line });
    };
    const onUp = (e) => {
      if (!downStart) return;
      const a = stateRef.current.active;
      downStart = null;
      if (!a) { setActive(null); return; }
      let s = "";
      for (let i = 0; i < a.len; i++) {
        const r = a.startR + a.dr * i;
        const c = a.startC + a.dc * i;
        if (r < 0 || r >= rows || c < 0 || c >= cols) { s = ""; break; }
        s += grid[r][c];
      }
      const sn = normalize(s);
      const rev = sn.split("").reverse().join("");
      const cur = stateRef.current.found;
      const match = placed.find(p => {
        if (cur.some(f => f.word === p.word)) return false;
        const w = normalize(p.word);
        return w === sn || w === rev;
      });
      if (match) {
        const next = [...cur, match];
        setFound(next);
        onWordFound && onWordFound(match.word);
        if (next.length === placed.length) {
          setTimeout(() => onAllFound && onAllFound(), 600);
        }
      }
      setActive(null);
    };

    card.addEventListener("pointerdown", onDown);
    card.addEventListener("pointermove", onMove);
    card.addEventListener("pointerup", onUp);
    card.addEventListener("pointercancel", onUp);
    return () => {
      card.removeEventListener("pointerdown", onDown);
      card.removeEventListener("pointermove", onMove);
      card.removeEventListener("pointerup", onUp);
      card.removeEventListener("pointercancel", onUp);
    };
  }, [rows, cols, grid, placed, onWordFound, onAllFound]);

  // Compute which cells lie on found strokes (to invert their color).
  const onStrokeSet = useMemo(() => {
    const s = new Set();
    for (const f of found) {
      const w = normalize(f.word);
      for (let i = 0; i < w.length; i++) {
        s.add((f.row + f.dr * i) + "_" + (f.col + f.dc * i));
      }
    }
    return s;
  }, [found]);

  const cs = cellSize.current || 0;

  return (
    <div ref={cardRef} className="grid-card">
      <div className="sel-layer">
        {/* found strokes */}
        {found.map((f, i) => (
          <Stroke key={i} {...f} cs={cs} rows={rows} cols={cols} found />
        ))}
        {/* active stroke */}
        {active && active.len > 1 && (
          <Stroke
            row={active.startR}
            col={active.startC}
            dr={active.dr}
            dc={active.dc}
            word={"X".repeat(active.len)}
            cs={cs}
            rows={rows}
            cols={cols}
            active
          />
        )}
      </div>
      <div
        className="grid"
        style={{ "--cols": cols, "--rows": rows }}
      >
        {grid.map((row, r) =>
          row.map((ch, c) => {
            const key = r + "_" + c;
            const onStroke = onStrokeSet.has(key);
            const isHint = hintCell && hintCell.r === r && hintCell.c === c;
            return (
              <div
                key={key}
                className={[
                  "cell",
                  c === cols - 1 ? "no-r" : "",
                  r === rows - 1 ? "no-b" : "",
                  onStroke ? "on-stroke" : "",
                  isHint ? "hint-flash" : "",
                ].join(" ")}
              >
                {ch}
              </div>
            );
          })
        )}
      </div>
    </div>
  );
}

function Stroke({ row, col, dr, dc, word, cs, rows, cols, found, active }) {
  const len = normalize(word).length;
  const padding = 18;

  // Diagonals → fall back to a rotated pill (no corner-snapping needed for the mockup).
  if (dr !== 0 && dc !== 0) {
    const cx = padding + col * cs + cs / 2;
    const cy = padding + row * cs + cs / 2;
    const thickness = cs * 0.92;
    const stepDist = Math.hypot(dr * cs, dc * cs);
    const length = (len - 1) * stepDist + thickness;
    const angle = Math.atan2(dr, dc) * 180 / Math.PI;
    return (
      <div
        className={"sel-stroke " + (found ? "found" : "") + (active ? " active" : "")}
        style={{
          position: "absolute",
          left: cx - thickness / 2,
          top: cy - thickness / 2,
          width: length,
          height: thickness,
          transformOrigin: `${thickness / 2}px ${thickness / 2}px`,
          transform: `rotate(${angle}deg)`,
          borderRadius: thickness / 2,
        }}
      />
    );
  }

  // Horizontal / vertical → one square per cell, only round corners that touch a grid corner.
  const cells = [];
  for (let i = 0; i < len; i++) {
    const r = row + dr * i;
    const c = col + dc * i;
    const radius = cs * 0.46;
    const r0 = (r === 0          && c === 0)           ? radius : 0; // TL of cell at grid TL
    const r1 = (r === 0          && c === cols - 1)    ? radius : 0; // TR of cell at grid TR
    const r2 = (r === rows - 1   && c === cols - 1)    ? radius : 0; // BR of cell at grid BR
    const r3 = (r === rows - 1   && c === 0)           ? radius : 0; // BL of cell at grid BL
    cells.push(
      <div
        key={i}
        className={"sel-stroke " + (found ? "found" : "") + (active ? " active" : "")}
        style={{
          position: "absolute",
          left: padding + c * cs,
          top: padding + r * cs,
          width: cs,
          height: cs,
          borderTopLeftRadius: r0,
          borderTopRightRadius: r1,
          borderBottomRightRadius: r2,
          borderBottomLeftRadius: r3,
        }}
      />
    );
  }
  return <React.Fragment>{cells}</React.Fragment>;
}

window.WordSearch = WordSearch;
window.normalize = normalize;
window.MOCKUP_GRID = MOCKUP_GRID;
window.MOCKUP_PLACED = MOCKUP_PLACED;
window.placeWords = placeWords;
