import { MeshBasicMaterial, MeshStandardMaterial, OctahedronBufferGeometry } from "three";
import { mergeVertices } from "three/examples/jsm/utils/BufferGeometryUtils.js";
import { GameAssetType } from "../../../../../engine/asset/GameAssetType.js";
import { GLTFAssetLoader } from "../../../../../engine/asset/loaders/GLTFAssetLoader.js";
import Entity from "../../../../../engine/ecs/Entity.js";
import { EntityNode } from "../../../../../engine/ecs/parent/EntityNode.js";
import { TransformAttachmentSystem } from "../../../../../engine/ecs/transform-attachment/TransformAttachmentSystem.js";
import { Transform } from "../../../../../engine/ecs/transform/Transform.js";
import { EngineHarness } from "../../../../../engine/EngineHarness.js";
import { SGMesh } from "../../../../../engine/graphics/ecs/mesh-v2/aggregate/SGMesh.js";
import { SGMeshEvents } from "../../../../../engine/graphics/ecs/mesh-v2/aggregate/SGMeshEvents.js";
import { SGMeshSystem } from "../../../../../engine/graphics/ecs/mesh-v2/aggregate/SGMeshSystem.js";
import { ShadedGeometry } from "../../../../../engine/graphics/ecs/mesh-v2/ShadedGeometry.js";
import { ShadedGeometrySystem } from "../../../../../engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.js";
import { makeGeometryIndexed } from "../../../../../engine/graphics/geometry/buffered/makeGeometryIndexed.js";
import { load_and_set_cubemap_v0 } from "../../../../../engine/graphics/load_and_set_cubemap_v0.js";
import { make_ray_from_viewport_position } from "../../../../../engine/graphics/make_ray_from_viewport_position.js";
import { Color } from "../../../../color/Color.js";
import { seededRandom } from "../../../../math/random/seededRandom.js";
import { v3_dot } from "../../../vec3/v3_dot.js";
import { AABB3 } from "../../aabb/AABB3.js";
import { compute_aabb_from_points } from "../../aabb/compute_aabb_from_points.js";
import { SurfacePoint3 } from "../../SurfacePoint3.js";
import { compute_triangle_area_3d } from "../../triangle/compute_triangle_area_3d.js";
import { expandConnectivityByLocality } from "../expandConnectivityByLocality.js";
import { TopoMesh } from "../struct/TopoMesh.js";
import { three_buffer_geometry_to_topo_mesh } from "../three/three_buffer_geometry_to_topo_mesh.js";
import { topo_mesh_to_three_buffer_geometry } from "../three/topo_mesh_to_three_buffer_geometry.js";
import { mesh_flood_fill } from "../util/mesh_flood_fill.js";

const harness = new EngineHarness();

/**
 *
 * @param {TopoMesh} mesh
 * @param {TopoTriangle[]} faces
 * @param {EntityNode} entity
 */
function set_highlight({
                           mesh,
                           faces,
                           entity
                       }) {

    entity.children.slice().forEach(e => {
        entity.removeChild(e);

        e.destroy();
    });


    const m = new TopoMesh();

    const vertex_map = new Map();

    faces.forEach(f => {
        m.addFaceCopy(f, vertex_map);
    });

    const selected_geo = topo_mesh_to_three_buffer_geometry(m);

    entity.addChild(EntityNode.fromComponents(
        new Transform(),
        ShadedGeometry.from(selected_geo, new MeshBasicMaterial({
            transparent: true,
            opacity: 0.7,
            color: '#00c4ff',
            depthTest: true
        }))
    ));

    entity.addChild(EntityNode.fromComponents(
        new Transform(),
        ShadedGeometry.from(selected_geo, new MeshBasicMaterial({
            wireframe: true,
            transparent: true,
            opacity: 0.7,
            color: '#ffffff',
            depthTest: false
        }))
    ))

}

/**
 *
 * @param {Engine} engine
 * @return {Promise<void>}
 */
async function main(engine) {
    await EngineHarness.buildBasics({
        engine,
        focus: { x: -10.283977088703375, y: 1.7301942199921359, z: -0.3232510720362 },
        pitch: 0.2700000000000029,
        distance: 17,
        yaw: -Math.PI / 2,
        cameraFarDistance: 100,
        enableTerrain: false
    });

    load_and_set_cubemap_v0(engine.graphics,'data/textures/cubemaps/hip_miramar/32/', '.png')

    const path = 'data/models/sponza-pbr/gltf/sponza.glb';

    const ecd = engine.entityManager.dataset;

    const entityBuilder = new Entity();
    entityBuilder
        .add(SGMesh.fromURL(path))
        .add(Transform.fromJSON({}))
        .build(ecd);

    const random = seededRandom();

    entityBuilder.addEventListener(SGMeshEvents.AssetLoaded, () => {
        /**
         *
         * @type {EntityNode}
         */
        const node = entityBuilder.getComponent(SGMesh).__node;

        node.traverse(n => {

            const sg = n.entity.getComponent(ShadedGeometry);

            if (sg === null) {
                return;
            }

            n.entity.removeComponent(ShadedGeometry);

            let hue = 0;

            do {
                hue = random();

            } while (Math.abs(hue - 0.53855555555) < 0.11); // shift away from hue of selection mask

            sg.material = new MeshStandardMaterial({
                color: Color.fromHSV(hue, 0.9, 0.9).toHex()
            });

            n.entity.add(sg);

        });
    });

    // create a mesh
    let source_geo = new OctahedronBufferGeometry(5, 3);

    makeGeometryIndexed(source_geo);
    source_geo = mergeVertices(source_geo, 0.002);

    const entity_source = new Entity();
    entity_source
        .add(ShadedGeometry.from(source_geo, new MeshStandardMaterial({
            color: '#b60000'
        })))
        .add(Transform.fromJSON({
            position: {
                x: 10, y: 6, z: 15
            }
        }))
        .build(ecd);

    const highlight = new EntityNode();
    highlight.entity.add(new Transform());


    engine.devices.pointer.on.tap.add(
        /**
         *
         * @param {Vector2} position
         * @param {MouseEvent|TouchEvent} event
         */
        (position, event) => {
            if (event instanceof MouseEvent) {
                if ((event.buttons & (1)) !== 0) {
                    return;
                }
            }

            /**
             *
             * @type {ShadedGeometrySystem}
             */
            const sgs = engine.entityManager.getSystem(ShadedGeometrySystem);

            const contact = new SurfacePoint3();

            const ray = make_ray_from_viewport_position(engine, position);

            const ray_origin = ray.origin;
            const ray_direction = ray.direction;


            highlight.destroy();

            const raycast_result = sgs.raycastNearest(contact, ray_origin.x, ray_origin.y, ray_origin.z, ray_direction.x, ray_direction.y, ray_direction.z);

            if (raycast_result !== undefined) {
                //got a hit

                const sg = raycast_result.mesh;
                makeGeometryIndexed(sg.geometry);
                const geometry = mergeVertices(sg.geometry);
                // console.warn(raycast_result, contact, geometry);


                const topo = three_buffer_geometry_to_topo_mesh(geometry);

                const aabb_array = [];
                const position_attribute = geometry.getAttribute('position');
                compute_aabb_from_points(aabb_array, position_attribute.array, 0, position_attribute.count, 3);
                const aabb3 = new AABB3(...aabb_array);
                //
                // weld_duplicate_vertices(topo, aabb3);
                expandConnectivityByLocality(topo, aabb3);

                // identify triangle that was clicked
                const face = topo.getFaceByIndex(contact.index);

                const selection = mesh_flood_fill({
                    mesh: topo,
                    initial_set: [face],
                    filter(current, prev) {
                        current.computeNormal();

                        const area = compute_triangle_area_3d(
                            current.vertices[0].x,
                            current.vertices[0].z,
                            current.vertices[0].y,

                            current.vertices[1].x,
                            current.vertices[1].z,
                            current.vertices[1].y,

                            current.vertices[2].x,
                            current.vertices[2].z,
                            current.vertices[2].y,
                        );

                        if (area < 0.0003) {
                            // area very small, allow jumping across
                            return true;
                        }

                        return v3_dot(current.normal[0], current.normal[1], current.normal[2], contact.normal.x, contact.normal.y, contact.normal.z) >= 0.8;
                        // return true;
                    }
                });

                // console.log(`selection: ${selection.length}`, selection);

                highlight.transform.copy(ecd.getComponent(raycast_result.entity, Transform));
                set_highlight({
                    mesh: topo, faces: selection, entity: highlight
                });

                highlight.build(ecd);
            }
        });
}

harness.initialize({
    configuration(config, engine) {
        config.addSystem(new ShadedGeometrySystem(engine));
        config.addSystem(new TransformAttachmentSystem());
        config.addSystem(new SGMeshSystem(engine));

        const loader = new GLTFAssetLoader();
        config.addLoader(GameAssetType.ModelGLTF_JSON, loader)
        config.addLoader(GameAssetType.ModelGLTF, loader)
    }
}).then(main);
