import * as THREE from "three";
import type RAPIER from "@dimforge/rapier3d-compat";

export const ORIGIN = {
  latitude: 55.899338,
  longitude: 37.409289,
  label: "Новые Химки · ТЦ «Новые Химки»",
};

export type BuiltWorld = {
  cameraOccluders: THREE.Object3D[];
  traffic: THREE.Group[];
};

type BuildingSpec = {
  x: number;
  z: number;
  width: number;
  depth: number;
  height: number;
  color: string;
  windows: string;
  rows: number;
  columns: number;
  rotation?: number;
};

function canvasTexture(width: number, height: number, draw: (ctx: CanvasRenderingContext2D) => void) {
  const canvas = document.createElement("canvas");
  canvas.width = width;
  canvas.height = height;
  const ctx = canvas.getContext("2d");
  if (!ctx) throw new Error("Canvas 2D недоступен");
  draw(ctx);
  const texture = new THREE.CanvasTexture(canvas);
  texture.colorSpace = THREE.SRGBColorSpace;
  texture.anisotropy = 4;
  return texture;
}

function facadeTexture(base: string, windowColor: string, columns: number, rows: number) {
  return canvasTexture(512, 512, (ctx) => {
    ctx.fillStyle = base;
    ctx.fillRect(0, 0, 512, 512);
    const cellW = 512 / columns;
    const cellH = 512 / rows;
    for (let row = 0; row < rows; row += 1) {
      for (let col = 0; col < columns; col += 1) {
        const lit = ((row * 19 + col * 31) % 11) < 2;
        ctx.fillStyle = lit ? "#d8c994" : windowColor;
        ctx.fillRect(col * cellW + cellW * 0.22, row * cellH + cellH * 0.2, cellW * 0.56, cellH * 0.48);
        ctx.fillStyle = "rgba(255,255,255,.18)";
        ctx.fillRect(col * cellW + cellW * 0.25, row * cellH + cellH * 0.22, cellW * 0.06, cellH * 0.42);
      }
    }
    ctx.fillStyle = "rgba(20,25,31,.16)";
    for (let row = 1; row < rows; row += 1) ctx.fillRect(0, row * cellH - 2, 512, 4);
  });
}

function asphaltTexture() {
  let seed = 413;
  const random = () => {
    seed = (seed * 16807) % 2147483647;
    return seed / 2147483647;
  };
  const texture = canvasTexture(512, 512, (ctx) => {
    ctx.fillStyle = "#30343a";
    ctx.fillRect(0, 0, 512, 512);
    for (let i = 0; i < 9000; i += 1) {
      const c = 38 + Math.floor(random() * 36);
      ctx.fillStyle = `rgba(${c},${c},${c},${0.08 + random() * 0.16})`;
      const size = random() * 2.2 + 0.35;
      ctx.fillRect(random() * 512, random() * 512, size, size);
    }
    ctx.strokeStyle = "rgba(16,18,21,.25)";
    ctx.lineWidth = 2;
    for (let i = 0; i < 18; i += 1) {
      ctx.beginPath();
      ctx.moveTo(random() * 512, random() * 512);
      ctx.bezierCurveTo(random() * 512, random() * 512, random() * 512, random() * 512, random() * 512, random() * 512);
      ctx.stroke();
    }
  });
  texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
  texture.repeat.set(18, 18);
  return texture;
}

function addStaticBox(
  physics: RAPIER.World,
  R: typeof RAPIER,
  x: number,
  y: number,
  z: number,
  width: number,
  height: number,
  depth: number,
  rotation = 0,
) {
  const body = physics.createRigidBody(
    R.RigidBodyDesc.fixed().setTranslation(x, y, z).setRotation({
      x: 0,
      y: Math.sin(rotation / 2),
      z: 0,
      w: Math.cos(rotation / 2),
    }),
  );
  physics.createCollider(R.ColliderDesc.cuboid(width / 2, height / 2, depth / 2), body);
}

function addBuilding(
  scene: THREE.Scene,
  physics: RAPIER.World,
  R: typeof RAPIER,
  spec: BuildingSpec,
  occluders: THREE.Object3D[],
) {
  const texture = facadeTexture(spec.color, spec.windows, spec.columns, spec.rows);
  texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
  texture.repeat.set(Math.max(1, spec.width / 22), Math.max(1, spec.height / 28));
  const material = new THREE.MeshStandardMaterial({ map: texture, roughness: 0.7, metalness: 0.04 });
  const mesh = new THREE.Mesh(new THREE.BoxGeometry(spec.width, spec.height, spec.depth), material);
  mesh.position.set(spec.x, spec.height / 2, spec.z);
  mesh.rotation.y = spec.rotation ?? 0;
  mesh.castShadow = true;
  mesh.receiveShadow = true;
  scene.add(mesh);
  occluders.push(mesh);
  addStaticBox(physics, R, spec.x, spec.height / 2, spec.z, spec.width, spec.height, spec.depth, spec.rotation);

  const roof = new THREE.Mesh(
    new THREE.BoxGeometry(spec.width * 0.23, 2.1, spec.depth * 0.34),
    new THREE.MeshStandardMaterial({ color: "#858a8f", roughness: 0.9 }),
  );
  roof.position.set(spec.x, spec.height + 1.05, spec.z);
  roof.rotation.y = spec.rotation ?? 0;
  roof.castShadow = true;
  scene.add(roof);
}

function addMall(
  scene: THREE.Scene,
  physics: RAPIER.World,
  R: typeof RAPIER,
  occluders: THREE.Object3D[],
) {
  const mall = new THREE.Group();
  mall.position.set(0, 0, -69);

  const red = new THREE.MeshStandardMaterial({ color: "#a71920", roughness: 0.52, metalness: 0.08 });
  const darkRed = new THREE.MeshStandardMaterial({ color: "#711016", roughness: 0.7 });
  const glassTexture = canvasTexture(512, 256, (ctx) => {
    const gradient = ctx.createLinearGradient(0, 0, 0, 256);
    gradient.addColorStop(0, "#648394");
    gradient.addColorStop(1, "#18252d");
    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, 512, 256);
    ctx.strokeStyle = "rgba(210,230,236,.46)";
    ctx.lineWidth = 4;
    for (let x = 0; x <= 512; x += 48) {
      ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, 256); ctx.stroke();
    }
    for (let y = 0; y <= 256; y += 52) {
      ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(512, y); ctx.stroke();
    }
  });

  const body = new THREE.Mesh(new THREE.BoxGeometry(68, 13, 28), red);
  body.position.y = 6.5;
  body.castShadow = body.receiveShadow = true;
  mall.add(body);

  const frame = new THREE.Mesh(new THREE.BoxGeometry(53, 10.5, 1.25), darkRed);
  frame.position.set(0, 6, 14.15);
  mall.add(frame);
  const glass = new THREE.Mesh(
    new THREE.PlaneGeometry(48, 7.5),
    new THREE.MeshStandardMaterial({ map: glassTexture, roughness: 0.16, metalness: 0.2 }),
  );
  glass.position.set(0, 5.6, 14.82);
  mall.add(glass);

  const entrance = new THREE.Mesh(
    new THREE.BoxGeometry(9, 5.4, 1.55),
    new THREE.MeshStandardMaterial({ color: "#7693a0", roughness: 0.15, metalness: 0.28 }),
  );
  entrance.position.set(0, 2.7, 15.1);
  mall.add(entrance);

  const signTexture = canvasTexture(1024, 160, (ctx) => {
    ctx.clearRect(0, 0, 1024, 160);
    ctx.font = "900 86px Arial, sans-serif";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.fillStyle = "#f1e5d2";
    ctx.shadowColor = "rgba(0,0,0,.55)";
    ctx.shadowBlur = 7;
    ctx.fillText("НОВЫЕ ХИМКИ", 512, 72);
    ctx.font = "700 26px Arial, sans-serif";
    ctx.letterSpacing = "8px";
    ctx.fillText("ТОРГОВЫЙ ЦЕНТР", 512, 133);
  });
  const sign = new THREE.Mesh(
    new THREE.PlaneGeometry(49, 7.7),
    new THREE.MeshBasicMaterial({ map: signTexture, transparent: true, depthWrite: false }),
  );
  sign.position.set(0, 13.65, 14.9);
  mall.add(sign);

  const shopSign = (text: string, color: string, x: number) => {
    const tex = canvasTexture(256, 128, (ctx) => {
      ctx.fillStyle = color; ctx.fillRect(0, 0, 256, 128);
      ctx.fillStyle = "white"; ctx.font = "900 54px Arial";
      ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(text, 128, 64);
    });
    const panel = new THREE.Mesh(new THREE.PlaneGeometry(5.2, 2.6), new THREE.MeshBasicMaterial({ map: tex }));
    panel.position.set(x, 6.1, 14.87);
    mall.add(panel);
  };
  shopSign("24", "#af171f", 26.5);
  shopSign("KFC", "#b70d19", 20.8);

  scene.add(mall);
  occluders.push(body);
  addStaticBox(physics, R, 0, 6.5, -69, 68, 13, 28);
}

function addRoads(scene: THREE.Scene) {
  const asphalt = new THREE.MeshStandardMaterial({ map: asphaltTexture(), roughness: 0.96, metalness: 0.01 });
  const roadA = new THREE.Mesh(new THREE.PlaneGeometry(34, 520), asphalt);
  roadA.rotation.x = -Math.PI / 2;
  roadA.position.y = 0.015;
  roadA.receiveShadow = true;
  scene.add(roadA);

  const roadB = new THREE.Mesh(new THREE.PlaneGeometry(520, 36), asphalt.clone());
  roadB.rotation.x = -Math.PI / 2;
  roadB.position.set(0, 0.018, 5);
  roadB.receiveShadow = true;
  scene.add(roadB);

  const ring = new THREE.Mesh(new THREE.RingGeometry(17, 39, 96), asphalt.clone());
  ring.rotation.x = -Math.PI / 2;
  ring.position.set(0, 0.035, 5);
  ring.receiveShadow = true;
  scene.add(ring);

  const island = new THREE.Mesh(
    new THREE.CylinderGeometry(16.3, 16.8, 0.36, 80),
    new THREE.MeshStandardMaterial({ color: "#61894b", roughness: 1 }),
  );
  island.position.set(0, 0.18, 5);
  island.receiveShadow = true;
  scene.add(island);

  const curbMaterial = new THREE.MeshStandardMaterial({ color: "#d7d5ce", roughness: 0.95 });
  const curb = new THREE.Mesh(new THREE.TorusGeometry(16.85, 0.33, 8, 80), curbMaterial);
  curb.rotation.x = Math.PI / 2;
  curb.position.set(0, 0.26, 5);
  curb.receiveShadow = true;
  scene.add(curb);

  const markingMaterial = new THREE.MeshBasicMaterial({ color: "#ecece5" });
  const dashGeometry = new THREE.BoxGeometry(0.16, 0.025, 3.3);
  for (const x of [-5.2, 5.2]) {
    for (let z = -250; z < 250; z += 8.5) {
      if (Math.abs(z - 5) < 47) continue;
      const dash = new THREE.Mesh(dashGeometry, markingMaterial);
      dash.position.set(x, 0.055, z);
      scene.add(dash);
    }
  }
  const lateralDash = new THREE.BoxGeometry(3.3, 0.025, 0.16);
  for (const z of [-0.1, 10.1]) {
    for (let x = -250; x < 250; x += 8.5) {
      if (Math.abs(x) < 46) continue;
      const dash = new THREE.Mesh(lateralDash, markingMaterial);
      dash.position.set(x, 0.058, z);
      scene.add(dash);
    }
  }
}

function addTrees(scene: THREE.Scene) {
  const positions: Array<[number, number]> = [];
  for (let i = -9; i <= 9; i += 1) {
    positions.push([-28 - (i % 2) * 3, i * 18]);
    positions.push([29 + (i % 2) * 3, i * 18 + 7]);
  }
  positions.push([-45, -48], [-51, -62], [43, -51], [51, -85], [-11, 5], [10, 7]);
  const trunkGeometry = new THREE.CylinderGeometry(0.32, 0.45, 4.1, 7);
  const crownGeometry = new THREE.IcosahedronGeometry(2.6, 1);
  const trunks = new THREE.InstancedMesh(trunkGeometry, new THREE.MeshStandardMaterial({ color: "#614630", roughness: 1 }), positions.length);
  const crowns = new THREE.InstancedMesh(crownGeometry, new THREE.MeshStandardMaterial({ color: "#3d713e", roughness: 1 }), positions.length);
  const matrix = new THREE.Matrix4();
  positions.forEach(([x, z], index) => {
    matrix.makeTranslation(x, 2.05, z); trunks.setMatrixAt(index, matrix);
    const scale = 0.82 + (index % 5) * 0.06;
    matrix.compose(new THREE.Vector3(x, 5.05, z), new THREE.Quaternion(), new THREE.Vector3(scale, scale * 1.1, scale));
    crowns.setMatrixAt(index, matrix);
  });
  trunks.castShadow = crowns.castShadow = true;
  trunks.receiveShadow = crowns.receiveShadow = true;
  scene.add(trunks, crowns);
}

function addStreetFurniture(scene: THREE.Scene) {
  const lampMaterial = new THREE.MeshStandardMaterial({ color: "#323941", roughness: 0.55, metalness: 0.65 });
  const bulbMaterial = new THREE.MeshStandardMaterial({ color: "#fff3c7", emissive: "#c99e4a", emissiveIntensity: 0.65 });
  for (let i = -6; i <= 6; i += 1) {
    for (const x of [-23, 23]) {
      const z = i * 29 + 2;
      const pole = new THREE.Mesh(new THREE.CylinderGeometry(0.11, 0.16, 7.2, 8), lampMaterial);
      pole.position.set(x, 3.6, z);
      const lamp = new THREE.Mesh(new THREE.SphereGeometry(0.34, 12, 8), bulbMaterial);
      lamp.position.set(x, 7.15, z);
      scene.add(pole, lamp);
    }
  }

  const guardMaterial = new THREE.MeshStandardMaterial({ color: "#7c8589", roughness: 0.42, metalness: 0.76 });
  for (let i = -7; i <= 7; i += 1) {
    const rail = new THREE.Mesh(new THREE.BoxGeometry(4.8, 0.09, 0.09), guardMaterial);
    rail.position.set(i * 5, 0.78, -25.5);
    const post = new THREE.Mesh(new THREE.CylinderGeometry(0.06, 0.06, 0.85, 6), guardMaterial);
    post.position.set(i * 5, 0.42, -25.5);
    scene.add(rail, post);
  }
}

function makeCar(color: string) {
  const car = new THREE.Group();
  const body = new THREE.Mesh(
    new THREE.BoxGeometry(3.9, 0.72, 1.72),
    new THREE.MeshStandardMaterial({ color, roughness: 0.34, metalness: 0.52 }),
  );
  body.position.y = 0.74;
  const cabin = new THREE.Mesh(
    new THREE.BoxGeometry(2.05, 0.65, 1.55),
    new THREE.MeshStandardMaterial({ color: "#26343d", roughness: 0.18, metalness: 0.55 }),
  );
  cabin.position.set(-0.15, 1.35, 0);
  car.add(body, cabin);
  for (const x of [-1.25, 1.25]) for (const z of [-0.87, 0.87]) {
    const wheel = new THREE.Mesh(new THREE.CylinderGeometry(0.32, 0.32, 0.2, 12), new THREE.MeshStandardMaterial({ color: "#111214" }));
    wheel.rotation.x = Math.PI / 2;
    wheel.position.set(x, 0.45, z);
    car.add(wheel);
  }
  car.traverse((child) => { if (child instanceof THREE.Mesh) child.castShadow = true; });
  return car;
}

function addTraffic(scene: THREE.Scene) {
  const colors = ["#3d4e5c", "#9c1f24", "#d6d6d1", "#1f2528", "#3f5c87"];
  const traffic: THREE.Group[] = [];
  for (let i = 0; i < 7; i += 1) {
    const car = makeCar(colors[i % colors.length]);
    car.position.set(i % 2 === 0 ? -9.2 : 9.2, 0, 62 + i * 31);
    car.rotation.y = i % 2 === 0 ? Math.PI / 2 : -Math.PI / 2;
    car.userData.phase = i * 43;
    car.userData.lane = i % 2 === 0 ? -9.2 : 9.2;
    car.userData.direction = i % 2 === 0 ? -1 : 1;
    traffic.push(car);
    scene.add(car);
  }
  return traffic;
}

export function buildKhimkiWorld(scene: THREE.Scene, physics: RAPIER.World, R: typeof RAPIER): BuiltWorld {
  const cameraOccluders: THREE.Object3D[] = [];
  const ground = new THREE.Mesh(
    new THREE.PlaneGeometry(620, 620),
    new THREE.MeshStandardMaterial({ color: "#557a48", roughness: 1 }),
  );
  ground.rotation.x = -Math.PI / 2;
  ground.position.y = -0.035;
  ground.receiveShadow = true;
  scene.add(ground);
  addStaticBox(physics, R, 0, -0.14, 0, 620, 0.2, 620);

  addRoads(scene);
  addMall(scene, physics, R, cameraOccluders);

  const buildings: BuildingSpec[] = [
    { x: -66, z: -84, width: 29, depth: 26, height: 76, color: "#b7ada2", windows: "#405a69", rows: 14, columns: 7 },
    { x: 63, z: -92, width: 27, depth: 25, height: 88, color: "#a69f9b", windows: "#40515d", rows: 17, columns: 7 },
    { x: -94, z: -36, width: 30, depth: 31, height: 61, color: "#b78a6f", windows: "#344d5a", rows: 13, columns: 8 },
    { x: 101, z: -30, width: 34, depth: 28, height: 69, color: "#b8b2a9", windows: "#38525f", rows: 15, columns: 9 },
    { x: -105, z: 52, width: 38, depth: 24, height: 54, color: "#9f765f", windows: "#314853", rows: 12, columns: 10, rotation: 0.16 },
    { x: 98, z: 58, width: 42, depth: 25, height: 57, color: "#c0b3a6", windows: "#3e5660", rows: 12, columns: 11, rotation: -0.14 },
    { x: -67, z: 119, width: 50, depth: 27, height: 45, color: "#b4a094", windows: "#3a515d", rows: 10, columns: 12 },
    { x: 70, z: 128, width: 46, depth: 28, height: 49, color: "#a78672", windows: "#334a56", rows: 11, columns: 12 },
    { x: -132, z: -118, width: 35, depth: 31, height: 72, color: "#c1b8ae", windows: "#3d5968", rows: 16, columns: 9 },
    { x: 132, z: -130, width: 32, depth: 30, height: 66, color: "#a9745c", windows: "#304852", rows: 15, columns: 8 },
  ];
  buildings.forEach((building) => addBuilding(scene, physics, R, building, cameraOccluders));

  addTrees(scene);
  addStreetFurniture(scene);
  const traffic = addTraffic(scene);
  return { cameraOccluders, traffic };
}
