using System.Diagnostics.CodeAnalysis;

using Syntriax.Engine.Core.Abstract;

namespace Syntriax.Engine.Core;

public static class BehaviourControllerExtensions
{
    /// <summary>
    /// Tries to get a <see cref="IBehaviour"/> of the specified type.
    /// </summary>
    /// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
    /// <param name="behaviourController">The <see cref="IBehaviourController"/> to search in.</param>
    /// <param name="behaviour">When this method returns, contains the <see cref="IBehaviour"/> of the specified type, if found; otherwise, null.</param>
    /// <returns><see cref="true"/> if a <see cref="IBehaviour"/> of the specified type was found; otherwise, <see cref="false"/>.</returns>
    public static bool TryGetBehaviour<T>(this IBehaviourController behaviourController, [NotNullWhen(returnValue: true)] out T? behaviour)
    {
        behaviour = behaviourController.GetBehaviour<T>();
        return behaviour is not null;
    }

    /// <summary>
    /// Gets an existing <see cref="IBehaviour"/> of the specified type, or adds and returns a new one if it doesn't exist.
    /// </summary>
    /// <typeparam name="T">The type of <see cref="IBehaviour"/> to get or add.</typeparam>
    /// <param name="behaviourController">The <see cref="IBehaviourController"/> to search in.</param>
    /// <param name="args">Optional arguments to pass to the constructor of the <see cref="IBehaviour"/> if a new one is added.</param>
    /// <returns>The existing or newly added <see cref="IBehaviour"/> of the specified type.</returns>
    public static T GetOrAddBehaviour<T>(this IBehaviourController behaviourController, params object?[]? args) where T : class, IBehaviour
        => behaviourController.GetBehaviour<T>() ?? behaviourController.AddBehaviour<T>(args);

    /// <summary>
    /// Tries to get a <see cref="IBehaviour"/> of the specified type in the parent hierarchy.
    /// </summary>
    /// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
    /// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
    /// <param name="behaviour">When this method returns, contains the <see cref="IBehaviour"/> of the specified type, if found; otherwise, null.</param>
    /// <returns><see cref="true"/> if a <see cref="IBehaviour"/> of the specified type was found in the parent hierarchy; otherwise, <see cref="false"/>.</returns>
    public static bool TryGetBehaviourInParent<T>(this IBehaviourController behaviourController, [NotNullWhen(returnValue: true)] out T? behaviour) where T : class
    {
        behaviour = GetBehaviourInParent<T>(behaviourController);
        return behaviour is not null;
    }

    /// <summary>
    /// Gets a <see cref="IBehaviour"/> of the specified type in the parent hierarchy.
    /// </summary>
    /// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
    /// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
    /// <returns>The <see cref="IBehaviour"/> of the specified type if found; otherwise, null.</returns>
    public static T? GetBehaviourInParent<T>(this IBehaviourController behaviourController) where T : class
    {
        IBehaviourController? controller = behaviourController;

        while (controller is not null)
        {
            if (behaviourController.GetBehaviour<T>() is T behaviour)
                return behaviour;

            controller = controller.HierarchyObject.Parent?.BehaviourController;
        }

        return default;
    }

    /// <summary>
    /// Tries to get a <see cref="IBehaviour"/> of the specified type in the child hierarchy.
    /// </summary>
    /// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
    /// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
    /// <param name="behaviour">When this method returns, contains the <see cref="IBehaviour"/> of the specified type, if found; otherwise, null.</param>
    /// <returns><see cref="true"/> if a <see cref="IBehaviour"/> of the specified type was found in the child hierarchy; otherwise, <see cref="false"/>.</returns>
    public static bool TryGetBehaviourInChildren<T>(this IBehaviourController behaviourController, [NotNullWhen(returnValue: true)] out T? behaviour) where T : class
    {
        behaviour = GetBehaviourInChildren<T>(behaviourController);
        return behaviour is not null;
    }

    /// <summary>
    /// Gets a <see cref="IBehaviour"/> of the specified type in the child hierarchy.
    /// </summary>
    /// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
    /// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
    /// <returns>The <see cref="IBehaviour"/> of the specified type if found; otherwise, null.</returns>
    public static T? GetBehaviourInChildren<T>(this IBehaviourController behaviourController) where T : class
    {
        if (behaviourController.GetBehaviour<T>() is T localBehaviour)
            return localBehaviour;

        foreach (IHierarchyObject child in behaviourController.HierarchyObject)
            if (GetBehaviourInChildren<T>(child.BehaviourController) is T behaviour)
                return behaviour;

        return default;
    }
}