import {
  BackSide,
  BoxGeometry,
  CircleGeometry,
  Color,
  ConeGeometry,
  CylinderGeometry,
  FogExp2,
  Group,
  Mesh,
  MeshLambertMaterial,
  PlaneGeometry,
  Scene,
  type Material,
} from "three";

/** Muted outdoor palette — fog-choked manor grounds, not neon. */
const PALETTE = {
  skyTop: 0x6a7a72,
  skyHorizon: 0xa8a890,
  fog: 0x8a9080,
  grass: 0x3d4a32,
  grassDark: 0x2a3424,
  dirt: 0x5c4a32,
  dirtLight: 0x6e5a3e,
  path: 0x6a5c48,
  bark: 0x3a2e22,
  foliage: 0x2f3d28,
  foliageDark: 0x243020,
  rock: 0x5a5850,
  rockDark: 0x3e3c36,
  trunk: 0x2c241c,
};

function lambert(color: number, opts: { flat?: boolean; side?: typeof BackSide } = {}): MeshLambertMaterial {
  return new MeshLambertMaterial({
    color,
    flatShading: opts.flat ?? true,
    ...(opts.side !== undefined ? { side: opts.side } : {}),
  });
}

function addMesh(
  parent: Group,
  geometry: ConstructorParameters<typeof Mesh>[0],
  material: Material,
  x: number,
  y: number,
  z: number,
  sx = 1,
  sy = 1,
  sz = 1,
  rotY = 0,
): Mesh {
  const mesh = new Mesh(geometry, material);
  mesh.position.set(x, y, z);
  mesh.scale.set(sx, sy, sz);
  mesh.rotation.y = rotY;
  mesh.receiveShadow = false;
  mesh.castShadow = false;
  parent.add(mesh);
  return mesh;
}

/** Low-poly pine: trunk + stacked cones. */
function makeTree(foliage: MeshLambertMaterial, bark: MeshLambertMaterial, scale = 1): Group {
  const g = new Group();
  const trunk = new Mesh(new CylinderGeometry(0.08, 0.12, 0.7, 5), bark);
  trunk.position.y = 0.35;
  g.add(trunk);

  const tiers = [
    { y: 0.85, r: 0.55, h: 0.7 },
    { y: 1.25, r: 0.42, h: 0.6 },
    { y: 1.55, r: 0.28, h: 0.5 },
  ];
  for (const tier of tiers) {
    const cone = new Mesh(new ConeGeometry(tier.r, tier.h, 6), foliage);
    cone.position.y = tier.y;
    g.add(cone);
  }
  g.scale.setScalar(scale);
  return g;
}

/** Dead / bare tree — just trunk and a few branch boxes. */
function makeDeadTree(bark: MeshLambertMaterial, scale = 1): Group {
  const g = new Group();
  const trunk = new Mesh(new CylinderGeometry(0.06, 0.1, 1.4, 5), bark);
  trunk.position.y = 0.7;
  g.add(trunk);

  const arm = new Mesh(new BoxGeometry(0.7, 0.07, 0.07), bark);
  arm.position.set(0.2, 1.15, 0);
  arm.rotation.z = -0.5;
  g.add(arm);

  const arm2 = new Mesh(new BoxGeometry(0.45, 0.06, 0.06), bark);
  arm2.position.set(-0.15, 1.35, 0.05);
  arm2.rotation.z = 0.65;
  g.add(arm2);

  g.scale.setScalar(scale);
  return g;
}

function makeRock(rock: MeshLambertMaterial, rockDark: MeshLambertMaterial, scale = 1): Group {
  const g = new Group();
  const a = new Mesh(new BoxGeometry(0.5, 0.28, 0.4), rock);
  a.position.y = 0.12;
  a.rotation.y = 0.3;
  g.add(a);
  const b = new Mesh(new BoxGeometry(0.28, 0.18, 0.32), rockDark);
  b.position.set(0.18, 0.08, 0.1);
  g.add(b);
  g.scale.setScalar(scale);
  return g;
}

/**
 * Builds a fixed outdoor set for the character creator:
 * grass clearing, dirt path, pines, rocks, fog sky.
 * Keeps triangle counts low so the preview stays snappy.
 */
export function buildEnvironment(scene: Scene): Group {
  const root = new Group();
  root.name = "environment";

  // --- atmosphere ----------------------------------------------------------
  scene.background = new Color(PALETTE.skyHorizon);
  scene.fog = new FogExp2(PALETTE.fog, 0.085);

  // Gradient sky dome (inside of a sphere-ish cone stack of rings via large sphere).
  const skyGeo = new ConeGeometry(18, 14, 8, 1, true);
  // Cone points up; flip so we stand inside a bowl of sky.
  const sky = new Mesh(skyGeo, lambert(PALETTE.skyTop, { side: BackSide, flat: true }));
  sky.position.y = 5;
  sky.rotation.x = Math.PI;
  // Paint horizon band with a second inverted cone / ring.
  root.add(sky);

  const horizon = new Mesh(
    new CylinderGeometry(17, 17, 4, 10, 1, true),
    lambert(PALETTE.skyHorizon, { side: BackSide, flat: true }),
  );
  horizon.position.y = 1.5;
  root.add(horizon);

  // --- ground --------------------------------------------------------------
  const grassMat = lambert(PALETTE.grass);
  const grassDarkMat = lambert(PALETTE.grassDark);
  const dirtMat = lambert(PALETTE.dirt);
  const pathMat = lambert(PALETTE.path);

  // Large grass disc (circle reads softer than a huge square at the fog edge).
  const ground = new Mesh(new CircleGeometry(14, 12), grassMat);
  ground.rotation.x = -Math.PI / 2;
  ground.position.y = 0;
  root.add(ground);

  // Darker grass patches.
  for (const [x, z, s] of [
    [-2.2, -1.8, 1.4],
    [2.8, 1.2, 1.1],
    [-1.0, 3.0, 1.3],
    [3.5, -2.5, 0.9],
  ] as const) {
    const patch = new Mesh(new CircleGeometry(s, 6), grassDarkMat);
    patch.rotation.x = -Math.PI / 2;
    patch.position.set(x, 0.005, z);
    root.add(patch);
  }

  // Dirt clearing under the character.
  const clearing = new Mesh(new CircleGeometry(1.35, 8), dirtMat);
  clearing.rotation.x = -Math.PI / 2;
  clearing.position.y = 0.01;
  root.add(clearing);

  // Path leading toward camera / back.
  const path = new Mesh(new PlaneGeometry(1.1, 8), pathMat);
  path.rotation.x = -Math.PI / 2;
  path.position.set(0, 0.012, 3.2);
  root.add(path);

  const pathBack = new Mesh(new PlaneGeometry(0.9, 5), pathMat);
  pathBack.rotation.x = -Math.PI / 2;
  pathBack.position.set(0.4, 0.012, -3.5);
  pathBack.rotation.z = 0.15;
  root.add(pathBack);

  // --- props ---------------------------------------------------------------
  const bark = lambert(PALETTE.bark);
  const foliage = lambert(PALETTE.foliage);
  const foliageDark = lambert(PALETTE.foliageDark);
  const rock = lambert(PALETTE.rock);
  const rockDark = lambert(PALETTE.rockDark);
  const trunk = lambert(PALETTE.trunk);

  const trees: Array<[number, number, number, number]> = [
    [-3.2, -2.4, 1.15, 0.4],
    [-4.5, 1.2, 1.35, 1.1],
    [3.8, -1.6, 1.05, -0.3],
    [4.2, 2.0, 1.4, 0.8],
    [-2.8, 3.6, 0.95, 0.2],
    [2.5, 4.0, 1.2, -0.6],
    [-5.5, -0.5, 1.5, 0],
    [5.0, -3.2, 1.1, 1.4],
  ];
  trees.forEach(([x, z, s, rot], i) => {
    const tree = makeTree(i % 2 === 0 ? foliage : foliageDark, bark, s);
    tree.position.set(x, 0, z);
    tree.rotation.y = rot;
    root.add(tree);
  });

  // A couple of bare trees for RE / SH silhouette variety.
  for (const [x, z, s, rot] of [
    [-4.0, 3.8, 1.1, 0.5],
    [4.8, 0.5, 0.95, -0.8],
  ] as const) {
    const dead = makeDeadTree(trunk, s);
    dead.position.set(x, 0, z);
    dead.rotation.y = rot;
    root.add(dead);
  }

  for (const [x, z, s, rot] of [
    [1.6, -1.1, 0.7, 0.2],
    [-1.4, -1.4, 0.9, 1.0],
    [2.2, 1.8, 0.55, -0.4],
    [-2.5, 0.8, 0.65, 0.7],
    [0.8, 2.4, 0.45, 0.1],
  ] as const) {
    const r = makeRock(rock, rockDark, s);
    r.position.set(x, 0, z);
    r.rotation.y = rot;
    root.add(r);
  }

  // Low hedge / bush clumps (cones).
  for (const [x, z, s] of [
    [-1.8, 2.2, 0.45],
    [1.9, -2.0, 0.38],
    [-3.5, -3.0, 0.5],
  ] as const) {
    const bush = new Mesh(new ConeGeometry(0.45 * s * 2, 0.5 * s * 2, 5), foliageDark);
    bush.position.set(x, 0.2 * s * 2, z);
    root.add(bush);
  }

  // Distant low hills (flattened boxes at fog edge).
  const hillMat = lambert(PALETTE.grassDark);
  for (const [x, z, sx, sy, sz] of [
    [-8, -6, 5, 1.2, 3],
    [7, -7, 4, 0.9, 3.5],
    [-6, 8, 6, 1.4, 3],
    [9, 5, 4, 1.1, 4],
  ] as const) {
    addMesh(root, new BoxGeometry(1, 1, 1), hillMat, x, sy * 0.35, z, sx, sy, sz);
  }

  scene.add(root);
  return root;
}

export { PALETTE as ENV_PALETTE };
