// 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;
using JetBrains.Annotations;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Framework.Layout;
using osuTK;

namespace osu.Game.Screens.Edit.Compose.Components
{
    /// <summary>
    /// A box that displays the drag selection and provides selection events for users to handle.
    /// </summary>
    public partial class DragBox : CompositeDrawable, IStateful<Visibility>
    {
        public Drawable Box { get; private set; }

        /// <summary>
        /// Creates a new <see cref="DragBox"/>.
        /// </summary>
        public DragBox()
        {
            RelativeSizeAxes = Axes.Both;
            AlwaysPresent = true;
            Alpha = 0;
        }

        [BackgroundDependencyLoader]
        private void load()
        {
            InternalChild = Box = CreateBox();
        }

        protected virtual Drawable CreateBox() => new BoxWithBorders();

        /// <summary>
        /// Handle a forwarded mouse event.
        /// </summary>
        /// <param name="e">The mouse event.</param>
        public virtual void HandleDrag(MouseButtonEvent e)
        {
            Box.Position = Vector2.ComponentMin(e.MouseDownPosition, e.MousePosition);
            Box.Size = Vector2.ComponentMax(e.MouseDownPosition, e.MousePosition) - Box.Position;
        }

        private Visibility state;

        public Visibility State
        {
            get => state;
            set
            {
                if (value == state) return;

                state = value;
                this.FadeTo(state == Visibility.Hidden ? 0 : 1, 250, Easing.OutQuint);
                StateChanged?.Invoke(state);
            }
        }

        public override void Hide() => State = Visibility.Hidden;

        public override void Show() => State = Visibility.Visible;

        [CanBeNull]
        public event Action<Visibility> StateChanged;

        public partial class BoxWithBorders : CompositeDrawable
        {
            private readonly LayoutValue cache = new LayoutValue(Invalidation.RequiredParentSizeToFit);

            public BoxWithBorders()
            {
                AddLayout(cache);
            }

            protected override void Update()
            {
                base.Update();

                if (!cache.IsValid)
                {
                    createContent();
                    cache.Validate();
                }
            }

            private void createContent()
            {
                if (DrawSize == Vector2.Zero)
                {
                    ClearInternal();
                    return;
                }

                // Make lines the same width independent of display resolution.
                float lineThickness = DrawWidth > 0
                    ? DrawWidth / ScreenSpaceDrawQuad.Width * 2
                    : DrawHeight / ScreenSpaceDrawQuad.Height * 2;

                Padding = new MarginPadding(-lineThickness / 2);

                InternalChildren = new Drawable[]
                {
                    new Box
                    {
                        RelativeSizeAxes = Axes.X,
                        Height = lineThickness,
                    },
                    new Box
                    {
                        RelativeSizeAxes = Axes.X,
                        Height = lineThickness,
                        Anchor = Anchor.BottomRight,
                        Origin = Anchor.BottomRight,
                    },
                    new Box
                    {
                        RelativeSizeAxes = Axes.Y,
                        Width = lineThickness,
                    },
                    new Box
                    {
                        RelativeSizeAxes = Axes.Y,
                        Width = lineThickness,
                        Anchor = Anchor.BottomRight,
                        Origin = Anchor.BottomRight,
                    },
                    new Box
                    {
                        RelativeSizeAxes = Axes.Both,
                        Alpha = 0.1f
                    }
                };
            }
        }
    }
}