/* global React, window, document, Image */
const { useRef: useRefHt, useEffect: useEffectHt, useState: useStateHt } = React;

const HT_INK = '#5a5347';
const HT_BG = '#efece4';

function htSmooth(a, b, x) {
  const k = Math.min(1, Math.max(0, (x - a) / (b - a)));
  return k * k * (3 - 2 * k);
}

/* Retrato em halftone: foto convertida em pontos graphite sobre cream.
   Tamanho do ponto = escuridão; auto-contraste + gamma para lidar com
   fundos diferentes, e vinheta radial para focar no rosto.
   Opts extras (defaults = comportamento original, retratos não mudam):
   clip (percentis p_clip..p_1-clip no auto-contraste), contrast (S-curve
   de meios-tons), sharpen (unsharp mask na grade), vignette (0 sem, 1 atual). */
function drawHalftone(canvas, img, opts) {
  const cols = opts.cols;
  const zoom = opts.zoom || 1;
  const ox = opts.ox == null ? 0.5 : opts.ox;
  const oy = opts.oy == null ? 0.5 : opts.oy;
  const gamma = opts.gamma || 0.9;
  const clip = opts.clip || 0;
  const contrast = opts.contrast || 0;
  const sharpen = opts.sharpen || 0;
  const vignette = opts.vignette == null ? 1 : opts.vignette;
  const rect = canvas.getBoundingClientRect();
  const W = rect.width, H = rect.height;
  if (!W || !H) return;
  const dpr = Math.min(window.devicePixelRatio || 1, 2);
  canvas.width = Math.round(W * dpr);
  canvas.height = Math.round(H * dpr);
  const ctx = canvas.getContext('2d');
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);

  const aspect = W / H;
  const rows = Math.max(1, Math.round(cols / aspect)); // células ~quadradas em qualquer proporção
  const off = document.createElement('canvas');
  off.width = cols; off.height = rows;
  const octx = off.getContext('2d');
  // recorte na proporção do canvas, com zoom e foco
  let cropW, cropH;
  if (img.width / img.height > aspect) { cropH = img.height / zoom; cropW = cropH * aspect; }
  else { cropW = img.width / zoom; cropH = cropW / aspect; }
  const sx = (img.width - cropW) * ox;
  const sy = (img.height - cropH) * oy;
  octx.drawImage(img, sx, sy, cropW, cropH, 0, 0, cols, rows);
  const data = octx.getImageData(0, 0, cols, rows).data;

  const n = cols * rows;
  const lum = new Float32Array(n);
  let mn = 1, mx = 0;
  for (let k = 0; k < n; k++) {
    const r = data[k * 4], g = data[k * 4 + 1], b = data[k * 4 + 2];
    const l = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
    lum[k] = l;
    if (l < mn) mn = l;
    if (l > mx) mx = l;
  }

  // Unsharp mask na grade (antes da normalização: os percentis absorvem overshoots)
  if (sharpen > 0) {
    const blur = new Float32Array(n);
    for (let j = 0; j < rows; j++) {
      for (let i = 0; i < cols; i++) {
        let s = 0;
        for (let dj = -1; dj <= 1; dj++) {
          const jj = Math.min(rows - 1, Math.max(0, j + dj));
          for (let di = -1; di <= 1; di++) {
            const ii = Math.min(cols - 1, Math.max(0, i + di));
            s += lum[jj * cols + ii];
          }
        }
        blur[j * cols + i] = s / 9;
      }
    }
    for (let k = 0; k < n; k++) {
      lum[k] = Math.min(1, Math.max(0, lum[k] + sharpen * (lum[k] - blur[k])));
    }
  }

  // Auto-contraste: min/max exato (clip=0, original) ou percentis (outliers não achatam)
  if (clip > 0) {
    const hist = new Uint32Array(256);
    for (let k = 0; k < n; k++) hist[Math.min(255, (lum[k] * 255) | 0)]++;
    const target = clip * n;
    let acc = 0, lo = 0, hi = 255;
    for (let b = 0; b < 256; b++) { acc += hist[b]; if (acc >= target) { lo = b; break; } }
    acc = 0;
    for (let b = 255; b >= 0; b--) { acc += hist[b]; if (acc >= target) { hi = b; break; } }
    if (hi / 255 - lo / 255 >= 0.05) { mn = lo / 255; mx = hi / 255; } // senão, foto quase plana: min/max exato
  }
  const range = Math.max(0.0001, mx - mn);

  ctx.clearRect(0, 0, W, H);
  ctx.fillStyle = HT_BG;
  ctx.fillRect(0, 0, W, H);
  ctx.fillStyle = HT_INK;

  const cw = W / cols, ch = H / rows;
  const maxR = Math.min(cw, ch) * 0.64;

  for (let j = 0; j < rows; j++) {
    for (let i = 0; i < cols; i++) {
      let l = Math.min(1, Math.max(0, (lum[j * cols + i] - mn) / range));
      l = Math.pow(l, gamma);
      if (contrast > 0) {
        const s = l * l * (3 - 2 * l); // S-curve com ponto fixo em 0.5, preserva 0 e 1
        l = l + contrast * (s - l);
      }
      let dark = 1 - l;
      const nx = (i + 0.5) / cols - 0.5;
      const ny = (j + 0.5) / rows - 0.5;
      const nd = Math.sqrt(nx * nx + ny * ny) / 0.707;
      const vig = htSmooth(1.02, 0.52, nd); // 1 no centro, 0 nas bordas
      dark *= 1 - vignette * (1 - vig);
      if (dark <= 0.03) continue;
      const r = dark * maxR;
      ctx.beginPath();
      ctx.arc((i + 0.5) * cw, (j + 0.5) * ch, r, 0, 6.283185);
      ctx.fill();
    }
  }
}

function Halftone({ src, alt, initials, cols, zoom, focusX, focusY, gamma, clip, contrast, sharpen, vignette }) {
  const canvasRef = useRefHt(null);
  const imgRef = useRefHt(null);
  const [failed, setFailed] = useStateHt(false);
  const n = cols || 64;

  useEffectHt(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    let cancelled = false;
    const opts = { cols: n, zoom: zoom, ox: focusX, oy: focusY, gamma: gamma, clip: clip, contrast: contrast, sharpen: sharpen, vignette: vignette };
    const render = () => { if (imgRef.current) drawHalftone(canvas, imgRef.current, opts); };
    const img = new Image();
    img.onload = () => { if (cancelled) return; imgRef.current = img; render(); };
    img.onerror = () => { if (!cancelled) setFailed(true); };
    img.src = src;
    let ro = null;
    if ('ResizeObserver' in window) { ro = new ResizeObserver(render); ro.observe(canvas); }
    return () => { cancelled = true; if (ro) ro.disconnect(); };
  }, [src, n, zoom, focusX, focusY, gamma, clip, contrast, sharpen, vignette]);

  if (failed) {
    return (
      <span style={{ fontFamily: 'var(--font-display)', fontSize: 'clamp(48px, 6vw, 80px)', fontWeight: 500, letterSpacing: '-0.05em', color: 'var(--ink)' }}>
        {initials}<span style={{ color: 'var(--graphite)' }}>.</span>
      </span>
    );
  }
  return <canvas ref={canvasRef} role="img" aria-label={alt} style={{ display: 'block', width: '100%', height: '100%' }} />;
}

window.Halftone = Halftone;
