// noinspection JSVoidFunctionReturnValueUsed,JSUnresolvedReference

import { Document, type TypedArray } from "@gltf-transform/core";
import { Vector2 } from "three/src/math/Vector2.js";
import { Vector3 } from "three/src/math/Vector3.js";
import { Matrix4 } from "three/src/math/Matrix4.js";

/** Exports the colors used for the axes, primary and secondary. They match the orientation gizmo. */
export const AxesColors = {
  x: [
    [247, 60, 60],
    [148, 36, 36],
  ],
  z: [
    [108, 203, 38],
    [65, 122, 23],
  ],
  y: [
    [23, 140, 240],
    [14, 84, 144],
  ],
};

function buildSimpleGltf(
  doc: Document,
  rawPositions: number[],
  rawIndices: number[],
  rawColors: number[] | null,
  transform: Matrix4,
  name: string = "__helper",
  mode: number = WebGL2RenderingContext.LINES,
) {
  const buffer = doc.getRoot().listBuffers()[0] ?? doc.createBuffer(name + "Buffer");
  const scene = doc.getRoot().getDefaultScene() ?? doc.getRoot().listScenes()[0] ?? doc.createScene(name + "Scene");
  if (!scene) throw new Error("Scene is undefined");
  if (!rawPositions) throw new Error("rawPositions is undefined");
  const positions = doc
    .createAccessor(name + "Position")
    .setArray(new Float32Array(rawPositions) as TypedArray)
    .setType("VEC3")
    .setBuffer(buffer);
  const indices = doc
    .createAccessor(name + "Indices")
    .setArray(new Uint32Array(rawIndices) as TypedArray)
    .setType("SCALAR")
    .setBuffer(buffer);
  let colors = null;
  if (rawColors) {
    colors = doc
      .createAccessor(name + "Color")
      .setArray(new Float32Array(rawColors) as TypedArray)
      .setType("VEC4")
      .setBuffer(buffer);
  }
  const material = doc.createMaterial(name + "Material").setAlphaMode("OPAQUE");
  const geometry = doc
    .createPrimitive()
    .setIndices(indices)
    .setAttribute("POSITION", positions)
    .setMode(mode as any)
    .setMaterial(material);
  if (rawColors) {
    geometry.setAttribute("COLOR_0", colors);
  }
  if (mode == WebGL2RenderingContext.TRIANGLES) {
    geometry.setExtras({
      face_triangles_end: [
        rawIndices.length / 6,
        (rawIndices.length * 2) / 6,
        (rawIndices.length * 3) / 6,
        (rawIndices.length * 4) / 6,
        (rawIndices.length * 5) / 6,
        rawIndices.length,
      ],
    });
  } else if (mode == WebGL2RenderingContext.LINES) {
    geometry.setExtras({ edge_points_end: [rawIndices.length / 3, (rawIndices.length * 2) / 3, rawIndices.length] });
  }
  const mesh = doc.createMesh(name + "Mesh").addPrimitive(geometry);
  const node = doc
    .createNode(name + "Node")
    .setMesh(mesh)
    .setMatrix(transform.elements as any);
  scene.addChild(node);
}

/**
 * Create a new Axes helper as a GLTF model, useful for debugging positions and orientations.
 */
export function newAxes(doc: Document, size: Vector3, transform: Matrix4) {
  let rawIndices = [0, 1, 2, 3, 4, 5];
  let rawPositions = [0, 0, 0, size.x, 0, 0, 0, 0, 0, 0, size.y, 0, 0, 0, 0, 0, 0, -size.z];
  let rawColors = [
    ...(AxesColors.x[0] ?? [255, 0, 0]),
    255,
    ...(AxesColors.x[1] ?? [255, 0, 0]),
    255,
    ...(AxesColors.y[0] ?? [0, 255, 0]),
    255,
    ...(AxesColors.y[1] ?? [0, 255, 0]),
    255,
    ...(AxesColors.z[0] ?? [0, 0, 255]),
    255,
    ...(AxesColors.z[1] ?? [0, 0, 255]),
    255,
  ].map((x) => x / 255.0);
  // Axes at (0, 0, 0)
  buildSimpleGltf(doc, rawPositions, rawIndices, rawColors, new Matrix4(), "__helper_axes");
  buildSimpleGltf(doc, [0, 0, 0], [0], [1, 1, 1, 1], new Matrix4(), "__helper_axes", WebGL2RenderingContext.POINTS);
}

/**
 * Create a new Grid helper as a GLTF model, useful for debugging sizes with an OrthographicCamera.
 *
 * The grid is built as a box of triangles (representing lines) looking to the inside of the box.
 * This ensures that only the back of the grid is always visible, regardless of the camera position.
 */
export function newGridBox(doc: Document, size: Vector3, baseTransform: Matrix4, divisions = 10) {
  // Create transformed positions for the inner faces of the box
  let allPositions: number[] = [];
  let allIndices: number[] = [];
  for (let axis of [new Vector3(1, 0, 0), new Vector3(0, 1, 0), new Vector3(0, 0, -1)]) {
    for (let positive of [1, -1]) {
      let offset = axis.clone().multiply(size.clone().multiplyScalar(0.5 * positive));
      let translation = new Matrix4().makeTranslation(offset.x, offset.y, offset.z);
      let rotation = new Matrix4().lookAt(new Vector3(), offset, new Vector3(0, 1, 0));
      let size2 = new Vector2();
      if (axis.x) size2.set(size.z, size.y);
      if (axis.y) size2.set(size.x, size.z);
      if (axis.z) size2.set(size.x, size.y);
      let transform = new Matrix4().multiply(translation).multiply(rotation);
      let [rawPositions, rawIndices] = newGridPlane(size2, divisions);
      let baseIndex = allPositions.length / 3;
      for (let i of rawIndices) {
        allIndices.push(i + baseIndex);
      }
      // Apply transform to the positions before adding them to the list
      for (let i = 0; i < rawPositions.length; i += 3) {
        let pos = new Vector3(rawPositions[i], rawPositions[i + 1], rawPositions[i + 2]);
        pos.applyMatrix4(transform);
        allPositions.push(pos.x, pos.y, pos.z);
      }
    }
  }
  let colors = new Array((allPositions.length / 3) * 4).fill(1);
  buildSimpleGltf(doc, allPositions, allIndices, colors, baseTransform, "__helper_grid", WebGL2RenderingContext.TRIANGLES);
}

export function newGridPlane(size: Vector2, divisions = 10, divisionWidth = 0.002): [number[], number[]] {
  const rawPositions = [];
  const rawIndices = [];
  // Build the grid as triangles
  for (let i = 0; i <= divisions; i++) {
    const x = -size.x / 2 + (size.x * i) / divisions;
    const y = -size.y / 2 + (size.y * i) / divisions;

    // Vertical quad (two triangles)
    rawPositions.push(x - (divisionWidth * size.x) / 2, -size.y / 2, 0);
    rawPositions.push(x + (divisionWidth * size.x) / 2, -size.y / 2, 0);
    rawPositions.push(x + (divisionWidth * size.x) / 2, size.y / 2, 0);
    rawPositions.push(x - (divisionWidth * size.x) / 2, size.y / 2, 0);
    const baseIndex = i * 4;
    rawIndices.push(baseIndex, baseIndex + 1, baseIndex + 2);
    rawIndices.push(baseIndex, baseIndex + 2, baseIndex + 3);

    // Horizontal quad (two triangles)
    rawPositions.push(-size.x / 2, y - (divisionWidth * size.y) / 2, 0);
    rawPositions.push(size.x / 2, y - (divisionWidth * size.y) / 2, 0);
    rawPositions.push(size.x / 2, y + (divisionWidth * size.y) / 2, 0);
    rawPositions.push(-size.x / 2, y + (divisionWidth * size.y) / 2, 0);
    const baseIndex2 = (divisions + 1 + i) * 4;
    rawIndices.push(baseIndex2, baseIndex2 + 1, baseIndex2 + 2);
    rawIndices.push(baseIndex2, baseIndex2 + 2, baseIndex2 + 3);
  }
  return [rawPositions, rawIndices];
}
