import { SurfacePoint3 } from "../../src/core/geom/3d/SurfacePoint3.js";
import Vector2 from "../../src/core/geom/Vector2.js";
import Vector3 from "../../src/core/geom/Vector3.js";
import { obtainTerrain } from "../../src/engine/ecs/terrain/util/obtainTerrain.js";
import { Transform } from "../../src/engine/ecs/transform/Transform.js";
import Mesh from "../../src/engine/graphics/ecs/mesh/Mesh.js";
import { MeshEvents } from "../../src/engine/graphics/ecs/mesh/MeshEvents.js";
import { make_ray_from_viewport_position } from "../../src/engine/graphics/make_ray_from_viewport_position.js";
import BottomLeftResizeHandleView from "../../src/view/elements/BottomLeftResizeHandleView.js";
import ComponentAddAction from "../actions/concrete/ComponentAddAction.js";
import EntityCreateAction from "../actions/concrete/EntityCreateAction.js";
import SelectionAddAction from "../actions/concrete/SelectionAddAction.js";
import SelectionClearAction from "../actions/concrete/SelectionClearAction.js";
import MeshLibraryView from "./library/MeshLibraryView.js";

/**
 *
 * @param {Editor} editor
 */
export function prepareMeshLibrary(editor) {
    let resolveEngine;

    const pEngine = new Promise(function (resolve, reject) {
        resolveEngine = resolve;

    });

    /**
     *
     * @type {Promise<GraphicsEngine>}
     */
    const pGraphicsEngine = pEngine.then(function (e) {
        return e.graphics;
    });

    const pRenderer = pGraphicsEngine.then(function (graphicsEngine) {
        return graphicsEngine.renderer;
    });

    const pAssetManager = pEngine.then(e => e.assetManager);

    const meshLibraryView = new MeshLibraryView(
        editor.meshLibrary,
        pAssetManager,
        pRenderer
    );

    function handleDropEvent(event) {
        event.stopPropagation();
        event.preventDefault();

        const dataText = event.dataTransfer.getData('text/json');
        if (dataText === "") {
            //no data
            return;
        }

        const data = JSON.parse(dataText);

        const type = data.type;

        if (type !== "Mesh") {
            //wrong type
            return;
        }

        const url = data.url;

        const engine = editor.engine;
        const graphics = engine.graphics;

        const position = new Vector2(event.clientX, event.clientY);

        graphics.viewport.positionGlobalToLocal(position, position);

        const normalizedPosition = new Vector2();

        //compute world position for drop
        const ray = make_ray_from_viewport_position(engine, position);

        const source = ray.origin;
        const direction = ray.direction;


        const entityManager = engine.entityManager;
        const ecd = entityManager.dataset;

        const terrain = obtainTerrain(ecd);

        const worldPosition = new Vector3();

        const mesh = new Mesh();

        mesh.castShadow = true;
        mesh.receiveShadow = true;

        const transform = new Transform();

        const actions = editor.actions;
        actions.mark('New Mesh Placed from Library');
        const entityCreateAction = new EntityCreateAction();
        actions.do(entityCreateAction);

        const sp3 = new SurfacePoint3();

        const hit_found = terrain.raycastFirstSync(sp3, source.x, source.y, source.z, direction.x, direction.y, direction.z);

        function handleMeshSetEvent() {
            const bb = mesh.boundingBox;

            const c0 = new Vector3(bb.x0, bb.y0, bb.z0);
            const c1 = new Vector3(bb.x1, bb.y1, bb.z1);

            const diagonal = c0.distanceTo(c1);

            const offset = direction.clone().multiplyScalar(diagonal);

            transform.position.add(offset);

            //remove listener
            ecd.removeEntityEventListener(entityCreateAction.entity, MeshEvents.DataSet, handleMeshSetEvent);
        }

        if (hit_found) {
            //got a terrain ray hit, set world placement position to that point
            worldPosition.copy(sp3.position);
        } else {
            //set position to the source of the ray pick if there's nothing else available
            worldPosition.copy(source);


            //wait for mesh to load
            ecd.addEntityEventListener(entityCreateAction.entity, MeshEvents.DataSet, handleMeshSetEvent);
        }

        transform.position.copy(worldPosition);

        mesh.url = url;


        actions.doMany([
            new ComponentAddAction(entityCreateAction.entity, transform),
            new ComponentAddAction(entityCreateAction.entity, mesh),
            //automatically select newly placed object
            new SelectionClearAction(),
            new SelectionAddAction([entityCreateAction.entity])
        ]);

    }

    function handleDragOverEvent(event) {
        event.preventDefault();
    }


    meshLibraryView.on.linked.add(function () {
        resolveEngine(editor.engine);

        const viewport = editor.engine.graphics.viewport;

        viewport.el.addEventListener('drop', handleDropEvent);
        viewport.el.addEventListener('dragover', handleDragOverEvent);
    });

    meshLibraryView.on.unlinked.add(function () {
        const viewport = editor.engine.graphics.viewport;

        viewport.el.removeEventListener('drop', handleDropEvent);
        viewport.el.removeEventListener('dragover', handleDragOverEvent);
    });

    meshLibraryView.size.set(400, 400);

    const resizeHandleView = new BottomLeftResizeHandleView(meshLibraryView);
    meshLibraryView.addChild(resizeHandleView);

    return meshLibraryView;
}