using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Radzen.Blazor.Rendering;
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;

namespace Radzen.Blazor
{
    /// <summary>
    /// A color picker component that allows users to select colors through various input methods including color palette, RGB sliders, hex input, and predefined swatches.
    /// RadzenColorPicker provides a comprehensive color selection interface with alpha channel support.
    /// Displays a button showing the current color. Clicking opens a popup with multiple color selection methods including visual picker (click on hue/saturation gradient to select colors visually),
    /// hue/alpha sliders to fine-tune hue and transparency, RGB input to enter specific Red/Green/Blue values (0-255), hex input to enter hex color codes (#RRGGBB or #RRGGBBAA),
    /// and predefined swatches for quick selection.
    /// The Value is a hex color string (e.g., "#FF0000" for red, "#FF0000AA" for semi-transparent red). Supports alpha channel (transparency) in RGBA format.
    /// Use for applications requiring color customization like themes, charts, or design tools.
    /// </summary>
    /// <example>
    /// Basic color picker:
    /// <code>
    /// &lt;RadzenColorPicker @bind-Value=@backgroundColor /&gt;
    /// @code {
    ///     string backgroundColor = "#FF5733";
    /// }
    /// </code>
    /// Color picker with predefined swatches:
    /// <code>
    /// &lt;RadzenColorPicker @bind-Value=@color Change=@OnColorChange&gt;
    ///     &lt;RadzenColorPickerItem Value="#FF0000" /&gt;
    ///     &lt;RadzenColorPickerItem Value="#00FF00" /&gt;
    ///     &lt;RadzenColorPickerItem Value="#0000FF" /&gt;
    /// &lt;/RadzenColorPicker&gt;
    /// </code>
    /// </example>
    public partial class RadzenColorPicker : FormComponent<string>
    {
        private string? toggleAriaLabel;

        /// <summary>
        /// Gets or sets the toggle popup aria label text.
        /// </summary>
        /// <value>The toggle popup aria label text.</value>
        [Parameter]
        public string ToggleAriaLabel { get => toggleAriaLabel ?? Localize(nameof(RadzenStrings.ColorPicker_ToggleAriaLabel)); set => toggleAriaLabel = value; }

        private string? popupAriaLabel;

        /// <summary>
        /// Gets or sets the popup aria label text.
        /// </summary>
        /// <value>The popup aria label text.</value>
        [Parameter]
        public string PopupAriaLabel { get => popupAriaLabel ?? Localize(nameof(RadzenStrings.ColorPicker_PopupAriaLabel)); set => popupAriaLabel = value; }

        /// <summary>
        /// Gets or sets the open callback.
        /// </summary>
        /// <value>The open callback.</value>
        [Parameter]
        public EventCallback Open { get; set; }

        /// <summary>
        /// Gets or sets the close callback.
        /// </summary>
        /// <value>The close callback.</value>
        [Parameter]
        public EventCallback Close { get; set; }

        /// <summary>
        /// Gets or sets the icon.
        /// </summary>
        /// <value>The icon.</value>
        [Parameter]
        public string? Icon { get; set; }

        /// <summary>
        /// Gets or sets the icon color.
        /// </summary>
        /// <value>The icon color.</value>
        [Parameter]
        public string? IconColor { get; set; }

        private string? hexText;

        /// <summary>
        /// Gets or sets the hexadecimal color label text.
        /// </summary>
        /// <value>The hexadecimal text.</value>
        [Parameter]
        public string HexText { get => hexText ?? Localize(nameof(RadzenStrings.ColorPicker_HexText)); set => hexText = value; }

        private string? redText;

        /// <summary>
        /// Gets or sets the red color label text.
        /// </summary>
        /// <value>The red text.</value>
        [Parameter]
        public string RedText { get => redText ?? Localize(nameof(RadzenStrings.ColorPicker_RedText)); set => redText = value; }

        private string? greenText;

        /// <summary>
        /// Gets or sets the green color label text.
        /// </summary>
        /// <value>The green text.</value>
        [Parameter]
        public string GreenText { get => greenText ?? Localize(nameof(RadzenStrings.ColorPicker_GreenText)); set => greenText = value; }

        private string? blueText;

        /// <summary>
        /// Gets or sets the blue color label text.
        /// </summary>
        /// <value>The blue text.</value>
        [Parameter]
        public string BlueText { get => blueText ?? Localize(nameof(RadzenStrings.ColorPicker_BlueText)); set => blueText = value; }

        private string? alphaText;

        /// <summary>
        /// Gets or sets the alpha label text.
        /// </summary>
        /// <value>The alpha text.</value>
        [Parameter]
        public string AlphaText { get => alphaText ?? Localize(nameof(RadzenStrings.ColorPicker_AlphaText)); set => alphaText = value; }

        private string? buttonText;

        /// <summary>
        /// Gets or sets the button text.
        /// </summary>
        /// <value>The button text.</value>
        [Parameter]
        public string ButtonText { get => buttonText ?? Localize(nameof(RadzenStrings.ColorPicker_ButtonText)); set => buttonText = value; }

        RadzenPopup Popup { get; set; } = default!;
        bool isPopupOpen;

        internal event EventHandler<string>? SelectedColorChanged;

        string AlphaGradientStart
        {
            get
            {
                var rgb = RGB.Parse(Color);
                rgb?.Alpha = 0;
                return rgb?.ToCSS() ?? string.Empty;
            }
        }

        string AlphaGradientEnd
        {
            get
            {
                var rgb = RGB.Parse(Color);
                rgb?.Alpha = 1;
                return rgb?.ToCSS() ?? string.Empty;
            }
        }

        string Hex
        {
            get
            {
                var rgb = RGB.Parse(Color);

                if (rgb != null)
                {
                    return rgb.ToHex();
                }

                return String.Empty;
            }
        }

        double Red
        {
            get
            {
                var rgb = RGB.Parse(Color);
                return rgb?.Red ?? default;
            }
        }

        double Alpha
        {
            get
            {
                return Math.Round(AlphaHandleLeft * 100);
            }
        }

        double Green
        {
            get
            {
                var rgb = RGB.Parse(Color);
                return rgb?.Green ?? default;
            }
        }

        double Blue
        {
            get
            {
                var rgb = RGB.Parse(Color);
                return rgb?.Blue ?? default;
            }
        }

        async Task UpdateColorUsingHsvHandles()
        {
            var hsv = new HSV {
                Hue = HueHandleLeft,
                Saturation = SaturationHandleLeft,
                Value = 1 - SaturationHandleTop,
                Alpha = AlphaHandleLeft
            };

            Color = hsv.ToRGB().ToCSS();

            await TriggerChange();
        }

        Rect? lastHslRect;

        async Task OnSaturationMove(DraggableEventArgs args)
        {
            lastHslRect = args.Rect; ;

            if (args.Rect != null)
            {
                SaturationHandleLeft = Math.Clamp((args.ClientX - args.Rect.Left) / args.Rect.Width, 0, 1);
                SaturationHandleTop = Math.Clamp((args.ClientY - args.Rect.Top) / args.Rect.Height, 0, 1);
            }

            await UpdateColorUsingHsvHandles();
        }

        async Task TriggerChange()
        {
            if (SelectedColorChanged != null)
            {
                SelectedColorChanged.Invoke(this, Color);
            }

            if (!ShowButton)
            {
                await OnChanged();
            }

            StateHasChanged();
        }

        async Task OnChanged()
        {
            await ValueChanged.InvokeAsync(Color);

            if (FieldIdentifier.FieldName != null)
            {
                EditContext?.NotifyFieldChanged(FieldIdentifier);
            }

            await Change.InvokeAsync(Color);
        }

        async Task ChangeRGB(object value)
        {
            var colorString = value as string;
            if (string.IsNullOrEmpty(colorString))
            {
                return;
            }

            var rgb = RGB.Parse(colorString);
            if (rgb == null)
            {
                return;
            }

            rgb.Alpha = AlphaHandleLeft;
            await UpdateColor(rgb);
        }

        internal async Task SelectColor(string value)
        {
            var rgb = RGB.Parse(value);
            if (rgb == null)
            {
                return;
            }

            await UpdateColor(rgb);

            if (!ShowButton)
            {
                await Popup.CloseAsync();
            }
        }

        async Task UpdateColor(RGB? rgb)
        {
            if (rgb == null)
            {
                return;
            }

            Color = rgb.ToCSS();

            var hsv = rgb.ToHSV();

            SaturationHandleLeft = hsv.Saturation;
            SaturationHandleTop = 1 - hsv.Value;
            HueHandleLeft = hsv.Hue;
            AlphaHandleLeft = hsv.Alpha;

            await TriggerChange();
        }

        async Task ChangeAlpha(double value)
        {
            if (value >= 0 && value <= 100)
            {
                var rgb = RGB.Parse(Color);
                if (rgb == null)
                {
                    return;
                }
                AlphaHandleLeft = rgb.Alpha = Math.Round(value / 100, 2);

                Color = rgb.ToCSS();

                await TriggerChange();
            }
        }

        async Task ChangeAlpha(object alpha)
        {
            var alphaString = alpha as string;
            if (alphaString != null && Double.TryParse(alphaString, out var value))
            {
                await ChangeAlpha(value);
            }
        }

        async Task ChangeColor(double value, Action<RGB, double> update)
        {
            if (value >= 0 && value <= 255)
            {
                var rgb = RGB.Parse(Color);
                if (rgb == null)
                {
                    return;
                }

                update(rgb, value);

                await UpdateColor(rgb);
            }
        }

        async Task ChangeColor(object color, Action<RGB, double> update)
        {
            var colorString = color as string;
            if (colorString != null && Double.TryParse(colorString, out var value))
            {
                await ChangeColor(value, update);
            }
        }

        Rect? lastAlphaRect;

        async Task OnAlphaMove(DraggableEventArgs args)
        {
            lastAlphaRect = args.Rect;

            if (args.Rect != null)
            {
                AlphaHandleLeft = Math.Round(Math.Clamp((args.ClientX - args.Rect.Left) / args.Rect.Width, 0, 1), 2);
            }

            await UpdateColorUsingHsvHandles();
        }

        Rect? lastHueRect;
        async Task OnHueMove(DraggableEventArgs args)
        {
            lastHueRect = args.Rect;

            if (args.Rect != null)
            {
                HueHandleLeft = Math.Clamp((args.ClientX - args.Rect.Left) / args.Rect.Width, 0, 1);
            }

            await UpdateColorUsingHsvHandles();
        }

        async Task OnClick()
        {
            await OnChanged();
            await Popup.CloseAsync();
        }

        async Task OnClosePopup()
        {
            isPopupOpen = false;
            if (ShowButton)
            {
                SetInitialValue();
            }

            await Close.InvokeAsync(null);
        }

        async Task OnPopupOpen()
        {
            isPopupOpen = true;
            await Open.InvokeAsync(null);
        }

        /// <summary>
        /// Gets or sets a value indicating whether button is shown.
        /// </summary>
        /// <value><c>true</c> if button shown; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool ShowButton { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether HSV is shown.
        /// </summary>
        /// <value><c>true</c> if HSV is shown; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool ShowHSV { get; set; } = true;

        /// <summary>
        /// Gets or sets a value indicating whether RGBA is shown.
        /// </summary>
        /// <value><c>true</c> if RGBA is shown; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool ShowRGBA { get; set; } = true;

        /// <summary>
        /// Gets or sets a value indicating whether colors are shown.
        /// </summary>
        /// <value><c>true</c> if colors are shown; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool ShowColors { get; set; } = true;

        /// <summary>
        /// Gets or sets the child content.
        /// </summary>
        /// <value>The child content.</value>
        [Parameter]
        public RenderFragment? ChildContent { get; set; }

        /// <summary>
        /// Gets or sets the render mode.
        /// </summary>
        /// <value>The render mode.</value>
        [Parameter]
        public PopupRenderMode PopupRenderMode { get; set; } = PopupRenderMode.Initial;

        double SaturationHandleLeft { get; set; }
        double SaturationHandleTop { get; set; }
        double HueHandleLeft { get; set; }
        double AlphaHandleLeft { get; set; } = 1;
        string Color { get; set; } = "rgb(255, 255, 255)";

        async Task Toggle()
        {
            if (!Disabled)
            {
                await Popup.ToggleAsync(Element);
            }
        }
        /// <summary>
        /// Gets or sets a value indicating whether the dropdown arrow is shown.
        /// </summary>
        /// <value><c>true</c> if the arrow is shown; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool ShowArrow { get; set; } = true;

        /// <summary>
        /// Gets or sets the size of the component.
        /// </summary>
        [Parameter]
        public InputSize InputSize { get; set; } = InputSize.Medium;

        /// <inheritdoc />
        protected override string GetComponentCssClass()
        {
            return GetClassList("rz-colorpicker").AddInputSize(InputSize).ToString();
        }

        /// <inheritdoc />
        protected override void OnInitialized()
        {
            SetInitialValue();

            base.OnInitialized();
        }

        void SetInitialValue()
        {
            var value = Value;

            if (String.IsNullOrEmpty(Value) || RGB.Parse(Value) == null)
            {
                value = "rgb(255, 255, 255)";
            }

            if (value != Color)
            {
                Color = value ?? string.Empty;
                SelectedColorChanged?.Invoke(this, Color);

                var hsvSource = RGB.Parse(Color);
                if (hsvSource != null)
                {
                    var hsv = hsvSource.ToHSV();
                    SaturationHandleLeft = hsv.Saturation;
                    SaturationHandleTop = 1 - hsv.Value;
                    HueHandleLeft = hsv.Hue;
                    AlphaHandleLeft = hsv.Alpha;
                }
            }
        }

        /// <inheritdoc />
        public override async Task SetParametersAsync(ParameterView parameters)
        {
            var valueChanged = parameters.DidParameterChange(nameof(Value), Value);

            await base.SetParametersAsync(parameters);

            if (valueChanged)
            {
                SetInitialValue();
            }
        }

        async Task OnHueKeyPress(KeyboardEventArgs args)
        {
            var key = args.Code != null ? args.Code : args.Key;

            if (key == "ArrowLeft" || key == "ArrowRight")
            {
                preventKeyPress = true;

                if (lastHueRect == null && JSRuntime != null)
                {
                    lastHueRect = await JSRuntime.InvokeAsync<Rect>("Radzen.clientRect", (GetId() + "hue"));
                }

                if (lastHueRect != null)
                {
                    await OnHueMove(new DraggableEventArgs() { Rect = lastHueRect, ClientX = lastHueRect.Left + lastHueRect.Width * HueHandleLeft + (key == "ArrowLeft" ? -1 : 1) });
                }
            }
            else if (key == "Escape")
            {
                await ClosePopup();
            }
            else
            {
                preventKeyPress = false;
            }
        }

        async Task OnAlphaKeyPress(KeyboardEventArgs args)
        {
            var key = args.Code != null ? args.Code : args.Key;

            if (key == "ArrowLeft" || key == "ArrowRight")
            {
                preventKeyPress = true;

                if (lastAlphaRect == null && JSRuntime != null)
                {
                    lastAlphaRect = await JSRuntime.InvokeAsync<Rect>("Radzen.clientRect", (GetId() + "alpha"));
                }

                if (lastAlphaRect != null)
                {
                    await OnAlphaMove(new DraggableEventArgs() { Rect = lastAlphaRect, ClientX = lastAlphaRect.Left + lastAlphaRect.Width * AlphaHandleLeft + (key == "ArrowLeft" ? -3 : 3) });
                }
            }
            else if (key == "Escape")
            {
                await ClosePopup();
            }
            else
            {
                preventKeyPress = false;
            }
        }

        async Task OnHslKeyPress(KeyboardEventArgs args)
        {
            var key = args.Code != null ? args.Code : args.Key;

            if (lastHslRect == null && JSRuntime != null)
            {
                lastHslRect = await JSRuntime.InvokeAsync<Rect>("Radzen.clientRect", (GetId() + "hsl"));
            }

            if (key == "ArrowLeft" || key == "ArrowRight" || key == "ArrowUp" || key == "ArrowDown")
            {
                preventKeyPress = true;

                if (lastHslRect != null)
                {
                    await OnSaturationMove(new DraggableEventArgs()
                    {
                        Rect = lastHslRect,
                        ClientX = lastHslRect.Left + lastHslRect.Width * SaturationHandleLeft + (key == "ArrowLeft" ? -1 : key == "ArrowRight" ? 1 : 0),
                        ClientY = lastHslRect.Top + lastHslRect.Height * SaturationHandleTop + (key == "ArrowUp" ? -1 : key == "ArrowDown" ? 1 : 0)
                    });
                }
            }
            else if (key == "Escape")
            {
                await ClosePopup();
            }
            else
            {
                preventKeyPress = false;
            }
        }

        bool preventKeyPress;
        bool stopKeypressPropagation;
        async Task OnKeyPress(KeyboardEventArgs args, Task task)
        {
            var key = args.Code != null ? args.Code : args.Key;

            if (key == "Space" || key == "Enter")
            {
                preventKeyPress = true;
                stopKeypressPropagation = true;

                await task;
            }
            else if (key == "Escape")
            {
                stopKeypressPropagation = true;
                await ClosePopup();
            }
            else
            {
                preventKeyPress = false;
                stopKeypressPropagation = false;
            }
        }

        internal async Task ClosePopup()
        {
            await Popup.CloseAsync();
            if (JSRuntime != null)
            {
                await JSRuntime.InvokeVoidAsync("Radzen.focusElement", GetId());
            }
        }
    }
}
