import * as THREE from 'three';
import { buildExtendedModel } from './extended-models.ts';

import {
  materialPresets,
  texturePresets,
  type MaterialSettings,
} from './materials.ts';
export {
  materialPresets,
  texturePresets,
  type MaterialSettings,
} from './materials.ts';

// Deterministic, tileable scalar texture. Shared as color modulation and subtle bump.
export function createTexture(
  kind: string,
  size = 256,
): THREE.CanvasTexture | null {
  if (kind === 'none' || typeof document === 'undefined') return null;
  const canvas = document.createElement('canvas');
  canvas.width = canvas.height = size;
  const ctx = canvas.getContext('2d');
  if (!ctx) return null;
  const data = ctx.createImageData(size, size);
  const tau = Math.PI * 2;
  const field = (x: number, y: number) =>
    Math.sin(((tau * x) / size) * 3 + Math.cos(((tau * y) / size) * 2)) * 0.5 +
    Math.sin(((tau * (x + y)) / size) * 7) * 0.25;
  let seed = 621;
  const random = () => {
    seed = (seed * 1664525 + 1013904223) >>> 0;
    return seed / 4294967296;
  };
  for (let y = 0; y < size; y++)
    for (let x = 0; x < size; x++) {
      const n = random();
      let v = 230;
      if (kind === 'brushed')
        v = 205 + 27 * Math.sin(((tau * y) / size) * 92) + n * 15;
      if (kind === 'frost') v = 210 + n * 45;
      if (kind === 'grain') v = 185 + n * 70;
      if (kind === 'marble')
        v = 224 + 25 * Math.sin(((tau * x) / size) * 4 + field(x, y) * 4);
      if (kind === 'wood')
        v = 170 + 60 * Math.sin(((tau * y) / size) * 12 + field(x, y) * 3);
      if (kind === 'concrete') v = 195 + field(x, y) * 32 + n * 28;
      if (kind === 'carbon')
        v =
          ((Math.floor(x / 16) + Math.floor(y / 16)) % 2 ? 125 : 200) +
          Math.sin(((tau * (x + y)) / size) * 64) * 18;
      const i = (y * size + x) * 4;
      data.data[i] =
        data.data[i + 1] =
        data.data[i + 2] =
          Math.max(0, Math.min(255, v));
      data.data[i + 3] = 255;
    }
  ctx.putImageData(data, 0, 0);
  const tex = new THREE.CanvasTexture(canvas);
  tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
  tex.repeat.set(2, 2);
  tex.colorSpace = THREE.SRGBColorSpace;
  return tex;
}
export function makeMaterial(settings: MaterialSettings) {
  const map = createTexture(settings.texture);
  const bump = map?.clone();
  if (bump) {
    bump.colorSpace = THREE.NoColorSpace;
    bump.needsUpdate = true;
  }
  return new THREE.MeshPhysicalMaterial({
    color: settings.color,
    metalness: settings.metalness,
    roughness: settings.roughness,
    transmission: settings.transmission,
    ior: settings.ior,
    thickness: settings.thickness,
    clearcoat: 0.35,
    clearcoatRoughness: 0.15,
    envMapIntensity: 1.4,
    map,
    bumpMap: bump ?? null,
    bumpScale: settings.texture === 'brushed' ? 0.012 : 0.045,
    wireframe: settings.wireframe ?? false,
    iridescence: settings.iridescence ?? 0,
    iridescenceIOR: 1.3,
    side: THREE.DoubleSide,
  });
}
export function buildObject(
  kind: string,
  material: THREE.Material,
): THREE.Group {
  const g = new THREE.Group();
  g.name = kind;
  const mesh = (
    geo: THREE.BufferGeometry,
    p = [0, 0, 0],
    r = [0, 0, 0],
    sc = [1, 1, 1],
  ) => {
    const m = new THREE.Mesh(geo, material);
    m.position.set(...(p as [number, number, number]));
    m.rotation.set(...(r as [number, number, number]));
    m.scale.set(...(sc as [number, number, number]));
    m.castShadow = true;
    m.receiveShadow = true;
    g.add(m);
    return m;
  };
  const box = (a: number, b: number, c: number, p: number[], r = [0, 0, 0]) =>
    mesh(new THREE.BoxGeometry(a, b, c), p, r);
  const sphere = (rad: number, p: number[], scale = [1, 1, 1]) =>
    mesh(new THREE.SphereGeometry(rad, 40, 24), p, [0, 0, 0], scale);
  const cyl = (
    a: number,
    b: number,
    h: number,
    p: number[],
    r = [0, 0, 0],
    segments = 48,
  ) => mesh(new THREE.CylinderGeometry(a, b, h, segments), p, r);
  const torus = (
    rad: number,
    tube: number,
    p: number[],
    r = [0, 0, 0],
    arc = Math.PI * 2,
  ) => mesh(new THREE.TorusGeometry(rad, tube, 16, 80, arc), p, r);
  if (kind === 'knot')
    mesh(new THREE.TorusKnotGeometry(0.85, 0.25, 180, 28, 2, 3));
  else if (kind === 'bottle') {
    const points = [
      [0.01, -1],
      [0.51, -1],
      [0.57, -0.91],
      [0.57, 0.37],
      [0.5, 0.52],
      [0.24, 0.66],
      [0.24, 0.82],
      [0.01, 0.82],
    ].map(([x, y]) => new THREE.Vector2(x, y));
    mesh(new THREE.LatheGeometry(points, 64));
    cyl(0.3, 0.3, 0.28, [0, 0.95, 0]);
    for (let i = 0; i < 28; i++) {
      const a = (i / 28) * Math.PI * 2;
      box(
        0.012,
        0.23,
        0.028,
        [Math.sin(a) * 0.301, 0.95, Math.cos(a) * 0.301],
        [0, a, 0],
      );
    }
  } else if (kind === 'ring') {
    torus(0.78, 0.14, [0, 0, 0]);
    mesh(new THREE.OctahedronGeometry(0.37, 0), [0, 0.94, 0]);
    for (let i = 0; i < 4; i++)
      cyl(0.024, 0.024, 0.28, [
        (i % 2 ? 1 : -1) * 0.17,
        0.88,
        (i < 2 ? 1 : -1) * 0.16,
      ]);
  } else if (kind === 'chip') {
    box(1.5, 0.2, 1.5, [0, 0, 0]);
    box(0.94, 0.18, 0.94, [0, 0.19, 0]);
    for (let side = 0; side < 4; side++)
      for (let i = 0; i < 12; i++) {
        const a = (side * Math.PI) / 2;
        const x = (i - 5.5) * 0.105;
        box(
          0.045,
          0.065,
          0.29,
          [
            Math.cos(a) * x + Math.sin(a) * 0.85,
            -0.03,
            Math.cos(a) * 0.85 - Math.sin(a) * x,
          ],
          [0, a, 0],
        );
      }
    for (let i = 0; i < 5; i++)
      box(0.58, 0.013, 0.028, [0, 0.29, (i - 2) * 0.09]);
    g.rotation.x = 0.4;
  } else if (kind === 'tower') {
    box(0.75, 2.3, 0.7, [-0.24, 0, 0]);
    box(0.61, 1.5, 0.7, [0.48, -0.4, 0.2]);
    box(1.6, 0.1, 1.35, [0, -1.2, 0]);
    for (let i = 0; i < 15; i++)
      box(0.81, 0.018, 0.76, [-0.24, -1 + i * 0.15, 0]);
    for (let i = 0; i < 5; i++)
      box(0.015, 2.2, 0.76, [-0.57 + i * 0.165, 0, 0]);
  } else if (kind === 'dumbbell') {
    cyl(0.12, 0.12, 1.5, [0, 0, 0], [0, 0, Math.PI / 2]);
    for (const x of [-0.85, 0.85]) {
      cyl(0.48, 0.48, 0.38, [x, 0, 0], [0, 0, Math.PI / 2], 12);
      cyl(0.34, 0.34, 0.12, [x * 1.28, 0, 0], [0, 0, Math.PI / 2], 12);
    }
    for (let i = 0; i < 20; i++)
      torus(0.122, 0.01, [(i - 9.5) * 0.035, 0, 0], [0, Math.PI / 2, 0]);
    g.rotation.z = 0.45;
  } else if (kind === 'mug') {
    mesh(
      new THREE.LatheGeometry(
        [
          [0, -0.68],
          [0.54, -0.68],
          [0.61, -0.58],
          [0.66, 0.65],
          [0.59, 0.65],
          [0.55, -0.53],
          [0, -0.53],
        ].map(([x, y]) => new THREE.Vector2(x, y)),
        64,
      ),
    );
    torus(0.4, 0.105, [0.63, 0, 0], [0, 0, -Math.PI / 2], Math.PI);
  } else if (kind === 'orb') {
    sphere(0.5, [0, 0, 0]);
    for (let i = 0; i < 3; i++) {
      const a = (i * Math.PI) / 3;
      torus(1, 0.028, [0, 0, 0], [a, a * 0.7, 0]);
      sphere(0.11, [Math.cos(a) * 1, Math.sin(a) * 1, 0]);
    }
  } else if (kind === 'bird') {
    // Smooth body and sculpted swept wings. All parts inherit the selected material.
    sphere(0.5, [0, 0, 0], [0.65, 0.8, 1.38]);
    sphere(0.27, [0, 0.4, 0.53], [0.9, 1, 1]);
    mesh(
      new THREE.ConeGeometry(0.115, 0.37, 32),
      [0, 0.39, 0.87],
      [Math.PI / 2, 0, 0],
    );
    for (const sign of [-1, 1]) {
      const pos: number[] = [];
      const idx: number[] = [];
      const steps = 32,
        rows = 14;
      for (let u = 0; u <= steps; u++) {
        const t = u / steps;
        const span = 0.18 + t * 1.65;
        const chord = (1 - t) * 0.88 + 0.08;
        for (let v = 0; v <= rows; v++) {
          const q = v / rows;
          pos.push(
            sign * span,
            0.08 +
              Math.sin(t * Math.PI * 0.76) * 0.77 +
              Math.sin(q * Math.PI) * 0.085,
            0.1 - t * 0.84 + (q - 0.5) * chord,
          );
        }
      }
      for (let u = 0; u < steps; u++)
        for (let v = 0; v < rows; v++) {
          const a = u * (rows + 1) + v,
            b = a + rows + 1;
          idx.push(a, b, a + 1, b, b + 1, a + 1);
        }
      const geo = new THREE.BufferGeometry();
      geo.setAttribute('position', new THREE.Float32BufferAttribute(pos, 3));
      geo.setIndex(idx);
      geo.computeVertexNormals();
      mesh(geo);
      for (let f = 0; f < 5; f++) {
        const t = 0.35 + f * 0.105;
        const curve = new THREE.CatmullRomCurve3([
          new THREE.Vector3(sign * 0.27, 0.12, -0.02),
          new THREE.Vector3(sign * (0.4 + t), 0.49, -0.35),
          new THREE.Vector3(sign * (0.8 + t), 0.64, -0.65 - f * 0.05),
        ]);
        mesh(new THREE.TubeGeometry(curve, 22, 0.025, 8, false));
      }
    }
    for (let i = -1; i <= 1; i++)
      mesh(
        new THREE.ConeGeometry(0.16, 0.72, 24),
        [i * 0.14, -0.06, -0.83],
        [-Math.PI / 2, 0, i * 0.12],
      );
    g.rotation.y = -0.5;
  } else if (kind === 'wrench') {
    const shape = new THREE.Shape();
    shape.moveTo(-0.16, -0.75);
    shape.lineTo(-0.18, 0.5);
    shape.lineTo(-0.43, 0.77);
    shape.lineTo(-0.39, 1.12);
    shape.lineTo(-0.19, 1.28);
    shape.lineTo(-0.18, 0.91);
    shape.lineTo(0.17, 0.83);
    shape.lineTo(0.34, 1.13);
    shape.lineTo(0.48, 0.87);
    shape.lineTo(0.37, 0.58);
    shape.lineTo(0.16, 0.43);
    shape.lineTo(0.16, -0.75);
    shape.absarc(0, -0.89, 0.3, Math.PI / 6, (Math.PI * 5) / 6, true);
    shape.closePath();
    const hole = new THREE.Path();
    hole.absarc(0, -0.89, 0.15, 0, Math.PI * 2, true);
    shape.holes.push(hole);
    const geo = new THREE.ExtrudeGeometry(shape, {
      depth: 0.16,
      bevelEnabled: true,
      bevelSegments: 3,
      steps: 1,
      bevelSize: 0.045,
      bevelThickness: 0.045,
      curveSegments: 32,
    });
    geo.translate(0, 0, -0.08);
    mesh(geo);
    box(0.1, 0.76, 0.015, [0, -0.03, 0.14]);
    g.rotation.z = -0.5;
  } else if (kind === 'bolt') {
    cyl(0.45, 0.45, 0.25, [0, 0.8, 0], [0, 0, 0], 6);
    cyl(0.22, 0.22, 1.42, [0, 0, 0]);
    const points = [];
    for (let i = 0; i <= 650; i++) {
      const t = i / 650;
      points.push(
        new THREE.Vector3(
          Math.cos(t * Math.PI * 26) * 0.235,
          -0.68 + t * 1.35,
          Math.sin(t * Math.PI * 26) * 0.235,
        ),
      );
    }
    mesh(
      new THREE.TubeGeometry(
        new THREE.CatmullRomCurve3(points),
        650,
        0.035,
        6,
        false,
      ),
    );
    g.rotation.z = -0.48;
  } else if (kind === 'vase') {
    const pts = [];
    for (let i = 0; i <= 48; i++) {
      const t = i / 48;
      pts.push(
        new THREE.Vector2(
          0.24 + Math.sin(t * Math.PI) * 0.4 + Math.sin(t * Math.PI * 2) * 0.1,
          t * 2 - 1,
        ),
      );
    }
    for (let i = 48; i >= 0; i--) {
      const t = i / 48;
      pts.push(
        new THREE.Vector2(
          Math.max(
            0.05,
            0.18 +
              Math.sin(t * Math.PI) * 0.4 +
              Math.sin(t * Math.PI * 2) * 0.1,
          ),
          Math.max(-0.92, t * 2 - 1),
        ),
      );
    }
    mesh(new THREE.LatheGeometry(pts, 72));
  } else if (kind === 'gem') mesh(new THREE.IcosahedronGeometry(1, 0));
  else if (kind === 'helix') {
    for (const offset of [0, Math.PI]) {
      const pts = [];
      for (let i = 0; i <= 120; i++) {
        const t = i / 120;
        pts.push(
          new THREE.Vector3(
            Math.cos(t * Math.PI * 4 + offset) * 0.58,
            t * 2.3 - 1.15,
            Math.sin(t * Math.PI * 4 + offset) * 0.58,
          ),
        );
      }
      mesh(
        new THREE.TubeGeometry(
          new THREE.CatmullRomCurve3(pts),
          140,
          0.075,
          12,
          false,
        ),
      );
    }
    for (let i = 0; i < 18; i++) {
      const t = i / 17,
        a = t * Math.PI * 4;
      const c = cyl(
        0.026,
        0.026,
        1.16,
        [0, t * 2.3 - 1.15, 0],
        [0, 0, Math.PI / 2],
      );
      c.rotation.y = -a;
    }
  } else if (kind === 'speaker') {
    box(1.25, 1.8, 0.65, [0, 0, 0]);
    for (const y of [-0.3, 0.52]) {
      const r = y < 0 ? 0.42 : 0.21;
      torus(r, 0.038, [0, y, 0.36]);
      mesh(
        new THREE.ConeGeometry(r * 0.86, 0.16, 48),
        [0, y, 0.38],
        [Math.PI / 2, 0, 0],
      );
      sphere(r * 0.3, [0, y, 0.42], [1, 1, 0.5]);
    }
  } else if (kind === 'chair') {
    box(1.15, 0.13, 1, [0, -0.15, 0]);
    box(1.15, 1.05, 0.1, [0, 0.42, -0.43], [0.12, 0, 0]);
    for (const x of [-0.46, 0.46])
      for (const z of [-0.36, 0.36])
        cyl(0.045, 0.035, 0.82, [x, -0.61, z], [z * 0.1, 0, -x * 0.1]);
  } else {
    const extended = buildExtendedModel(kind, material);
    if (extended) g.add(extended);
    else mesh(new THREE.IcosahedronGeometry(0.9, 2));
  }
  const bounds = new THREE.Box3().setFromObject(g);
  const center = bounds.getCenter(new THREE.Vector3());
  g.position.sub(center);
  const size = bounds.getSize(new THREE.Vector3());
  const scale = 2.65 / Math.max(size.x, size.y, size.z);
  const wrap = new THREE.Group();
  wrap.add(g);
  wrap.scale.setScalar(scale);
  return wrap;
}
export function disposeObject(group: THREE.Object3D) {
  const mats = new Set<THREE.Material>();
  const maps = new Set<THREE.Texture>();
  group.traverse((o) => {
    if (o instanceof THREE.Mesh) {
      o.geometry.dispose();
      for (const m of Array.isArray(o.material) ? o.material : [o.material])
        mats.add(m);
    }
  });
  mats.forEach((m) => {
    for (const value of Object.values(m))
      if (value instanceof THREE.Texture) maps.add(value);
    m.dispose();
  });
  maps.forEach((t) => t.dispose());
}
