const { useMemo, useId } = React;

function mulberry32(seed) {
  let a = seed >>> 0;
  return function () {
    a |= 0; a = (a + 0x6D2B79F5) | 0;
    let t = Math.imul(a ^ (a >>> 15), 1 | a);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

function pointInPolygon(px, py, poly) {
  let inside = false;
  for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
    const xi = poly[i].x, yi = poly[i].y;
    const xj = poly[j].x, yj = poly[j].y;
    const intersect =
      yi > py !== yj > py &&
      px < ((xj - xi) * (py - yi)) / (yj - yi) + xi;
    if (intersect) inside = !inside;
  }
  return inside;
}

window.generateSpeckles = function generateSpeckles(pts, opts = {}) {
  const {
    seed = 7,
    count = 180,
    sizeMin = 0.008,
    sizeMax = 0.04,
    color = '#2a1d13',
    distribution = 'uniform',
    variance = 0.5,
  } = opts;
  const rng = mulberry32(seed);
  const b = window.eggBounds(pts);
  const out = [];
  const maxAttempts = count * 10;
  let attempts = 0;
  while (out.length < count && attempts < maxAttempts) {
    attempts++;
    const px = b.minX + rng() * b.w;
    const py = b.minY + rng() * b.h;
    if (!pointInPolygon(px, py, pts)) continue;
    let accept = 1;
    const tx = (px - b.minX) / b.w;
    if (distribution === 'polar') {
      accept = 0.3 + 1.3 * (1 - tx);
    } else if (distribution === 'belt') {
      const d = Math.abs(tx - 0.5);
      accept = 1.4 - d * 2;
    }
    if (rng() > accept) continue;
    const r = sizeMin + rng() * (sizeMax - sizeMin);
    const nPts = 6 + Math.floor(rng() * 4);
    const blob = [];
    for (let k = 0; k < nPts; k++) {
      const a = (k / nPts) * Math.PI * 2;
      const rr = r * (1 - variance / 2 + rng() * variance);
      blob.push({ x: px + Math.cos(a) * rr, y: py + Math.sin(a) * rr });
    }
    out.push({ cx: px, cy: py, r, blob, color });
  }
  return out;
};

// Preset parameters: length, breadth, asymmetry, pyriformity.
window.SPECIES = {
  hummingbird: {
    label: 'Hummingbird (Ruby-throated)',
    sci: 'Archilochus colubris',
    note: '13 mm long. The smallest egg in North America.',
    params: { L: 1.3, B: 0.88, w: 0.04, p: 0.0 },
    shell: { base: '#f0ebd6', accent: null, speckles: null },
  },
  chicken: {
    label: 'Chicken',
    sci: 'Gallus gallus domesticus',
    note: 'The canonical ovoid. The reference egg.',
    params: { L: 2.0, B: 1.44, w: 0.15, p: 0.0 },
    shell: { base: '#e9c591', accent: null, speckles: null },
  },
  robin: {
    label: 'American Robin',
    sci: 'Turdus migratorius',
    note: 'Famous for its blue. Pale uniform shell.',
    params: { L: 1.7, B: 1.22, w: 0.09, p: 0.0 },
    shell: { base: '#a9d3d0', accent: null, speckles: null },
  },
  song_thrush: {
    label: 'Song Thrush',
    sci: 'Turdus philomelos',
    note: 'Sky-blue shell with fine black speckling at the blunt end.',
    params: { L: 1.75, B: 1.30, w: 0.14, p: 0.0 },
    shell: {
      base: '#89b8bd',
      accent: '#2a1c12',
      speckles: { count: 90, sizeMin: 0.008, sizeMax: 0.025, distribution: 'polar', variance: 0.5 },
    },
  },
  guillemot: {
    label: 'Common Guillemot',
    sci: 'Uria aalge',
    note: "The pyriform classic. Rolls in a tight circle on a cliff ledge.",
    params: { L: 2.2, B: 1.28, w: 0.25, p: 0.42 },
    shell: {
      base: '#c8d3b5',
      accent: '#2a1d14',
      speckles: { count: 160, sizeMin: 0.015, sizeMax: 0.055, distribution: 'polar', variance: 0.65 },
    },
  },
  emu: {
    label: 'Emu',
    sci: 'Dromaius novaehollandiae',
    note: 'Deep forest-green shell. Nearly symmetrical.',
    params: { L: 2.1, B: 1.52, w: 0.08, p: 0.0 },
    shell: { base: '#4a5942', accent: null, speckles: null },
  },
  kiwi: {
    label: 'Kiwi',
    sci: 'Apteryx mantelli',
    note: 'Up to 25% of the mother\u2019s body mass. Smooth and pale.',
    params: { L: 2.2, B: 1.44, w: 0.10, p: 0.0 },
    shell: { base: '#ede0c6', accent: null, speckles: null },
  },
  owl: {
    label: 'Tawny Owl',
    sci: 'Strix aluco',
    note: 'Almost spherical. Owls trend round.',
    params: { L: 1.55, B: 1.35, w: 0.03, p: 0.0 },
    shell: { base: '#f3efe0', accent: null, speckles: null },
  },
  ostrich: {
    label: 'Ostrich',
    sci: 'Struthio camelus',
    note: 'The largest bird egg. Creamy shell, stippled pores.',
    params: { L: 2.3, B: 1.88, w: 0.06, p: 0.0 },
    shell: {
      base: '#eadfc3',
      accent: '#a68b5a',
      speckles: { count: 40, sizeMin: 0.005, sizeMax: 0.012, distribution: 'uniform', variance: 0.4 },
    },
  },
  murre: {
    label: 'Thick-billed Murre',
    sci: 'Uria lomvia',
    note: 'The egg D\u2019Arcy Thompson couldn\u2019t describe.',
    params: { L: 2.3, B: 1.32, w: 0.28, p: 0.48 },
    shell: {
      base: '#b8c19a',
      accent: '#231912',
      speckles: { count: 180, sizeMin: 0.012, sizeMax: 0.045, distribution: 'uniform', variance: 0.6 },
    },
  },
  plover: {
    label: 'Ringed Plover',
    sci: 'Charadrius hiaticula',
    note: 'Heavily spotted. Laid in a scrape on shingle beach.',
    params: { L: 1.65, B: 1.18, w: 0.16, p: 0.12 },
    shell: {
      base: '#d6c896',
      accent: '#2b1b10',
      speckles: { count: 140, sizeMin: 0.01, sizeMax: 0.03, distribution: 'uniform', variance: 0.7 },
    },
  },
  quail: {
    label: 'Common Quail',
    sci: 'Coturnix coturnix',
    note: 'Small, heavily blotched, never the same pattern twice.',
    params: { L: 1.55, B: 1.15, w: 0.10, p: 0.05 },
    shell: {
      base: '#e6d9a8',
      accent: '#261a10',
      speckles: { count: 90, sizeMin: 0.015, sizeMax: 0.055, distribution: 'uniform', variance: 0.75 },
    },
  },
};

window.EggSVG = function EggSVG({
  points,
  width = 260,
  height = 260,
  shell = { base: '#e9c591', accent: null, speckles: null },
  speckleSeed = 7,
  showLabel = false,
  figNumber = 1,
  rotate = 0,
  inkColor = '#1a1713',
  flipX = false,
  padding = 18,
}) {
  const uid = useId().replace(/:/g, '');

  const { pathD, bounds, scale, speckles } = useMemo(() => {
    if (!points || points.length === 0) {
      return { pathD: '', bounds: null, scale: 1, speckles: [] };
    }
    const b = window.eggBounds(points);
    const availW = width - padding * 2;
    const availH = height - padding * 2;
    const s = Math.min(availW / b.w, availH / b.h);
    const cx = width / 2;
    const cy = height / 2;
    const midX = (b.minX + b.maxX) / 2;
    const midY = (b.minY + b.maxY) / 2;
    const xform = (p) => ({ x: flipX ? -(p.x - midX) : (p.x - midX), y: p.y - midY });
    const scaled = points.map(xform);
    let d = '';
    for (let i = 0; i < scaled.length; i++) {
      const sx = cx + scaled[i].x * s;
      const sy = cy - scaled[i].y * s;
      d += (i === 0 ? 'M' : 'L') + sx.toFixed(2) + ',' + sy.toFixed(2) + ' ';
    }
    d += 'Z';
    const sp = shell.speckles
      ? window.generateSpeckles(points, {
          ...shell.speckles,
          color: shell.accent || inkColor,
          seed: speckleSeed,
        })
      : [];
    return { pathD: d, bounds: b, scale: s, speckles: sp };
  }, [points, width, height, shell, speckleSeed, flipX, padding]);

  if (!pathD) return null;

  const transformSpeckle = (mx, my) => {
    const b = bounds;
    const midX = (b.minX + b.maxX) / 2;
    const midY = (b.minY + b.maxY) / 2;
    const dx = flipX ? -(mx - midX) : mx - midX;
    const dy = my - midY;
    return { x: width / 2 + dx * scale, y: height / 2 - dy * scale };
  };

  let speckleD = '';
  for (const sp of speckles) {
    const blob = sp.blob || [];
    if (blob.length === 0) continue;
    for (let i = 0; i < blob.length; i++) {
      const p = transformSpeckle(blob[i].x, blob[i].y);
      speckleD += (i === 0 ? 'M' : 'L') + p.x.toFixed(2) + ',' + p.y.toFixed(2) + ' ';
    }
    speckleD += 'Z ';
  }

  const base = shell.base;
  const shadeLight = lighten(base, 0.12);
  const shadeDark = darken(base, 0.18);

  return (
    <svg
      viewBox={`0 0 ${width} ${height}`}
      width={width}
      height={height}
      style={{ transform: rotate ? `rotate(${rotate}deg)` : undefined, display: 'block' }}
    >
      <defs>
        <radialGradient id={`grad-${uid}`} cx="38%" cy="35%" r="75%">
          <stop offset="0%" stopColor={shadeLight} />
          <stop offset="55%" stopColor={base} />
          <stop offset="100%" stopColor={shadeDark} />
        </radialGradient>
        <filter id={`grain-${uid}`} x="0" y="0" width="100%" height="100%">
          <feTurbulence type="fractalNoise" baseFrequency="2.5" numOctaves="2" seed={speckleSeed} />
          <feColorMatrix
            values="0 0 0 0 0.1
                    0 0 0 0 0.08
                    0 0 0 0 0.06
                    0 0 0 0.15 0"
          />
          <feComposite in2="SourceGraphic" operator="in" />
        </filter>
        <clipPath id={`clip-${uid}`}>
          <path d={pathD} />
        </clipPath>
      </defs>

      <path d={pathD} fill={`url(#grad-${uid})`} />

      {speckleD && (
        <g clipPath={`url(#clip-${uid})`}>
          <path d={speckleD} fill={shell.accent || inkColor} opacity="0.85" />
          <path d={speckleD} fill={shell.accent || inkColor} opacity="0.3" transform="translate(1, 1)" />
        </g>
      )}

      <g clipPath={`url(#clip-${uid})`}>
        <rect x="0" y="0" width={width} height={height} filter={`url(#grain-${uid})`} />
      </g>

      <path d={pathD} fill="none" stroke={inkColor} strokeWidth="0.9" opacity="0.55" />

      <ellipse
        cx={width * 0.38}
        cy={height * 0.32}
        rx={width * 0.08}
        ry={height * 0.05}
        fill="#fff"
        opacity="0.14"
      />

      {showLabel && (
        <text
          x={width / 2}
          y={height - 6}
          textAnchor="middle"
          fontFamily="'Cormorant Garamond', serif"
          fontStyle="italic"
          fontSize="13"
          fill={inkColor}
          opacity="0.65"
        >
          {figNumber}.
        </text>
      )}
    </svg>
  );
};

function hexToRgb(hex) {
  const h = hex.replace('#', '');
  return {
    r: parseInt(h.slice(0, 2), 16),
    g: parseInt(h.slice(2, 4), 16),
    b: parseInt(h.slice(4, 6), 16),
  };
}
function rgbToHex(r, g, b) {
  const clamp = (v) => Math.max(0, Math.min(255, Math.round(v)));
  return '#' + [clamp(r), clamp(g), clamp(b)].map((v) => v.toString(16).padStart(2, '0')).join('');
}
function lighten(hex, amt) {
  const { r, g, b } = hexToRgb(hex);
  return rgbToHex(r + (255 - r) * amt, g + (255 - g) * amt, b + (255 - b) * amt);
}
function darken(hex, amt) {
  const { r, g, b } = hexToRgb(hex);
  return rgbToHex(r * (1 - amt), g * (1 - amt), b * (1 - amt));
}

Object.assign(window, { EggSVG: window.EggSVG, SPECIES: window.SPECIES, generateSpeckles: window.generateSpeckles });
