// Copyright (c) Rotorz Limited. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root.

using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

namespace Unity.VisualScripting.ReorderableList.Element_Adder_Menu
{
    internal sealed class GenericElementAdderMenuBuilder<TContext> : IElementAdderMenuBuilder<TContext>
    {
        public GenericElementAdderMenuBuilder()
        {
            _typeDisplayNameFormatter = NicifyTypeName;
        }

        private Type _contractType;
        private IElementAdder<TContext> _elementAdder;
        private Func<Type, string> _typeDisplayNameFormatter;
        private List<Func<Type, bool>> _typeFilters = new List<Func<Type, bool>>();
        private List<IElementAdderMenuCommand<TContext>> _customCommands = new List<IElementAdderMenuCommand<TContext>>();

        public void SetContractType(Type contractType)
        {
            _contractType = contractType;
        }

        public void SetElementAdder(IElementAdder<TContext> elementAdder)
        {
            _elementAdder = elementAdder;
        }

        public void SetTypeDisplayNameFormatter(Func<Type, string> formatter)
        {
            _typeDisplayNameFormatter = formatter ?? NicifyTypeName;
        }

        public void AddTypeFilter(Func<Type, bool> typeFilter)
        {
            if (typeFilter == null)
            {
                throw new ArgumentNullException(nameof(typeFilter));
            }

            _typeFilters.Add(typeFilter);
        }

        public void AddCustomCommand(IElementAdderMenuCommand<TContext> command)
        {
            if (command == null)
            {
                throw new ArgumentNullException(nameof(command));
            }

            _customCommands.Add(command);
        }

        public IElementAdderMenu GetMenu()
        {
            var menu = new GenericElementAdderMenu();

            AddCommandsToMenu(menu, _customCommands);

            if (_contractType != null)
            {
                AddCommandsToMenu(menu, ElementAdderMeta.GetMenuCommands<TContext>(_contractType));
                AddConcreteTypesToMenu(menu, ElementAdderMeta.GetConcreteElementTypes(_contractType, _typeFilters.ToArray()));
            }

            return menu;
        }

        private void AddCommandsToMenu(GenericElementAdderMenu menu, IList<IElementAdderMenuCommand<TContext>> commands)
        {
            if (commands.Count == 0)
            {
                return;
            }

            if (!menu.IsEmpty)
            {
                menu.AddSeparator();
            }

            foreach (var command in commands)
            {
                if (_elementAdder != null && command.CanExecute(_elementAdder))
                {
                    menu.AddItem(command.Content, () => command.Execute(_elementAdder));
                }
                else
                {
                    menu.AddDisabledItem(command.Content);
                }
            }
        }

        private void AddConcreteTypesToMenu(GenericElementAdderMenu menu, Type[] concreteTypes)
        {
            if (concreteTypes.Length == 0)
            {
                return;
            }

            if (!menu.IsEmpty)
            {
                menu.AddSeparator();
            }

            foreach (var concreteType in concreteTypes)
            {
                var content = new GUIContent(_typeDisplayNameFormatter(concreteType));
                if (_elementAdder != null && _elementAdder.CanAddElement(concreteType))
                {
                    menu.AddItem(content, () =>
                    {
                        if (_elementAdder.CanAddElement(concreteType))
                        {
                            _elementAdder.AddElement(concreteType);
                        }
                    });
                }
                else
                {
                    menu.AddDisabledItem(content);
                }
            }
        }

        private static string NicifyTypeName(Type type)
        {
            return ObjectNames.NicifyVariableName(type.Name);
        }
    }
}
