#nullable enable
using System.Text;
using UICatalog;
using UICatalog.Scenarios;

/// <summary>
///     Demonstrates creating and drawing regions through mouse dragging.
/// </summary>
[ScenarioMetadata ("Regions", "Region Tester")]
[ScenarioCategory ("Mouse and Keyboard")]
[ScenarioCategory ("Drawing")]
public class RegionScenario : Scenario
{
    private readonly Region _region = new ();
    private Point? _dragStart;
    private bool _isDragging;

    private readonly Rune? _previewFillRune = Glyphs.Stipple;

    private RegionDrawStyles _drawStyle;
    private RegionOp _regionOp;

    public override void Main ()
    {
        Application.Init ();

        Window app = new ()
        {
            Title = GetQuitKeyAndName (),
            TabStop = TabBehavior.TabGroup
        };
        app.Padding!.Thickness = new (1);

        var tools = new ToolsView { Title = "Tools", X = Pos.AnchorEnd (), Y = 2 };

        tools.CurrentAttribute = app.GetAttributeForRole (VisualRole.HotNormal);

        tools.SetStyle += b =>
                          {
                              _drawStyle = b;
                              app.SetNeedsDraw ();
                          };

        tools.RegionOpChanged += (s, e) => { _regionOp = e; };

        //tools.AddLayer += () => canvas.AddLayer ();

        app.Add (tools);

        // Add drag handling to window
        app.MouseEvent += (s, e) =>
                          {
                              if (e.Flags.HasFlag (MouseFlags.Button1Pressed))
                              {
                                  if (!e.Flags.HasFlag (MouseFlags.ReportMousePosition))
                                  { // Start drag
                                      _dragStart = e.ScreenPosition;
                                      _isDragging = true;
                                  }
                                  else
                                  {
                                      // Drag
                                      if (_isDragging && _dragStart.HasValue)
                                      {
                                          app.SetNeedsDraw ();
                                      }
                                  }
                              }

                              if (e.Flags.HasFlag (MouseFlags.Button1Released))
                              {
                                  if (_isDragging && _dragStart.HasValue)
                                  {
                                      // Add the new region
                                      AddRectangleFromPoints (_dragStart.Value, e.ScreenPosition, _regionOp);
                                      _isDragging = false;
                                      _dragStart = null;
                                  }

                                  app.SetNeedsDraw ();
                              }
                          };

        // Draw the regions
        app.DrawingContent += (s, e) =>
                              {
                                  // Draw all regions with single line style
                                  //_region.FillRectangles (_attribute.Value, _fillRune);
                                  switch (_drawStyle)
                                  {
                                      case RegionDrawStyles.FillOnly:
                                          _region.FillRectangles (Application.Driver, tools.CurrentAttribute!.Value, _previewFillRune);

                                          break;

                                      case RegionDrawStyles.InnerBoundaries:
                                          _region.DrawBoundaries (app.LineCanvas, LineStyle.Single, tools.CurrentAttribute);
                                          _region.FillRectangles (Application.Driver, tools.CurrentAttribute!.Value, (Rune)' ');

                                          break;

                                      case RegionDrawStyles.OuterBoundary:
                                          _region.DrawOuterBoundary (app.LineCanvas, LineStyle.Single, tools.CurrentAttribute);
                                          _region.FillRectangles (Application.Driver, tools.CurrentAttribute!.Value, (Rune)' ');

                                          break;
                                  }

                                  // If currently dragging, draw preview rectangle
                                  if (_isDragging && _dragStart.HasValue)
                                  {
                                      Point currentMousePos = Application.GetLastMousePosition ()!.Value;
                                      Rectangle previewRect = GetRectFromPoints (_dragStart.Value, currentMousePos);
                                      var previewRegion = new Region (previewRect);

                                      previewRegion.FillRectangles (Application.Driver, tools.CurrentAttribute!.Value, (Rune)' ');

                                      previewRegion.DrawBoundaries (
                                                                    app.LineCanvas,
                                                                    LineStyle.Dashed,
                                                                    new (
                                                                         tools.CurrentAttribute!.Value.Foreground.GetBrighterColor (),
                                                                         tools.CurrentAttribute!.Value.Background));
                                  }
                              };

        Application.Run (app);

        // Clean up
        app.Dispose ();
        Application.Shutdown ();
    }

    private void AddRectangleFromPoints (Point start, Point end, RegionOp op)
    {
        Rectangle rect = GetRectFromPoints (start, end);
        var region = new Region (rect);
        _region.Combine (region, op); // Or RegionOp.MinimalUnion if you want minimal rectangles
    }

    private Rectangle GetRectFromPoints (Point start, Point end)
    {
        int left = Math.Min (start.X, end.X);
        int top = Math.Min (start.Y, end.Y);
        int right = Math.Max (start.X, end.X);
        int bottom = Math.Max (start.Y, end.Y);

        // Ensure minimum width and height of 1
        int width = Math.Max (1, right - left + 1);
        int height = Math.Max (1, bottom - top + 1);

        return new (left, top, width, height);
    }
}

public enum RegionDrawStyles
{
    FillOnly = 0,

    InnerBoundaries = 1,

    OuterBoundary = 2
}

public class ToolsView : Window
{
    //private Button _addLayerBtn;
    private readonly AttributeView _attributeView = new ();
    private OptionSelector<RegionDrawStyles>? _stylePicker;
    private OptionSelector<RegionOp>? _regionOpSelector;

    public Attribute? CurrentAttribute
    {
        get => _attributeView.Value;
        set => _attributeView.Value = value;
    }

    public ToolsView ()
    {
        BorderStyle = LineStyle.Dotted;
        Border!.Thickness = new (1, 2, 1, 1);
        Height = Dim.Auto ();
        Width = Dim.Auto ();
    }

    //public event Action AddLayer;

    public override void BeginInit ()
    {
        base.BeginInit ();

        _attributeView.ValueChanged += (s, e) => AttributeChanged?.Invoke (this, e);

        _stylePicker = new ()
        {
            Width = Dim.Fill (),
            X = 0, Y = Pos.Bottom (_attributeView) + 1,
            AssignHotKeys = true
        };
        _stylePicker.BorderStyle = LineStyle.Single;
        _stylePicker.Border!.Thickness = new (0, 1, 0, 0);
        _stylePicker.Title = "Draw Style";

        _stylePicker.ValueChanged += (s, a) => { SetStyle?.Invoke ((RegionDrawStyles)a.Value!); };
        _stylePicker.Value = RegionDrawStyles.FillOnly;

        _regionOpSelector = new ()
        {
            X = 0,
            Y = Pos.Bottom (_stylePicker) + 1,
            AssignHotKeys = true
        };
        _regionOpSelector.ValueChanged += (s, a) =>
                                          {
                                              if (a.Value is { })
                                              {
                                                  RegionOpChanged?.Invoke (this, (RegionOp)a.Value);
                                              }
                                          };
        _regionOpSelector.Value = RegionOp.MinimalUnion;

        //_addLayerBtn = new () { Text = "New Layer", X = Pos.Center (), Y = Pos.Bottom (_stylePicker) };

        //_addLayerBtn.Accepting += (s, a) => AddLayer?.Invoke ();
        Add (_attributeView, _stylePicker, _regionOpSelector); //, _addLayerBtn);
    }

    public event EventHandler<Attribute?>? AttributeChanged;
    public event EventHandler<RegionOp>? RegionOpChanged;
    public event Action<RegionDrawStyles>? SetStyle;
}

public class AttributeView : View
{
    public event EventHandler<Attribute?>? ValueChanged;
    private Attribute? _value;

    public Attribute? Value
    {
        get => _value;
        set
        {
            _value = value;
            ValueChanged?.Invoke (this, value);
        }
    }

    private static readonly HashSet<(int, int)> _foregroundPoints =
    [
        (0, 0), (1, 0), (2, 0),
        (0, 1), (1, 1), (2, 1)
    ];

    private static readonly HashSet<(int, int)> _backgroundPoints =
    [
        (3, 1),
        (1, 2), (2, 2), (3, 2)
    ];

    public AttributeView ()
    {
        Width = Dim.Fill ();
        Height = 4;

        BorderStyle = LineStyle.Single;
        Border!.Thickness = new (0, 1, 0, 0);
        Title = "Attribute";
    }

    /// <inheritdoc/>
    protected override bool OnDrawingContent ()
    {
        Color fg = Value?.Foreground ?? Color.Black;
        Color bg = Value?.Background ?? Color.Black;

        bool isTransparentFg = fg == GetAttributeForRole (VisualRole.Normal).Background;
        bool isTransparentBg = bg == GetAttributeForRole (VisualRole.Normal).Background;

        SetAttribute (new (fg, isTransparentFg ? Color.Gray : fg));

        // Square of foreground color
        foreach ((int, int) point in _foregroundPoints)
        {
            // Make pattern like this when it is same color as background of control
            /*▓▒
              ▒▓*/
            Rune rune;

            if (isTransparentFg)
            {
                rune = (Rune)(point.Item1 % 2 == point.Item2 % 2 ? '▓' : '▒');
            }
            else
            {
                rune = (Rune)'█';
            }

            AddRune (point.Item1, point.Item2, rune);
        }

        SetAttribute (new (bg, isTransparentBg ? Color.Gray : bg));

        // Square of background color
        foreach ((int, int) point in _backgroundPoints)
        {
            // Make pattern like this when it is same color as background of control
            /*▓▒
              ▒▓*/
            Rune rune;

            if (isTransparentBg)
            {
                rune = (Rune)(point.Item1 % 2 == point.Item2 % 2 ? '▓' : '▒');
            }
            else
            {
                rune = (Rune)'█';
            }

            AddRune (point.Item1, point.Item2, rune);
        }

        return true;
    }

    /// <inheritdoc/>
    protected override bool OnMouseEvent (MouseEventArgs mouseEvent)
    {
        if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked))
        {
            if (IsForegroundPoint (mouseEvent.Position.X, mouseEvent.Position.Y))
            {
                ClickedInForeground ();
            }
            else if (IsBackgroundPoint (mouseEvent.Position.X, mouseEvent.Position.Y))
            {
                ClickedInBackground ();
            }
        }

        mouseEvent.Handled = true;

        return mouseEvent.Handled;
    }

    private bool IsForegroundPoint (int x, int y) { return _foregroundPoints.Contains ((x, y)); }

    private bool IsBackgroundPoint (int x, int y) { return _backgroundPoints.Contains ((x, y)); }

    private void ClickedInBackground ()
    {
        if (LineDrawing.PromptForColor ("Background", Value!.Value.Background, out Color newColor))
        {
            Value = new (Value!.Value.Foreground, newColor);
            SetNeedsDraw ();
        }
    }

    private void ClickedInForeground ()
    {
        if (LineDrawing.PromptForColor ("Foreground", Value!.Value.Foreground, out Color newColor))
        {
            Value = new (newColor, Value!.Value.Background);
            SetNeedsDraw ();
        }
    }
}
