// Copyright (c) 2026 GeWuYou
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using GFramework.Core.Abstractions.Command;
using GFramework.Core.Abstractions.Coroutine;
using GFramework.Core.Abstractions.Events;
using GFramework.Core.Abstractions.Rule;
using GFramework.Core.Coroutine.Instructions;

namespace GFramework.Core.Coroutine.Extensions;

/// <summary>
///     命令协程扩展方法类
///     提供将命令的异步执行包装为协程的功能
/// </summary>
public static class CommandCoroutineExtensions
{
    /// <summary>
    ///     将 Command 的异步执行包装为协程，并处理异常
    /// </summary>
    /// <typeparam name="TCommand">命令类型，必须实现 IAsyncCommand 接口</typeparam>
    /// <param name="contextAware">上下文感知对象</param>
    /// <param name="command">要执行的命令实例</param>
    /// <param name="onError">错误回调处理</param>
    /// <returns>返回协程指令枚举器</returns>
    public static IEnumerator<IYieldInstruction> SendCommandCoroutineWithErrorHandler<TCommand>(
        this IContextAware contextAware,
        TCommand command,
        Action<Exception>? onError = null)
        where TCommand : class, IAsyncCommand
    {
        var task = contextAware.GetContext().SendCommandAsync(command);

        yield return task.AsCoroutineInstruction();

        if (!task.IsFaulted) yield break;
        if (onError != null)
            onError.Invoke(task.Exception!);
        else
            throw task.Exception!.InnerException ?? task.Exception;
    }

    /// <summary>
    /// 发送 Command 并等待指定 Event。
    /// </summary>
    /// <typeparam name="TCommand">命令类型，必须实现 IAsyncCommand</typeparam>
    /// <typeparam name="TEvent">事件类型</typeparam>
    /// <param name="contextAware">上下文感知对象</param>
    /// <param name="command">要执行的命令实例</param>
    /// <param name="onEvent">事件触发时的回调处理</param>
    /// <param name="timeout">
    /// 超时时间（秒）:
    /// <list type="bullet">
    /// <item><description>timeout &lt; 0: 无效，将抛出 ArgumentOutOfRangeException</description></item>
    /// <item><description>timeout == 0: 无超时，永久等待</description></item>
    /// <item><description>timeout &gt; 0: 启用超时机制</description></item>
    /// </list>
    /// </param>
    /// <exception cref="ArgumentOutOfRangeException">当 timeout 小于 0 时抛出。</exception>
    public static IEnumerator<IYieldInstruction> SendCommandAndWaitEventCoroutine<TCommand, TEvent>(
        this IContextAware contextAware,
        TCommand command,
        Action<TEvent>? onEvent = null,
        float timeout = 0f)
        where TCommand : IAsyncCommand
        where TEvent : class
    {
        // 参数检查部分
        if (timeout < 0f)
            throw new ArgumentOutOfRangeException(
                nameof(timeout),
                timeout,
                "Timeout must be greater than or equal to 0.");

        // 迭代器逻辑部分
        return SendCommandAndWaitEventIterator(contextAware, command, onEvent, timeout);
    }

    /// <summary>
    /// 发送 Command 并等待指定 Event 的迭代器实现。
    /// </summary>
    private static IEnumerator<IYieldInstruction> SendCommandAndWaitEventIterator<TCommand, TEvent>(
        IContextAware contextAware,
        TCommand command,
        Action<TEvent>? onEvent,
        float timeout)
        where TCommand : IAsyncCommand
        where TEvent : class
    {
        var context = contextAware.GetContext();
        var eventBus = context.GetService<IEventBus>()
                       ?? throw new InvalidOperationException("IEventBus not found.");

        WaitForEvent<TEvent>? wait = null;

        try
        {
            // 先注册事件监听
            wait = new WaitForEvent<TEvent>(eventBus);

            // 发送命令
            var task = context.SendCommandAsync(command);
            yield return task.AsCoroutineInstruction();

            // 等待事件
            if (timeout > 0f)
            {
                var timeoutWait = new WaitForEventWithTimeout<TEvent>(wait, timeout);
                yield return timeoutWait;

                if (timeoutWait.IsTimeout)
                    throw new TimeoutException(
                        $"Wait for event {typeof(TEvent).Name} timeout.");
            }
            else
            {
                yield return wait;
            }

            if (wait.EventData != null)
                onEvent?.Invoke(wait.EventData);
        }
        finally
        {
            wait?.Dispose();
        }
    }
}