import { Color } from "../engine/render/Color"
import type { Material } from "../engine/render/Material"
import type { CloudLayer, SkyConfig } from "../engine/render/Sky"
import { ChunkBuilder } from "../engine/render/ChunkBuilder"
import { Actor, type ActorDefinition } from "../engine/scene/Actor"
import { Mesh } from "../engine/scene/Mesh"
import { MeshBuilder } from "../engine/scene/MeshBuilder"
import {
  Prefab,
  type PlacedPrefab,
  type Prefab as PrefabDefinition,
} from "../engine/scene/Prefab"
import type { BoxCollider, Collider } from "../engine/world/Collider"
import { Level, type Level as RuntimeLevel } from "../engine/world/Level"
import {
  Terrain,
  type RollingTerrainConfig,
  type Terrain as Ground,
} from "../engine/world/Terrain"
import { Boulder } from "./actors/Boulder"
import { Bush } from "./actors/Bush"
import { Flower, type FlowerStyle } from "./actors/Flower"
import type { Mob, MobState } from "./actors/Mob"
import type { Tree } from "./actors/Tree"
import { Bee } from "./actors/mobs/Bee"
import { Frog } from "./actors/mobs/Frog"
import { Robin } from "./actors/mobs/Robin"
import { Birch } from "./actors/trees/Birch"
import { Oak } from "./actors/trees/Oak"
import { Spruce } from "./actors/trees/Spruce"
import type { Textures } from "./textures"

type Materials = {
  floor: Material
  grass: Material
  bark: Material
  birch: Material
  leaf: Material
  needle: Material
  rock: Material
  flower: Material
  wall: Material
  crate: Material
  npc: Material
  frog: Material
  bee: Material
  robin: Material
}

type WeightedTree = {
  definition: PrefabDefinition<Tree>
  weight: number
}

type MobSpawn = {
  definition: ActorDefinition<MobState, Ground>
  count: number
  minScale: number
  maxScale: number
  grounded: boolean
  phase: (random: () => number) => number
}

const ARENA = 12
const WALL_HEIGHT = 4
const WALL_THICKNESS = 1.5
const CRATE = { x: -2, z: -2, half: 1, height: 1 }
const FLOOR_LIFT = 0.02

const TERRAIN_CONFIG: RollingTerrainConfig = {
  inner: ARENA,
  outer: ARENA * 10,
  blend: 12,
  amplitude: 5,
  frequency: 0.14,
  peakHeight: 0,
  peakFrequency: 0.05,
  peakStart: 0.45,
}
const TERRAIN = Terrain.rolling(TERRAIN_CONFIG)

const TREE_COUNT = 50
const TREE_SEED = 0x5EED
const TREE_REACH = 1
const BOULDER_COUNT = 50
const BOULDER_SEED = 0xB0142
const BOULDER_REACH = 1
const BUSH_COUNT = 50
const BUSH_SEED = 0xB554
const BUSH_REACH = 1
const FLOWER_COUNT = 50
const FLOWER_SEED = 0xF10E
const FLOWER_REACH = 0.3
const FLOWER_STYLES: FlowerStyle[] = [Flower.white, Flower.red, Flower.yellow]
const FROG_COUNT = 20
const BEE_COUNT = 20
const ROBIN_COUNT = 20
const MOB_SEED = 0x30B
const MOB_REACH = 1
const CHUNK_GRID = 12
const TERRAIN_SUBDIV = 5
const GROUND_UV = 0.25

export const basicCumulus: CloudLayer = {
  kind: "basic",
  color: Color.rgb(248, 250, 255),
  coverage: 0.5,
  scale: 0.9,
  speed: 0.5,
  edge: 0.005,
}

export const fancyCumulus: CloudLayer = {
  kind: "fancy",
  color: Color.rgb(250, 251, 255),
  coverage: 0.5,
  scale: 0.6,
  speed: 0.5,
  edge: 0.02,
  warp: 0.4,
  relief: 7,
}

/** Concrete playground data assembled through engine-owned level mechanisms. */
export function buildLevel(textures: Textures): RuntimeLevel<Ground> {
  const materials = createMaterials(textures)
  const floor = Mesh.create()
  MeshBuilder.quad(
    floor,
    [-ARENA, FLOOR_LIFT, -ARENA],
    [ARENA, FLOOR_LIFT, -ARENA],
    [ARENA, FLOOR_LIFT, ARENA],
    [-ARENA, FLOOR_LIFT, ARENA],
    12,
    12,
  )

  const walls = Mesh.create()
  const thickness = WALL_THICKNESS
  MeshBuilder.slab(
    walls,
    -ARENA,
    ARENA,
    ARENA - thickness,
    ARENA,
    0,
    WALL_HEIGHT,
    0.5,
  )
  MeshBuilder.slab(
    walls,
    ARENA - thickness,
    ARENA,
    -ARENA,
    ARENA - thickness,
    0,
    WALL_HEIGHT,
    0.5,
  )
  MeshBuilder.slab(
    walls,
    -ARENA,
    -ARENA + thickness,
    -ARENA,
    ARENA - thickness,
    0,
    WALL_HEIGHT,
    0.5,
  )

  const crate = Mesh.create()
  MeshBuilder.box(crate, CRATE.x, CRATE.z, CRATE.half, 0, CRATE.height)

  const npcPosition = { x: 2, y: 0, z: -1 }
  const staticColliders: Collider[] = [
    wall(-ARENA, ARENA, ARENA - thickness, ARENA),
    wall(ARENA - thickness, ARENA, -ARENA, ARENA - thickness),
    wall(-ARENA, -ARENA + thickness, -ARENA, ARENA - thickness),
    {
      shape: "box",
      minX: CRATE.x - CRATE.half,
      maxX: CRATE.x + CRATE.half,
      minZ: CRATE.z - CRATE.half,
      maxZ: CRATE.z + CRATE.half,
      top: CRATE.height,
      standable: true,
    },
    {
      shape: "circle",
      x: npcPosition.x,
      z: npcPosition.z,
      radius: 0.5,
      top: Infinity,
      standable: false,
    },
  ]

  const treeDefinitions: WeightedTree[] = [
    { definition: Oak.create(materials.bark, materials.leaf), weight: 0.4 },
    {
      definition: Spruce.create(materials.bark, materials.needle),
      weight: 0.32,
    },
    { definition: Birch.create(materials.birch, materials.leaf), weight: 0.28 },
  ]
  const props = [
    ...placeTrees(treeDefinitions),
    ...placeBoulders(Boulder.create(materials.rock)),
    ...placeBushes(Bush.create(materials.leaf)),
    ...placeFlowers(Flower.create(materials.flower)),
  ]
  for (const prop of props) {
    if (prop.collider !== null) {
      staticColliders.push(prop.collider)
    }
  }

  const chunks = ChunkBuilder.build(
    {
      minX: TERRAIN.minX,
      minZ: TERRAIN.minZ,
      maxX: TERRAIN.maxX,
      maxZ: TERRAIN.maxZ,
      columns: CHUNK_GRID,
      rows: CHUNK_GRID,
      bakeCell(near, far, cell) {
        const ground = near.mesh(materials.grass)
        far.use(materials.grass, ground)
        Terrain.patch(
          TERRAIN,
          ground,
          cell.x0,
          cell.z0,
          cell.x1,
          cell.z1,
          TERRAIN_SUBDIV,
          TERRAIN_SUBDIV,
          GROUND_UV,
          (x, z) => Math.max(Math.abs(x), Math.abs(z)) >= ARENA,
        )
      },
    },
    props,
  )

  const frog = Frog.create(materials.frog)
  const bee = Bee.create(materials.bee)
  const robin = Robin.create(materials.robin)
  const actors = placeMobs([
    {
      definition: frog,
      count: FROG_COUNT,
      minScale: 0.5,
      maxScale: 0.85,
      grounded: true,
      phase: () => 0,
    },
    {
      definition: bee,
      count: BEE_COUNT,
      minScale: 0.5,
      maxScale: 0.8,
      grounded: false,
      phase: (random) => random() * 10,
    },
    {
      definition: robin,
      count: ROBIN_COUNT,
      minScale: 0.4,
      maxScale: 0.65,
      grounded: true,
      phase: () => 0,
    },
  ])

  const sky: SkyConfig = {
    zenith: Color.rgb(58, 108, 196),
    horizon: Color.rgb(178, 198, 226),
    sun: Color.rgb(255, 246, 214),
    sunDir: { x: 0.3, y: 0.5, z: -0.8 },
    sunSize: 0.04,
    clouds: fancyCumulus,
    skybox: { texture: textures.skybox },
  }

  return Level.create({
    terrain: TERRAIN,
    actorWorld: TERRAIN,
    actors,
    staticColliders,
    staticGroups: [
      { mesh: floor, material: materials.floor },
      { mesh: walls, material: materials.wall },
      { mesh: crate, material: materials.crate },
    ],
    chunks,
    billboards: [
      {
        position: npcPosition,
        size: { x: 1.1, y: 1.5 },
        material: materials.npc,
      },
    ],
    sky,
  })
}

function createMaterials(textures: Textures): Materials {
  return {
    floor: { texture: textures.floor, cull: false },
    grass: { texture: textures.grass, cull: true },
    bark: { texture: textures.bark, cull: true },
    birch: { texture: textures.birch, cull: true },
    leaf: { texture: textures.leaf, cull: true },
    needle: { texture: textures.needle, cull: true },
    rock: { texture: textures.rock, cull: true },
    flower: { texture: textures.flower, cull: false },
    wall: { texture: textures.wall, cull: false },
    crate: { texture: textures.crate, cull: false },
    npc: { texture: textures.npc, cull: false },
    frog: { texture: textures.frog, cull: false },
    bee: { texture: textures.bee, cull: false },
    robin: { texture: textures.robin, cull: false },
  }
}

function placeTrees(definitions: WeightedTree[]): PlacedPrefab[] {
  const random = mulberry(TREE_SEED)
  const maxDistance = TERRAIN_CONFIG.outer * TREE_REACH
  const trees: PlacedPrefab[] = []
  for (
    let guard = 0;
    trees.length < TREE_COUNT && guard < TREE_COUNT * 20;
    guard++
  ) {
    const point = scatterPoint(random, maxDistance, 5, 3)
    if (point === null) {
      continue
    }
    const definition = weightedTree(definitions, random())
    const tree: Tree = {
      position: {
        x: point.x,
        y: TERRAIN.heightAt(point.x, point.z),
        z: point.z,
      },
      growth: 0.08 + random() * 0.92,
      seed: (random() * 0xFFFFFFFF) | 0,
    }
    trees.push(Prefab.place(definition, tree))
  }
  return trees
}

function placeBoulders(definition: PrefabDefinition<Boulder>): PlacedPrefab[] {
  const random = mulberry(BOULDER_SEED)
  const maxDistance = TERRAIN_CONFIG.outer * BOULDER_REACH
  const boulders: PlacedPrefab[] = []
  for (
    let guard = 0;
    boulders.length < BOULDER_COUNT && guard < BOULDER_COUNT * 20;
    guard++
  ) {
    const point = scatterPoint(random, maxDistance, 4, 2)
    if (point === null) {
      continue
    }
    boulders.push(
      Prefab.place(definition, {
        position: {
          x: point.x,
          y: TERRAIN.heightAt(point.x, point.z),
          z: point.z,
        },
        radius: 0.35 + random() * random() * 2.2,
        seed: (random() * 0xFFFFFFFF) | 0,
      }),
    )
  }
  return boulders
}

function placeBushes(definition: PrefabDefinition<Bush>): PlacedPrefab[] {
  const random = mulberry(BUSH_SEED)
  const maxDistance = TERRAIN_CONFIG.outer * BUSH_REACH
  const bushes: PlacedPrefab[] = []
  for (
    let guard = 0;
    bushes.length < BUSH_COUNT && guard < BUSH_COUNT * 20;
    guard++
  ) {
    const point = scatterPoint(random, maxDistance, 3, 2)
    if (point === null) {
      continue
    }
    bushes.push(
      Prefab.place(definition, {
        position: {
          x: point.x,
          y: TERRAIN.heightAt(point.x, point.z),
          z: point.z,
        },
        size: 0.8 + random(),
        seed: (random() * 0xFFFFFFFF) | 0,
      }),
    )
  }
  return bushes
}

function placeFlowers(definition: PrefabDefinition<Flower>): PlacedPrefab[] {
  const random = mulberry(FLOWER_SEED)
  const maxDistance = TERRAIN_CONFIG.outer * FLOWER_REACH
  const flowers: PlacedPrefab[] = []
  for (
    let guard = 0;
    flowers.length < FLOWER_COUNT && guard < FLOWER_COUNT * 20;
    guard++
  ) {
    const point = scatterPoint(random, maxDistance, 2, 1)
    if (point === null) {
      continue
    }
    flowers.push(
      Prefab.place(definition, {
        position: {
          x: point.x,
          y: TERRAIN.heightAt(point.x, point.z),
          z: point.z,
        },
        style: FLOWER_STYLES[(random() * FLOWER_STYLES.length) | 0],
        size: 0.28 + random() * 0.22,
        seed: (random() * 0xFFFFFFFF) | 0,
      }),
    )
  }
  return flowers
}

function placeMobs(spawns: MobSpawn[]): Mob[] {
  const random = mulberry(MOB_SEED)
  const maxDistance = TERRAIN_CONFIG.outer * MOB_REACH
  const mobs: Mob[] = []
  for (const spawn of spawns) {
    let placed = 0
    for (
      let guard = 0;
      placed < spawn.count && guard < spawn.count * 20;
      guard++
    ) {
      const point = scatterPoint(random, maxDistance, 3, 2)
      if (point === null) {
        continue
      }
      const y = TERRAIN.heightAt(point.x, point.z)
      const scale = spawn.minScale + random() * (spawn.maxScale - spawn.minScale)
      const heading = random() * Math.PI * 2
      const state: MobState = {
        home: { x: point.x, y, z: point.z },
        position: { x: point.x, y, z: point.z },
        heading,
        scale,
        seed: (random() * 0xFFFFFFFF) | 0,
        vx: 0,
        vz: 0,
        vy: 0,
        timer: random() * 1.5,
        phase: spawn.phase(random),
        grounded: spawn.grounded,
      }
      mobs.push(Actor.create(spawn.definition, state))
      placed++
    }
  }
  return mobs
}

function scatterPoint(
  random: () => number,
  maxDistance: number,
  clearance: number,
  flatMargin: number,
): { x: number; z: number } | null {
  const angle = random() * Math.PI * 2
  const distance =
    ARENA + clearance + random() * (maxDistance - ARENA - clearance)
  const x = Math.cos(angle) * distance
  const z = Math.sin(angle) * distance
  return Math.max(Math.abs(x), Math.abs(z)) < TERRAIN_CONFIG.inner + flatMargin
    ? null
    : { x, z }
}

function weightedTree(
  definitions: WeightedTree[],
  roll: number,
): PrefabDefinition<Tree> {
  let cumulative = 0
  for (const entry of definitions) {
    cumulative += entry.weight
    if (roll < cumulative) {
      return entry.definition
    }
  }
  return definitions[definitions.length - 1].definition
}

function wall(
  minX: number,
  maxX: number,
  minZ: number,
  maxZ: number,
): BoxCollider {
  return {
    shape: "box",
    minX,
    maxX,
    minZ,
    maxZ,
    top: WALL_HEIGHT,
    standable: true,
  }
}

function mulberry(seed: number): () => number {
  let state = seed >>> 0
  return () => {
    state = (state + 0x6D2B79F5) | 0
    let value = Math.imul(state ^ (state >>> 15), 1 | state)
    value ^= value + Math.imul(value ^ (value >>> 7), 61 | value)
    return ((value ^ (value >>> 14)) >>> 0) / 4294967296
  }
}
