/* global THREE */

const COLORS = {
  steel: '#7A8087',
  steelDark: '#5C6066',
  panels: '#C8C4BC',
  panelsDark: '#A8A498',
  rubber: '#3A3A38',
  opener: '#5A5F66',
  openerLight: '#7A7F86',
  spring: '#8A6A4A',
  photoEyes: '#1A5C8A',
  hover: '#D4541A',
  selected: '#D4541A',
  header: '#6A6A62',
  wall: '#E8E2D4',
};

// ---- Geometry builders ----------------------------------------------------

function buildSpringGeometry() {
  // Tapered helix along CatmullRomCurve3 — 22 turns
  const turns = 22;
  const pointsPerTurn = 12;
  const totalPoints = turns * pointsPerTurn;
  const helixLength = 1.6;
  const points = [];

  for (let i = 0; i <= totalPoints; i++) {
    const t = i / totalPoints;
    const angle = t * turns * Math.PI * 2;
    const radius = 0.1;
    // Spring runs horizontally along X
    points.push(new THREE.Vector3(
      (t - 0.5) * helixLength,
      Math.cos(angle) * radius,
      Math.sin(angle) * radius
    ));
  }

  const curve = new THREE.CatmullRomCurve3(points);
  return new THREE.TubeGeometry(curve, totalPoints * 2, 0.018, 6, false);
}

// Door panel with windows (row of 4 rectangular windows in the 3rd panel up)
function buildDoorPanelsGroup() {
  const group = new THREE.Group();
  const panelHeight = 0.55;
  const panelWidth = 2.8;
  const panelDepth = 0.06;
  const gap = 0.01;
  const panels = 4;

  for (let i = 0; i < panels; i++) {
    const y = i * (panelHeight + gap) + panelHeight / 2;
    const geom = new THREE.BoxGeometry(panelWidth, panelHeight, panelDepth);
    const mat = new THREE.MeshLambertMaterial({ color: COLORS.panels });
    const mesh = new THREE.Mesh(geom, mat);
    mesh.position.set(0, y, 0);
    mesh.userData.panelIndex = i;
    group.add(mesh);

    // Inset shadow line at top of each panel
    const lineGeom = new THREE.BoxGeometry(panelWidth, 0.012, panelDepth + 0.002);
    const lineMat = new THREE.MeshLambertMaterial({ color: COLORS.panelsDark });
    const line = new THREE.Mesh(lineGeom, lineMat);
    line.position.set(0, y + panelHeight / 2 - 0.006, 0);
    group.add(line);
  }

  // Windows on third panel from bottom (i=2)
  const windowRowY = 2 * (panelHeight + gap) + panelHeight / 2;
  const windowW = 0.48;
  const windowH = 0.28;
  const windowZ = panelDepth / 2 + 0.001;
  for (let j = 0; j < 4; j++) {
    const x = -1.05 + j * 0.7;
    const wg = new THREE.BoxGeometry(windowW, windowH, 0.008);
    const wm = new THREE.MeshLambertMaterial({ color: '#D4E0E8' });
    const wMesh = new THREE.Mesh(wg, wm);
    wMesh.position.set(x, windowRowY, windowZ);
    group.add(wMesh);

    // Frame
    const frameGeom = new THREE.BoxGeometry(windowW + 0.03, windowH + 0.03, 0.012);
    const frameMat = new THREE.MeshLambertMaterial({ color: COLORS.panelsDark });
    const frame = new THREE.Mesh(frameGeom, frameMat);
    frame.position.set(x, windowRowY, windowZ - 0.006);
    group.add(frame);

    // Mullion
    const mullGeom = new THREE.BoxGeometry(0.01, windowH, 0.004);
    const mullMat = new THREE.MeshLambertMaterial({ color: COLORS.panelsDark });
    const mull = new THREE.Mesh(mullGeom, mullMat);
    mull.position.set(x, windowRowY, windowZ + 0.005);
    group.add(mull);
  }
  return group;
}

function buildHingeGeometry() {
  // A simple bracket hinge — V-shape
  const group = new THREE.Group();
  const plate = new THREE.Mesh(
    new THREE.BoxGeometry(0.22, 0.1, 0.02),
    new THREE.MeshLambertMaterial({ color: COLORS.steel })
  );
  plate.position.set(0, 0, 0.04);
  group.add(plate);
  return group;
}

function buildRollerGeometry() {
  // Roller wheel (nylon) with stem
  const group = new THREE.Group();
  const wheel = new THREE.Mesh(
    new THREE.CylinderGeometry(0.06, 0.06, 0.04, 16),
    new THREE.MeshLambertMaterial({ color: '#EDE7D8' })
  );
  wheel.rotation.z = Math.PI / 2;
  group.add(wheel);
  const stem = new THREE.Mesh(
    new THREE.CylinderGeometry(0.014, 0.014, 0.12, 8),
    new THREE.MeshLambertMaterial({ color: COLORS.steel })
  );
  stem.rotation.z = Math.PI / 2;
  stem.position.x = 0.06;
  group.add(stem);
  return group;
}

function buildTrackGeometry(side = 'left') {
  // L-shaped rail: vertical + quarter-curve + horizontal along the ceiling
  // Track runs in the door plane (z≈0) then curves back toward the ceiling (+z)
  const group = new THREE.Group();
  const RAD = 0.4; // curve radius
  const vertTop = 2.4; // where the vertical ends and the curve begins
  const horizZ = RAD; // horizontal track sits RAD back from the door plane

  // Vertical rail (from floor to top of door opening)
  const rail = new THREE.Mesh(
    new THREE.BoxGeometry(0.06, vertTop, 0.12),
    new THREE.MeshLambertMaterial({ color: COLORS.steelDark })
  );
  rail.position.set(0, vertTop / 2, 0);
  group.add(rail);

  // Quarter-curve built as a TubeGeometry for predictable orientation.
  // Starts at top of vertical rail (0, vertTop, 0), sweeps UP and BACK to
  // (0, vertTop+RAD, RAD) where the horizontal ceiling track begins.
  const curvePoints = [];
  const segs = 16;
  for (let i = 0; i <= segs; i++) {
    const a = (i / segs) * (Math.PI / 2); // 0 -> PI/2
    // Arc centered at (0, vertTop, RAD): bulges downward-outward first, then up.
    // Start (a=0): (0, vertTop, 0). End (a=PI/2): (0, vertTop+RAD, RAD).
    const y = vertTop + RAD * Math.sin(a);
    const zz = RAD - RAD * Math.cos(a);
    curvePoints.push(new THREE.Vector3(0, y, zz));
  }
  const curvePath = new THREE.CatmullRomCurve3(curvePoints);
  const curve = new THREE.Mesh(
    new THREE.TubeGeometry(curvePath, 20, 0.05, 6, false),
    new THREE.MeshLambertMaterial({ color: COLORS.steelDark })
  );
  group.add(curve);

  // Horizontal rail along the ceiling, starting at z=RAD and running back
  const horizLen = 1.6;
  const horiz = new THREE.Mesh(
    new THREE.BoxGeometry(0.06, 0.12, horizLen),
    new THREE.MeshLambertMaterial({ color: COLORS.steelDark })
  );
  horiz.position.set(0, vertTop + RAD, RAD + horizLen / 2);
  group.add(horiz);

  return group;
}

function buildBottomSealGeometry() {
  // Long rubber strip
  return new THREE.BoxGeometry(2.84, 0.07, 0.1);
}

function buildOpenerRailGeometry() {
  const group = new THREE.Group();
  // Rail (long beam)
  const rail = new THREE.Mesh(
    new THREE.BoxGeometry(0.1, 0.12, 2.8),
    new THREE.MeshLambertMaterial({ color: COLORS.openerLight })
  );
  rail.position.set(0, 0, 0);
  group.add(rail);

  // Carriage block (positioned at the door-header end of the rail)
  const carriage = new THREE.Mesh(
    new THREE.BoxGeometry(0.18, 0.18, 0.22),
    new THREE.MeshLambertMaterial({ color: COLORS.opener })
  );
  carriage.position.set(0, -0.02, -1.25);
  group.add(carriage);

  // J-arm / hockey stick (drops from carriage down to top of door)
  const arm1 = new THREE.Mesh(
    new THREE.BoxGeometry(0.05, 0.55, 0.05),
    new THREE.MeshLambertMaterial({ color: COLORS.rubber })
  );
  arm1.position.set(0, -0.35, -1.25);
  group.add(arm1);
  const arm2 = new THREE.Mesh(
    new THREE.BoxGeometry(0.05, 0.05, 0.25),
    new THREE.MeshLambertMaterial({ color: COLORS.rubber })
  );
  arm2.position.set(0, -0.6, -1.37);
  group.add(arm2);

  // Motor head (at opposite / ceiling end of the rail)
  const motor = new THREE.Mesh(
    new THREE.BoxGeometry(0.35, 0.25, 0.45),
    new THREE.MeshLambertMaterial({ color: '#EDE7D8' })
  );
  motor.position.set(0, 0.05, 1.25);
  group.add(motor);

  return group;
}

// ---- Parts registry ------------------------------------------------------

const PARTS = [
  {
    id: 'springs',
    label: 'Torsion Spring',
    shortLabel: 'Springs',
    checklistCopy: 'Should be oiled and inspected for damage, rust, sagging, dirt, wear and tear.',
    detail: 'Mounted horizontally above the door on a steel shaft, the torsion spring stores the energy that lifts the door. A wound spring holds roughly 150–200 lbs of tension — when one breaks, the door becomes dead weight and the opener can\'t lift it. Springs wear by cycles; a standard spring is rated for ~10,000 cycles (about 7 years of daily use). Look for gaps in the coil, rust, or a door that suddenly feels twice as heavy.',
    dangerNote: 'Never attempt to replace a torsion spring yourself. The stored energy can cause serious injury.',
    chatPrompt: 'Tell me about torsion springs — how they work, when they break, and whether I can replace one myself.',
    category: 'Door Interior',
    resting: [0, 2.45, 0.08],
    exploded: [0, 3.3, 0.45],
    rotation: [0, 0, 0],
    color: COLORS.spring,
    build: buildSpringGeometry,
    isMesh: true,
  },
  {
    id: 'cables',
    label: 'Lift Cables',
    shortLabel: 'Cables',
    checklistCopy: 'Not bent or frayed.',
    detail: 'Steel cables run from the bottom bracket of the door up to cable drums on the torsion shaft. They carry the load as the spring unwinds, lifting the door along the track. A frayed cable is a warning sign; a broken cable lets the door drop and can snap back with enough force to injure anyone nearby.',
    dangerNote: 'A broken cable under tension can whip with injurious force. Keep hands clear when operating a suspect door.',
    chatPrompt: 'Tell me about garage door lift cables — what they do, signs of wear, and the danger of a broken cable.',
    category: 'Door Interior',
    resting: [-1.35, 1.2, 0.08],
    exploded: [-1.85, 1.2, 0.25],
    rotation: [0, 0, 0],
    color: COLORS.steel,
    build: () => new THREE.CylinderGeometry(0.008, 0.008, 2.3, 6),
    isMesh: true,
  },
  {
    id: 'rollers',
    label: 'Rollers',
    shortLabel: 'Rollers',
    checklistCopy: 'Wheels rotate freely, are free of cracking or damage.',
    detail: 'Small wheels that ride inside the track, one at every hinge. Nylon rollers are quiet but wear faster; steel rollers last longer but get noisy without periodic oil. A roller that sticks or has a cracked wheel will drag the door off-track.',
    chatPrompt: 'Tell me about garage door rollers — nylon vs steel, how to inspect them, and when to replace them.',
    category: 'Door Interior',
    resting: [-1.42, 1.5, 0.0],
    exploded: [-1.95, 1.5, 0.0],
    rotation: [0, 0, 0],
    color: '#EDE7D8',
    build: buildRollerGeometry,
    isMesh: false,
    instances: [
      [-1.42, 0.3, 0.0],
      [-1.42, 1.0, 0.0],
      [-1.42, 1.7, 0.0],
      [-1.42, 2.4, 0.0],
    ],
  },
  {
    id: 'track',
    label: 'Track',
    shortLabel: 'Track',
    checklistCopy: 'Make sure track is properly spaced and door is not scraping the opening.',
    detail: 'The steel rail that guides the door up and over the opening. The vertical section holds the door flush to the opening; the curve redirects it along the ceiling. A track bent inward causes scraping; too far out lets the door sag. A common source of "door won\'t close all the way" service calls.',
    chatPrompt: 'Tell me about garage door tracks — proper spacing, common bends, and how a misaligned track causes problems.',
    category: 'Door Interior',
    resting: [-1.5, 0, 0],
    exploded: [-2.1, 0, 0],
    rotation: [0, 0, 0],
    color: COLORS.steelDark,
    build: buildTrackGeometry,
    isMesh: false,
  },
  {
    id: 'bottom-seal',
    label: 'Bottom Seal',
    shortLabel: 'Bottom seal',
    checklistCopy: 'Not cracked, ripped, or split.',
    detail: 'A rubber or vinyl strip along the bottom edge of the door. It creates the weather seal against the garage floor, keeps out water, leaves, mice, and drafts. Replacement is a DIY-friendly job — the seal slides into a retainer channel — and is often the difference between a dry garage and a flooded one.',
    chatPrompt: 'Tell me about garage door bottom seals — what they do, how to tell when they are worn, and how to replace one.',
    category: 'Door Interior',
    resting: [0, -0.03, 0.05],
    exploded: [0, -0.5, 0.25],
    rotation: [0, 0, 0],
    color: COLORS.rubber,
    build: buildBottomSealGeometry,
    isMesh: true,
  },
  {
    id: 'opener-carriage',
    label: 'Opener Rail & Carriage',
    shortLabel: 'Opener rail',
    checklistCopy: 'Check for damage. Should run smoothly along rail.',
    detail: 'The long rail running from the motor head to the header bracket, with the carriage sliding along it. The carriage pulls the door up via the J-arm; the belt or chain inside the rail drives the carriage. Most opener failures show up here — a sagging belt, a worn sprocket, or a carriage that chatters along the rail.',
    chatPrompt: 'Tell me about the garage door opener rail and carriage — how the drive system works and what can go wrong with it.',
    category: 'Motor',
    resting: [0, 2.85, 1.5],
    exploded: [0, 3.8, 2.1],
    rotation: [0, 0, 0],
    color: COLORS.opener,
    build: buildOpenerRailGeometry,
    isMesh: false,
  },
];

// Supporting (non-clickable) structural elements
function buildStaticScene() {
  const group = new THREE.Group();

  // Back wall
  const wall = new THREE.Mesh(
    new THREE.BoxGeometry(8, 5, 0.1),
    new THREE.MeshLambertMaterial({ color: COLORS.wall })
  );
  wall.position.set(0, 2.5, -0.2);
  group.add(wall);

  // Ceiling
  const ceiling = new THREE.Mesh(
    new THREE.BoxGeometry(8, 0.1, 4),
    new THREE.MeshLambertMaterial({ color: '#DDD5C6' })
  );
  ceiling.position.set(0, 4.8, 1.5);
  group.add(ceiling);

  // Floor
  const floor = new THREE.Mesh(
    new THREE.BoxGeometry(8, 0.05, 4),
    new THREE.MeshLambertMaterial({ color: '#B8B3A8' })
  );
  floor.position.set(0, -0.15, 1.5);
  group.add(floor);

  // (Header removed per design: keep the top of the opening clean so spring + shaft read clearly)

  // Header bracket (where opener rail attaches)
  const bracket = new THREE.Mesh(
    new THREE.BoxGeometry(0.2, 0.25, 0.1),
    new THREE.MeshLambertMaterial({ color: COLORS.steelDark })
  );
  bracket.position.set(0, 2.9, 0.1);
  group.add(bracket);

  // Torsion shaft (long horizontal rod above door, just above header)
  const shaftY = 2.45;
  const shaft = new THREE.Mesh(
    new THREE.CylinderGeometry(0.025, 0.025, 3.0, 8),
    new THREE.MeshLambertMaterial({ color: COLORS.steel })
  );
  shaft.rotation.z = Math.PI / 2;
  shaft.position.set(0, shaftY, 0.08);
  group.add(shaft);

  // Cable drums at each end of the shaft
  for (const dx of [-1.35, 1.35]) {
    const drum = new THREE.Mesh(
      new THREE.CylinderGeometry(0.09, 0.09, 0.08, 12),
      new THREE.MeshLambertMaterial({ color: COLORS.steelDark })
    );
    drum.rotation.z = Math.PI / 2;
    drum.position.set(dx, shaftY, 0.08);
    group.add(drum);
  }

  // Bearing plates at ends of shaft (mounted to header)
  for (const dx of [-1.45, 1.45]) {
    const plate = new THREE.Mesh(
      new THREE.BoxGeometry(0.08, 0.18, 0.12),
      new THREE.MeshLambertMaterial({ color: COLORS.steelDark })
    );
    plate.position.set(dx, shaftY, 0.05);
    group.add(plate);
  }

  // Hinges between panels (non-clickable decorative)
  const hingePositions = [
    [-1.1, 0.55 + 0.01 / 2, 0.04],
    [0, 0.55 + 0.01 / 2, 0.04],
    [1.1, 0.55 + 0.01 / 2, 0.04],
    [-1.1, 1.11 + 0.01, 0.04],
    [0, 1.11 + 0.01, 0.04],
    [1.1, 1.11 + 0.01, 0.04],
    [-1.1, 1.67 + 0.02, 0.04],
    [0, 1.67 + 0.02, 0.04],
    [1.1, 1.67 + 0.02, 0.04],
    [-1.1, 2.23 + 0.03, 0.04],
    [0, 2.23 + 0.03, 0.04],
    [1.1, 2.23 + 0.03, 0.04],
  ];
  for (const [x, y, z] of hingePositions) {
    const hinge = new THREE.Mesh(
      new THREE.BoxGeometry(0.16, 0.06, 0.015),
      new THREE.MeshLambertMaterial({ color: COLORS.steel })
    );
    hinge.position.set(x, y, z);
    group.add(hinge);
  }

  // Right track mirror (matches buildTrackGeometry at origin 1.5,0,0)
  {
    const RAD = 0.4;
    const vertTop = 2.4;
    const rightTrack = new THREE.Mesh(
      new THREE.BoxGeometry(0.06, vertTop, 0.12),
      new THREE.MeshLambertMaterial({ color: COLORS.steelDark })
    );
    rightTrack.position.set(1.5, vertTop / 2, 0);
    group.add(rightTrack);

    const rCurvePoints = [];
    const rSegs = 16;
    for (let i = 0; i <= rSegs; i++) {
      const a = (i / rSegs) * (Math.PI / 2);
      const y = vertTop + RAD * Math.sin(a);
      const zz = RAD - RAD * Math.cos(a);
      rCurvePoints.push(new THREE.Vector3(1.5, y, zz));
    }
    const rCurvePath = new THREE.CatmullRomCurve3(rCurvePoints);
    const rightCurve = new THREE.Mesh(
      new THREE.TubeGeometry(rCurvePath, 20, 0.05, 6, false),
      new THREE.MeshLambertMaterial({ color: COLORS.steelDark })
    );
    group.add(rightCurve);

    const horizLen = 1.6;
    const rightHoriz = new THREE.Mesh(
      new THREE.BoxGeometry(0.06, 0.12, horizLen),
      new THREE.MeshLambertMaterial({ color: COLORS.steelDark })
    );
    rightHoriz.position.set(1.5, vertTop + RAD, RAD + horizLen / 2);
    group.add(rightHoriz);
  }

  // Right rollers (decorative)
  for (const y of [0.3, 1.0, 1.7, 2.4]) {
    const wheel = new THREE.Mesh(
      new THREE.CylinderGeometry(0.06, 0.06, 0.04, 16),
      new THREE.MeshLambertMaterial({ color: '#EDE7D8' })
    );
    wheel.rotation.z = Math.PI / 2;
    wheel.position.set(1.42, y, 0);
    group.add(wheel);
    const stem = new THREE.Mesh(
      new THREE.CylinderGeometry(0.014, 0.014, 0.12, 8),
      new THREE.MeshLambertMaterial({ color: COLORS.steel })
    );
    stem.rotation.z = Math.PI / 2;
    stem.position.set(1.42 - 0.06, y, 0);
    group.add(stem);
  }

  return group;
}

window.ANATOMY = { COLORS, PARTS, buildStaticScene, buildDoorPanelsGroup };
