using Godot;
using System;
using System.Collections.Generic;

public partial class Player : CharacterBody2D
{
	private const double TransitionTime = 0.5;
	private const double CameraTransitionTime = 0.25;

	public enum State
	{
		Normal,
		Wait,
		Transition,
		ReadChat,
		ChatWithNPC
	}

	private State _state;

	[Export]
	public State CurrentState
	{
		get => _state;
		set
		{
			_sprite.Play("default");
			switch (value)
			{
				case State.Normal:
					if (_state == State.Transition) _doorSounds.Play();
					Visible = true;
					break;
				case State.Transition:
					Visible = false;
					break;
				case State.ReadChat:
					if (CurrentState == State.Wait) return;
					_nextLabel.Visible = false;
					_exitLabel.Visible = true;
					_chatLogContainer.CurrentState = ChatLogContainer.ChatState.Default;
					break;
				case State.Wait:
					break;
				case State.ChatWithNPC:
					_nextLabel.Visible = true;
					_exitLabel.Visible = false;
					_chatLogContainer.CurrentState = ChatLogContainer.ChatState.Default;
					break;
				default:
					throw new ArgumentOutOfRangeException();
			}

			_state = value;
		}
	}

	[Export] public float Speed = 300.0f;

	// Get the gravity from the project settings to be synced with RigidBody nodes.
	public float Gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle();

	public List<Node2D> InteractableObjects = new List<Node2D>();

	public Node2D InteractableObject
	{
		get
		{
			if (InteractableObjects.Count == 0) return null;
			var nearObj = InteractableObjects[0];
			var minDistance = (GlobalPosition - nearObj.GlobalPosition).Length();
			foreach (var obj in InteractableObjects)
			{
				var distance = (GlobalPosition - obj.GlobalPosition).Length();
				if (minDistance > distance)
				{
					minDistance = distance;
					nearObj = obj;
				}
			}

			return nearObj;
		}
	}

	private AnimatedSprite2D _sprite;
	private PanelContainer _chatLog;
	private PanelContainer _nextLabel;
	private PanelContainer _exitLabel;
	private ChatLogContainer _chatLogContainer;
	private Camera2D _camera;
	private AudioCollection _footsteps;
	private AudioCollection _doorSounds;

	private Vector2 _newPosition;
	private double _currentTransitionTime = 0;
	private double _currentCameraTransitionTime = 0;
	private Vector2 _previousPosition;
	private Vector2 _nextPosition;
	private Vector2 _cameraDefaultPosition = new(0, -20);
	private Vector2 _cameraChatLogPosition = new(108, -20);

	private PackedScene _dialogBox = GD.Load<PackedScene>("res://prefabs/Dialog.tscn");

	public ColorRect CRect { get; private set; }

	public override void _Ready()
	{
		_sprite = (AnimatedSprite2D)FindChild("AnimatedSprite2D");
		_chatLog = (PanelContainer)FindChild("ChatLog");
		_nextLabel = (PanelContainer)FindChild("NextLabel");
		_exitLabel = (PanelContainer)FindChild("ExitLabel");
		_chatLogContainer = (ChatLogContainer)FindChild("ChatLogContainer");
		_camera = (Camera2D)FindChild("Camera2D");
		_footsteps = (AudioCollection)FindChild("Footsteps");
		_doorSounds = (AudioCollection)FindChild("DoorSounds");
		CRect = (ColorRect)FindChild("ColorRect");
	}

	public override void _PhysicsProcess(double delta)
	{
		switch (CurrentState)
		{
			case State.Normal:

				HideChatLog(delta);

				Vector2 velocity = Velocity;

				Vector2 direction = Input.GetVector("move_left", "move_right", "move_up", "move_down");
				if (direction != Vector2.Zero)
				{
					_footsteps.Play();
					velocity.X = direction.X * Speed;
					_sprite.Play("walk");
					_sprite.FlipH = direction.X switch
					{
						> 0 => false,
						< 0 => true,
						_ => _sprite.FlipH
					};
				}
				else
				{
					// Footsteps.Stop();
					velocity.X = Mathf.MoveToward(Velocity.X, 0, Speed);
					_sprite.Play("default");
				}

				Velocity = velocity;
				MoveAndSlide();
				break;
			case State.Transition:
				if (_currentTransitionTime < TransitionTime)
				{
					GlobalPosition = _previousPosition.Lerp(_nextPosition,
						(float)(_currentTransitionTime * 1 / TransitionTime));
					_currentTransitionTime += delta;
				}
				else
				{
					_currentTransitionTime = 0;
					CurrentState = State.Normal;
				}

				break;
			case State.ReadChat:
				ShowChatLog(delta);
				break;
			case State.ChatWithNPC:
				ShowChatLog(delta);
				break;
			case State.Wait:
				// _camera.Offset = _cameraDefaultPosition;
				HideChatLog(delta, 3);
				break;
			default:
				throw new ArgumentOutOfRangeException();
		}
	}

	private void HideChatLog(double delta, double transitionTime = CameraTransitionTime)
	{
		if (_camera.Offset != _cameraDefaultPosition)
		{
			if (_currentCameraTransitionTime < transitionTime)
			{
				_camera.Offset = _cameraChatLogPosition.Lerp(_cameraDefaultPosition,
					(float)(_currentCameraTransitionTime * 1 / transitionTime));
				_currentCameraTransitionTime += delta;
			}
			else
			{
				_currentCameraTransitionTime = 0;
			}
		}
	}

	private void ShowChatLog(double delta)
	{
		if (_camera.Offset != _cameraChatLogPosition)
		{
			if (_currentCameraTransitionTime < CameraTransitionTime)
			{
				_camera.Offset = _cameraDefaultPosition.Lerp(_cameraChatLogPosition,
					(float)(_currentCameraTransitionTime * 1 / CameraTransitionTime));
				_currentCameraTransitionTime += delta;
			}
			else
			{
				_currentCameraTransitionTime = 0;
			}
		}
	}

	
	public void AddMessage(NPC npc)
	{
		var msg = _dialogBox.Instantiate<Dialog>();
		msg.Text = npc.Message;
		msg.Author = npc.NPCName;
		_chatLogContainer.AddChild(msg);
		_chatLogContainer.CurrentState = ChatLogContainer.ChatState.Default;
		if (npc.IsDialogEnded) CurrentState = State.ReadChat;
	}
	
	public override void _Input(InputEvent @event)
	{
		if (@event.IsActionPressed("Interact"))
		{
			if (CurrentState == State.Transition) return;
			
			if (InteractableObject is NPC npc)
			{

				switch (CurrentState)
				{
					case State.Normal:

						if (GlobalPosition.X - npc.GlobalPosition.X > 0)
						{
							npc.Flip = false;
							_sprite.FlipH = true;
						}
						else
						{
							npc.Flip = true;
							_sprite.FlipH = false;
						}
						CurrentState = State.ChatWithNPC;
						AddMessage(npc);
						break;
					case State.ChatWithNPC:
						AddMessage(npc);
						break;
				}
			}

			if (InteractableObject is Door door)
			{
				_doorSounds.Play();
				_previousPosition = GlobalPosition;
				_nextPosition = door.Exit.GlobalPosition;
				CurrentState = State.Wait;
			}

			if (InteractableObject is Interactable obj)
			{
				obj.Interact();
			}
		}

		if (@event.IsActionPressed("show_chat"))
		{
			switch (CurrentState)
			{
				case State.Normal:
					CurrentState = State.ReadChat;
					break;
				case State.ReadChat:
					CurrentState = State.Normal;
					_chatLogContainer.CurrentState = ChatLogContainer.ChatState.Hidden;
					break;
			}
		}
	}

	private void _on_door_opened()
	{
		if (CurrentState == State.Wait)
		{
			CurrentState = State.Transition;
		}
	}
	
}