// 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')
    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, ...(AxesColors.x[1]), 255,
        ...(AxesColors.y[0]), 255, ...(AxesColors.y[1]), 255,
        ...(AxesColors.z[0]), 255, ...(AxesColors.z[1]), 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);
    // Axes at center
    if (new Matrix4() != transform) {
        buildSimpleGltf(doc, rawPositions, rawIndices, rawColors, transform, '__helper_axes_center');
        buildSimpleGltf(doc, [0, 0, 0], [0], [1, 1, 1, 1], transform, '__helper_axes_center', 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 async 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];
}