#pragma once

#include "extensions/common/characteranimator.h"
#include "extensions/thirdpersoncamera/displayactor.h"
#include "extensions/thirdpersoncamera/inputhandler.h"
#include "extensions/thirdpersoncamera/orbitcamera.h"
#include "mxtypes.h"

#include <SDL3/SDL_events.h>
#include <cstdint>
#include <functional>

class IslePathActor;
class LegoNavController;
class LegoPathActor;
class LegoROI;
class LegoWorld;
class Vector3;

namespace Extensions
{
namespace ThirdPersonCamera
{

class Controller {
public:
	Controller();

	void Enable();
	void Disable(bool p_preserveTouch = false);
	bool IsEnabled() const { return m_enabled; }
	bool IsActive() const { return m_active; }

	void OnActorEnter(IslePathActor* p_actor);
	void OnActorExit(IslePathActor* p_actor);
	void OnCamAnimEnd(LegoPathActor* p_actor);

	void Tick(float p_deltaTime);

	void SetWalkAnimId(uint8_t p_walkAnimId);
	uint8_t GetWalkAnimId() const { return m_animator.GetWalkAnimId(); }
	void SetIdleAnimId(uint8_t p_idleAnimId);
	uint8_t GetIdleAnimId() const { return m_animator.GetIdleAnimId(); }
	void TriggerExtraAnim(uint8_t p_id);
	bool IsExtraAnimBlocking() const;
	int8_t GetFrozenExtraAnimId() const;
	void SetExtraAnimHandler(Common::IExtraAnimHandler* p_handler) { m_animator.SetExtraAnimHandler(p_handler); }

	void SetDisplayActorIndex(uint8_t p_displayActorIndex) { m_display.SetDisplayActorIndex(p_displayActorIndex); }
	uint8_t GetDisplayActorIndex() const { return m_display.GetDisplayActorIndex(); }
	LegoROI* GetDisplayROI() const { return m_display.GetDisplayROI(); }
	Common::CustomizeState& GetCustomizeState() { return m_display.GetCustomizeState(); }

	void ApplyCustomizeChange(uint8_t p_changeType, uint8_t p_partIndex)
	{
		m_display.ApplyCustomizeChange(p_changeType, p_partIndex);
	}
	void SetClickAnimObjectId(MxU32 p_clickAnimObjectId) { m_animator.SetClickAnimObjectId(p_clickAnimObjectId); }
	void StopClickAnimation();
	bool IsInVehicle() const { return m_animator.IsInVehicle(); }
	LegoROI* GetRideVehicleROI() const { return m_animator.GetRideVehicleROI(); }

	// Signal that an external animation is active.
	// p_lockDisplay: true if the display ROI is being driven by the animation (performer),
	//                false if just spectating (free to move).
	// p_onStop is called before the display ROI is destroyed (Deactivate/OnWorldDisabled).
	void SetAnimPlaying(
		bool p_animPlaying,
		bool p_animLockDisplay = true,
		std::function<void()> p_animStopCallback = nullptr
	)
	{
		m_animPlaying = p_animPlaying;
		m_animLockDisplay = p_animPlaying && p_animLockDisplay;
		m_animStopCallback = p_animPlaying ? std::move(p_animStopCallback) : nullptr;
	}
	bool IsAnimPlaying() const { return m_animPlaying; }
	bool IsAnimLockDisplay() const { return m_animLockDisplay; }

	void OnWorldEnabled(LegoWorld* p_world);
	void OnWorldDisabled(LegoWorld* p_world);

	MxBool HandleCameraRelativeMovement(
		LegoNavController* p_nav,
		const Vector3& p_curPos,
		const Vector3& p_curDir,
		Vector3& p_newPos,
		Vector3& p_newDir,
		float p_deltaTime
	);

	void HandleSDLEventImpl(SDL_Event* p_event);

	bool ConsumeAutoDisable() { return m_input.ConsumeAutoDisable(); }
	bool ConsumeAutoEnable() { return m_input.ConsumeAutoEnable(); }

	bool IsLeftButtonHeld() const { return m_input.IsLeftButtonHeld(); }
	bool IsLmbForwardEngaged() const { return m_lmbForwardEngaged; }
	void SetLmbForwardEngaged(bool p_lmbForwardEngaged) { m_lmbForwardEngaged = p_lmbForwardEngaged; }

	MxBool HandleFirstPersonForward(
		LegoNavController* p_nav,
		const Vector3& p_curPos,
		const Vector3& p_curDir,
		Vector3& p_newPos,
		Vector3& p_newDir,
		float p_deltaTime
	);

	float GetOrbitDistance() const { return m_orbit.GetOrbitDistance(); }
	void SetOrbitDistance(float p_distance) { m_orbit.SetOrbitDistance(p_distance); }
	void ResetTouchState() { m_input.ResetTouchState(); }
	void SuppressGestures() { m_input.SuppressGestures(); }

	bool TryClaimFinger(const SDL_TouchFingerEvent& p_event) { return m_input.TryClaimFinger(p_event); }
	bool TryReleaseFinger(SDL_FingerID p_id) { return m_input.TryReleaseFinger(p_id); }
	bool IsFingerTracked(SDL_FingerID p_id) const { return m_input.IsFingerTracked(p_id); }
	int GetTouchCount() const { return m_input.GetTouchCount(); }
	SDL_FingerID GetFingerID(int p_idx) const { return m_input.GetFingerID(p_idx); }

	void FreezeDisplayActor() { m_display.FreezeDisplayActor(); }
	void UnfreezeDisplayActor() { m_display.UnfreezeDisplayActor(); }
	bool IsDisplayActorFrozen() const { return m_display.IsDisplayActorFrozen(); }

	LegoROI* GetPlayerROI() const { return m_playerROI; }

	static constexpr float CAMERA_ZONE_X = InputHandler::CAMERA_ZONE_X;
	static constexpr float MIN_DISTANCE = OrbitCamera::MIN_DISTANCE;

private:
	void CancelExternalAnim();
	void Deactivate();
	void ReinitForCharacter(bool p_preserveCamera = false);

	OrbitCamera m_orbit;
	InputHandler m_input;
	DisplayActor m_display;
	Common::CharacterAnimator m_animator;

	bool m_enabled;
	bool m_active;
	bool m_pendingWorldTransition;
	bool m_animPlaying;
	bool m_animLockDisplay;
	std::function<void()> m_animStopCallback;
	bool m_lmbForwardEngaged;
	LegoROI* m_playerROI;
};

} // namespace ThirdPersonCamera
} // namespace Extensions
