using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace Kit.Helpers
{
    public static class SystemWebMvcExtensions
    {
        /// <summary>
        /// Преобразует перечень объектов в перечень элементов html-списка
        /// </summary>
        /// <typeparam name="T">Тип преобразуемого объекта</typeparam>
        /// <param name="items">Перечень преобразуемых объектов</param>
        /// <param name="valueSelector">Функция, возвращающая значение, используемое в качестве Value для элемента списка.
        /// Для преобразования возвращаемого значения в строку вызывается стандартная реализация ToString()</param>
        /// <param name="textSelector">Функция, возвращающая значение, используемое в качестве Text для элемента списка.
        /// Для преобразования возвращаемого значения в строку вызывается стандартная реализация ToString()</param>
        public static IEnumerable<SelectListItem> ToSelectList<T>(
            this IEnumerable<T> items, 
            Func<T, object> valueSelector, 
            Func<T, object> textSelector)
        {
            if (valueSelector == null) throw new ArgumentNullException("valueSelector");
            if (textSelector == null) throw new ArgumentNullException("textSelector");

            return items.Select(i => new SelectListItem
            {
                Value = valueSelector(i).ToString(), 
                Text = textSelector(i).ToString()
            });
        }

        /// <summary>
        /// Преобразует перечень объектов в перечень элементов html-списка
        /// </summary>
        /// <typeparam name="T">Тип преобразуемого объекта</typeparam>
        /// <param name="items">Перечень преобразуемых объектов</param>
        /// <param name="valueSelector">Функция, возвращающая значение, используемое в качестве Value для элемента списка.</param>
        /// <param name="textSelector">Функция, возвращающая значение, используемое в качестве Text для элемента списка.</param>
        public static IEnumerable<SelectListItem> ToSelectList<T>(
            this IEnumerable<T> items, 
            Func<T, string> valueSelector, 
            Func<T, string> textSelector)
        {
            if (valueSelector == null) throw new ArgumentNullException("valueSelector");
            if (textSelector == null) throw new ArgumentNullException("textSelector");

            return items.Select(i => new SelectListItem
            {
                Value = valueSelector(i), 
                Text = textSelector(i)
            });
        }

        /// <summary>
        /// Преобразует перечень объектов в перечень элементов html-списка
        /// </summary>
        /// <typeparam name="T">Тип преобразуемого объекта</typeparam>
        /// <param name="items">Перечень преобразуемых объектов</param>
        /// <param name="valueSelector">Функция, возвращающая значение, используемое в качестве Value для элемента списка.</param>
        /// <param name="textSelector">Функция, возвращающая значение, используемое в качестве Text для элемента списка.</param>
        /// <param name="selectedValues">Список значений, которые необходимо отметить в результирующем списке как выбранные.</param>
        public static IEnumerable<SelectListItem> ToSelectList<T, TValue, TText>(
            this IEnumerable<T> items,
            Func<T, TValue> valueSelector,
            Func<T, TText> textSelector,
            Func<T, bool> disabled)
        {
            if (valueSelector == null) throw new ArgumentNullException("valueSelector");
            if (textSelector == null) throw new ArgumentNullException("textSelector");

            return items.Select(i => new SelectListItem
            {
                Value = (valueSelector(i) != null ? valueSelector(i).ToString() : string.Empty),
                Text = (textSelector(i) != null ? textSelector(i).ToString() : string.Empty),
                Selected = disabled(i)
            });
        }


        /// <summary>
        /// Преобразует перечень объектов в перечень элементов html-списка
        /// </summary>
        /// <typeparam name="T">Тип преобразуемого объекта</typeparam>
        /// <param name="items">Перечень преобразуемых объектов</param>
        /// <param name="valueSelector">Функция, возвращающая значение, используемое в качестве Value для элемента списка.</param>
        /// <param name="textSelector">Функция, возвращающая значение, используемое в качестве Text для элемента списка.</param>
        /// <param name="selectedValues">Список значений, которые необходимо отметить в результирующем списке как выбранные.</param>
        public static IEnumerable<SelectListItem> ToSelectList<T, TValue, TText>(
            this IEnumerable<T> items, 
            Func<T, TValue> valueSelector, 
            Func<T, TText> textSelector, 
            TValue selectedValue)
        {
            if (valueSelector == null) throw new ArgumentNullException("valueSelector");
            if (textSelector == null) throw new ArgumentNullException("textSelector");

            return items.Select(i => new SelectListItem
            {
                Value = (valueSelector(i) != null ? valueSelector(i).ToString() : string.Empty),
                Text = (textSelector(i) != null ? textSelector(i).ToString() : string.Empty),
                Selected = (selectedValue != null && selectedValue.Equals(valueSelector(i)))
            });
        }

        /// <summary>
        /// Преобразует перечень объектов в перечень элементов html-списка
        /// </summary>
        /// <typeparam name="T">Тип преобразуемого объекта</typeparam>
        /// <param name="items">Перечень преобразуемых объектов</param>
        /// <param name="valueSelector">Функция, возвращающая значение, используемое в качестве Value для элемента списка.</param>
        /// <param name="textSelector">Функция, возвращающая значение, используемое в качестве Text для элемента списка.</param>
        /// <param name="selectedValues">Список значений, которые необходимо отметить в результирующем списке как выбранные.</param>
        public static IEnumerable<SelectListItem> ToSelectList<T, TValue, TText>(
            this IEnumerable<T> items,
            Func<T, TValue> valueSelector,
            Func<T, TText> textSelector,
            IEnumerable<TValue> selectedValues)
        {
            if (valueSelector == null) throw new ArgumentNullException("valueSelector");
            if (textSelector == null) throw new ArgumentNullException("textSelector");

            return items.Select(i => new SelectListItem
            {
                Value = (valueSelector(i) != null ? valueSelector(i).ToString() : string.Empty),
                Text = (textSelector(i) != null ? textSelector(i).ToString() : string.Empty),
                Selected = selectedValues != null && selectedValues.Contains(valueSelector(i))
            });
        }


        private static string GetLambdaAction(LambdaExpression actionSelector)
        {
            UnaryExpression? unaryExpression = (UnaryExpression)actionSelector.Body;
            MethodCallExpression? methodCallExpression = (MethodCallExpression)unaryExpression.Operand;
            ConstantExpression? constantExpression = (ConstantExpression)methodCallExpression.Object;
            MethodInfo? methodInfo = (MethodInfo)constantExpression.Value;
            string actionName = methodInfo.Name;

            return actionName;
        }

        public static string? Action<TController>(this IUrlHelper urlHelper, Expression<Func<TController, Delegate>> actionSelector, object? values = null, string? protocol = null) where TController : Controller
        {
            // получаем имя контроллера
            string controllerName = typeof(TController).Name;
            if (controllerName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) && controllerName.Length > "Controller".Length)
            {
                controllerName = controllerName.Remove(controllerName.Length - "Controller".Length, "Controller".Length);
            }

            return urlHelper.Action(GetLambdaAction(actionSelector), controllerName, values, protocol);
        }
    }
}