// A renderable 3D node from raw geometry — the primitive shapes (box/sphere/cylinder/plane) and
// Mesh.from for a custom Geometry. A GLB import is a different kind: see Model.load.

import { cx, cy, cz, type Vec3Like } from "../math/vec"
import {
  Geometry,
  type CylinderOptions,
  type PlaneOptions,
  type SphereOptions,
} from "./Geometry"
import { Material } from "./Material"
import { Node } from "./Node"

/** Common transform/render options every primitive factory accepts. */
export type MeshOptions = {
  material?: Material
  position?: Vec3Like
  eulerAngles?: Vec3Like
  scale?: Vec3Like | number
  name?: string
  castShadows?: boolean
  receiveShadows?: boolean
}

export class Mesh extends Node {
  private _geometry?: Geometry

  constructor(geometry?: Geometry, material?: Material) {
    super()
    if (geometry) {
      const mat = material ?? Material.unlit()
      this._geometry = geometry
      this._materials = [ mat ]
      _creator.setMesh(this.id, Material.idOf(mat), geometry.vertices, geometry.normals, geometry.indices, geometry.uv, geometry._meshKind, geometry.uv1)
    }
  }

  override get geometry(): Geometry | undefined { return this._geometry }

  /** A Mesh always carries a material (slot 0) — see Node.setMaterial for the slot API. */
  override get material(): Material { return this._materials![0]! }
  override set material(m: Material) { this.setMaterial(m, 0) }

  set culling(v: boolean) { _creator.setCulling(this.id, v) }
  set castShadows(v: boolean) { _creator.setCastShadows(this.id, v) }
  set receiveShadows(v: boolean) { _creator.setReceiveShadows(this.id, v) }

  // --- factories ---

  static box(options: MeshOptions & { size?: Vec3Like | number } = {}): Mesh {
    // the size goes into the geometry (not a scale after): the lightmap chart is laid out per face area
    return apply(new Mesh(Geometry.box(options.size ?? 1), options.material), options)
  }

  static sphere(options: MeshOptions & SphereOptions = {}): Mesh {
    return apply(new Mesh(Geometry.sphere(options), options.material), options)
  }

  static cylinder(options: MeshOptions & CylinderOptions = {}): Mesh {
    return apply(new Mesh(Geometry.cylinder(options), options.material), options)
  }

  static plane(options: MeshOptions & PlaneOptions = {}): Mesh {
    return apply(new Mesh(Geometry.plane(options), options.material), options)
  }

  /** Wrap a custom Geometry. */
  static from(geometry: Geometry, options: MeshOptions = {}): Mesh {
    return apply(new Mesh(geometry, options.material), options)
  }
}

const sizeToVec = (s: Vec3Like | number): [number, number, number] => (typeof s === "number" ? [ s, s, s ] : [ cx(s), cy(s), cz(s) ])

const apply = (mesh: Mesh, o: MeshOptions): Mesh => {
  if (o.position) mesh.position = o.position
  if (o.eulerAngles) mesh.eulerAngles = o.eulerAngles
  if (o.scale !== undefined) mesh.scale = typeof o.scale === "number" ? [ o.scale, o.scale, o.scale ] : o.scale
  if (o.name !== undefined) mesh.name = o.name
  if (o.castShadows !== undefined) mesh.castShadows = o.castShadows
  if (o.receiveShadows !== undefined) mesh.receiveShadows = o.receiveShadows
  return mesh
}
