import * as BABYLON from 'babylonjs';
import { MESH_PARAMS } from '../constants';
import {
  gizmoManager,
  multiMeshTNode,
  singleMeshTNode,
  scene,
  selectedMeshes,
  getParamOfSelectedMeshes,
  getUserMeshes,
  newVector,
  getBoundingMeshData,
  newStandardMaterial,
  addHighlightExclusion,
  restoreParents,
  newMetaDataEntry,
  serializeScene,
  buildMeshPositionsArray,
  toRadians,
} from '../helpers';
import { reactProps as props } from '../Canvas';
import _ from 'lodash';

let parentList = [];
let previousSingleNode = {};

export const newGizmoManager = () => {
  const thickness = 1;
  return new BABYLON.GizmoManager(scene, thickness);
};

export const cleanupGizmoAndTNodes = () => {
  const singleAtRest = singleMeshTNode.position.equals(newVector(0, 0, 0));
  const multiAtRest = multiMeshTNode.position.equals(newVector(0, 0, 0));
  const resetNodes = type => {
    const single = type === 'single';
    const node = single ? singleMeshTNode : multiMeshTNode;

    gizmoManager.attachToNode(null);
    if (!_.isEmpty(parentList)) {
      previousSingleNode = {};
      if (single) previousSingleNode = singleMeshTNode.getChildren()[0];
      restoreParents(parentList);
    }
    node.position = new BABYLON.Vector3(0, 0, 0);
    parentList = [];
  };

  if (!singleAtRest) resetNodes('single');
  if (!multiAtRest) resetNodes('multi');
};

export const attachToSelectMeshesNode = () => {
  const singleSelection = selectedMeshes.length === 1;
  const multiSelection = selectedMeshes.length > 1;

  cleanupGizmoAndTNodes();
  if (singleSelection) handleSingleMeshGizmo(selectedMeshes[0]);
  if (multiSelection) handleMultiMeshGizmo();
};

export const handleSingleMeshGizmo = mesh => {
  if (mesh) {
    const meshCenter = getParamOfSelectedMeshes(MESH_PARAMS.center);
    const isChildOfNode = previousSingleNode?.name === mesh.name;
    const parent = mesh.parent;
    const currentMeshTracking = scene.metadata.meshChangeTracking.find(trackedMesh => trackedMesh.meshId === mesh.id);
    const radiansX = toRadians(currentMeshTracking?.rx);
    const radiansY = toRadians(currentMeshTracking?.ry);
    const radiansZ = toRadians(currentMeshTracking?.rz);
    const newRotation =  newVector(radiansX, radiansY, radiansZ);

    parentList.push({ mesh, parent });

    if (isChildOfNode) restoreParents(parentList);

    singleMeshTNode.position = meshCenter;
    singleMeshTNode.rotation = newRotation;
    singleMeshTNode.scaling = newVector(1, 1, 1);

    mesh.setParent(singleMeshTNode);
    gizmoManager.attachToNode(singleMeshTNode);
  }
};

export const handleMultiMeshGizmo = () => {
  const meshCenter = getParamOfSelectedMeshes(MESH_PARAMS.center);
  const meshScaling = getParamOfSelectedMeshes(MESH_PARAMS.scaling);
  const meshRotation = getParamOfSelectedMeshes(MESH_PARAMS.rotation);
  const nodeChildren = multiMeshTNode.getChildren();

  getUserMeshes().forEach(mesh => {
    const isChildOfNode = _.includes(nodeChildren, mesh);
    const parent = mesh.parent;

    parentList.push({ mesh, parent });

    if (isChildOfNode) restoreParents(parentList);
  });

  multiMeshTNode.rotation = meshRotation;
  multiMeshTNode.position = meshCenter;
  multiMeshTNode.scaling = meshScaling;

  selectedMeshes.forEach(mesh => mesh.setParent(multiMeshTNode));
  gizmoManager.attachToNode(multiMeshTNode);
};

export const addInitialGizmoBehaviors = () => {
  addBoundingBoxGizmoBehaviors();
  addScalingGizmoBehaviors();
  addPositionGizmoBehaviors();
  addRotationGizmoBehaviors();
};

export const createBoundingMesh = () => {
  const oldBoundingMesh = scene.getMeshById('boundingMesh');
  oldBoundingMesh?.dispose();
  const meshesToUse = !_.isEmpty(selectedMeshes) ? selectedMeshes : getUserMeshes();
  const boundingRect = getBoundingMeshData(meshesToUse);
  let boundingRectMaterial = scene.materials.find(mat => mat.name === 'boundingMaterial');
  if (!boundingRectMaterial) boundingRectMaterial = newStandardMaterial('boundingMaterial');
  const { maximum, minimum } = boundingRect;

  const height = maximum.y - minimum.y;
  const width = maximum.x - minimum.x;
  const depth = maximum.z - minimum.z;
  const rectOptions = { width, height, depth };
  let nodeLocation = getParamOfSelectedMeshes(MESH_PARAMS.center);

  if (!_.isEmpty(selectedMeshes)) {
    nodeLocation = selectedMeshes.length > 1
      ? multiMeshTNode.position.clone()
      : singleMeshTNode.position.clone();
  }

  const boundingMesh = BABYLON.MeshBuilder.CreateBox('boundingMesh', rectOptions, scene);
  boundingRectMaterial.alpha = 0;
  boundingMesh.material = boundingRectMaterial;
  boundingMesh.position = nodeLocation || newVector(1, 1, 1);
  boundingMesh.isPickable = false;
  boundingMesh.visibility = 0;


  addHighlightExclusion(boundingMesh);

  return boundingMesh;
};

export const addBoundingBoxGizmoBehaviors = () => {
  gizmoManager.boundingBoxGizmoEnabled = true;

  gizmoManager.onAttachedToNodeObservable.add(() => {
    const boundingBoxEnabled = gizmoManager.boundingBoxGizmoEnabled;
    const newBox = createBoundingMesh();
    if (boundingBoxEnabled) gizmoManager.attachToMesh(newBox);
  });

  gizmoManager.boundingBoxGizmoEnabled = false;
};

const addScalingGizmoBehaviors = () => {
  gizmoManager.scaleGizmoEnabled = true;
  const scaleGizmo = gizmoManager.gizmos.scaleGizmo;

  scaleGizmo.onDragEndObservable.add(() => {
    attachToSelectMeshesNode();
    createBoundingMesh();
    newMetaDataEntry('meshChangeTracking', buildMeshPositionsArray());
    props.setSerializedData?.(serializeScene());
  });

  gizmoManager.scaleGizmoEnabled = false;
};

const addPositionGizmoBehaviors = () => {
  gizmoManager.positionGizmoEnabled = true;
  const positionGizmo = gizmoManager.gizmos.positionGizmo;

  positionGizmo.onDragEndObservable.add(() => {
    createBoundingMesh();
    newMetaDataEntry('meshChangeTracking', buildMeshPositionsArray());
    props.setSerializedData?.(serializeScene());
  });

  gizmoManager.positionGizmoEnabled = false;
};

const addRotationGizmoBehaviors = () => {
  gizmoManager.rotationGizmoEnabled = true;
  const rotationGizmo = gizmoManager.gizmos.rotationGizmo;

  rotationGizmo.onDragEndObservable.add(() => {
    createBoundingMesh();
    newMetaDataEntry('meshChangeTracking', buildMeshPositionsArray());
    props.setSerializedData?.(serializeScene());
  });

  gizmoManager.rotationGizmoEnabled = false;
};