import {TargetType, TARGET_TYPES} from "../../Raycast";
import {Vector2 as Vector22} from "three/src/math/Vector2";
import {Raycaster as Raycaster2} from "three/src/core/Raycaster";
import {NodeContext as NodeContext2} from "../../../../poly/NodeContext";
import {TypeAssert} from "../../../../poly/Assert";
import {Plane as Plane2} from "three/src/math/Plane";
import {Vector3 as Vector32} from "three/src/math/Vector3";
import {ParamType as ParamType2} from "../../../../poly/ParamType";
import {AttribType, ATTRIBUTE_TYPES} from "../../../../../core/geometry/Constant";
import {objectTypeFromConstructor, ObjectType} from "../../../../../core/geometry/Constant";
import {CoreGeometry} from "../../../../../core/geometry/Geometry";
import {Triangle as Triangle2} from "three/src/math/Triangle";
import {Poly as Poly2} from "../../../../Poly";
import {RaycastCPUVelocityController} from "./VelocityController";
import {CoreType} from "../../../../../core/Type";
import {CPUIntersectWith, CPU_INTERSECT_WITH_OPTIONS} from "./CpuConstants";
const RaycastCPUController2 = class {
  constructor(_node) {
    this._node = _node;
    this._mouse = new Vector22();
    this._mouse_array = [0, 0];
    this._raycaster = new Raycaster2();
    this._plane = new Plane2();
    this._plane_intersect_target = new Vector32();
    this._hit_position_array = [0, 0, 0];
    this.velocity_controller = new RaycastCPUVelocityController(this._node);
  }
  update_mouse(context) {
    const canvas = context.viewer?.canvas();
    if (!(canvas && context.cameraNode)) {
      return;
    }
    if (context.event instanceof MouseEvent) {
      this._mouse.x = context.event.offsetX / canvas.offsetWidth * 2 - 1;
      this._mouse.y = -(context.event.offsetY / canvas.offsetHeight) * 2 + 1;
      this._mouse.toArray(this._mouse_array);
      this._node.p.mouse.set(this._mouse_array);
    }
    this._raycaster.setFromCamera(this._mouse, context.cameraNode.object);
  }
  process_event(context) {
    this._prepare_raycaster(context);
    const type = CPU_INTERSECT_WITH_OPTIONS[this._node.pv.intersectWith];
    switch (type) {
      case CPUIntersectWith.GEOMETRY: {
        return this._intersect_with_geometry(context);
      }
      case CPUIntersectWith.PLANE: {
        return this._intersect_with_plane(context);
      }
    }
    TypeAssert.unreachable(type);
  }
  _intersect_with_plane(context) {
    this._plane.normal.copy(this._node.pv.planeDirection);
    this._plane.constant = this._node.pv.planeOffset;
    this._raycaster.ray.intersectPlane(this._plane, this._plane_intersect_target);
    this._set_position_param(this._plane_intersect_target);
    this._node.trigger_hit(context);
  }
  _intersect_with_geometry(context) {
    if (!this._resolved_targets) {
      this.update_target();
    }
    if (this._resolved_targets) {
      const intersections = this._raycaster.intersectObjects(this._resolved_targets, true);
      const intersection = intersections[0];
      if (intersection) {
        this._set_position_param(intersection.point);
        if (this._node.pv.geoAttribute == true) {
          this._resolve_geometry_attribute(intersection);
        }
        context.value = {intersect: intersection};
        this._node.trigger_hit(context);
      } else {
        this._node.trigger_miss(context);
      }
    }
  }
  _resolve_geometry_attribute(intersection) {
    const attrib_type = ATTRIBUTE_TYPES[this._node.pv.geoAttributeType];
    const val = RaycastCPUController2.resolve_geometry_attribute(intersection, this._node.pv.geoAttributeName, attrib_type);
    if (val != null) {
      switch (attrib_type) {
        case AttribType.NUMERIC: {
          this._node.p.geoAttributeValue1.set(val);
          return;
        }
        case AttribType.STRING: {
          if (CoreType.isString(val)) {
            this._node.p.geoAttributeValues.set(val);
          }
          return;
        }
      }
      TypeAssert.unreachable(attrib_type);
    }
  }
  static resolve_geometry_attribute(intersection, attribute_name, attrib_type) {
    const object_type = objectTypeFromConstructor(intersection.object.constructor);
    switch (object_type) {
      case ObjectType.MESH:
        return this.resolve_geometry_attribute_for_mesh(intersection, attribute_name, attrib_type);
      case ObjectType.POINTS:
        return this.resolve_geometry_attribute_for_point(intersection, attribute_name, attrib_type);
    }
  }
  static resolve_geometry_attribute_for_mesh(intersection, attribute_name, attrib_type) {
    const geometry = intersection.object.geometry;
    if (geometry) {
      const attribute = geometry.getAttribute(attribute_name);
      if (attribute) {
        switch (attrib_type) {
          case AttribType.NUMERIC: {
            const position = geometry.getAttribute("position");
            if (intersection.face) {
              this._vA.fromBufferAttribute(position, intersection.face.a);
              this._vB.fromBufferAttribute(position, intersection.face.b);
              this._vC.fromBufferAttribute(position, intersection.face.c);
              this._uvA.fromBufferAttribute(attribute, intersection.face.a);
              this._uvB.fromBufferAttribute(attribute, intersection.face.b);
              this._uvC.fromBufferAttribute(attribute, intersection.face.c);
              intersection.uv = Triangle2.getUV(intersection.point, this._vA, this._vB, this._vC, this._uvA, this._uvB, this._uvC, this._hitUV);
              return this._hitUV.x;
            }
            return;
          }
          case AttribType.STRING: {
            const core_geometry = new CoreGeometry(geometry);
            const core_point = core_geometry.points()[0];
            if (core_point) {
              return core_point.stringAttribValue(attribute_name);
            }
            return;
          }
        }
        TypeAssert.unreachable(attrib_type);
      }
    }
  }
  static resolve_geometry_attribute_for_point(intersection, attribute_name, attrib_type) {
    const geometry = intersection.object.geometry;
    if (geometry && intersection.index != null) {
      switch (attrib_type) {
        case AttribType.NUMERIC: {
          const attribute = geometry.getAttribute(attribute_name);
          if (attribute) {
            return attribute.array[intersection.index];
          }
          return;
        }
        case AttribType.STRING: {
          const core_geometry = new CoreGeometry(geometry);
          const core_point = core_geometry.points()[intersection.index];
          if (core_point) {
            return core_point.stringAttribValue(attribute_name);
          }
          return;
        }
      }
      TypeAssert.unreachable(attrib_type);
    }
  }
  _set_position_param(hit_position) {
    hit_position.toArray(this._hit_position_array);
    if (this._node.pv.tpositionTarget) {
      if (Poly2.playerMode()) {
        this._found_position_target_param = this._found_position_target_param || this._node.p.positionTarget.found_param_with_type(ParamType2.VECTOR3);
      } else {
        const target_param = this._node.p.positionTarget;
        this._found_position_target_param = target_param.found_param_with_type(ParamType2.VECTOR3);
      }
      if (this._found_position_target_param) {
        this._found_position_target_param.set(this._hit_position_array);
      }
    } else {
      this._node.p.position.set(this._hit_position_array);
    }
    this.velocity_controller.process(hit_position);
  }
  _prepare_raycaster(context) {
    const points_param = this._raycaster.params.Points;
    if (points_param) {
      points_param.threshold = this._node.pv.pointsThreshold;
    }
    let camera_node = context.cameraNode;
    if (this._node.pv.overrideCamera) {
      if (this._node.pv.overrideRay) {
        this._raycaster.ray.origin.copy(this._node.pv.rayOrigin);
        this._raycaster.ray.direction.copy(this._node.pv.rayDirection);
      } else {
        const found_camera_node = this._node.p.camera.found_node_with_context(NodeContext2.OBJ);
        if (found_camera_node) {
          camera_node = found_camera_node;
        }
      }
    }
    if (camera_node && !this._node.pv.overrideRay) {
      camera_node.prepare_raycaster(this._mouse, this._raycaster);
    }
  }
  update_target() {
    const targetType = TARGET_TYPES[this._node.pv.targetType];
    switch (targetType) {
      case TargetType.NODE: {
        return this._update_target_from_node();
      }
      case TargetType.SCENE_GRAPH: {
        return this._update_target_from_scene_graph();
      }
    }
    TypeAssert.unreachable(targetType);
  }
  _update_target_from_node() {
    const node = this._node.p.targetNode.value.ensure_node_context(NodeContext2.OBJ);
    if (node) {
      const found_obj = this._node.pv.traverseChildren ? node.object : node.children_display_controller.sop_group;
      if (found_obj) {
        this._resolved_targets = [found_obj];
      } else {
        this._resolved_targets = void 0;
      }
    } else {
      this._node.states.error.set("node is not an object");
    }
  }
  _update_target_from_scene_graph() {
    const objects = this._node.scene().objectsByMask(this._node.pv.objectMask);
    if (objects.length > 0) {
      this._resolved_targets = objects;
    } else {
      this._resolved_targets = void 0;
    }
  }
  async update_position_target() {
    if (this._node.p.positionTarget.isDirty()) {
      await this._node.p.positionTarget.compute();
    }
  }
  static PARAM_CALLBACK_update_target(node) {
    node.cpu_controller.update_target();
  }
  static PARAM_CALLBACK_print_resolve(node) {
    node.cpu_controller.print_resolve();
  }
  print_resolve() {
    this.update_target();
    console.log(this._resolved_targets);
  }
};
export let RaycastCPUController = RaycastCPUController2;
RaycastCPUController._vA = new Vector32();
RaycastCPUController._vB = new Vector32();
RaycastCPUController._vC = new Vector32();
RaycastCPUController._uvA = new Vector22();
RaycastCPUController._uvB = new Vector22();
RaycastCPUController._uvC = new Vector22();
RaycastCPUController._hitUV = new Vector22();
