// Step 2 — the annotated facial-analysis hero (contours, width guides, landmark
// dots, leader-line metric pills, score) plus the detail cards below. View-aware:
// metrics the detected view can't measure are greyed; the nose overlay switches
// to a silhouette on profiles.

const fmtVal = (m) => {
  if (m.unit === "deg") return `${m.value}°`;
  if (m.unit === "mm") return `${m.value} mm`;
  return `${m.value}`;
};

const NASAL_LABELS = {
  radixWidth: "Radix width", bridgeWidth: "Bridge width", alarWidth: "Alar width",
  nostrilSymmetry: "Nostril symmetry", columellaVisibility: "Columella visibility",
  tipDefinition: "Tip definition", tipBulbosity: "Tip bulbosity",
  nasalAxisDeviation: "Nasal axis deviation", alarGrooveVisibility: "Alar groove visibility",
  nasalBaseProportion: "Nasal base proportion",
  nasofrontalAngle: "Nasofrontal angle", nasolabialAngle: "Nasolabial angle",
  dorsalHumpSize: "Dorsal hump size", bridgeHeight: "Bridge height",
  tipProjection: "Tip projection", tipRotation: "Tip rotation",
  columellaLobule: "Columella-lobule", radixDepth: "Radix depth",
  nasalLength: "Nasal length", supratipBreak: "Supratip break",
};

const VIEW_LABEL = { frontal: "Front-facing", three_quarter: "Three-quarter", profile: "Profile" };
const VIEW_NOTE = {
  frontal: "Front metrics are exact; side-profile depth metrics are estimates (≈).",
  three_quarter: "Partial view — front and side metrics shown; several are approximate.",
  profile: "Side-profile metrics are exact; front-only metrics need a front photo (N/A).",
};

// ── MediaPipe contour loops (subset) and width guides ───────────────────────
const FACE_OVAL = [10,338,297,332,284,251,389,356,454,323,361,288,397,365,379,378,400,377,152,148,176,149,150,136,172,58,132,93,234,127,162,21,54,103,67,109,10];
const L_EYE = [33,7,163,144,145,153,154,155,133,173,157,158,159,160,161,246,33];
const R_EYE = [263,249,390,373,374,380,381,382,362,398,384,385,386,387,388,466,263];
const L_BROW = [70,63,105,66,107,55,65,52,53,46,70];
const R_BROW = [300,293,334,296,336,285,295,282,283,276,300];
const LIPS = [61,185,40,39,37,0,267,269,270,409,291,375,321,405,314,17,84,181,91,146,61];
const NOSE_BRIDGE = [168,6,197,195,5,4,1];
const CONTOURS = [FACE_OVAL, L_EYE, R_EYE, L_BROW, R_BROW, LIPS, NOSE_BRIDGE];
const WIDTH_GUIDES = [[468,473],[33,133],[263,362],[65,295],[49,279],[61,291],[116,345],[172,397],[176,400]];
const ANCHOR_DOTS = [10,9,168,1,2,152,234,454,116,345,172,397,49,279,33,133,263,362,468,473,65,295,61,291,176,400,164,0];

// Peripheral nose landmarks — angular-sorted into a clean closed outline (the
// upper dorsal sides where the nose blends into the cheek are inherently soft,
// so the curve is smoothed rather than implying a hard edge).
const NOSE_OUTLINE_IDX = [168, 129, 49, 64, 98, 94, 327, 294, 279, 358];
// Profile silhouette midline (radix → dorsum → tip → columella → subnasale).
const PROFILE_SILHOUETTE_IDX = [168, 6, 197, 195, 5, 4, 1, 19, 94, 2];
const NASAL_PTS_PROFILE = [168, 1, 2, 94];

// Catmull-Rom → cubic-Bézier smooth path through ordered points.
function smoothPath(points, closed) {
  const n = points.length;
  if (n < 3) return points.map((p, i) => `${i ? "L" : "M"} ${p[0]} ${p[1]}`).join(" ");
  const at = (i) => points[closed ? (i + n) % n : Math.max(0, Math.min(n - 1, i))];
  let d = `M ${points[0][0]} ${points[0][1]}`;
  const last = closed ? n : n - 1;
  for (let i = 0; i < last; i++) {
    const p0 = at(i - 1), p1 = at(i), p2 = at(i + 1), p3 = at(i + 2);
    const c1x = p1[0] + (p2[0] - p0[0]) / 6, c1y = p1[1] + (p2[1] - p0[1]) / 6;
    const c2x = p2[0] - (p3[0] - p1[0]) / 6, c2y = p2[1] - (p3[1] - p1[1]) / 6;
    d += ` C ${c1x} ${c1y} ${c2x} ${c2y} ${p2[0]} ${p2[1]}`;
  }
  return d + (closed ? " Z" : "");
}

function noseOutlinePath(lm) {
  const pts = NOSE_OUTLINE_IDX.map((i) => lm[i]).filter(Boolean);
  if (pts.length < 3) return null;
  const cx = pts.reduce((s, p) => s + p[0], 0) / pts.length;
  const cy = pts.reduce((s, p) => s + p[1], 0) / pts.length;
  const ordered = pts.slice().sort((a, b) => Math.atan2(a[1] - cy, a[0] - cx) - Math.atan2(b[1] - cy, b[0] - cx));
  return smoothPath(ordered, true);
}

function profileSilhouettePath(lm) {
  const pts = PROFILE_SILHOUETTE_IDX.map((i) => lm[i]).filter(Boolean);
  return smoothPath(pts, false);
}

// metric → anchor landmark + which gutter
const PILLS = [
  { key: "foreheadHeight", idx: 10, side: "right", name: "Forehead Height" },
  { key: "browAlignment", idx: 295, side: "right", name: "Brow Alignment" },
  { key: "interpupillaryDistance", idx: 473, side: "right", name: "Interpupillary" },
  { key: "eyeWidthAvg", idx: 263, side: "right", name: "Eye Width" },
  { key: "alarWidth", idx: 279, side: "right", name: "Nose Width" },
  { key: "noseLength", idx: 1, side: "right", name: "Nose Length" },
  { key: "philtrumLength", idx: 164, side: "right", name: "Philtrum" },
  { key: "mouthWidth", idx: 291, side: "right", name: "Mouth Width" },
  { key: "faceLength", idx: 234, side: "left", name: "Face Length" },
  { key: "faceLengthOverWidth", idx: 127, side: "left", name: "Face L/W" },
  { key: "cheekboneWidth", idx: 116, side: "left", name: "Cheekbone Width" },
  { key: "jawWidth", idx: 172, side: "left", name: "Jaw Width" },
  { key: "chinProjection", idx: 152, side: "left", name: "Chin Projection" },
  { key: "leftRightDeviation", idx: 176, side: "left", name: "L/R Deviation" },
];

function lookup(a, key) {
  return a.facial[key] || a.nasal.front[key] || a.nasal.side[key];
}

// greedy de-overlap of pills within a gutter
function placePills(pills, a, lm, w, h, gutter) {
  const PH = 34, GAP = 8, PW = 168;
  const byside = { left: [], right: [] };
  for (const p of pills) {
    const m = lookup(a, p.key);
    if (!m || m.status === "na" || !lm[p.idx]) continue;
    byside[p.side].push({ ...p, m, ax: lm[p.idx][0], ay: lm[p.idx][1] });
  }
  const out = [];
  for (const side of ["left", "right"]) {
    const items = byside[side].sort((u, v) => u.ay - v.ay);
    let prev = -Infinity;
    for (const it of items) {
      let y = Math.max(it.ay - PH / 2, prev + GAP);
      y = Math.min(y, h - PH);
      prev = y + PH;
      const x = side === "left" ? -gutter + 10 : w + gutter - PW - 10;
      out.push({ ...it, x, y, pw: PW, ph: PH });
    }
  }
  return out;
}

const FaceOverlay = ({ session }) => {
  const [w, h] = session.image_size;
  const lm = session.landmarks_2d;
  const a = session.analysis;
  const isProfile = session.view && session.view.base === "profile";
  const gutter = Math.max(210, w * 0.62);
  const vb = `${-gutter} 0 ${w + 2 * gutter} ${h}`;
  const pills = isProfile ? [] : placePills(PILLS, a, lm, w, h, gutter);
  const score = a.score;

  const pt = (i) => (lm[i] ? `${lm[i][0]},${lm[i][1]}` : "");

  const padPct = (h / (w + 2 * gutter)) * 100;

  return (
    <div className="an-hero">
     <div className="an-svg-wrap" style={{ paddingBottom: `${padPct}%` }}>
      <svg className="an-svg" viewBox={vb} preserveAspectRatio="xMidYMid meet">
        <image href={session.image_url} x="0" y="0" width={w} height={h} />

        {/* facial contours + width guides + dots (hidden on profile — the mesh is frontal) */}
        {!isProfile && (
          <g className="mesh">
            {CONTOURS.map((seq, i) => (
              <polyline key={`c${i}`} className="contour" points={seq.map(pt).join(" ")} />
            ))}
            {WIDTH_GUIDES.map(([x, y], i) => lm[x] && lm[y] ? (
              <line key={`g${i}`} className="wguide" x1={lm[x][0]} y1={lm[x][1]} x2={lm[y][0]} y2={lm[y][1]} />
            ) : null)}
            {ANCHOR_DOTS.map((i) => lm[i] ? (
              <circle key={`d${i}`} className="anchor-dot" cx={lm[i][0]} cy={lm[i][1]} r="2.4" />
            ) : null)}
            {noseOutlinePath(lm) && <path className="nose-outline" d={noseOutlinePath(lm)} />}
          </g>
        )}

        {/* nose silhouette on profile */}
        {isProfile && (
          <g className="nasal-ov">
            <path className="nose-outline" d={profileSilhouettePath(lm)} />
            {NASAL_PTS_PROFILE.map((i) => lm[i] ? (
              <circle key={i} className="nasal-pt" cx={lm[i][0]} cy={lm[i][1]} r="3" />
            ) : null)}
          </g>
        )}

        {/* leader-line metric pills */}
        {pills.map((p) => {
          const cy = p.y + p.ph / 2;
          const edge = p.side === "left" ? p.x + p.pw : p.x;
          const elbow = p.side === "left" ? Math.max(p.ax - 14, edge + 10) : Math.min(p.ax + 14, edge - 10);
          const d = `M ${p.ax} ${p.ay} L ${elbow} ${p.ay} L ${elbow} ${cy} L ${edge} ${cy}`;
          const est = p.m.confidence === "low";
          const tagW = Math.max(44, (p.m.tag || "").length * 6.5 + 12);
          return (
            <g key={p.key} className="pill">
              <path className="leader" d={d} />
              <circle className="leader-end" cx={p.ax} cy={p.ay} r="2" />
              <rect className="pill-bg" x={p.x} y={p.y} width={p.pw} height={p.ph} rx="7" />
              <text className="pill-name" x={p.x + 10} y={p.y + 14}>{p.name}</text>
              <text className="pill-val" x={p.x + 10} y={p.y + 27}>{fmtVal(p.m)}</text>
              <rect className={`pill-tag ${est ? "est" : p.m.color}`} x={p.x + p.pw - tagW - 8} y={p.y + p.ph / 2 - 8} width={tagW} height={16} rx="4" />
              <text className={`pill-tagtext ${est ? "est" : p.m.color}`} x={p.x + p.pw - tagW / 2 - 8} y={p.y + p.ph / 2 + 3.5} textAnchor="middle">{est ? "EST" : p.m.tag}</text>
            </g>
          );
        })}

        {/* score + view badge */}
        {score && (
          <g className="score-badge">
            <rect x={w / 2 - 70} y="16" width="140" height="54" rx="12" />
            <text className="score-num" x={w / 2 - 54} y="46">{score.value} / 10</text>
            <text className="score-band" x={w / 2 - 54} y="61">{score.band}</text>
          </g>
        )}
        <g className="view-badge">
          <rect x={w / 2 + 16} y="28" width="86" height="22" rx="6" />
          <text x={w / 2 + 59} y="43" textAnchor="middle">{(VIEW_LABEL[a.view] || a.view).toUpperCase()}</text>
        </g>
      </svg>
     </div>
    </div>
  );
};

const TagDot = ({ m }) => (m.tag ? <span className={`tagdot ${m.color}`}>{m.tag}</span> : null);

const MetricRow = ({ label, m }) => {
  if (m.status === "na") {
    return (
      <div className="an-row na" title={m.note || "Not measurable from this view"}>
        <span>{label}</span>
        <span className="an-v"><span className="tagdot na">needs front photo</span></span>
      </div>
    );
  }
  const est = m.confidence === "low";
  return (
    <div className="an-row">
      <span>{label}{est ? <span className="conf-flag" title={m.note || ""}>≈</span> : null}</span>
      <span className="an-v">{fmtVal(m)} {est ? <span className="tagdot est">EST</span> : <TagDot m={m} />}</span>
    </div>
  );
};

const AnalysisScreen = ({ ctx }) => {
  const { state, set } = ctx;
  const session = state.session;
  if (!session || !session.analysis) {
    return <PlaceholderStep title="Face & Nose Analysis" body="Add a portrait in step 1 to run the analysis." />;
  }
  const a = session.analysis;
  const f = a.facial;
  const view = a.view || (session.view && session.view.base) || "frontal";
  return (
    <div className="analysis">
      <FaceOverlay session={session} />
      <div className="an-viewbanner">
        <span className="an-viewtag">{VIEW_LABEL[view] || view}</span>
        <span className="an-viewnote">{VIEW_NOTE[view]}</span>
      </div>
      <div className="an-cards">
        <div className="an-card">
          <h3>Facial Structure</h3>
          <MetricRow label="Face length" m={f.faceLength} />
          <MetricRow label="Face L/W ratio" m={f.faceLengthOverWidth} />
          <MetricRow label="Cheekbone width" m={f.cheekboneWidth} />
          <MetricRow label="Jaw width" m={f.jawWidth} />
          <MetricRow label="Chin projection" m={f.chinProjection} />
          <MetricRow label="Facial thirds" m={f.facialThirds} />
          <MetricRow label="Jaw definition" m={f.jawDefinition} />
          <MetricRow label="Forehead projection" m={f.foreheadProjection} />
          <MetricRow label="Midface projection" m={f.midfaceProjection} />
        </div>
        <div className="an-card">
          <h3>Nasal · Front</h3>
          {Object.entries(a.nasal.front).map(([k, m]) => <MetricRow key={k} label={NASAL_LABELS[k] || k} m={m} />)}
        </div>
        <div className="an-card">
          <h3>Nasal · Side</h3>
          {Object.entries(a.nasal.side).map(([k, m]) => <MetricRow key={k} label={NASAL_LABELS[k] || k} m={m} />)}
        </div>
        <div className="an-card report">
          <h3>AI Report</h3>
          <div className="an-sub summary">Aesthetic Summary</div>
          <ul>{a.report.aesthetic_summary.map((t, i) => <li key={i}>{t}</li>)}</ul>
          <div className="an-sub opportunity">Opportunity Areas</div>
          <ul>{(a.report.opportunity_areas.length ? a.report.opportunity_areas : ["No significant opportunities flagged"]).map((t, i) => <li key={i}>{t}</li>)}</ul>
          <div className="an-sub constraint">Medical Constraint Warnings</div>
          <ul>{a.report.medical_constraints.map((t, i) => <li key={i}>{t}</li>)}</ul>
        </div>
      </div>
      <button className="btn btn-primary an-continue" onClick={() => set({ step: "adjust" })}>Continue to adjustment →</button>
    </div>
  );
};

Object.assign(window, { AnalysisScreen, FaceOverlay, MetricRow });
