let __opentypePromise = null;
function loadOpentype() {
  if (__opentypePromise) return __opentypePromise;
  __opentypePromise = new Promise((resolve, reject) => {
    if (window.opentype) return resolve(window.opentype);
    const s = document.createElement('script');
    s.src = 'https://cdnjs.cloudflare.com/ajax/libs/opentype.js/1.3.4/opentype.min.js';
    s.onload = () => resolve(window.opentype);
    s.onerror = reject;
    document.head.appendChild(s);
  });
  return __opentypePromise;
}

// opentype.js 1.3.4 cannot parse WOFF2.
const FONT_URL = 'https://cdn.jsdelivr.net/npm/@fontsource/fraunces/files/fraunces-latin-400-normal.woff';

let __fontPromise = null;
function loadFraunces() {
  if (__fontPromise) return __fontPromise;
  __fontPromise = loadOpentype().then(opentype =>
    new Promise((resolve, reject) => {
      opentype.load(FONT_URL, (err, font) => err ? reject(err) : resolve(font));
    })
  ).catch(e => {
    __fontPromise = null;
    throw e;
  });
  return __fontPromise;
}

function splitContours(commands) {
  const contours = [];
  let current = [];
  for (const cmd of commands) {
    if (cmd.type === 'M') {
      if (current.length) contours.push(current);
      current = [cmd];
    } else {
      current.push(cmd);
    }
  }
  if (current.length) contours.push(current);
  return contours;
}

function contourBounds(commands) {
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
  for (const c of commands) {
    const xs = [c.x, c.x1, c.x2].filter(v => v !== undefined);
    const ys = [c.y, c.y1, c.y2].filter(v => v !== undefined);
    for (const x of xs) { if (x < minX) minX = x; if (x > maxX) maxX = x; }
    for (const y of ys) { if (y < minY) minY = y; if (y > maxY) maxY = y; }
  }
  return { minX, minY, maxX, maxY };
}

function commandsToPath(commands, offsetX, offsetY) {
  let d = '';
  for (const c of commands) {
    if (c.type === 'M') d += `M ${(c.x + offsetX).toFixed(2)} ${(c.y + offsetY).toFixed(2)} `;
    else if (c.type === 'L') d += `L ${(c.x + offsetX).toFixed(2)} ${(c.y + offsetY).toFixed(2)} `;
    else if (c.type === 'C') d += `C ${(c.x1 + offsetX).toFixed(2)} ${(c.y1 + offsetY).toFixed(2)} ${(c.x2 + offsetX).toFixed(2)} ${(c.y2 + offsetY).toFixed(2)} ${(c.x + offsetX).toFixed(2)} ${(c.y + offsetY).toFixed(2)} `;
    else if (c.type === 'Q') d += `Q ${(c.x1 + offsetX).toFixed(2)} ${(c.y1 + offsetY).toFixed(2)} ${(c.x + offsetX).toFixed(2)} ${(c.y + offsetY).toFixed(2)} `;
    else if (c.type === 'Z') d += 'Z ';
  }
  return d;
}

function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); }

window.MorphingO = function MorphingO() {
  const { useEffect, useRef, useState } = React;
  const slotRef = useRef(null);
  const [ready, setReady] = useState(false);

  useEffect(() => {
    let cancelled = false;
    let cleanup = null;

    loadFraunces().then(font => {
      if (cancelled || !slotRef.current) return;
      const existing = slotRef.current.querySelector('svg');
      if (existing) existing.remove();
      try {
        cleanup = setup(slotRef.current, font);
        setReady(true);
      } catch (e) {
        console.error('MorphingO setup failed:', e);
      }
    }).catch(e => {
      console.warn('Fraunces font load failed, falling back to text', e);
    });

    return () => {
      cancelled = true;
      if (cleanup) cleanup();
    };
  }, []);

  // React must not reconcile the imperatively mounted SVG.
  return (
    <span
      ref={slotRef}
      className="morph-o-slot"
      style={{
        position: 'relative',
        display: 'inline-block',
        minWidth: '0.55em',
        minHeight: '1ex',
      }}
    />
  );
};

function setup(slot, font) {
  const parent = slot.closest('h1, h2, h3, p, span, div') || slot.parentElement;
  const fontSizePx = parseFloat(getComputedStyle(parent).fontSize) || 180;
  const FONT_SIZE = fontSizePx;

  const glyph = font.charToGlyph('o');
  const scale = FONT_SIZE / font.unitsPerEm;

  const advance = glyph.advanceWidth * scale;
  const ascender = font.ascender * scale;
  const descender = font.descender * scale;
  const lineHeight = ascender - descender;

  const padX = 4;
  const padY = 4;
  const svgW = advance + padX * 2;
  const svgH = lineHeight + padY * 2;
  const baselineY = ascender + padY;

  slot.style.width = advance + 'px';
  slot.style.height = '1em';
  slot.style.verticalAlign = 'baseline';

  slot.textContent = '';

  const path = glyph.getPath(0, 0, FONT_SIZE);
  const contours = splitContours(path.commands);

  let outerContour = contours[0];
  let innerContour = contours[1] || null;
  if (contours.length >= 2) {
    const a0 = contourBounds(contours[0]);
    const a1 = contourBounds(contours[1]);
    const area0 = (a0.maxX - a0.minX) * (a0.maxY - a0.minY);
    const area1 = (a1.maxX - a1.minX) * (a1.maxY - a1.minY);
    if (area1 > area0) { outerContour = contours[1]; innerContour = contours[0]; }
  }

  const innerBbox = innerContour ? contourBounds(innerContour) : null;
  const counterCx = innerBbox ? (innerBbox.minX + innerBbox.maxX) / 2 : advance / 2;
  const counterCy = innerBbox ? (innerBbox.minY + innerBbox.maxY) / 2 : 0;
  const counterA = innerBbox ? (innerBbox.maxX - innerBbox.minX) / 2 : FONT_SIZE * 0.18;
  const counterB = innerBbox ? (innerBbox.maxY - innerBbox.minY) / 2 : FONT_SIZE * 0.22;

  const outerPathStr = commandsToPath(outerContour, padX, baselineY);

  const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  svg.setAttribute('viewBox', `0 0 ${svgW} ${svgH}`);
  svg.setAttribute('width', svgW);
  svg.setAttribute('height', svgH);
  svg.style.position = 'absolute';
  const slotHeightPx = parseFloat(getComputedStyle(slot).height);
  svg.style.left = (-padX) + 'px';
  svg.style.top = (slotHeightPx - baselineY) + 'px';
  svg.style.pointerEvents = 'none';

  const oPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  oPath.setAttribute('fill', 'currentColor');
  oPath.setAttribute('fill-rule', 'evenodd');
  svg.appendChild(oPath);
  slot.appendChild(svg);

  function buildEgg(cx, cy, a, b, k, theta) {
    const N = 96;
    const pts = [];
    const cosT = Math.cos(theta);
    const sinT = Math.sin(theta);
    for (let i = 0; i < N; i++) {
      const t = (i / N) * Math.PI * 2;
      const x0 = a * Math.cos(t);
      const y0 = b * Math.sin(t);
      const taper = 1 - k * (y0 / b);
      const x1 = x0 * taper;
      const y1 = y0;
      const xr = x1 * cosT - y1 * sinT;
      const yr = x1 * sinT + y1 * cosT;
      pts.push([xr + cx + padX, yr + cy + baselineY]);
    }
    let d = `M ${pts[0][0].toFixed(2)} ${pts[0][1].toFixed(2)}`;
    for (let i = 0; i < N; i++) {
      const curr = pts[i];
      const next = pts[(i + 1) % N];
      const mx = (curr[0] + next[0]) / 2;
      const my = (curr[1] + next[1]) / 2;
      d += ` Q ${curr[0].toFixed(2)} ${curr[1].toFixed(2)} ${mx.toFixed(2)} ${my.toFixed(2)}`;
    }
    d += ' Z';
    return d;
  }

  let params = { a: counterA, b: counterB, k: 0.12, theta: 0 };
  let targetParams = { ...params };
  const playStart = performance.now();

  function updateTargets(now) {
    const t = (now - playStart) / 1000;
    const aRange = counterA * 0.22;
    const bRange = counterB * 0.18;
    targetParams.a = counterA + aRange * Math.sin(t * 0.47);
    targetParams.b = counterB + bRange * Math.sin(t * 0.33 + 1.1);
    targetParams.k = 0.28 * Math.sin(t * 0.61 + 2.3);
    targetParams.theta = 0.18 * Math.sin(t * 0.29 + 0.7);
  }

  function render() {
    const egg = buildEgg(counterCx, counterCy, params.a, params.b, params.k, params.theta);
    oPath.setAttribute('d', outerPathStr + ' ' + egg);
  }

  let stopped = false;
  // The preview sandbox throttles requestAnimationFrame.
  const FPS = 60;
  function tick() {
    if (stopped) return;
    const now = performance.now();
    updateTargets(now);
    const ease = 0.08;
    params.a += (targetParams.a - params.a) * ease;
    params.b += (targetParams.b - params.b) * ease;
    params.k += (targetParams.k - params.k) * ease;
    params.theta += (targetParams.theta - params.theta) * ease;
    render();
  }
  render();
  const intervalId = setInterval(tick, 1000 / FPS);

  return () => {
    stopped = true;
    clearInterval(intervalId);
    if (svg.parentNode) svg.parentNode.removeChild(svg);
  };
}
