import type { CanonicalBone } from "@psx/shared";
import type { Measurements, Palette } from "../image/analyze.js";
import { jointWorld, type Skeleton } from "../rig/skeleton.js";
import {
  add,
  computeFlatNormals,
  createMesh,
  lerp,
  normalise,
  orientedBox,
  scale,
  sub,
  tube,
  tubePath,
  type MeshData,
  type Vec3Tuple,
} from "./MeshBuilder.js";

/**
 * Silent Hill–style low-poly humanoid from measured proportions.
 *
 * Design targets (SH1 / early RE, not Roblox):
 *  - ~600–1200 triangles, faceted, vertex-coloured
 *  - Hex cross-sections so limbs read round at 240p
 *  - Rings oriented along bones (A-pose arms stay clean)
 *  - Soft torso taper (shoulder → chest → waist → hip)
 *  - Slightly oversized head, short neck, defined shoulder mass
 *  - Feet point forward with a heel; hands are flat paddles
 *
 * Geometry is still drawn between canonical joints so skin weights and clips
 * stay portable across every character the harness produces.
 */

export interface HumanoidOptions {
  /** Cross-section sides for limbs (6 ≈ SH1). */
  sides?: number;
  /** Body depth as a fraction of width. Front view cannot measure this. */
  depthRatio?: number;
}

export function buildHumanoidMesh(
  skeleton: Skeleton,
  measurements: Measurements,
  palette: Palette,
  options: HumanoidOptions = {},
): MeshData {
  const mesh = createMesh();
  const sides = options.sides ?? 6;
  const depthRatio = options.depthRatio ?? 0.58;
  const { height } = skeleton;
  const across = (fraction: number): number => fraction * height;
  const at = (bone: CanonicalBone): Vec3Tuple => jointWorld(skeleton, bone);
  const half = (widthFraction: number): number => across(widthFraction) / 2;

  const hips = at("Hips");
  const spine = at("Spine");
  const spine1 = at("Spine1");
  const spine2 = at("Spine2");
  const neck = at("Neck");
  const head = at("Head");

  const hipW = half(measurements.widths.hip);
  const waistW = half(measurements.widths.waist) * 0.92;
  const chestW = half(measurements.widths.chest);
  const shoulderW = half(measurements.widths.shoulder);
  const neckW = half(measurements.widths.neck);
  const headW = half(measurements.widths.head);

  // --- pelvis / torso ------------------------------------------------------
  // Wider hips, pinched waist, full chest, soft shoulder shelf — the SH1
  // jacket silhouette. Slight forward lean on the upper chest for depth.
  const pelvisTop = lerp(hips, spine, 0.35);
  tube(mesh, hips, pelvisTop, hipW * 1.05, hipW * 0.98, depthRatio + 0.08, palette.torsoLower, {
    sides,
    sections: 2,
  });

  // Drop the hip joint centre slightly so the pelvis has volume under the belt.
  const belly = lerp(spine, spine1, 0.35);
  tube(mesh, pelvisTop, belly, hipW * 0.95, waistW, depthRatio + 0.05, palette.torsoLower, {
    sides,
    sections: 3,
    profile: (t) => ({ width: 1 - 0.04 * Math.sin(t * Math.PI), depth: 1 }),
  });

  const rib = lerp(spine1, spine2, 0.55);
  const ribPoint: Vec3Tuple = [rib[0], rib[1], rib[2] + height * 0.012];
  tube(mesh, belly, ribPoint, waistW, chestW * 0.98, depthRatio + 0.1, palette.torsoUpper, {
    sides,
    sections: 3,
  });

  // Shoulder yoke — wider than the ribcage, flatter in depth (coat shoulders).
  const yoke: Vec3Tuple = [spine2[0], spine2[1] + height * 0.01, spine2[2] + height * 0.008];
  tube(mesh, ribPoint, yoke, chestW * 0.95, shoulderW * 0.92, depthRatio * 0.85, palette.torsoUpper, {
    sides,
    sections: 2,
  });

  // Collar / neck base.
  tube(mesh, yoke, neck, neckW * 1.55, neckW * 1.15, 0.85, palette.torsoUpper, {
    sides: 6,
    sections: 1,
  });

  // --- neck + head ---------------------------------------------------------
  // Short neck, then an egg-shaped head (taller than wide, slightly deeper face).
  tube(mesh, neck, head, neckW, neckW * 0.95, 0.9, palette.skin, { sides: 6, sections: 1 });

  const crownY = height;
  const chin = head;
  const crown: Vec3Tuple = [0, crownY, head[2] * 0.3];
  const brow = lerp(chin, crown, 0.45);
  const browFwd: Vec3Tuple = [brow[0], brow[1], brow[2] + headW * 0.15];

  // Face (skin) — slightly forward of the skull centreline.
  tube(mesh, chin, browFwd, headW * 0.72, headW * 0.88, 0.95, palette.skin, {
    sides: 6,
    sections: 2,
    profile: (t) => ({ width: 0.9 + 0.15 * Math.sin(t * Math.PI), depth: 1 }),
  });

  // Cranial mass + hair cap sitting on top.
  const skull: Vec3Tuple = [0, (brow[1] + crownY) * 0.5, head[2] * 0.2];
  const hairTop: Vec3Tuple = [0, crownY, head[2] * 0.15];
  tube(mesh, browFwd, skull, headW * 0.9, headW * 0.95, 1.05, palette.skin, {
    sides: 6,
    sections: 1,
    caps: false,
  });
  tube(mesh, skull, hairTop, headW * 0.98, headW * 0.7, 1.05, palette.hair, {
    sides: 6,
    sections: 2,
    profile: (t) => ({ width: 1 + 0.08 * (1 - t), depth: 1 + 0.05 * (1 - t) }),
  });

  // --- shoulders + arms ----------------------------------------------------
  for (const prefix of ["Left", "Right"] as const) {
    const side = prefix === "Left" ? 1 : -1;
    const shoulder = at(`${prefix}Shoulder` as CanonicalBone);
    const arm = at(`${prefix}Arm` as CanonicalBone);
    const foreArm = at(`${prefix}ForeArm` as CanonicalBone);
    const hand = at(`${prefix}Hand` as CanonicalBone);

    const upperHalf = half(measurements.widths.upperArm) * 0.95;
    const foreHalf = half(measurements.widths.forearm) * 0.95;

    // Deltoid mass bridging torso to upper arm (the SH "coat shoulder").
    const deltoid = lerp(shoulder, arm, 0.35);
    tube(mesh, shoulder, deltoid, upperHalf * 1.25, upperHalf * 1.1, 1.05, palette.torsoUpper, {
      sides,
      sections: 2,
    });

    // Continuous arm path so the elbow bends without a hard mesh break.
    tubePath(
      mesh,
      [deltoid, arm, foreArm, hand],
      [upperHalf * 1.05, upperHalf, foreHalf * 1.05, foreHalf * 0.88],
      0.92,
      palette.arm,
      { sides, sectionsPerSegment: 2 },
    );

    // Hand paddle past the wrist, flattened.
    const handDir = normalise(sub(hand, foreArm));
    const handCentre = add(hand, scale(handDir, across(0.028)));
    orientedBox(
      mesh,
      handCentre,
      foreHalf * 0.85,
      foreHalf * 0.35,
      across(0.03),
      handDir,
      palette.hand,
    );
    void side;
  }

  // --- legs + feet ---------------------------------------------------------
  for (const prefix of ["Left", "Right"] as const) {
    const upLeg = at(`${prefix}UpLeg` as CanonicalBone);
    const leg = at(`${prefix}Leg` as CanonicalBone);
    const foot = at(`${prefix}Foot` as CanonicalBone);
    const toe = at(`${prefix}ToeBase` as CanonicalBone);

    const thighHalf = half(measurements.widths.thigh) * 1.02;
    const calfHalf = half(measurements.widths.calf);

    // Hip join: slight flair so legs don't spike out of a flat pelvis.
    const hipJoin = lerp(hips, upLeg, 0.55);
    tube(mesh, hipJoin, upLeg, hipW * 0.42, thighHalf * 1.05, depthRatio + 0.15, palette.leg, {
      sides,
      sections: 1,
    });

    tubePath(
      mesh,
      [upLeg, leg, foot],
      [thighHalf, thighHalf * 0.92, calfHalf * 0.9],
      depthRatio + 0.18,
      palette.leg,
      { sides, sectionsPerSegment: 3 },
    );

    // Boot: sole sits on y=0 so the character is grounded (not floating).
    const footHalfW = half(measurements.widths.foot) * 0.95;
    const footAxis = normalise(sub(toe, foot));
    // If toe is almost under ankle, force forward +Z so boots still read.
    const forward: Vec3Tuple =
      Math.hypot(footAxis[0], footAxis[2]) < 0.2 ? [0, 0, 1] : [footAxis[0], 0, footAxis[2]];
    const bootTop = Math.max(across(0.045), foot[1] * 0.55);
    const bootCentre: Vec3Tuple = [
      foot[0],
      bootTop * 0.5,
      foot[2] + across(0.025),
    ];
    orientedBox(
      mesh,
      bootCentre,
      footHalfW,
      bootTop * 0.5,
      across(0.055),
      normalise(forward),
      palette.foot,
    );
  }

  computeFlatNormals(mesh);
  return mesh;
}
