using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class SkillModule : MonoBehaviour
{
    [SerializeField] private int _maxSlots = 4;
    [SerializeField] private LayerMask _groundLayer; // 바닥 레이캐스트용

    // 범위 지정 시작/종료 이벤트 (외부에서 RotationMode/UI 등 반응)
    public event Action OnAreaSelectStarted;
    public event Action OnAreaSelectEnded;

    private PlayerStateMachine _stateMachine;
    private Animator _anim;
    private Transform _transform;

    private SkillInstance[] _equippedSkills;
    private ISkillEffect[] _skillEffects;

    // 차지
    private int _chargingSlot = -1;
    private float _chargeTimer = 0f;

    // 캐스팅
    private int _castingSlot = -1;
    private float _castTimer = 0f;

    // 채널링
    private int _channelingSlot = -1;
    private float _channelTimer = 0f;
    private float _channelTickAccumulator = 0f;

    // 범위 지정 (Remote)
    private int _areaSelectSlot = -1;
    private GameObject _areaIndicator;

    // Remote 타겟 위치
    private Vector3 _remoteTargetPos;
    private bool _hasRemoteTarget;

    public bool IsAreaSelecting => _areaSelectSlot >= 0;
    public bool IsCasting => _castingSlot >= 0;
    public bool IsChanneling => _channelingSlot >= 0;

    [SerializeField] private List<WeaponSkillSet> _weaponSkills;
    [SerializeField] private Weapon _equippedWeapon;

    private Dictionary<WeaponType, WeaponSkillSet> _dicSkills = new Dictionary<WeaponType, WeaponSkillSet>();

    // 슬롯 이름 → 런타임 매핑 (Dispatcher 테이블)
    private Dictionary<string, IUseableRuntime> _skillBindData = new Dictionary<string, IUseableRuntime>();

    // 퀵슬롯에 바인딩할 슬롯 이름 목록 (스킬용)
    private static readonly string[] SkillSlotNames =
        { "UseSlot_Q", "UseSlot_E", "UseSlot_R", "UseSlot_T" };

    private void Awake()
    {
        _stateMachine = GetComponent<PlayerStateMachine>();
        _anim = GetComponent<Animator>();
        _transform = transform;

        _equippedSkills = new SkillInstance[_maxSlots];
        _skillEffects = new ISkillEffect[_maxSlots];
    }

    private void Start()
    {
        // 무기별 스킬셋 테이블 구성
        foreach (WeaponSkillSet wss in _weaponSkills)
        {
            _dicSkills[wss.WeaponType] = wss;
        }

        // 현재 장착 무기의 스킬셋 로드 (무기 미장착 시 스킬 없음)
        if (_equippedWeapon != null
            && _dicSkills.TryGetValue(_equippedWeapon.WType, out WeaponSkillSet out_wss)
            && out_wss != null)
        {
            LoadWeaponSkills(out_wss);
            RegisterSkillsToQuickslot(out_wss);
        }

        // 각 슬롯에 dispatcher를 한 번만 바인딩 (이후 내용물만 _skillBindData로 교체)
        foreach (string slotName in SkillSlotNames)
        {
            string captured = slotName;
            GameManager.Instance.Level.BindSlotAction(captured, (state) => DispatchSlot(captured, state));
        }
    }

    private void DispatchSlot(string slotName, InputState state)
    {
        if (!_skillBindData.TryGetValue(slotName, out IUseableRuntime runtime) || runtime == null)
            return;

        runtime.Execute(new UseContext
        {
            Caster = gameObject,
            Target = null,
            UseInputState = state
        });
    }

    private void RegisterSkillsToQuickslot(WeaponSkillSet wss)
    {
        for (int i = 0; i < SkillSlotNames.Length; i++)
        {
            if (i < wss.Skills.Count && wss.Skills[i] != null)
                _skillBindData[SkillSlotNames[i]] = wss.Skills[i].CreateRuntime();
            else
                _skillBindData.Remove(SkillSlotNames[i]);
        }
    }

    private void Update()
    {
        TickCooldowns();
        TickCharge();
        TickCast();
        TickChannel();
        TickAreaSelect();
    }

    #region 스킬 장착
    public void LoadWeaponSkills(WeaponSkillSet skillSet)
    {
        for (int i = 0; i < _maxSlots; i++)
        {
            if (i < skillSet.Skills.Count && skillSet.Skills[i] != null)
            {
                _equippedSkills[i] = new SkillInstance(skillSet.Skills[i]);
                _skillEffects[i] = ResolveEffect(skillSet.Skills[i].TargetType);
            }
            else
            {
                _equippedSkills[i] = null;
                _skillEffects[i] = null;
            }
        }

        ApplyPassives();
    }

    private ISkillEffect ResolveEffect(TargetType targetType)
    {
        return targetType switch
        {
            TargetType.Self => GetComponent<BuffEffect>(),
            TargetType.Single => GetComponent<DamageEffect>(),
            TargetType.Area => GetComponent<AreaEffect>(),
            TargetType.Projectile => GetComponent<ProjectileEffect>(),
            TargetType.Zone => GetComponent<ZoneEffect>(),
            _ => null
        };
    }
    #endregion

    #region 입력
    public void SkillInput(int slotIndex, InputState inputState)
    {
        if (slotIndex < 0 || slotIndex >= _maxSlots) return;

        SkillInstance skill = _equippedSkills[slotIndex];
        if (skill == null) return;
        if (skill.Data.SkillType == SkillType.Passive) return;

        if (inputState == InputState.Started)
        {
            // 범위 지정 중에 같은 스킬 키를 다시 누르면 취소
            if (_areaSelectSlot == slotIndex)
            {
                CancelAreaSelect();
                return;
            }

            if (!CanUseSkill(skill)) return;

            // Remote 스킬: 먼저 범위 지정 모드 진입
            if (skill.Data.CastMethod == CastMethod.Remote)
            {
                StartAreaSelect(slotIndex);
            }
            else
            {
                // Self 스킬: 바로 ActivationType 흐름
                BeginActivation(slotIndex);
            }
        }
        else if (inputState == InputState.Canceled)
        {
            if (_chargingSlot == slotIndex)
            {
                ReleaseCharge();
            }
            else if (_channelingSlot == slotIndex)
            {
                CancelChannel();
            }
        }
    }

    /// <summary>
    /// 범위 지정 모드 중 마우스 클릭 입력 (InputManager에서 호출)
    /// </summary>
    public void AreaConfirmInput(InputState inputState)
    {
        if (_areaSelectSlot < 0) return;

        if (inputState == InputState.Started)
        {
            ConfirmAreaSelect();
        }
    }
    #endregion

    #region 발동 분기
    private void BeginActivation(int slotIndex)
    {
        SkillInstance skill = _equippedSkills[slotIndex];

        switch (skill.Data.ActivationType)
        {
            case ActivationType.Instant:
                ExecuteSkill(slotIndex);
                break;
            case ActivationType.Charge:
                StartCharge(slotIndex);
                break;
            case ActivationType.Cast:
                StartCast(slotIndex);
                break;
            case ActivationType.Channel:
                StartChannel(slotIndex);
                break;
        }
    }
    #endregion

    #region 실행
    private bool CanUseSkill(SkillInstance skill)
    {
        if (skill.IsOnCooldown) return false;

        PlayerState state = _stateMachine.CurrentState;
        if (state == PlayerState.Dead || state == PlayerState.Hit
            || state == PlayerState.Dodge || state == PlayerState.Trans
            || state == PlayerState.Action || state == PlayerState.Cast
            || state == PlayerState.Channel)
            return false;

        return true;
    }

    private void ExecuteSkill(int slotIndex)
    {
        SkillInstance skill = _equippedSkills[slotIndex];
        ISkillEffect effect = _skillEffects[slotIndex];

        _stateMachine.ChangeState(PlayerState.Attack);

        if (!string.IsNullOrEmpty(skill.Data.AnimTrigger))
            _anim.SetTrigger(skill.Data.AnimTrigger);

        float chargeRatio = 1f;
        if (skill.Data.ActivationType == ActivationType.Charge)
        {
            float maxCharge = skill.CurrentLevelData.ChargeTimeMax;
            chargeRatio = maxCharge > 0 ? Mathf.Clamp01(_chargeTimer / maxCharge) : 1f;
        }

        // CastMethod에 따라 실행 방식 분기
        if (_hasRemoteTarget)
        {
            effect?.ExecuteAtPosition(skill, _transform, _remoteTargetPos, chargeRatio);
            ClearRemoteTarget();
        }
        else
        {
            effect?.Execute(skill, _transform, chargeRatio);
        }

        skill.StartCooldown();
        _chargeTimer = 0f;
        _chargingSlot = -1;

        // ActionDuration 경과 후 Idle 복귀
        float duration = skill.CurrentLevelData.ActionDuration;
        if (duration > 0f)
        {
            _ = Util.RunDelayed(duration, () =>
            {
                if (this != null && _stateMachine != null
                    && _stateMachine.CurrentState == PlayerState.Attack)
                    _stateMachine.ChangeState(PlayerState.Idle);
            }, default);
        }
        else
        {
            _stateMachine.ChangeState(PlayerState.Idle);
        }
    }

    private void ClearRemoteTarget()
    {
        _hasRemoteTarget = false;
    }
    #endregion

    #region 차지
    private void StartCharge(int slotIndex)
    {
        _chargingSlot = slotIndex;
        _chargeTimer = 0f;

        _stateMachine.ChangeState(PlayerState.Charge);

        SkillInstance skill = _equippedSkills[slotIndex];
        if (!string.IsNullOrEmpty(skill.Data.AnimTrigger))
            _anim.SetTrigger(skill.Data.AnimTrigger);
    }

    private void ReleaseCharge()
    {
        if (_chargingSlot < 0) return;
        ExecuteSkill(_chargingSlot);
    }

    private void TickCharge()
    {
        if (_chargingSlot < 0) return;

        SkillInstance skill = _equippedSkills[_chargingSlot];
        _chargeTimer += Time.deltaTime;

        if (_chargeTimer >= skill.CurrentLevelData.ChargeTimeMax)
        {
            ReleaseCharge();
        }
    }
    #endregion

    #region 캐스팅
    private void StartCast(int slotIndex)
    {
        _castingSlot = slotIndex;
        _castTimer = 0f;

        _stateMachine.ChangeState(PlayerState.Cast);

        SkillInstance skill = _equippedSkills[slotIndex];
        if (!string.IsNullOrEmpty(skill.Data.AnimTrigger))
            _anim.SetTrigger(skill.Data.AnimTrigger);

        _stateMachine.OnStateChanged += OnCastInterrupted;
    }

    private void TickCast()
    {
        if (_castingSlot < 0) return;

        _castTimer += Time.deltaTime;

        SkillInstance skill = _equippedSkills[_castingSlot];
        if (_castTimer >= skill.CurrentLevelData.CastTime)
        {
            CompleteCast();
        }
    }

    private void CompleteCast()
    {
        if (_castingSlot < 0) return;

        _stateMachine.OnStateChanged -= OnCastInterrupted;
        ExecuteSkill(_castingSlot);
        _castingSlot = -1;
        _castTimer = 0f;
    }

    private void CancelCast()
    {
        _stateMachine.OnStateChanged -= OnCastInterrupted;
        _castingSlot = -1;
        _castTimer = 0f;
        ClearRemoteTarget();
    }

    private void OnCastInterrupted(PlayerState newState)
    {
        if (newState == PlayerState.Hit || newState == PlayerState.Dead
            || newState == PlayerState.Dodge)
        {
            CancelCast();
        }
    }
    #endregion

    #region 채널링
    private void StartChannel(int slotIndex)
    {
        _channelingSlot = slotIndex;
        _channelTimer = 0f;
        _channelTickAccumulator = 0f;

        _stateMachine.ChangeState(PlayerState.Channel);

        SkillInstance skill = _equippedSkills[slotIndex];
        if (!string.IsNullOrEmpty(skill.Data.AnimTrigger))
            _anim.SetTrigger(skill.Data.AnimTrigger);

        // 첫 틱 즉시 실행
        ISkillEffect effect = _skillEffects[slotIndex];
        ExecuteChannelTick(skill, effect);

        // 쿨다운은 채널 시작 시점에 시작
        skill.StartCooldown();

        _stateMachine.OnStateChanged += OnChannelInterrupted;
    }

    private void TickChannel()
    {
        if (_channelingSlot < 0) return;

        SkillInstance skill = _equippedSkills[_channelingSlot];
        SkillLevelData data = skill.CurrentLevelData;

        _channelTimer += Time.deltaTime;
        _channelTickAccumulator += Time.deltaTime;

        if (_channelTickAccumulator >= data.TickInterval)
        {
            _channelTickAccumulator -= data.TickInterval;
            ISkillEffect effect = _skillEffects[_channelingSlot];
            ExecuteChannelTick(skill, effect);
        }

        if (_channelTimer >= data.ChannelDuration)
        {
            EndChannel();
        }
    }

    private void ExecuteChannelTick(SkillInstance skill, ISkillEffect effect)
    {
        if (_hasRemoteTarget)
        {
            effect?.ExecuteAtPosition(skill, _transform, _remoteTargetPos, 1f);
        }
        else
        {
            effect?.Execute(skill, _transform, 1f);
        }
    }

    private void EndChannel()
    {
        _stateMachine.OnStateChanged -= OnChannelInterrupted;
        _stateMachine.ChangeState(PlayerState.Idle);
        _channelingSlot = -1;
        _channelTimer = 0f;
        _channelTickAccumulator = 0f;
        ClearRemoteTarget();
    }

    private void CancelChannel()
    {
        _stateMachine.OnStateChanged -= OnChannelInterrupted;
        _channelingSlot = -1;
        _channelTimer = 0f;
        _channelTickAccumulator = 0f;
        ClearRemoteTarget();
    }

    private void OnChannelInterrupted(PlayerState newState)
    {
        if (newState == PlayerState.Hit || newState == PlayerState.Dead
            || newState == PlayerState.Dodge)
        {
            CancelChannel();
        }
    }
    #endregion

    #region 범위 지정 (Remote)
    private void StartAreaSelect(int slotIndex)
    {
        _areaSelectSlot = slotIndex;
        SkillInstance skill = _equippedSkills[slotIndex];

        if (skill.Data.AreaIndicatorPrefab != null)
        {
            _areaIndicator = Instantiate(skill.Data.AreaIndicatorPrefab);
            float range = skill.CurrentLevelData.Range;
            _areaIndicator.transform.localScale = new Vector3(range * 2f, range * 2f, range * 2f);
        }

        // 범위 지정 중에는 좌클릭이 확정 용도로 쓰이므로 기본 공격 차단
        InputManager.Instance.SuppressNormalAttack = true;

        // 이벤트 발행 — 구독자(PlayerCharacterController 등)가 회전모드 등 처리
        OnAreaSelectStarted?.Invoke();
    }

    private void TickAreaSelect()
    {
        if (_areaSelectSlot < 0 || _areaIndicator == null) return;

        if (Mouse.current == null) return;
        Vector2 mousePos = Mouse.current.position.ReadValue();
        Ray ray = Camera.main.ScreenPointToRay(mousePos);
        if (Physics.Raycast(ray, out RaycastHit hit, 200f, _groundLayer))
        {
            _areaIndicator.transform.position = hit.point;
        }
    }

    private void ConfirmAreaSelect()
    {
        if (_areaSelectSlot < 0) return;

        int slotIndex = _areaSelectSlot;

        // 타겟 위치 저장
        _remoteTargetPos = _areaIndicator.transform.position;
        _hasRemoteTarget = true;

        // 인디케이터 정리
        Destroy(_areaIndicator);
        _areaIndicator = null;
        _areaSelectSlot = -1;

        // 범위 지정 종료 → 기본 공격 복구
        InputManager.Instance.SuppressNormalAttack = false;
        OnAreaSelectEnded?.Invoke();

        // ActivationType에 따라 흐름 시작
        BeginActivation(slotIndex);
    }

    public void CancelAreaSelect()
    {
        if (_areaIndicator != null)
            Destroy(_areaIndicator);

        _areaIndicator = null;
        _areaSelectSlot = -1;

        // 범위 지정 종료 → 기본 공격 복구
        InputManager.Instance.SuppressNormalAttack = false;
        OnAreaSelectEnded?.Invoke();
    }
    #endregion

    #region 쿨다운
    private void TickCooldowns()
    {
        foreach (var skill in _equippedSkills)
        {
            skill?.TickCooldown(Time.deltaTime);
        }
    }
    #endregion

    #region 패시브
    private void ApplyPassives()
    {
        foreach (var skill in _equippedSkills)
        {
            if (skill != null && skill.Data.SkillType == SkillType.Passive)
            {
                // PlayerStat에 스탯 보정 적용
            }
        }
    }
    #endregion

    #region 외부 접근
    public SkillInstance GetSkill(int slotIndex)
    {
        if (slotIndex < 0 || slotIndex >= _maxSlots) return null;
        return _equippedSkills[slotIndex];
    }

    public void SkillInputByData(SkillData data, InputState inputState)
    {
        int slotIndex = FindSlotByData(data);
        if (slotIndex < 0) return;
        SkillInput(slotIndex, inputState);
    }

    private int FindSlotByData(SkillData data)
    {
        for (int i = 0; i < _maxSlots; i++)
        {
            if (_equippedSkills[i] != null && _equippedSkills[i].Data == data)
                return i;
        }
        return -1;
    }

    public int MaxSlots => _maxSlots;
    #endregion
}
