/**
 * Lasso-tool pixel & path helpers.
 *
 * All functions take the lasso polygon `points` as an explicit
 * argument so they can be tested in isolation. The legacy gallery
 * editor calls them with its module-level `_lassoPoints` array.
 */

/**
 * Shift each polygon vertex along the outward normal by `grow` pixels.
 * Used by the lasso overlay (to draw the "feather" halo) and by
 * `buildLassoMask` (to bake the grown polygon into the mask).
 *
 * @param {{x: number, y: number}[]} points  Polygon vertices in draw order.
 * @param {number} grow                      Positive = expand outward, negative = contract.
 * @returns {{x: number, y: number}[]}       New array (same length, original is not mutated).
 */
export function lassoOffsetPoints(points, grow) {
  const n = points.length;
  if (n < 3 || !grow) return points;
  // Polygon winding (positive = CCW) — flip the normal so it points
  // away from the interior regardless of draw direction.
  let area = 0;
  for (let i = 0; i < n; i++) {
    const p = points[i], q = points[(i + 1) % n];
    area += (q.x - p.x) * (q.y + p.y);
  }
  const sign = area > 0 ? 1 : -1;
  const out = new Array(n);
  for (let i = 0; i < n; i++) {
    const a = points[(i - 1 + n) % n], b = points[i], c = points[(i + 1) % n];
    const e1x = b.x - a.x, e1y = b.y - a.y;
    const e2x = c.x - b.x, e2y = c.y - b.y;
    const l1 = Math.hypot(e1x, e1y) || 1;
    const l2 = Math.hypot(e2x, e2y) || 1;
    // Perpendicular (dy, -dx); flip via `sign` for outward direction.
    const n1x = (e1y / l1) * sign, n1y = (-e1x / l1) * sign;
    const n2x = (e2y / l2) * sign, n2y = (-e2x / l2) * sign;
    const nx = (n1x + n2x) / 2;
    const ny = (n1y + n2y) / 2;
    const nl = Math.hypot(nx, ny) || 1;
    out[i] = { x: b.x + (nx / nl) * grow, y: b.y + (ny / nl) * grow };
  }
  return out;
}


/**
 * Trace the lasso polygon on the given context (move-to + line-to,
 * closed). Caller is responsible for `stroke()` / `fill()` choice.
 */
export function getLassoPath(ctx, points) {
  if (!points || points.length < 1) return;
  ctx.beginPath();
  ctx.moveTo(points[0].x, points[0].y);
  for (let i = 1; i < points.length; i++) {
    ctx.lineTo(points[i].x, points[i].y);
  }
  ctx.closePath();
}


/**
 * Build a (optionally feathered, optionally grown) selection mask
 * from a lasso polygon.
 *
 * @param {{x: number, y: number}[]} points  Polygon vertices.
 * @param {number} w / h                     Output canvas dimensions.
 * @param {number} offX / offY               Translate the polygon by (offX, offY) before rasterising.
 * @param {number} feather                   Feather width in pixels. 0 = hard edge.
 * @param {number} grow                      Positive = dilate the polygon, negative = erode.
 * @returns {HTMLCanvasElement}              A `w × h` canvas with alpha = selection strength.
 */
export function buildLassoMask(points, w, h, offX, offY, feather, grow) {
  // Step 1: draw hard mask
  const hard = document.createElement('canvas');
  hard.width = w; hard.height = h;
  const hCtx = hard.getContext('2d');
  hCtx.beginPath();
  hCtx.moveTo(points[0].x - offX, points[0].y - offY);
  for (let i = 1; i < points.length; i++) {
    hCtx.lineTo(points[i].x - offX, points[i].y - offY);
  }
  hCtx.closePath();
  hCtx.fillStyle = '#fff';
  hCtx.fill();

  // Step 1b: grow / shrink — blur the hard mask, threshold low for
  // grow and high for shrink. Same technique as the bg-remove edge
  // tuner. RGB is left alone, alpha is replaced.
  if (grow && grow !== 0) {
    const blurC = document.createElement('canvas');
    blurC.width = w; blurC.height = h;
    const bctx = blurC.getContext('2d');
    bctx.filter = `blur(${Math.abs(grow)}px)`;
    bctx.drawImage(hard, 0, 0);
    bctx.filter = 'none';
    const blurred = bctx.getImageData(0, 0, w, h).data;
    const hd = hCtx.getImageData(0, 0, w, h);
    const out = hd.data;
    const thr = grow > 0 ? 32 : 200;
    for (let i = 0; i < out.length; i += 4) {
      const a = blurred[i + 3] >= thr ? 255 : 0;
      out[i] = a; out[i + 1] = a; out[i + 2] = a; out[i + 3] = a;
    }
    hCtx.putImageData(hd, 0, 0);
  }

  if (feather <= 0) return hard;

  // Step 2: pixel data and distance-based feather.
  const hardData = hCtx.getImageData(0, 0, w, h);
  const d = hardData.data;

  // Build inside/outside map.
  const inside = new Uint8Array(w * h);
  for (let i = 0; i < w * h; i++) {
    inside[i] = d[i * 4] > 128 ? 1 : 0;
  }

  // Distance from edge (for pixels inside the selection, distance to nearest outside pixel).
  const dist = new Float32Array(w * h);
  dist.fill(feather + 1);

  // Seed: edge pixels (inside pixels adjacent to outside pixels).
  for (let y = 0; y < h; y++) {
    for (let x = 0; x < w; x++) {
      const i = y * w + x;
      if (!inside[i]) { dist[i] = 0; continue; }
      const hasOutside = (x > 0 && !inside[i-1]) || (x < w-1 && !inside[i+1]) ||
                         (y > 0 && !inside[(y-1)*w+x]) || (y < h-1 && !inside[(y+1)*w+x]);
      if (hasOutside) dist[i] = 1;
    }
  }

  // Two-pass chamfer distance transform.
  for (let y = 0; y < h; y++) {
    for (let x = 0; x < w; x++) {
      const i = y * w + x;
      if (dist[i] === 0) continue;
      if (x > 0) dist[i] = Math.min(dist[i], dist[i-1] + 1);
      if (y > 0) dist[i] = Math.min(dist[i], dist[(y-1)*w+x] + 1);
    }
  }
  for (let y = h-1; y >= 0; y--) {
    for (let x = w-1; x >= 0; x--) {
      const i = y * w + x;
      if (dist[i] === 0) continue;
      if (x < w-1) dist[i] = Math.min(dist[i], dist[i+1] + 1);
      if (y < h-1) dist[i] = Math.min(dist[i], dist[(y+1)*w+x] + 1);
    }
  }

  // Pixels near the edge get reduced alpha.
  const result = document.createElement('canvas');
  result.width = w; result.height = h;
  const rCtx = result.getContext('2d');
  const rData = rCtx.createImageData(w, h);

  for (let i = 0; i < w * h; i++) {
    if (!inside[i]) continue;
    const edgeDist = dist[i];
    const alpha = edgeDist >= feather ? 255 : Math.round((edgeDist / feather) * 255);
    rData.data[i*4] = alpha;
    rData.data[i*4+1] = alpha;
    rData.data[i*4+2] = alpha;
    rData.data[i*4+3] = 255;
  }
  rCtx.putImageData(rData, 0, 0);
  return result;
}
