extends CharacterBody3D
class_name Player

const SPEED = 150.0
const RUN_SPEED = 400.0
const JUMP_VELOCITY = 200

@export var state_machine: LimboHSM 
@onready var idle_state: LimboState = $LimboHSM/Idle
@onready var move_state: LimboState = $LimboHSM/Move
@onready var jump_state: LimboState = $LimboHSM/Jump
@onready var fall_state: LimboState = $LimboHSM/Fall
@onready var pause_state: LimboState = $LimboHSM/Pause
@onready var crouch_state: LimboState = $LimboHSM/Crouch

var movement_input = Vector3.ZERO
var last_direction:= Vector3.ONE
@onready var camera: Camera3D = $Camera3D

var armed = false
var can_pause = true

func _ready():
  state_machine = $LimboHSM
  _initialize_state_machine()
  GDRx.on_physics_step()\
    .subscribe(check_buffered_jump).dispose_with(self)
  GDRx.on_physics_step()\
    .subscribe(check_coyote_jump).dispose_with(self)
  GDRx.on_physics_step()\
    .subscribe(handle_jump).dispose_with(self)
  GDRx.on_physics_step()\
    .subscribe(handle_move).dispose_with(self)
    
func _initialize_state_machine():
  state_machine.add_transition(idle_state, move_state, "to_move")
  state_machine.add_transition(move_state, idle_state, "to_idle")
  state_machine.add_transition(move_state, jump_state, "to_jump")
  state_machine.add_transition(idle_state, jump_state, "to_jump")
  state_machine.add_transition(jump_state, fall_state, "to_fall")
  state_machine.add_transition(state_machine.ANYSTATE, pause_state, "to_pause")
  state_machine.add_transition(state_machine.ANYSTATE, crouch_state, "to_crouch")
  state_machine.add_transition(crouch_state, idle_state, "to_idle")
  state_machine.add_transition(crouch_state, move_state, "to_move")
  
  state_machine.initial_state = idle_state
  state_machine.initialize(self)
  state_machine.set_active(true)


func apply_movement(delta: float) -> void:
  # Add the gravity.
  if not is_on_floor():
    velocity += get_gravity() * delta
  var k = RUN_SPEED if Input.is_key_pressed(KEY_SHIFT) else SPEED
  self.velocity.x = delta * k * Input.get_axis("move_left", "move_right")
  self.velocity.z = delta * k * Input.get_axis("move_forward", "move_back")
  
  if Input.is_key_pressed(KEY_E):
    var s: SpineSprite = $MeshInstance3D/SubViewport/SpineSprite
    var a = s.get_animation_state()
    a.set_animation("holding/shoot", false, 3)
    shoot_gun()
    armed = !armed
  Messagebus.DEBUG_XYZ.emit(self.global_transform.origin)
  move_and_slide()

func shoot_gun():
  var space_state = get_world_3d().direct_space_state
  var height = 0.2
  var range = 1.8
  var barrel = 0.3
  var start = self.to_global(Vector3(barrel * sign(last_direction.x), height, 0))
  var end = self.to_global(Vector3((0.4 * sign(last_direction.x)) - barrel, height, 0)) 
  line(start, end, Color.WHITE, 4)
  var query = PhysicsRayQueryParameters3D.create(start, end)
  query.exclude = [self]
  var result = space_state.intersect_ray(query)
  print(result)

func line(pos1: Vector3, pos2: Vector3, color = Color.BLACK, persist_ms = 0):
  var mesh_instance := MeshInstance3D.new()
  var immediate_mesh := ImmediateMesh.new()
  var material := ORMMaterial3D.new()

  mesh_instance.mesh = immediate_mesh
  mesh_instance.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF

  immediate_mesh.surface_begin(Mesh.PRIMITIVE_LINES, material)
  immediate_mesh.surface_add_vertex(pos1)
  immediate_mesh.surface_add_vertex(pos2)
  immediate_mesh.surface_end()

  material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
  material.albedo_color = color

  return await final_cleanup(mesh_instance, persist_ms)
  

func final_cleanup(mesh_instance: MeshInstance3D, persist_ms: float):
  get_tree().get_root().add_child(mesh_instance)
  if persist_ms == 1:
    await get_tree().physics_frame
    mesh_instance.queue_free()
  elif persist_ms > 0:
    await get_tree().create_timer(persist_ms).timeout
    mesh_instance.queue_free()
  else:
    return mesh_instance

func apply_jump() -> void:
  pass
  #move_and_slide()

func check_coyote_jump(delta) -> void:
  pass

func check_buffered_jump(delta) -> void:
  pass

func handle_jump(delta) -> void:
  pass

func handle_move(delta) -> void:
  pass

func check_jump_input(delta) -> void:
  check_coyote_jump(delta)
  check_buffered_jump(delta)
  if Input.is_action_just_pressed("jump") and is_on_floor():
    state_machine.dispatch("to_jump")
    self.velocity.y = delta * JUMP_VELOCITY
  
func _physics_process(delta: float) -> void:
  var i = Input.get_axis("move_left", "move_right")
  if snapped(i, 1):
    self.last_direction = Vector3(sign(i), 1, 1)
  var t: Material = $MeshInstance3D.material_override
  t.uv1_scale = self.last_direction
  # handle camera
  camera.position.x = lerp(camera.position.x, position.x, 0.08)
  camera.position.y = lerp(camera.position.y, position.y, 0.08) + 0.07
  camera.position.z = position.z + 4

func _unhandled_input(_event: InputEvent) -> void:
  if Input.is_action_pressed("zoom_in"):
    camera.fov -= 1 * (5 if Input.is_key_pressed(KEY_SHIFT) else 1)
    camera.size -= 1 * (0.2 if Input.is_key_pressed(KEY_SHIFT) else 0.05)
  if Input.is_action_pressed("zoom_out"):
    camera.fov += 1 * (5 if Input.is_key_pressed(KEY_SHIFT) else 1)
    camera.size += 1 * (0.2 if Input.is_key_pressed(KEY_SHIFT) else 0.05)
