import * as THREE from 'three';

export function roundedRect(w: number, h: number, r: number): THREE.Shape {
  const hw = w / 2;
  const hh = h / 2;
  const s = new THREE.Shape();
  s.moveTo(-hw + r, -hh);
  s.lineTo(hw - r, -hh);
  s.quadraticCurveTo(hw, -hh, hw, -hh + r);
  s.lineTo(hw, hh - r);
  s.quadraticCurveTo(hw, hh, hw - r, hh);
  s.lineTo(-hw + r, hh);
  s.quadraticCurveTo(-hw, hh, -hw, hh - r);
  s.lineTo(-hw, -hh + r);
  s.quadraticCurveTo(-hw, -hh, -hw + r, -hh);
  s.closePath();
  return s;
}

const CREAM = 0xefdfbc;
const CHROME = 0xc9ced6;
const DARK = 0x0d0b0a;

export interface Toaster {
  root: THREE.Group;
  lever: THREE.Group;
  dial: THREE.Group;
  /** World position of the slot mouth. */
  slotY: number;
  slotZ: number;
  /** Glowing elements inside; intensity 0..1. */
  setGlow(v: number): void;
}

/**
 * A retro two-slot toaster. Procedural for now: the generated GLB is prettier
 * but this one has a lever we can actually drive, and the shape is the hero of
 * the toasting scene either way.
 */
export function makeToaster(): Toaster {
  const root = new THREE.Group();

  const bodyMat = new THREE.MeshStandardMaterial({ color: CREAM, roughness: 0.62, metalness: 0.06 });
  const chromeMat = new THREE.MeshStandardMaterial({ color: CHROME, roughness: 0.22, metalness: 0.9 });
  const darkMat = new THREE.MeshStandardMaterial({ color: DARK, roughness: 0.95 });

  // A slice of bread is ~11cm and this game measures in slices: 1 unit ~ 11cm.
  // So a 28x18x15cm toaster is this big, and it dwarfs the bread. It should.
  const W = 2.52;
  const H = 1.62;
  const D = 1.36;
  const body = new THREE.Mesh(
    new THREE.ExtrudeGeometry(roundedRect(W, H, 0.42), {
      depth: D,
      bevelEnabled: true,
      bevelThickness: 0.05,
      bevelSize: 0.05,
      bevelSegments: 3,
      curveSegments: 20,
    }),
    bodyMat,
  );
  body.geometry.center();
  body.position.y = H / 2 + 0.04;
  body.castShadow = true;
  body.receiveShadow = true;
  root.add(body);

  // chrome cap with the slot cut into it
  const cap = new THREE.Mesh(new THREE.BoxGeometry(W * 0.84, 0.07, D * 0.88), chromeMat);
  cap.position.set(0, H + 0.04, 0);
  cap.castShadow = true;
  root.add(cap);

  const slot = new THREE.Mesh(new THREE.BoxGeometry(W * 0.52, 0.2, 0.3), darkMat);
  slot.position.set(0, H + 0.02, 0);
  root.add(slot);

  // elements: a few vertical bars either side of the slot, they glow when hot
  const glowMat = new THREE.MeshStandardMaterial({
    color: 0x3a1508,
    emissive: new THREE.Color(0xff3a08),
    emissiveIntensity: 0,
    roughness: 0.7,
  });
  for (let i = 0; i < 5; i++) {
    const x = (i / 4 - 0.5) * W * 0.46;
    for (const z of [-0.15, 0.15]) {
      const bar = new THREE.Mesh(new THREE.BoxGeometry(0.05, 0.9, 0.028), glowMat);
      bar.position.set(x, H - 0.52, z);
      root.add(bar);
    }
  }

  // feet
  const footMat = new THREE.MeshStandardMaterial({ color: 0x1a1a1a, roughness: 0.9 });
  for (const sx of [-1, 1]) {
    for (const sz of [-1, 1]) {
      const foot = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.11, 0.09, 12), footMat);
      foot.position.set(sx * W * 0.36, 0.045, sz * D * 0.3);
      root.add(foot);
    }
  }

  // browning dial on the right cheek
  const dial = new THREE.Group();
  const knob = new THREE.Mesh(new THREE.CylinderGeometry(0.19, 0.19, 0.09, 24), chromeMat);
  knob.rotation.z = Math.PI / 2;
  knob.castShadow = true;
  dial.add(knob);
  const pointer = new THREE.Mesh(new THREE.BoxGeometry(0.035, 0.16, 0.05), darkMat);
  pointer.position.set(0.05, 0.08, 0);
  dial.add(pointer);
  dial.position.set(W / 2 + 0.015, H * 0.42, 0.34);
  root.add(dial);

  // lever on a track
  const lever = new THREE.Group();
  const stem = new THREE.Mesh(new THREE.BoxGeometry(0.11, 0.11, 0.26), chromeMat);
  stem.position.z = 0.12;
  lever.add(stem);
  const grip = new THREE.Mesh(new THREE.BoxGeometry(0.18, 0.34, 0.13), bodyMat);
  grip.position.z = 0.3;
  grip.castShadow = true;
  lever.add(grip);
  lever.position.set(-W / 2 + 0.05, H * 0.72, D / 2 - 0.05);
  root.add(lever);

  const track = new THREE.Mesh(new THREE.BoxGeometry(0.05, 0.82, 0.07), darkMat);
  track.position.set(-W / 2 + 0.012, H * 0.55, D / 2 + 0.05);
  root.add(track);

  return {
    root,
    lever,
    dial,
    slotY: H + 0.04,
    slotZ: 0,
    setGlow(v: number) {
      glowMat.emissiveIntensity = v * 3.2;
      glowMat.color.setRGB(0.23 + v * 0.3, 0.08, 0.03);
    },
  };
}

/**
 * Built from primitives rather than a lathe: a lathe profile that touches the
 * axis collapses a whole ring of vertices onto one point, and the degenerate
 * triangles there render as a starburst of garbage normals.
 */
export function makePlate(): THREE.Group {
  const g = new THREE.Group();
  const mat = new THREE.MeshStandardMaterial({ color: 0xf4f2ec, roughness: 0.3, metalness: 0.02 });
  const well = new THREE.Mesh(new THREE.CylinderGeometry(0.84, 0.74, 0.045, 56), mat);
  well.position.y = 0.022;
  well.castShadow = true;
  well.receiveShadow = true;
  g.add(well);
  const rim = new THREE.Mesh(new THREE.TorusGeometry(0.84, 0.036, 10, 56), mat);
  rim.rotation.x = Math.PI / 2;
  rim.position.y = 0.05;
  rim.castShadow = true;
  rim.receiveShadow = true;
  g.add(rim);
  return g;
}

export function makeBench(): THREE.Group {
  const g = new THREE.Group();
  const loader = new THREE.TextureLoader();

  const wood = loader.load('/assets/img/bench_wood.png');
  wood.wrapS = wood.wrapT = THREE.RepeatWrapping;
  wood.repeat.set(4, 2);
  // The texture was generated as artwork, i.e. sRGB. Saying so is what keeps it
  // from washing out once it's lit.
  wood.colorSpace = THREE.SRGBColorSpace;
  wood.anisotropy = 8;
  const top = new THREE.Mesh(
    new THREE.BoxGeometry(20, 0.5, 9),
    new THREE.MeshStandardMaterial({ map: wood, color: 0xbb9068, roughness: 0.68 }),
  );
  top.position.y = -0.25;
  top.receiveShadow = true;
  g.add(top);

  // Splashback: a blurred kitchen so the scene has a horizon instead of fog.
  const back = loader.load('/assets/img/kitchen_backdrop.png');
  back.colorSpace = THREE.SRGBColorSpace;
  const wall = new THREE.Mesh(
    new THREE.PlaneGeometry(22, 8),
    new THREE.MeshBasicMaterial({ map: back, color: 0x6a6258 }),
  );
  wall.position.set(0, 3.2, -4.4);
  g.add(wall);
  return g;
}
