using System;
using UnityEngine;

// 보이스 진폭에 따라 입 관련 블렌드셰이프 그룹의 weight를 직접 제어
// LateUpdate에서 갱신해 Animator가 같은 프레임에 0으로 세팅한 값을 덮어씀
[RequireComponent(typeof(CharacterVoiceObject))]
public class LipSync : MonoBehaviour
{
    [Serializable]
    private struct LipShape
    {
        public string Name;
        [Range(0f, 100f)] public float MaxWeight; // amplitude=1일 때 도달할 weight
    }

    [Header("Refs")]
    [SerializeField] private SkinnedMeshRenderer _meshRenderer;

    // BMAC_OpenMouse_Big 클립의 입 관련 셰이프 프리셋
    [Header("Mouth Preset (입 최대 시 weight)")]
    [SerializeField] private LipShape[] _shapes =
    {
        new() { Name = "Expression_SurpriesedMouth", MaxWeight = 50f },
        new() { Name = "Expression_MouthSad_L",      MaxWeight = 10f },
        new() { Name = "Expression_MouthSad_R",      MaxWeight = 10f },
        new() { Name = "Expression_MouthWide_L",     MaxWeight = 30f },
        new() { Name = "Expression_MouthWide_R",     MaxWeight = 30f },
        new() { Name = "Expression_LipsOh",          MaxWeight = 100f },
        new() { Name = "Expression_LipsO",           MaxWeight = 5f },
    };

    [Header("Tuning")]
    [SerializeField, Range(0f, 20f)] private float _amplitudeScale = 6f;     // RMS → 0~1 매핑 배수
    [SerializeField, Range(0f, 0.05f)] private float _noiseFloor = 0.005f;
    [SerializeField, Range(0f, 30f)] private float _smoothingSpeed = 15f;
    [SerializeField] private int _sampleSize = 256;

    private AudioSource _audioSource;
    private int[] _indices;
    private float[] _sampleBuffer;
    private float _currentAmplitude;

    private void Awake()
    {
        var voiceObj = GetComponent<CharacterVoiceObject>();
        _audioSource = voiceObj != null ? voiceObj.VoiceSource : null;

        // 메시 자동 탐색 — 첫 번째 셰이프 이름을 가진 SkinnedMeshRenderer 사용
        if (_meshRenderer == null && _shapes.Length > 0)
        {
            string probe = _shapes[0].Name;
            foreach (var smr in GetComponentsInChildren<SkinnedMeshRenderer>(true))
            {
                if (smr.sharedMesh != null && smr.sharedMesh.GetBlendShapeIndex(probe) >= 0)
                {
                    _meshRenderer = smr;
                    break;
                }
            }
        }

        // 인덱스 캐시
        _indices = new int[_shapes.Length];
        if (_meshRenderer != null && _meshRenderer.sharedMesh != null)
        {
            var mesh = _meshRenderer.sharedMesh;
            for (int i = 0; i < _shapes.Length; i++)
            {
                _indices[i] = mesh.GetBlendShapeIndex(_shapes[i].Name);
                if (_indices[i] < 0)
                    Debug.LogWarning($"[LipSync] 블렌드셰이프 없음: {_shapes[i].Name}", this);
            }
        }
        else
        {
            for (int i = 0; i < _indices.Length; i++) _indices[i] = -1;
        }

        if (_audioSource == null)
            Debug.LogWarning("[LipSync] CharacterVoiceObject.VoiceSource 미할당", this);

        _sampleBuffer = new float[_sampleSize];
    }

    private void LateUpdate()
    {
        if (_audioSource == null || _meshRenderer == null) return;

        // PlayOneShot도 잡히도록 항상 샘플링 — 무음은 노이즈 플로어로 컷
        _audioSource.GetOutputData(_sampleBuffer, 0);

        float sumSq = 0f;
        for (int i = 0; i < _sampleBuffer.Length; i++)
            sumSq += _sampleBuffer[i] * _sampleBuffer[i];

        float rms = Mathf.Sqrt(sumSq / _sampleBuffer.Length);
        rms = Mathf.Max(0f, rms - _noiseFloor);
        float target = Mathf.Clamp01(rms * _amplitudeScale);

        _currentAmplitude = Mathf.Lerp(_currentAmplitude, target, Time.deltaTime * _smoothingSpeed);

        // Animator가 같은 프레임에 0으로 덮은 값을 LateUpdate에서 다시 씌움
        for (int i = 0; i < _shapes.Length; i++)
        {
            if (_indices[i] < 0) continue;
            _meshRenderer.SetBlendShapeWeight(_indices[i], _currentAmplitude * _shapes[i].MaxWeight);
        }
    }
}
