using System;
using System.Collections;
using UnityEngine;

namespace OCES.Audio
{
    public class BeatClock
    {
        int m_beatsPerBar;           // 从 TimeSig 解析
        int m_barsPerGrid;           // 从 Grid 字段读取
        double m_startDspTime, m_secondsPerBeat;       // 本次计数周期的起点

        Coroutine m_beatCoroutine, m_barCoroutine, m_gridCoroutine;
        Coroutine m_delayCoroutine;
        readonly Action<uint> m_onBeat, m_onBar, m_onGrid;

        readonly MonoBehaviour m_host;

        uint m_containerId;
        bool m_stopped, m_blendError;           // 检测到 Blend 多 BPM 情况时置为 true

        internal BeatClock(MonoBehaviour host, Action<uint> onBeat, Action<uint> onBar, Action<uint> onGrid)
        {
            this.m_host = host;
            this.m_onBeat = onBeat;
            this.m_onBar = onBar;
            this.m_onGrid = onGrid;
        }

        internal void Restart(MusicContainer container, float inheritedBpm, double dspTime, double startOffset = 0d)
        {
            //Debug.Log($"[BeatClock] Restarting {container.Id}, inheritedBpm = {inheritedBpm}, dspTime = {dspTime}");
            StopAll();
            this.m_blendError = this.m_stopped = false;
            this.m_containerId = container.Id;
            this.m_startDspTime = dspTime + startOffset;

            // BPM：优先用自己的，为 0 则用传入的继承值
            float bpm = container.Bpm > 0f ? container.Bpm : inheritedBpm;
            if (bpm <= 30f)
            {
                // BPM 无效，不启动任何回调
                return;
            }
            this.m_secondsPerBeat = 60 / bpm;

            // TimeSig：检查是否有效
            bool hasTimeSig = !string.IsNullOrEmpty(container.TimeSig);
            if (hasTimeSig)
            {
                this.m_beatsPerBar = MusicContainerConfig.GetBeatsPerBar(container.TimeSig);
            }

            // Grid：检查是否有效
            bool hasGrid = container.Grid > 0;
            if (hasGrid)
            {
                this.m_barsPerGrid = container.Grid;
            }

            double delay = this.m_startDspTime - AudioSettings.dspTime;
            if (delay > 0)
            {
                this.m_delayCoroutine = this.m_host.StartCoroutine(DelayedStart(delay, hasTimeSig, hasGrid));
            }
            else
            {
                StartCoroutines(hasTimeSig, hasGrid);
            }
        }
        
        IEnumerator DelayedStart(double delay, bool hasTimeSig, bool hasGrid)
        {
            yield return new WaitUntil(() => AudioSettings.dspTime >= this.m_startDspTime - 0.02);

            while (AudioSettings.dspTime < this.m_startDspTime)
                yield return null;

            if (this.m_stopped || this.m_blendError) yield break;

            this.m_delayCoroutine = null;
            StartCoroutines(hasTimeSig, hasGrid);
        }
        
        void StartCoroutines(bool hasTimeSig, bool hasGrid)
        {
            // 分层启动协程
            // Beat：只要 BPM 有效就启动
            this.m_beatCoroutine = this.m_host.StartCoroutine(BeatCoroutine());

            // Bar：TimeSig 有效时启动
            if (hasTimeSig)
            {
                this.m_barCoroutine = this.m_host.StartCoroutine(BarCoroutine());
            }

            // Grid：TimeSig 和 Grid 都有效时启动
            if (hasTimeSig && hasGrid)
            {
                this.m_gridCoroutine = this.m_host.StartCoroutine(GridCoroutine());
            }
        }

        internal void OnBlendError(MusicContainer container)
        {
            this.m_blendError = true;
            Debug.LogWarning($"[Music System] Blend Container {container.Id} 包含多个不同 BPM 的子 Container，节拍回调已停止。");
        }
        internal void StopAll()
        {
            this.m_stopped = true;
            if (this.m_delayCoroutine != null) this.m_host.StopCoroutine(this.m_delayCoroutine);
            if (this.m_beatCoroutine  != null) this.m_host.StopCoroutine(this.m_beatCoroutine);
            if (this.m_barCoroutine   != null) this.m_host.StopCoroutine(this.m_barCoroutine);
            if (this.m_gridCoroutine  != null) this.m_host.StopCoroutine(this.m_gridCoroutine);
            this.m_delayCoroutine = this.m_beatCoroutine = this.m_barCoroutine = this.m_gridCoroutine = null;
        }

        internal double GetNextDspTime(AlignMode mode)
        {
            double now = AudioSettings.dspTime;
            
            if (this.m_blendError || this.m_stopped)
            {
                return now;
            }
            
            double elapsed = now - this.m_startDspTime;
            if (elapsed < 0)
            {
                return this.m_startDspTime;
            }
            
            double period = mode switch
            {
                AlignMode.Beat => this.m_secondsPerBeat,
                AlignMode.Bar  => this.m_secondsPerBeat * this.m_beatsPerBar,
                _              => this.m_secondsPerBeat,
            };
            long next = (long)(elapsed / period) + 1L;
            return this.m_startDspTime + next * period;
        }

        IEnumerator BeatCoroutine()
        {
            double elapsed = AudioSettings.dspTime - this.m_startDspTime;
            long index = elapsed > 0.05 ? (long)(elapsed / this.m_secondsPerBeat) : 0;
            while (true)
            {
                double nextTime = this.m_startDspTime + index * this.m_secondsPerBeat;
                yield return new WaitUntil(() => AudioSettings.dspTime >= nextTime - 0.02);

                while (AudioSettings.dspTime < nextTime)
                    yield return null;

                if (this.m_blendError || this.m_stopped){
                    yield break;
                }
                this.m_onBeat?.Invoke(this.m_containerId);
                index++;
            }
        }

        IEnumerator BarCoroutine()
        {
            double elapsed = AudioSettings.dspTime - this.m_startDspTime;
            double secondsPerBar = this.m_secondsPerBeat * this.m_beatsPerBar;
            long index = elapsed > 0.05 ? (long)(elapsed / secondsPerBar) : 0;
            while (true)
            {
                double nextTime = this.m_startDspTime + index * secondsPerBar;
                yield return new WaitUntil(() => AudioSettings.dspTime >= nextTime - 0.02);

                while (AudioSettings.dspTime < nextTime)
                    yield return null;
                if (this.m_blendError || this.m_stopped){
                    yield break;
                }
                this.m_onBar?.Invoke(this.m_containerId);
                index++;
            }
        }

        IEnumerator GridCoroutine()
        {
            double elapsed = AudioSettings.dspTime - this.m_startDspTime;
            double secondsPerGrid = this.m_secondsPerBeat * this.m_beatsPerBar * this.m_barsPerGrid;
            long index = elapsed > 0.05 ? (long)(elapsed / secondsPerGrid) : 0;
            while (true)
            {
                double nextTime = this.m_startDspTime + index * secondsPerGrid;
                yield return new WaitUntil(() => AudioSettings.dspTime >= nextTime - 0.02);

                while (AudioSettings.dspTime < nextTime)
                    yield return null;
                if (this.m_blendError || this.m_stopped){
                    yield break;
                }
                this.m_onGrid?.Invoke(this.m_containerId);
                index++;
            }
        }
    }
}
