using Godot;

public partial class Watcher : Node2D
{
	public enum State
	{
		Waiting,
		Opening,
		Looking,
		PreparingToTeleport,
		Teleporting,
		LitUp
	}

	[Export] public float EyeDotSpeed = 2f;
	[Export] public Vector2 EyeDotDistance = new(4f, 3f);
	[Export] public float EyeTeleportDistance = 48f;

	public State CurrentState
	{
		get => _state;

		private set
		{
			_state = value;
			_timeSinceState = 0;
			GD.Print($"watcher: new state is {_state}");

			switch (_state)
			{
				case State.Waiting:
					HideEye();
					break;

				case State.Opening:
					_dotSprite.Position = _dotSpriteInitialPos;
					ShowEye();
					break;

				case State.Looking:
					break;

				case State.PreparingToTeleport:
					_sprite.PlayBackwards("default");
					break;

				case State.Teleporting:
					HideEye();
					break;

				case State.LitUp:
					_isLitUp = false;
					
					((AudioStreamPlayer2D)FindChild("SighPlayer2D")).Play();
					
					var newPosition = (_player.Position - Position).Normalized();
					if (newPosition.IsZeroApprox())
						newPosition = new Vector2(0, -1);
					Position = _player.Position + newPosition * Constants.HalfScreenSize * 1.5f; // TODO: magic number

					HideEye();
					break;

				default:
					break;
			}
		}
	}

	private Player _player;
	private AnimatedSprite2D _sprite;
	private Sprite2D _dotSprite;
	private Vector2 _dotSpriteInitialPos;
	private State _state;
	private float _timeSinceState;
	private bool _isPlayerInside;
	private bool _isLitUp;

	public override void _Ready()
	{
		_sprite = (AnimatedSprite2D)FindChild("AnimatedSprite2D");

		_dotSprite = (Sprite2D)FindChild("Sprite2D");
		_dotSpriteInitialPos = _dotSprite.Position;

		CurrentState = State.Waiting;
	}

	public override void _PhysicsProcess(double delta)
	{
		_timeSinceState += (float)delta;

		switch (_state)
		{
			case State.Waiting:
				break;

			case State.Opening:
				break;

			case State.Looking:
			{
				var playerRelative = _player.Position - Position;
				var normal = playerRelative.Normalized();
				_dotSprite.Position = _dotSprite.Position.Lerp(normal * EyeDotDistance, EyeDotSpeed * (float)delta);

				if (_timeSinceState > 5f) // 5 seconds to look at the player
					CurrentState = State.PreparingToTeleport;
			}
				break;

			case State.PreparingToTeleport:
				break;

			case State.Teleporting:
				if (_timeSinceState > 1f) // 1 second to teleport
				{
					var newPosition = _player.Position - Position;
					if (newPosition.Length() > EyeTeleportDistance)
						newPosition = newPosition.Normalized() * EyeTeleportDistance;

					Position += newPosition;
					CurrentState = State.Opening;
				}

				break;

			case State.LitUp:
				if (_timeSinceState > 2f) // 2 seconds of cooldown
				{
					CurrentState = State.Opening;
				}
				break;

			default:
				break;
		}

		CheckIfKilledPlayer();
		CheckIfLitUp();
	}

	private void AnimationEnded()
	{
		switch (CurrentState)
		{
			case State.Opening:
				CurrentState = State.Looking;
				break;

			case State.PreparingToTeleport:
				CurrentState = State.Teleporting;
				break;

			case State.LitUp:
				//QueueFree();
				GD.Print("TODO: watcher: teleport");
				break;

			case State.Waiting:
			case State.Looking:
			case State.Teleporting:
			default:
				break;
		}
	}

	private void LightEntered(Area2D area)
	{
		if (area.GetParentOrNull<GameCamera>() is null)
			return; // Not a flashlight

		_isLitUp = true;
		GD.Print("watcher: light enter");
	}

	private void LightExited(Area2D area)
	{
		if (area.GetParentOrNull<GameCamera>() is null)
			return; // Not a flashlight

		_isLitUp = false;
		GD.Print("watcher: light exit");
	}

	private void PlayerEntered(Node2D body)
	{
		if (body is not Player player)
			return;

		_isPlayerInside = true;
	}

	private void PlayerLeft(Node2D body)
	{
		if (body is not Player player)
			return;

		_isPlayerInside = false;
	}

	private void PlayerActivated(Node2D body)
	{
		GD.Print("watcher: player activated1");
		if (CurrentState != State.Waiting || body is not Player player)
			return;

		// TODO: turn off the player activation field for better performance

		GD.Print("watcher: player activated");
		_player = player;
		CurrentState = State.Opening;
	}

	void HideEye()
	{
		Modulate = new Color(1, 1, 1, 0);
		_sprite.Stop();
	}

	void ShowEye()
	{
		Modulate = new Color(1, 1, 1, 1);
		_sprite.Play("default");
	}

	void CheckIfKilledPlayer()
	{
		if (_player is null || !_isPlayerInside)
			return;

		if (CurrentState is State.LitUp or State.PreparingToTeleport or State.Teleporting or State.Waiting)
			return;

		_player.Kill(this);
	}

	void CheckIfLitUp()
	{
		if (!_isLitUp)
			return;

		if (CurrentState is State.Waiting or State.Opening or State.Teleporting or State.LitUp)
			return;

		CurrentState = State.LitUp;
	}
}