// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

#nullable disable

using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;

namespace osu.Game.Screens.Edit.Compose.Components
{
    /// <summary>
    /// Represents a display composite containing and managing the visibility state of the selection box's drag handles.
    /// </summary>
    public partial class SelectionBoxDragHandleContainer : CompositeDrawable
    {
        private Container<SelectionBoxScaleHandle> scaleHandles;
        private Container<SelectionBoxRotationHandle> rotationHandles;

        private readonly List<SelectionBoxDragHandle> allDragHandles = new List<SelectionBoxDragHandle>();

        public new MarginPadding Padding
        {
            get => base.Padding;
            set => base.Padding = value;
        }

        [BackgroundDependencyLoader]
        private void load()
        {
            InternalChildren = new Drawable[]
            {
                scaleHandles = new Container<SelectionBoxScaleHandle>
                {
                    RelativeSizeAxes = Axes.Both,
                },
                rotationHandles = new Container<SelectionBoxRotationHandle>
                {
                    RelativeSizeAxes = Axes.Both,
                    Padding = new MarginPadding(-12.5f),
                },
            };
        }

        public void AddScaleHandle(SelectionBoxScaleHandle handle)
        {
            bindDragHandle(handle);
            scaleHandles.Add(handle);
        }

        public void ClearScaleHandles()
        {
            foreach (var scaleHandle in scaleHandles)
                unbindDragHandle(scaleHandle);
            scaleHandles.Clear();
        }

        public void AddRotationHandle(SelectionBoxRotationHandle handle)
        {
            handle.Alpha = 0;
            handle.AlwaysPresent = true;

            bindDragHandle(handle);
            rotationHandles.Add(handle);
        }

        public void ClearRotationHandles()
        {
            foreach (var rotationHandle in rotationHandles)
                unbindDragHandle(rotationHandle);
            rotationHandles.Clear();
        }

        private void bindDragHandle(SelectionBoxDragHandle handle)
        {
            handle.HoverGained += updateRotationHandlesVisibility;
            handle.HoverLost += updateRotationHandlesVisibility;
            handle.MouseDown += updateRotationHandlesVisibility;
            handle.MouseUp += updateRotationHandlesVisibility;
            allDragHandles.Add(handle);
        }

        private void unbindDragHandle(SelectionBoxDragHandle handle)
        {
            handle.HoverGained -= updateRotationHandlesVisibility;
            handle.HoverLost -= updateRotationHandlesVisibility;
            handle.MouseDown -= updateRotationHandlesVisibility;
            handle.MouseUp -= updateRotationHandlesVisibility;
            allDragHandles.Remove(handle);
        }

        public void FlipScaleHandles(Direction direction)
        {
            foreach (var handle in scaleHandles)
            {
                if (direction == Direction.Horizontal && !handle.Anchor.HasFlag(Anchor.x1))
                    handle.Anchor ^= Anchor.x0 | Anchor.x2;
                if (direction == Direction.Vertical && !handle.Anchor.HasFlag(Anchor.y1))
                    handle.Anchor ^= Anchor.y0 | Anchor.y2;
            }
        }

        private SelectionBoxRotationHandle displayedRotationHandle;
        private SelectionBoxDragHandle activeHandle;

        private void updateRotationHandlesVisibility()
        {
            // if the active handle is a rotation handle and is held or hovered,
            // then no need to perform any updates to the rotation handles visibility.
            if (activeHandle is SelectionBoxRotationHandle && (activeHandle?.IsHeld == true || activeHandle?.IsHovered == true))
                return;

            displayedRotationHandle?.FadeOut(SelectionBoxControl.TRANSFORM_DURATION, Easing.OutQuint);
            displayedRotationHandle = null;

            // if the active handle is not a rotation handle but is held, then keep the rotation handle hidden.
            if (activeHandle?.IsHeld == true)
                return;

            activeHandle = rotationHandles.FirstOrDefault(h => h.IsHeld || h.IsHovered);
            activeHandle ??= allDragHandles.FirstOrDefault(h => h.IsHovered);

            if (activeHandle != null)
            {
                displayedRotationHandle = getCorrespondingRotationHandle(activeHandle, rotationHandles);
                displayedRotationHandle?.FadeIn(SelectionBoxControl.TRANSFORM_DURATION, Easing.OutQuint);
            }
        }

        /// <summary>
        /// Gets the rotation handle corresponding to the given handle.
        /// </summary>
        [CanBeNull]
        private static SelectionBoxRotationHandle getCorrespondingRotationHandle(SelectionBoxDragHandle handle, IEnumerable<SelectionBoxRotationHandle> rotationHandles)
        {
            if (handle is SelectionBoxRotationHandle rotationHandle)
                return rotationHandle;

            return rotationHandles.SingleOrDefault(r => r.Anchor == handle.Anchor);
        }
    }
}
