import { Callable, CharacterBody3D, Color, float64, ProjectSettings, Variant, Vector2, Vector3 } from 'godot'
import { export_, help, onready } from 'godot.annotations'
import PlayerInput from './player_input'
import DebugDraw from './debug_draw'
import Interactor from './interactor'

export default class Player extends CharacterBody3D {
  gravity = ProjectSettings.get_setting('physics/3d/default_gravity')

  @onready("Input")
  player_input!: PlayerInput

  @onready("Interactor")
  interactor!: Interactor

  @help('Forward walk speed in units per second')
  @export_(Variant.Type.TYPE_FLOAT)
  walk_speed = 3

  @help('Run speed in units per second')
  @export_(Variant.Type.TYPE_FLOAT)
  run_speed = 6

  @help('Turn speed in rotations per second')
  @export_(Variant.Type.TYPE_FLOAT)
  turn_speed = 1
  _rotation_speed = 2 * Math.PI

  is_running() {
    return this.player_input.is_running && !this.velocity.is_zero_approx()
  }

  _ready(): void {
    this._rotation_speed = this.turn_speed * 2 * Math.PI

    this.player_input.interact.connect(
      Callable.create(
        this,
        this.try_interact
      )
    )
  }

  try_interact() {
    this.interactor.try_interact_nearest()
  }

  get_movement(input: Vector2): Vector3 {
    let speed = this.walk_speed

    if (this.is_running()) {
      input = input.normalized()
      speed = this.run_speed
    }

    return Vector3.MULTIPLY(speed, new Vector3(
      input.x,
      0,
      input.y
    ))
  }

  get_gravitational_force(delta: float64): float64 {
    return -this.gravity * delta
  }

  is_kinda_equal(a: Vector2, b: Vector2, e: float64 = 0.1) {
    return a.distance_to(b) <= e
  }

  _draw_movement(movement: Vector3) {
    DebugDraw.draw_line(
      Vector3.ADD(
        Vector3.UP,
        this.position,
      ),
      Vector3.ADD(
        Vector3.UP,
        Vector3.ADD(this.position, movement)
      ),
      Color.GREEN
    )
  }

  rotate_toward(direction: Vector3, _delta: float64) {
    if (!direction.is_zero_approx()) {
      this.look_at(
        Vector3.ADD(this.global_position, direction),
        Vector3.UP,
        true
      )
    }
  }

  move_toward(movement: Vector3, delta: float64) {
    this.velocity = new Vector3(
      movement.x,
      this.velocity.y + this.get_gravitational_force(delta),
      movement.z
    )

    this.move_and_slide()
  }

  _physics_process(delta: float64): void {
    const input = this.player_input.movement_input

    const movement = this.get_movement(input)

    this._draw_movement(movement)

    this.rotate_toward(movement, delta)
    this.move_toward(movement, delta)
  }
}

