// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using System.CodeDom;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
using System.Drawing;
using System.Drawing.Design;
using System.Text.RegularExpressions;
using System.Windows.Forms.Design.Behavior;

namespace System.Windows.Forms.Design;

internal partial class TableLayoutPanelDesigner : FlowPanelDesigner
{
    private TableLayoutPanelBehavior _tlpBehavior;      // every resize col/row glyph is associated with this instance of behavior
    private Point _droppedCellPosition = InvalidPoint;  // used to insert new children

    // NEVER USE undoing DIRECTLY. ALWAYS USE THE PROPERTY
    private bool _undoing;
    private UndoEngine _undoEngine;

    private Control _localDragControl;                  // only valid if we're currently dragging a child control of the table
    private List<IComponent> _dragComponents;           // the components we are dragging
    private DesignerVerbCollection _verbs;              // add col/row and remove col/row tab verbs
    private DesignerTableLayoutControlCollection _controls;
    private DesignerVerb _removeRowVerb;
    private DesignerVerb _removeColVerb;
    private DesignerActionListCollection _actionLists;  // action list for the Smart Tag

    private BaseContextMenuStrip _designerContextMenuStrip;
    private int _curRow = -1;  // row cursor was over when context menu was dropped
    private int _curCol = -1;  // col cursor was over when context menu was dropped

    private IComponentChangeService _compSvc;
    private PropertyDescriptor _rowStyleProp;
    private PropertyDescriptor _colStyleProp;

    // Only used when adding controls via the toolbox
    private int _rowCountBeforeAdd; // What's the row count before a control is added
    private int _colCountBeforeAdd; // Ditto for column

    // TLP context menu row/column items.
    private ToolStripMenuItem _contextMenuRow;
    private ToolStripMenuItem _contextMenuCol;

    private int _ensureSuspendCount;

    private TableLayoutPanelBehavior Behavior => _tlpBehavior ??= new TableLayoutPanelBehavior(Table, this, Component.Site);

    private TableLayoutColumnStyleCollection ColumnStyles => Table.ColumnStyles;

    private TableLayoutRowStyleCollection RowStyles => Table.RowStyles;

    public int RowCount
    {
        get => Table.RowCount;
        set
        {
            if (value <= 0 && !Undoing)
            {
                throw new ArgumentException(string.Format(SR.TableLayoutPanelDesignerInvalidColumnRowCount, "RowCount"));
            }
            else
            {
                Table.RowCount = value;
            }
        }
    }

    public int ColumnCount
    {
        get => Table.ColumnCount;
        set
        {
            if (value <= 0 && !Undoing)
            {
                throw new ArgumentException(string.Format(SR.TableLayoutPanelDesignerInvalidColumnRowCount, "ColumnCount"));
            }
            else
            {
                Table.ColumnCount = value;
            }
        }
    }

    private bool IsLocalizable()
    {
        IDesignerHost host = GetService(typeof(IDesignerHost)) as IDesignerHost;
        if (host is not null)
        {
            PropertyDescriptor prop = TypeDescriptor.GetProperties(host.RootComponent)["Localizable"];
            if (prop is not null && prop.PropertyType == typeof(bool))
            {
                return (bool)prop.GetValue(host.RootComponent);
            }
        }

        return false;
    }

    private bool ShouldSerializeColumnStyles()
    {
        return !IsLocalizable();
    }

    private bool ShouldSerializeRowStyles()
    {
        return !IsLocalizable();
    }

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    private DesignerTableLayoutControlCollection Controls =>
        _controls ??= new DesignerTableLayoutControlCollection((TableLayoutPanel)Control);

    private ContextMenuStrip DesignerContextMenuStrip
    {
        get
        {
            if (_designerContextMenuStrip is null)
            {
                _designerContextMenuStrip = new BaseContextMenuStrip(Component.Site);

                // Remove all the verbs -- except the Edit Rows and Columns
                ContextMenuStripGroup group = _designerContextMenuStrip.Groups[StandardGroups.Verbs];
                foreach (DesignerVerb verb in Verbs)
                {
                    if (verb.Text.Equals(string.Format(SR.TableLayoutPanelDesignerEditRowAndCol)))
                    {
                        continue;
                    }

                    foreach (ToolStripItem item in group.Items)
                    {
                        if (item.Text.Equals(verb.Text))
                        {
                            group.Items.Remove(item);
                            break;
                        }
                    }
                }

                // Now build the new menus
                ToolStripDropDownMenu rowMenu = BuildMenu(true);
                ToolStripDropDownMenu colMenu = BuildMenu(false);

                _contextMenuRow = new ToolStripMenuItem
                {
                    DropDown = rowMenu,
                    Text = SR.TableLayoutPanelDesignerRowMenu
                };

                _contextMenuCol = new ToolStripMenuItem
                {
                    DropDown = colMenu,
                    Text = SR.TableLayoutPanelDesignerColMenu
                };

                group.Items.Insert(0, _contextMenuCol);
                group.Items.Insert(0, _contextMenuRow);

                group = _designerContextMenuStrip.Groups[StandardGroups.Edit];
                foreach (ToolStripItem item in group.Items)
                {
                    if (item.Text.Equals(SR.ContextMenuCut))
                    {
                        item.Text = SR.TableLayoutPanelDesignerContextMenuCut;
                    }
                    else if (item.Text.Equals(SR.ContextMenuCopy))
                    {
                        item.Text = SR.TableLayoutPanelDesignerContextMenuCopy;
                    }
                    else if (item.Text.Equals(SR.ContextMenuDelete))
                    {
                        item.Text = SR.TableLayoutPanelDesignerContextMenuDelete;
                    }
                }
            }

            bool onValidCell = IsOverValidCell(false);

            _contextMenuRow.Enabled = onValidCell;
            _contextMenuCol.Enabled = onValidCell;

            return _designerContextMenuStrip;
        }
    }

    private bool IsLoading
    {
        get
        {
            IDesignerHost host = GetService(typeof(IDesignerHost)) as IDesignerHost;

            if (host is not null)
            {
                return host.Loading;
            }

            return false;
        }
    }

    internal TableLayoutPanel Table => Component as TableLayoutPanel;

    private bool Undoing
    {
        get
        {
            if (_undoEngine is null)
            {
                _undoEngine = GetService(typeof(UndoEngine)) as UndoEngine;
                if (_undoEngine is not null)
                {
                    _undoEngine.Undoing += OnUndoing;
                    if (_undoEngine.UndoInProgress)
                    {
                        _undoing = true;
                        _undoEngine.Undone += OnUndone;
                    }
                }
            }

            return _undoing;
        }
        set
        {
            _undoing = value;
        }
    }

    public override DesignerVerbCollection Verbs
    {
        get
        {
            if (_verbs is null)
            {
                _removeColVerb = new DesignerVerb(SR.TableLayoutPanelDesignerRemoveColumn, OnVerbRemove);
                _removeRowVerb = new DesignerVerb(SR.TableLayoutPanelDesignerRemoveRow, OnVerbRemove);

                _verbs = new DesignerVerbCollection();

                _verbs.Add(new DesignerVerb(SR.TableLayoutPanelDesignerAddColumn, OnVerbAdd));
                _verbs.Add(new DesignerVerb(SR.TableLayoutPanelDesignerAddRow, OnVerbAdd));
                _verbs.Add(_removeColVerb);
                _verbs.Add(_removeRowVerb);
                _verbs.Add(new DesignerVerb(SR.TableLayoutPanelDesignerEditRowAndCol, OnVerbEdit));

                CheckVerbStatus();
            }

            return _verbs;
        }
    }

    private void RefreshSmartTag()
    {
        DesignerActionUIService actionUIService = (DesignerActionUIService)GetService(typeof(DesignerActionUIService));
        actionUIService?.Refresh(Component);
    }

    private void CheckVerbStatus()
    {
        if (Table is not null)
        {
            if (_removeColVerb is not null)
            {
                bool colState = Table.ColumnCount > 1;
                if (_removeColVerb.Enabled != colState)
                {
                    _removeColVerb.Enabled = colState;
                }
            }

            if (_removeRowVerb is not null)
            {
                bool rowState = Table.RowCount > 1;
                if (_removeRowVerb.Enabled != rowState)
                {
                    _removeRowVerb.Enabled = rowState;
                }
            }

            RefreshSmartTag();
        }
    }

    public override DesignerActionListCollection ActionLists
    {
        get
        {
            if (_actionLists is null)
            {
                BuildActionLists();
            }

            return _actionLists;
        }
    }

    private ToolStripDropDownMenu BuildMenu(bool isRow)
    {
        ToolStripMenuItem add = new();
        ToolStripMenuItem insert = new();
        ToolStripMenuItem delete = new();
        ToolStripSeparator separator = new();
        ToolStripLabel label = new();
        ToolStripMenuItem absolute = new();
        ToolStripMenuItem percent = new();
        ToolStripMenuItem autosize = new();

        add.Text = SR.TableLayoutPanelDesignerAddMenu;
        add.Tag = isRow;
        add.Name = "add";
        add.Click += OnAddClick;

        insert.Text = SR.TableLayoutPanelDesignerInsertMenu;
        insert.Tag = isRow;
        insert.Name = "insert";
        insert.Click += OnInsertClick;

        delete.Text = SR.TableLayoutPanelDesignerDeleteMenu;
        delete.Tag = isRow;
        delete.Name = "delete";
        delete.Click += OnDeleteClick;

        label.Text = SR.TableLayoutPanelDesignerLabelMenu;
        if (SR.TableLayoutPanelDesignerDontBoldLabel == "0")
        {
            label.Font = new Font(label.Font, FontStyle.Bold);
        }

        label.Name = "sizemode";

        absolute.Text = SR.TableLayoutPanelDesignerAbsoluteMenu;
        absolute.Tag = isRow;
        absolute.Name = "absolute";
        absolute.Click += OnAbsoluteClick;

        percent.Text = SR.TableLayoutPanelDesignerPercentageMenu;
        percent.Tag = isRow;
        percent.Name = "percent";
        percent.Click += OnPercentClick;

        autosize.Text = SR.TableLayoutPanelDesignerAutoSizeMenu;
        autosize.Tag = isRow;
        autosize.Name = "autosize";
        autosize.Click += OnAutoSizeClick;

        ToolStripDropDownMenu menu = new();
        menu.Items.AddRange((ToolStripItem[])[add, insert, delete, separator, label, absolute, percent, autosize]);
        menu.Tag = isRow;
        menu.Opening += OnRowColMenuOpening;

        IUIService uis = GetService(typeof(IUIService)) as IUIService;
        if (uis is not null)
        {
            menu.Renderer = (ToolStripProfessionalRenderer)uis.Styles["VsRenderer"];
            if (uis.Styles["VsColorPanelText"] is Color color)
            {
                menu.ForeColor = color;
            }
        }

        return menu;
    }

    private void BuildActionLists()
    {
        _actionLists = new DesignerActionListCollection();

        // Add Column action list
        _actionLists.Add(new TableLayouPanelRowColumnActionList(this));

        // if one actionList has AutoShow == true then the chrome panel will popup when the user DnD the DataGridView onto the form
        // It would make sense to promote AutoShow to DesignerActionListCollection.
        // But we don't own the DesignerActionListCollection so we just set AutoShow on the first ActionList.
        _actionLists[0].AutoShow = true;
    }

    private class TableLayouPanelRowColumnActionList : DesignerActionList
    {
        private readonly TableLayoutPanelDesigner _owner;

        public TableLayouPanelRowColumnActionList(TableLayoutPanelDesigner owner) : base(owner.Component)
        {
            _owner = owner;
        }

        public override DesignerActionItemCollection GetSortedActionItems()
        {
            DesignerActionItemCollection items =
            [
                // We don't promote these Items to DesignerVerbs, since we need to be able
                // to disable/enable the Remove entries, based on the number of Rows/Cols.
                // Unfortunately, you cannot do that via the DesignerAction stuff.
                new DesignerActionMethodItem(this,
                    memberName: nameof(AddColumn),
                    displayName: SR.TableLayoutPanelDesignerAddColumn,
                    includeAsDesignerVerb: false),
                new DesignerActionMethodItem(this,
                    memberName: nameof(AddRow),
                    displayName: SR.TableLayoutPanelDesignerAddRow,
                    includeAsDesignerVerb: false),
            ];

            if (_owner.Table.ColumnCount > 1)
            {
                items.Add(new DesignerActionMethodItem(this,
                    memberName: nameof(RemoveColumn),
                    displayName: SR.TableLayoutPanelDesignerRemoveColumn,
                    includeAsDesignerVerb: false));
            }

            if (_owner.Table.RowCount > 1)
            {
                items.Add(new DesignerActionMethodItem(this,
                    memberName: nameof(RemoveRow),
                    displayName: SR.TableLayoutPanelDesignerRemoveRow,
                    includeAsDesignerVerb: false));
            }

            items.Add(new DesignerActionMethodItem(this,
                memberName: nameof(EditRowAndCol),
                displayName: SR.TableLayoutPanelDesignerEditRowAndCol,
                includeAsDesignerVerb: false));

            return items;
        }

        public void AddColumn() => _owner.OnAdd(false);

        public void AddRow() => _owner.OnAdd(true);

        public void RemoveColumn() => _owner.OnRemove(false);

        public void RemoveRow() => _owner.OnRemove(true);

        public void EditRowAndCol() => _owner.OnEdit();
    }

    private void RemoveControlInternal(Control c)
    {
        Table.ControlRemoved -= OnControlRemoved;
        Table.Controls.Remove(c);
        Table.ControlRemoved += OnControlRemoved;
    }

    private void AddControlInternal(Control c, int col, int row)
    {
        Table.ControlAdded -= OnControlAdded;
        Table.Controls.Add(c, col, row);
        Table.ControlAdded += OnControlAdded;
    }

    private void ControlAddedInternal(Control control, Point newControlPosition, bool localReposition, bool fullTable, DragEventArgs de)
    {
        // If the table is full - we'll want to 'autogrow' either the row or column based on the grow style property
        // before we actually add the control.
        if (fullTable)
        {
            if (Table.GrowStyle == TableLayoutPanelGrowStyle.AddRows)
            {
                PropertyDescriptor rowProp = TypeDescriptor.GetProperties(Table)["RowCount"];
                rowProp?.SetValue(Table, Table.GetRowHeights().Length);

                newControlPosition.X = 0;
                newControlPosition.Y = Table.RowCount - 1;
            }
            else if (Table.GrowStyle == TableLayoutPanelGrowStyle.AddColumns)
            {
                PropertyDescriptor colProp = TypeDescriptor.GetProperties(Table)["ColumnCount"];
                colProp?.SetValue(Table, Table.GetColumnWidths().Length);

                newControlPosition.X = Table.ColumnCount - 1;
                newControlPosition.Y = 0;
            }
            else
            {
                // fixed growstyle - what do we do here?
            }
        }

        DesignerTransaction trans = null;
        PropertyDescriptor controlsProp = TypeDescriptor.GetProperties(Table)["Controls"];
        // find the control that currently resides at our newControlPosition - we'll want to either
        // remove it or swap it.
        try
        {
            // Are we doing a local copy
            bool localCopy = ((de is not null) && (de.Effect == DragDropEffects.Copy) && localReposition);

            Control existingControl = ((TableLayoutPanel)Control).GetControlFromPosition(newControlPosition.X, newControlPosition.Y);

            if (localCopy)
            {
                Debug.Assert(existingControl is null, "We shouldn't be able to do a local copy of a cell with an existing control");
                IDesignerHost host = GetService(typeof(IDesignerHost)) as IDesignerHost;
                if (host is not null)
                {
                    trans = host.CreateTransaction(string.Format(SR.BehaviorServiceCopyControl, control.Site.Name));
                }

                // Need to do this after the transaction is created
                PropChanging(controlsProp);
            }

            // does the newControlPosition contain a valid control
            // if so - we need to perform a 'swap' function if this is local - or default
            // to controls.add if this is from an external source
            else if (existingControl is not null && !existingControl.Equals(control))
            {
                if (localReposition)
                {
                    // If we're swapping controls, create a DesignerTransaction
                    // so this can be undoable.
                    IDesignerHost host = GetService(typeof(IDesignerHost)) as IDesignerHost;
                    if (host is not null)
                    {
                        trans = host.CreateTransaction(string.Format(SR.TableLayoutPanelDesignerControlsSwapped, control.Site.Name, existingControl.Site.Name));
                    }

                    // Need to do this after the transaction is created
                    PropChanging(controlsProp);
                    RemoveControlInternal(existingControl);// we found our control to swap
                }
                else
                {
                    // here we externally dragged a control onto a valid control in our table
                    // we'll try to find a place to put it (since we shouldn't be here if our table
                    // was full

                    // MartinTh -- we shouldn't ever get here...
                    PropChanging(controlsProp);
                    existingControl = null;// null this out since we're not swapping
                }
            }
            else
            {
                // here we have a truly empty cell

                // If we are not doing a local move, then the DropSourceBehavior created the transaction for us
                if (localReposition)
                {
                    IDesignerHost host = GetService(typeof(IDesignerHost)) as IDesignerHost;
                    if (host is not null)
                    {
                        trans = host.CreateTransaction(string.Format(SR.BehaviorServiceMoveControl, control.Site.Name));
                    }
                }

                existingControl = null;
                PropChanging(controlsProp);
            }

            // Need to do this after the transaction has been created
            if (localCopy)
            {
                List<IComponent> temp = [control];
                temp = DesignerUtils.CopyDragObjects(temp, Component.Site);
                control = temp[0] as Control;
            }

            // if we are locally repositioning this control - remove it (internally)
            // from the table's child collection and add something in its place. This
            // will be a control to swap it with
            if (localReposition)
            {
                Point oldPosition = GetControlPosition(control);
                if (oldPosition != InvalidPoint)
                {
                    RemoveControlInternal(control);

                    if (oldPosition != newControlPosition)
                    {// guard against dropping it back on itself
                        if (existingControl is not null)
                        {
                            // we have something to swap...
                            AddControlInternal(existingControl, oldPosition.X, oldPosition.Y);
                        }
                    }
                }
            }

            // Finally - set our new control to the new position
            if (localReposition)
            {
                // If we are doing a local drag, then the control previously got removed
                AddControlInternal(control, newControlPosition.X, newControlPosition.Y);
            }
            else
            {
                // If not, then the control has already been added, and all we need to do is set the position
                Table.SetCellPosition(control, new TableLayoutPanelCellPosition(newControlPosition.X, newControlPosition.Y));
            }

            PropChanged(controlsProp);

            if (de is not null)
            {
                base.OnDragComplete(de);
            }

            trans?.Commit();
            trans = null;

            // Set the selection to be the newly added control - but only if we are doing a local copy
            if (localCopy)
            {
                ISelectionService selSvc = GetService(typeof(ISelectionService)) as ISelectionService;
                selSvc?.SetSelectedComponents(new object[] { control }, SelectionTypes.Primary | SelectionTypes.Replace);
            }
        }

        // VSWhidbey #390285
        catch (ArgumentException argumentEx)
        {
            IUIService uiService = GetService(typeof(IUIService)) as IUIService;
            uiService?.ShowError(argumentEx);
        }

        catch (Exception ex) when (!ex.IsCriticalException())
        {
        }

        finally
        {
            trans?.Cancel();
        }
    }

    private void CreateEmptyTable()
    {
        // set the table's default rows and columns
        PropertyDescriptor colProp = TypeDescriptor.GetProperties(Table)["ColumnCount"];
        colProp?.SetValue(Table, DesignerUtils.s_defaultColumnCount);

        PropertyDescriptor rowProp = TypeDescriptor.GetProperties(Table)["RowCount"];
        rowProp?.SetValue(Table, DesignerUtils.s_defaultRowCount);

        // this will make sure we have styles created for every row & column
        EnsureAvailableStyles();

        InitializeNewStyles();
    }

    private void InitializeNewStyles()
    {
        // adjust the two absolutely positioned columns
        Table.ColumnStyles[0].SizeType = SizeType.Percent;
        Table.ColumnStyles[0].Width = DesignerUtils.s_minimumStylePercent;
        Table.ColumnStyles[1].SizeType = SizeType.Percent;
        Table.ColumnStyles[1].Width = DesignerUtils.s_minimumStylePercent;

        // adjust two absolutely positioned rows
        Table.RowStyles[0].SizeType = SizeType.Percent;
        Table.RowStyles[0].Height = DesignerUtils.s_minimumStylePercent;
        Table.RowStyles[1].SizeType = SizeType.Percent;
        Table.RowStyles[1].Height = DesignerUtils.s_minimumStylePercent;
    }

    /// <summary>
    ///  Returns true if an empty subset of size subsetColumns x subsetRows exists in the cells
    ///  array. cells[c,r] == true if the corresponding cell contains a control
    /// </summary>
    /// <param name="cells"></param>
    /// <param name="columns"></param>
    /// <param name="rows"></param>
    /// <param name="subsetColumns"></param>
    /// <param name="subsetRows"></param>
    /// <returns></returns>
    private static bool SubsetExists(bool[,] cells, int columns, int rows, int subsetColumns, int subsetRows)
    {
        bool exists = false;
        int column;
        int row;

        for (row = 0; row < rows - subsetRows + 1; row++)
        {
            for (column = 0; column < columns - subsetColumns + 1; column++)
            {
                if (!cells[column, row])
                {
                    exists = true;
                    for (int m = row; (m < row + subsetRows) && exists; m++)
                    {
                        for (int n = column; n < column + subsetColumns; n++)
                        {
                            if (cells[n, m])
                            {
                                exists = false;
                                break;
                            }
                        }
                    }

                    if (exists)
                    {
                        break;
                    }
                }
            }

            if (exists)
            {
                break;
            }
        }

        return exists;
    }

    protected internal override bool CanAddComponent(IComponent component)
    {
        if (Table.GrowStyle != TableLayoutPanelGrowStyle.FixedSize)
        {
            return true;
        }

        Control newControl = GetControl(component);
        if (newControl is null)
        {
            // this case should have been filtered out by CanParent
            return false;
        }

        int rowSpan = Table.GetRowSpan(newControl);
        int columnSpan = Table.GetColumnSpan(newControl);

        // under certain conditions RowCount and ColumnCount are not accurate
        int numRows = Table.GetRowHeights().Length;
        int numColumns = Table.GetColumnWidths().Length;
        int numOccupiedCells = 0; // total occupied cells in the TableLayoutPanel

        int totalCells = numRows * numColumns;
        int cellsNeeded = rowSpan * columnSpan;

        // cache which cells have controls in them
        bool[,] occupiedCells = null;
        if (cellsNeeded > 1)
        {
            occupiedCells = new bool[numColumns, numRows];
        }

        if (cellsNeeded <= totalCells)
        {
            for (int row = 0; row < numRows; row++)
            {
                for (int column = 0; column < numColumns; column++)
                {
                    if (Table.GetControlFromPosition(column, row) is not null)
                    {
                        numOccupiedCells++;
                        if (cellsNeeded > 1)
                        {
                            occupiedCells[column, row] = true;
                        }
                    }
                }
            }
        }

        // Check if the table has enough empty cells to accomodate the new component
        if (numOccupiedCells + cellsNeeded > totalCells)
        {
            IUIService uiService = (IUIService)GetService(typeof(IUIService));
            uiService.ShowError(SR.TableLayoutPanelFullDesc);

            return false;
        }

        // if the new control spans several rows or columns, check if the
        // table has a contiguous free area to accomodate the control
        if (cellsNeeded > 1)
        {
            if (!SubsetExists(occupiedCells, numColumns, numRows, columnSpan, rowSpan))
            {
                IUIService uiService = (IUIService)GetService(typeof(IUIService));
                uiService.ShowError(SR.TableLayoutPanelSpanDesc);

                return false;
            }
        }

        return true;
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
            host?.TransactionClosing -= OnTransactionClosing;

            if (_undoEngine is not null)
            {
                if (Undoing)
                {
                    _undoEngine.Undone -= OnUndone;
                }

                _undoEngine.Undoing -= OnUndoing;
            }

            if (_compSvc is not null)
            {
                _compSvc.ComponentChanged -= OnComponentChanged;
                _compSvc.ComponentChanging -= OnComponentChanging;
            }

            if (Table is not null)
            {
                Table.ControlAdded -= OnControlAdded;
                Table.ControlRemoved -= OnControlRemoved;
            }

            _contextMenuRow?.Dispose();

            _contextMenuCol?.Dispose();

            _rowStyleProp = null;
            _colStyleProp = null;
        }

        base.Dispose(disposing);
    }

    protected override void DrawBorder(Graphics graphics)
    {
        if (Table.CellBorderStyle != TableLayoutPanelCellBorderStyle.None)
        {
            // only draw a fake border if there is no borderstyle.
            return;
        }

        base.DrawBorder(graphics);

        Rectangle rc = Control.DisplayRectangle;
        rc.Width--;
        rc.Height--;

        int[] cw = Table.GetColumnWidths();
        int[] rh = Table.GetRowHeights();

        using Pen pen = BorderPen;

        if (cw.Length > 1)
        {
            bool isRTL = (Table.RightToLeft == RightToLeft.Yes);

            // offset by padding
            int startX = isRTL ? rc.Right : rc.Left;
            for (int i = 0; i < cw.Length - 1; i++)
            {
                if (isRTL)
                {
                    startX -= cw[i];
                }
                else
                {
                    startX += cw[i];
                }

                graphics.DrawLine(pen, startX, rc.Top, startX, rc.Bottom);
            }
        }

        if (rh.Length > 1)
        {
            int startY = rc.Top;
            for (int i = 0; i < rh.Length - 1; i++)
            {
                startY += rh[i];
                graphics.DrawLine(pen, rc.Left, startY, rc.Right, startY);
            }
        }
    }

    internal void SuspendEnsureAvailableStyles() => _ensureSuspendCount++;

    internal void ResumeEnsureAvailableStyles(bool performEnsure)
    {
        if (_ensureSuspendCount > 0)
        {
            _ensureSuspendCount--;

            if (_ensureSuspendCount == 0 && performEnsure)
            {
                EnsureAvailableStyles();
            }
        }
    }

    private bool EnsureAvailableStyles()
    {
        if (IsLoading || Undoing || _ensureSuspendCount > 0)
        {
            return false;
        }

        int[] cw = Table.GetColumnWidths();
        int[] rh = Table.GetRowHeights();

        Table.SuspendLayout();
        try
        {
            // if we have more columns then column styles add some...
            if (cw.Length > Table.ColumnStyles.Count)
            {
                int colDifference = cw.Length - Table.ColumnStyles.Count;
                PropChanging(_rowStyleProp);
                for (int i = 0; i < colDifference; i++)
                {
                    Table.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, DesignerUtils.s_minimumStyleSize));
                }

                PropChanged(_rowStyleProp);
            }

            // if we have more rows then row styles add some...
            if (rh.Length > Table.RowStyles.Count)
            {
                int rowDifference = rh.Length - Table.RowStyles.Count;
                PropChanging(_colStyleProp);
                for (int i = 0; i < rowDifference; i++)
                {
                    Table.RowStyles.Add(new RowStyle(SizeType.Absolute, DesignerUtils.s_minimumStyleSize));
                }

                PropChanged(_colStyleProp);
            }
        }
        finally
        {
            Table.ResumeLayout();
        }

        return true;
    }

    private Control ExtractControlFromDragEvent(DragEventArgs de)
    {
        if (de.Data is DropSourceBehavior.BehaviorDataObject data)
        {
            _dragComponents = [..data.DragComponents];
            return _dragComponents[0] as Control;
        }

        return null;
    }

    private Point GetCellPosition(Point pos)
    {
        // get some runtime table info
        int[] rows = Table.GetRowHeights();
        int[] columns = Table.GetColumnWidths();

        // By using DisplayRectangle here we handle the case where we are scrolled. VSWhidbey #399557
        Point startingPoint = Table.PointToScreen(Table.DisplayRectangle.Location);
        Rectangle bounds = new(startingPoint, Table.DisplayRectangle.Size);

        Point position = new(-1, -1);

        bool isRTL = Table.RightToLeft == RightToLeft.Yes;
        int offset = bounds.X;

        // find column ...
        if (isRTL)
        {
            if (pos.X <= bounds.X)
            { // if pos.X >= bounds.Right, position.X = -1
                position.X = columns.Length;
            }
            else if (pos.X < bounds.Right)
            { // it must be within the bounds
                offset = bounds.Right;

                // loop through the columns and identify where the mouse is
                for (int i = 0; i < columns.Length; i++)
                {
                    position.X = i;
                    if (pos.X >= offset - columns[i])
                    {
                        break;
                    }

                    offset -= columns[i];
                }
            }
        }
        else
        {
            if (pos.X >= bounds.Right)
            {
                position.X = columns.Length;
            }
            else if (pos.X > bounds.X)
            { // if pos.X <= bounds.X, position.X = -1.
              // loop through the columns and identify where the mouse is
                for (int i = 0; i < columns.Length; i++)
                { // it must be within the bounds
                    position.X = i;
                    if (pos.X <= offset + columns[i])
                    {
                        break;
                    }

                    offset += columns[i];
                }
            }
        }

        // find row ...
        offset = bounds.Y;

        if (pos.Y >= bounds.Bottom)
        {
            position.Y = rows.Length;
        }
        else if (pos.Y > bounds.Y)
        { // if pos.Y <= bounds.Y, position.Y = -1
          // loop through the rows and identify where the mouse is
            for (int i = 0; i < rows.Length; i++)
            {
                if (pos.Y <= offset + rows[i])
                {
                    position.Y = i;
                    break;
                }

                offset += rows[i];
            }
        }

        return position;
    }

    private Point GetControlPosition(Control control)
    {
        TableLayoutPanelCellPosition pos = Table.GetPositionFromControl(control);
        if ((pos.Row == -1) && (pos.Column == -1))
        {
            return InvalidPoint;
        }

        return new Point(pos.Column, pos.Row);
    }

    public override GlyphCollection GetGlyphs(GlyphSelectionType selectionType)
    {
        GlyphCollection glyphs = base.GetGlyphs(selectionType);

        PropertyDescriptor prop = TypeDescriptor.GetProperties(Component)["Locked"];
        bool locked = (prop is not null) && ((bool)prop.GetValue(Component));

        // Before adding glyphs for every row/column, make sure we have a column/rowstyle for every column/row
        bool safeToRefresh = EnsureAvailableStyles();

        // if we're somehow selected, not locked, and not inherited -then offer up glyphs for every
        // column/row line
        if (selectionType != GlyphSelectionType.NotSelected && !locked && InheritanceAttribute != InheritanceAttribute.InheritedReadOnly)
        {
            // get the correctly translated bounds
            // By using DisplayRectangle here we handle the case where we are scrolled. VSWhidbey #399689
            Point loc = BehaviorService.MapAdornerWindowPoint(Table.Handle, Table.DisplayRectangle.Location);
            Rectangle bounds = new(loc, Table.DisplayRectangle.Size);

            Point controlLoc = BehaviorService.ControlToAdornerWindow(Control);
            Rectangle checkBounds = new(controlLoc, Control.ClientSize); // Can't use Control.Size since that will include any scrollbar

            int[] cw = Table.GetColumnWidths();
            int[] rh = Table.GetRowHeights();
            int halfSize = DesignerUtils.s_resizeGlyphSize / 2;

            bool isRTL = (Table.RightToLeft == RightToLeft.Yes);
            int startLoc = isRTL ? bounds.Right : bounds.X;

            if (safeToRefresh)
            {
                // add resize glyphs for each column and row
                for (int i = 0; i < cw.Length - 1; i++)
                {
                    // Do not add a glyph for columns of 0 width. This can happen for percentage columns, where the table is not
                    // big enough for there to be any space for percentage columns
                    if (cw[i] == 0)
                    {
                        continue;
                    }

                    if (isRTL)
                    {
                        startLoc -= cw[i];
                    }
                    else
                    {
                        startLoc += cw[i];// x offset of column line
                    }

                    Rectangle gBounds = new(startLoc - halfSize, checkBounds.Top, DesignerUtils.s_resizeGlyphSize, checkBounds.Height);
                    // Don't add glyphs for columns that are not within the clientrectangle.
                    if (!checkBounds.Contains(gBounds))
                    {
                        continue;
                    }

                    Debug.Assert(Table.ColumnStyles[i] is not null, "Table's ColumnStyle[" + i + "] is null!");
                    if (Table.ColumnStyles[i] is not null)
                    {
                        TableLayoutPanelResizeGlyph g = new(gBounds, Table.ColumnStyles[i], Cursors.VSplit, Behavior);
                        glyphs.Add(g);
                    }
                }

                startLoc = bounds.Y;// reset for the rows...

                for (int i = 0; i < rh.Length - 1; i++)
                {
                    // Do not add a glyph for rows of 0 height. This can happen for percentage columns, where the table is not
                    // big enough for there to be any space for percentage columns
                    if (rh[i] == 0)
                    {
                        continue;
                    }

                    startLoc += rh[i];// y offset of row line
                    Rectangle gBounds = new(checkBounds.Left, startLoc - halfSize, checkBounds.Width, DesignerUtils.s_resizeGlyphSize);
                    if (!checkBounds.Contains(gBounds))
                    {
                        continue;
                    }

                    Debug.Assert(Table.RowStyles[i] is not null, $"Table's RowStyle[{i}] is null!");
                    if (Table.RowStyles[i] is not null)
                    {
                        TableLayoutPanelResizeGlyph g = new(gBounds, Table.RowStyles[i], Cursors.HSplit, Behavior);
                        glyphs.Add(g);
                    }
                }
            }
        }

        return glyphs;
    }

    public override void Initialize(IComponent component)
    {
        base.Initialize(component);

        IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
        if (host is not null)
        {
            host.TransactionClosing += OnTransactionClosing;
            _compSvc = host.GetService(typeof(IComponentChangeService)) as IComponentChangeService;
        }

        if (_compSvc is not null)
        {
            _compSvc.ComponentChanging += OnComponentChanging;
            _compSvc.ComponentChanged += OnComponentChanged;
        }

        Control.ControlAdded += OnControlAdded;
        Control.ControlRemoved += OnControlRemoved;

        _rowStyleProp = TypeDescriptor.GetProperties(Table)["RowStyles"];
        _colStyleProp = TypeDescriptor.GetProperties(Table)["ColumnStyles"];

        // VSWhidbey #424845. If the TLP is inheritedreadonly, so should all of the children
        if (InheritanceAttribute == InheritanceAttribute.InheritedReadOnly)
        {
            for (int i = 0; i < Control.Controls.Count; i++)
            {
                TypeDescriptor.AddAttributes(Control.Controls[i], InheritanceAttribute.InheritedReadOnly);
            }
        }
    }

    protected override InheritanceAttribute InheritanceAttribute =>
        (base.InheritanceAttribute == InheritanceAttribute.Inherited)
            || (base.InheritanceAttribute == InheritanceAttribute.InheritedReadOnly)
                ? InheritanceAttribute.InheritedReadOnly
                : base.InheritanceAttribute;

    public override void InitializeNewComponent(IDictionary defaultValues)
    {
        base.InitializeNewComponent(defaultValues);
        CreateEmptyTable();
    }

    protected override IComponent[] CreateToolCore(ToolboxItem tool, int x, int y, int width, int height, bool hasLocation, bool hasSize)
    {
        _rowCountBeforeAdd = Math.Max(0, Table.GetRowHeights().Length); // don't want negative
        _colCountBeforeAdd = Math.Max(0, Table.GetColumnWidths().Length);

        return base.CreateToolCore(tool, x, y, width, height, hasLocation, hasSize);
    }

    private void OnControlAdded(object sender, ControlEventArgs e)
    {
        if (IsLoading || Undoing)
        {
            return;
        }

        // Calculate the number cells spanned by controls in the Table
        // This can be slow, but it is the only way to really calculate the number of cells spanned.
        // We cannot rely on checking the control's span since the TLP's growstyle might affect it.
        // E.g. RowCount = ColumnCount = 2, GrowStyle = AddRows, button in cell(0,0), button.ColumnSpan = 6
        int totalArea = 0;
        int[] rows = Table.GetRowHeights();
        int[] columns = Table.GetColumnWidths();

        for (int row = 0; row < rows.Length; row++)
        {
            for (int column = 0; column < columns.Length; column++)
            {
                if (Table.GetControlFromPosition(column, row) is not null)
                {
                    ++totalArea;
                }
            }
        }

        // The control we are about to place, have already been added to the TLP's control collection, so -1 here.
        // This is because we want to know if the table was full BEFORE the control was added.

        bool fullTable = (totalArea - 1) >= (Math.Max(1, _colCountBeforeAdd) * Math.Max(1, _rowCountBeforeAdd));

        if (_droppedCellPosition == InvalidPoint)
        {
            _droppedCellPosition = GetControlPosition(e.Control);
        }

        Debug.Assert(fullTable || (_droppedCellPosition != InvalidPoint), "Why is neither fullTable or droppedCellPosition set?");

        ControlAddedInternal(e.Control, _droppedCellPosition, false, fullTable, null);

        _droppedCellPosition = InvalidPoint;
    }

    private void OnControlRemoved(object sender, ControlEventArgs e)
    {
        // Need to do this to make sure undo/redo works
        // Since the Row/Col extended property is DesignerSerializationVisibility.Hidden, the undo engine
        // will not serialize the value out, so we need to reset it here. VSWhidbey #392705.
        if (e is not null && e.Control is not null)
        {
            Table.SetCellPosition(e.Control, new TableLayoutPanelCellPosition(-1, -1));
        }
    }

    private bool IsOverValidCell(bool dragOp)
    {
        Point dropPoint = GetCellPosition(Control.MousePosition);

        // check if cell position is valid.
        int[] rows = Table.GetRowHeights();
        int[] columns = Table.GetColumnWidths();

        if (dropPoint.Y < 0 || dropPoint.Y >= rows.Length || dropPoint.X < 0 || dropPoint.X >= columns.Length)
        {
            return false;
        }

        if (dragOp)
        {
            Control existingControl = ((TableLayoutPanel)Control).GetControlFromPosition(dropPoint.X, dropPoint.Y);

            // If the cell is not empty, and we are not doing a local drag, then show the no-smoking cursor
            // or if we are doing a multi-select local drag, then show the no-smoking cursor.
            // or if we are doig a local drag, and the cell is not empty, and we are doing a copy
            if ((existingControl is not null && _localDragControl is null) ||
                (_localDragControl is not null && _dragComponents.Count > 1) ||
                (_localDragControl is not null && existingControl is not null && Control.ModifierKeys == Keys.Control))
            {
                return false;
            }
        }

        return true;
    }

    protected override void OnContextMenu(int x, int y)
    {
        Point cell = GetCellPosition(new Point(x, y));

        _curRow = cell.Y;
        _curCol = cell.X;

        // Set the SizeMode correctly
        EnsureAvailableStyles();

        DesignerContextMenuStrip.Show(x, y);
    }

    protected override void OnDragEnter(DragEventArgs de)
    {
        base.OnDragEnter(de);

        // peak at what just entered her e- it it's a local control
        // we'll cache it off
        if (_localDragControl is null)
        {
            Control dragControl = ExtractControlFromDragEvent(de);
            if (dragControl is not null && Table.Controls.Contains(dragControl))
            {
                _localDragControl = dragControl;
            }
        }
    }

    protected override void OnDragLeave(EventArgs e)
    {
        _localDragControl = null; // VSWhidbey #275678
        _dragComponents = null;
        base.OnDragLeave(e);
    }

    protected override void OnDragDrop(DragEventArgs de)
    {
        _droppedCellPosition = GetCellPosition(Control.MousePosition);

        // the scenario where we just dropped our own child control
        if (_localDragControl is not null)
        {
            // local drag to our TLP - we need to re-insert or swap it...
            ControlAddedInternal(_localDragControl, _droppedCellPosition, true, false, de);
            _localDragControl = null;
        }
        else
        {
            _rowCountBeforeAdd = Math.Max(0, Table.GetRowHeights().Length); // don't want negative
            _colCountBeforeAdd = Math.Max(0, Table.GetColumnWidths().Length);

            // If from the outside, just let the base class handle it
            base.OnDragDrop(de);

            // VSWhidbey #390230
            // Devdiv Bugs 40804
            // This will not fix VSWhidbey #390230 in the copy/paste scenario but it will for the
            // main drag/drop scneario.
            // We need to do this after the controls are added (after base.OnDragDrop above)
            // because Span is an "attached" property which is not available unless the control is parented to
            // a table. However in the case when the table is full and can't grow, setting control's
            // span after the Add is too late, because the runtime had already thrown an exception.
            // Unfortunally cancelling the transaction throws as well, so we need a way to undo the Add
            // or access internal properties of the control.
            // dragComps is null when dragging off the toolbox
            if (_dragComponents is not null)
            {
                foreach (Control dragControl in _dragComponents)
                {
                    if (dragControl is not null)
                    {
                        PropertyDescriptor columnSpan = TypeDescriptor.GetProperties(dragControl)["ColumnSpan"];
                        PropertyDescriptor rowSpan = TypeDescriptor.GetProperties(dragControl)["RowSpan"];
                        columnSpan?.SetValue(dragControl, 1);

                        rowSpan?.SetValue(dragControl, 1);
                    }
                }
            }
        }

        _droppedCellPosition = InvalidPoint;
        _dragComponents = null;
    }

    protected override void OnDragOver(DragEventArgs de)
    {
        // If we are not over a valid cell, then do not allow the drop
        if (!IsOverValidCell(true))
        {
            de.Effect = DragDropEffects.None;
            return;
        }

        base.OnDragOver(de);
    }

    private Dictionary<string, bool> _extenderProperties;

    private Dictionary<string, bool> ExtenderProperties
    {
        get
        {
            if (_extenderProperties is null && Component is not null)
            {
                _extenderProperties = [];

                AttributeCollection attribs = TypeDescriptor.GetAttributes(Component.GetType());

                foreach (Attribute a in attribs)
                {
                    ProvidePropertyAttribute extender = a as ProvidePropertyAttribute;
                    if (extender is not null)
                    {
                        _extenderProperties[extender.PropertyName] = true;
                    }
                }
            }

            return _extenderProperties;
        }
    }

    private bool DoesPropertyAffectPosition(MemberDescriptor member)
    {
        bool affectsPosition = false;
        DesignerSerializationVisibilityAttribute dsv = member.Attributes[typeof(DesignerSerializationVisibilityAttribute)] as DesignerSerializationVisibilityAttribute;
        if (dsv is not null)
        {
            affectsPosition = dsv.Visibility == DesignerSerializationVisibility.Hidden && ExtenderProperties.ContainsKey(member.Name);
        }

        return affectsPosition;
    }

    private void OnComponentChanging(object sender, ComponentChangingEventArgs e)
    {
        Control changingControl = e.Component as Control;

        if (changingControl is not null && changingControl.Parent == Component &&
            e.Member is not null && DoesPropertyAffectPosition(e.Member))
        {
            PropertyDescriptor controlsProp = TypeDescriptor.GetProperties(Component)["Controls"];
            _compSvc.OnComponentChanging(Component, controlsProp);
        }
    }

    private void OnComponentChanged(object sender, ComponentChangedEventArgs e)
    {
        // VSWhidbey 233871
        // When the Row or Column property is being set on a control in the TLP, a Row/Col Style is not being added.
        // After the property is being set, the SelectionManager::OnSelectionChanged gets called. It in turn calls
        // GetGlyphs, and the TLP designer's GetGlyphs calls EnsureAvaiableStyles. Since no style was added, we will add
        // a default one of type Absolute, Height/Width 20. But but but... If the control has added its glyph before we
        // add the style, the glyphs wil be misaligned, since EnsureAvailableStyles also causes the TLP to do a layout. This
        // layout will actually size the control to a smaller size. So let's trap the Row/Col property changing, call
        // EnsureAvailableStyles, which will force the layout BEFORE the SelectionManager is called.
        if (e.Component is not null)
        {
            Control c = e.Component as Control;
            if (c is not null && c.Parent is not null && c.Parent.Equals(Control) && e.Member is not null && (e.Member.Name == "Row" || e.Member.Name == "Column"))
            {
                EnsureAvailableStyles();
            }

            if (c is not null && c.Parent == Component &&
                e.Member is not null && DoesPropertyAffectPosition(e.Member))
            {
                PropertyDescriptor controlsProp = TypeDescriptor.GetProperties(Component)["Controls"];
                _compSvc.OnComponentChanged(Component, controlsProp, null, null);
            }
        }

        CheckVerbStatus();
    }

    private void OnTransactionClosing(object sender, DesignerTransactionCloseEventArgs e)
    {
        ISelectionService selSvc = GetService(typeof(ISelectionService)) as ISelectionService;
        if (selSvc is not null && Table is not null)
        {
            ICollection selectedComps = selSvc.GetSelectedComponents();
            bool selectedComponentHasTableParent = false;
            foreach (object comp in selectedComps)
            {
                Control c = comp as Control;
                if (c is not null && c.Parent == Table)
                {
                    selectedComponentHasTableParent = true;
                    break;
                }
            }

            if (selSvc.GetComponentSelected(Table) || selectedComponentHasTableParent)
            {
                // force an internal 'onlayout' event to refresh our control
                Table.SuspendLayout();
                EnsureAvailableStyles();
                Table.ResumeLayout(false);
                Table.PerformLayout();
            }
        }
    }

    private void OnUndoing(object sender, EventArgs e)
    {
        if (!Undoing)
        {
            _undoEngine?.Undone += OnUndone;
            Undoing = true;
        }
    }

    private void OnUndone(object sender, EventArgs e)
    {
        if (Undoing)
        {
            _undoEngine?.Undone -= OnUndone;
            Undoing = false;

            bool isSafeToRefresh = EnsureAvailableStyles();
            if (isSafeToRefresh)
            {
                Refresh();
            }
        }
    }

    protected override void OnMouseDragBegin(int x, int y)
    {
        if (IsOverValidCell(true))
        {
            // make sure we have a valid toolbox item and we're not just drawing a rect
            IToolboxService tbx = (IToolboxService)GetService(typeof(IToolboxService));
            if (tbx is not null && tbx.GetSelectedToolboxItem((IDesignerHost)GetService(typeof(IDesignerHost))) is not null)
            {
                _droppedCellPosition = GetCellPosition(Control.MousePosition);
            }
        }
        else
        {
            _droppedCellPosition = InvalidPoint;
            Cursor.Current = Cursors.No;
        }

        base.OnMouseDragBegin(x, y);
    }

    protected override void OnMouseDragMove(int x, int y)
    {
        // If they are trying to draw in a cell that already has a control, then we
        // do not want to draw an outline
        if (_droppedCellPosition == InvalidPoint)
        {
            Cursor.Current = Cursors.No;
            return;
        }

        base.OnMouseDragMove(x, y);
    }

    protected override void OnMouseDragEnd(bool cancel)
    {
        if (_droppedCellPosition == InvalidPoint)
        {
            // If they are trying to draw in a cell that already has a control, then just act like a cancel
            cancel = true;
        }

        base.OnMouseDragEnd(cancel);
    }

    private void OnRowColMenuOpening(object sender, CancelEventArgs e)
    {
        e.Cancel = false;
        // Set the size mode correctly
        ToolStripDropDownMenu menu = sender as ToolStripDropDownMenu;
        if (menu is not null)
        {
            int selCount = 0;
            ISelectionService selSvc = GetService(typeof(ISelectionService)) as ISelectionService;
            if (selSvc is not null)
            {
                selCount = selSvc.SelectionCount;
            }

            // Always make sure and set the Enabled state in case the user
            // has changed the selection since the last time the menu was shown.
            bool enabled = (selCount == 1) && (InheritanceAttribute != InheritanceAttribute.InheritedReadOnly);

            menu.Items["add"].Enabled = enabled;
            menu.Items["insert"].Enabled = enabled;
            menu.Items["delete"].Enabled = enabled;
            menu.Items["sizemode"].Enabled = enabled;
            menu.Items["absolute"].Enabled = enabled;
            menu.Items["percent"].Enabled = enabled;
            menu.Items["autosize"].Enabled = enabled;

            if (selCount == 1)
            {
                ((ToolStripMenuItem)menu.Items["absolute"]).Checked = false;
                ((ToolStripMenuItem)menu.Items["percent"]).Checked = false;
                ((ToolStripMenuItem)menu.Items["autosize"]).Checked = false;

                bool isRow = (bool)menu.Tag;
                switch (isRow ? Table.RowStyles[_curRow].SizeType : Table.ColumnStyles[_curCol].SizeType)
                {
                    case SizeType.Absolute:
                        ((ToolStripMenuItem)menu.Items["absolute"]).Checked = true;
                        break;
                    case SizeType.Percent:
                        ((ToolStripMenuItem)menu.Items["percent"]).Checked = true;
                        break;
                    case SizeType.AutoSize:
                        ((ToolStripMenuItem)menu.Items["autosize"]).Checked = true;
                        break;
                    default:
                        Debug.Fail("Unknown SizeType!");
                        break;
                }

                if ((isRow ? Table.RowCount : Table.ColumnCount) < 2)
                {
                    // can't remove a row/column if we only have
                    menu.Items["delete"].Enabled = false;
                }
            }
        }
    }

    private void OnAdd(bool isRow)
    {
        // get the property and add to it...
        IDesignerHost host = GetService(typeof(IDesignerHost)) as IDesignerHost;
        if (host is not null && Table.Site is not null)
        {
            using DesignerTransaction t = host.CreateTransaction(
                string.Format(isRow
                    ? SR.TableLayoutPanelDesignerAddRowUndoUnit
                    : SR.TableLayoutPanelDesignerAddColumnUndoUnit, Table.Site.Name));

            try
            {
                Table.SuspendLayout(); // To avoid flickering
                                       // This ensures that the Row/Col Style gets set BEFORE the row is added. This in turn
                                       // ensures that the row/col shows up. Since we turn off tablelayout, a style won't have been added
                                       // when EnsureVisibleStyles is called from the shadowed property.
                InsertRowCol(isRow, isRow ? Table.RowCount : Table.ColumnCount);
                Table.ResumeLayout();
                t.Commit();
            }
            catch (CheckoutException checkoutException)
            {
                if (CheckoutException.Canceled.Equals(checkoutException))
                {
                    t?.Cancel();
                }
                else
                {
                    throw;
                }
            }
        }
    }

    private void OnAddClick(object sender, EventArgs e) => OnAdd((bool)((ToolStripMenuItem)sender).Tag); // Tag = isRow

    internal void InsertRowCol(bool isRow, int index)
    {
        // We shadow the ColumnCount/RowCount property, so let's add the style first
        // to make sure that the right style is added at the right location.
        try
        {
            if (isRow)
            {
                PropertyDescriptor rowProp = TypeDescriptor.GetProperties(Table)["RowCount"];
                if (rowProp is not null)
                {
                    PropChanging(_rowStyleProp);
                    Table.RowStyles.Insert(index, new RowStyle(SizeType.Absolute, DesignerUtils.s_minimumStyleSize));
                    PropChanged(_rowStyleProp);

                    rowProp.SetValue(Table, Table.RowCount + 1);
                }
            }
            else
            {
                PropertyDescriptor colProp = TypeDescriptor.GetProperties(Table)["ColumnCount"];
                if (colProp is not null)
                {
                    PropChanging(_colStyleProp);
                    Table.ColumnStyles.Insert(index, new ColumnStyle(SizeType.Absolute, DesignerUtils.s_minimumStyleSize));
                    PropChanged(_colStyleProp);

                    colProp.SetValue(Table, Table.ColumnCount + 1);
                }
            }
        }
        catch (InvalidOperationException ex)
        {
            IUIService uiService = (IUIService)GetService(typeof(IUIService));
            uiService.ShowError(ex.Message);
        }

        // VSWhidbey # 490635
        BehaviorService.Invalidate(BehaviorService.ControlRectInAdornerWindow(Control));
    }

    internal void FixUpControlsOnInsert(bool isRow, int index)
    {
        PropertyDescriptor childProp = TypeDescriptor.GetProperties(Table)["Controls"];
        PropChanging(childProp);

        foreach (Control child in Table.Controls)
        {
            int currentIndex = isRow ? Table.GetRow(child) : Table.GetColumn(child);
            PropertyDescriptor prop = TypeDescriptor.GetProperties(child)[isRow ? "Row" : "Column"];
            PropertyDescriptor spanProp = TypeDescriptor.GetProperties(child)[isRow ? "RowSpan" : "ColumnSpan"];

            if (currentIndex == -1)
            {
                // this is a flow element. We don't really know where
                // this is going to go, so we cannot fix up anything.
                continue;
            }

            // push all controls >= the original row/col into the new row/col
            if (currentIndex >= index)
            {
                prop?.SetValue(child, currentIndex + 1);
            }
            else
            {
                // If the control is before the row/col we are inserting and the control has a span that includes the inserted row/col
                // the increase the span to include the insert row/col
                int span = isRow ? Table.GetRowSpan(child) : Table.GetColumnSpan(child); // span is always at least 1
                if (currentIndex + span > index)
                {
                    spanProp?.SetValue(child, span + 1);
                }
            }
        }

        PropChanged(childProp);
    }

    private void OnInsertClick(object sender, EventArgs e)
    {
        IDesignerHost host = GetService(typeof(IDesignerHost)) as IDesignerHost;
        if (host is not null && Table.Site is not null)
        {
            bool isRow = (bool)((ToolStripMenuItem)sender).Tag;
            using DesignerTransaction t = host.CreateTransaction(string.Format(
                isRow ? SR.TableLayoutPanelDesignerAddRowUndoUnit : SR.TableLayoutPanelDesignerAddColumnUndoUnit,
                Table.Site.Name));

            try
            {
                Table.SuspendLayout();
                InsertRowCol(isRow, isRow ? _curRow : _curCol);
                FixUpControlsOnInsert(isRow, isRow ? _curRow : _curCol);
                Table.ResumeLayout();
                t.Commit();
            }
            catch (CheckoutException checkoutException)
            {
                if (CheckoutException.Canceled.Equals(checkoutException))
                {
                    t?.Cancel();
                }
                else
                {
                    throw;
                }
            }
            catch (InvalidOperationException ex)
            {
                IUIService uiService = (IUIService)GetService(typeof(IUIService));
                uiService.ShowError(ex.Message);
            }
        }
    }

    internal void FixUpControlsOnDelete(bool isRow, int index, List<Control> deleteList)
    {
        PropertyDescriptor childProp = TypeDescriptor.GetProperties(Table)["Controls"];
        PropChanging(childProp);

        foreach (Control child in Table.Controls)
        {
            int currentIndex = isRow ? Table.GetRow(child) : Table.GetColumn(child);
            PropertyDescriptor prop = TypeDescriptor.GetProperties(child)[isRow ? "Row" : "Column"];
            PropertyDescriptor spanProp = TypeDescriptor.GetProperties(child)[isRow ? "RowSpan" : "ColumnSpan"];

            if (currentIndex == index)
            {
                // We add the deleteList.Contains check just to make extra sure. Could be
                // that the deleteList for some reason already contained the child.
                if (!deleteList.Contains(child))
                {
                    deleteList.Add(child);
                }

                continue;
            }

            if (currentIndex == -1 || deleteList.Contains(child))
            {
                // If this is a flow element. We don't really know where this is going to go, so we cannot fix up anything.
                // If the child has already been marked for deletion, we can keep going
                continue;
            }

            Debug.Assert(currentIndex != index);

            // push all controls >= the original row/col into the new row/col, but only
            if (currentIndex > index)
            {
                prop?.SetValue(child, currentIndex - 1);
            }
            else
            {
                // If the control is before the row/col we are removing and the control has a span that includes the row/col
                // we are deleting, then decrease the span.
                int span = isRow ? Table.GetRowSpan(child) : Table.GetColumnSpan(child); // span is always at least 1
                if (currentIndex + span > index)
                {
                    // We've bled into the row/col, shrink up as expected
                    spanProp?.SetValue(child, span - 1);
                }
            }
        }

        PropChanged(childProp);
    }

    internal void DeleteRowCol(bool isRow, int index)
    {
        if (isRow)
        {
            PropertyDescriptor rowProp = TypeDescriptor.GetProperties(Table)["RowCount"];
            if (rowProp is not null)
            {
                rowProp.SetValue(Table, Table.RowCount - 1);

                PropChanging(_rowStyleProp);
                Table.RowStyles.RemoveAt(index);
                PropChanged(_rowStyleProp);
            }
        }
        else
        {
            PropertyDescriptor colProp = TypeDescriptor.GetProperties(Table)["ColumnCount"];
            if (colProp is not null)
            {
                colProp.SetValue(Table, Table.ColumnCount - 1);

                PropChanging(_colStyleProp);
                Table.ColumnStyles.RemoveAt(index);
                PropChanged(_colStyleProp);
            }
        }
    }

    private void OnRemoveInternal(bool isRow, int index)
    {
        if ((isRow ? Table.RowCount : Table.ColumnCount) < 2)
        {
            // can't remove a row/column if we only have 1
            return;
        }

        IDesignerHost host = GetService(typeof(IDesignerHost)) as IDesignerHost;
        if (host is not null && Table.Site is not null)
        {
            using DesignerTransaction t = host.CreateTransaction(string.Format(
                isRow ? SR.TableLayoutPanelDesignerRemoveRowUndoUnit : SR.TableLayoutPanelDesignerRemoveColumnUndoUnit,
                Table.Site.Name));

            try
            {
                Table.SuspendLayout();
                List<Control> deleteList = [];

                // First fix up any controls in the row/col we are deleting
                FixUpControlsOnDelete(isRow, index, deleteList);
                // Then delete the row col
                DeleteRowCol(isRow, index);

                // Now delete any child control

                // IF YOU CHANGE THIS, YOU SHOULD ALSO CHANGE THE CODE IN StyleCollectionEditor.OnOkButtonClick
                if (deleteList.Count > 0)
                {
                    PropertyDescriptor childProp = TypeDescriptor.GetProperties(Table)["Controls"];
                    PropChanging(childProp);
                    foreach (Control control in deleteList)
                    {
                        List<IComponent> al = [];
                        DesignerUtils.GetAssociatedComponents(control, host, al);
                        foreach (IComponent comp in al)
                        {
                            _compSvc.OnComponentChanging(comp, null);
                        }

                        host.DestroyComponent(control);
                    }

                    PropChanged(childProp);
                }

                Table.ResumeLayout();
                t.Commit();
            }
            catch (CheckoutException checkoutException)
            {
                if (CheckoutException.Canceled.Equals(checkoutException))
                {
                    t?.Cancel();
                }
                else
                {
                    throw;
                }
            }
        }
    }

    private void OnRemove(bool isRow) => OnRemoveInternal(isRow, isRow ? Table.RowCount - 1 : Table.ColumnCount - 1);

    private void OnDeleteClick(object sender, EventArgs e)
    {
        try
        {
            bool isRow = (bool)((ToolStripMenuItem)sender).Tag;
            OnRemoveInternal(isRow, isRow ? _curRow : _curCol);
        }
        catch (InvalidOperationException ex)
        {
            IUIService uiService = (IUIService)GetService(typeof(IUIService));
            uiService.ShowError(ex.Message);
        }
    }

    private void ChangeSizeType(bool isRow, SizeType newType)
    {
        TableLayoutStyleCollection styles;
        try
        {
            styles = isRow ? Table.RowStyles : Table.ColumnStyles;

            int index = isRow ? _curRow : _curCol;

            if (styles[index].SizeType == newType)
            {
                // nuthin' to do
                return;
            }

            int[] rh = Table.GetRowHeights();
            int[] ch = Table.GetColumnWidths();

            if ((isRow && rh.Length < index - 1) || (!isRow && ch.Length < index - 1))
            {
                // something got messed up
                Debug.Fail("Our indices are outta whack, how did that happen?");
                return;
            }

            IDesignerHost host = GetService(typeof(IDesignerHost)) as IDesignerHost;
            if (host is not null && Table.Site is not null)
            {
                using DesignerTransaction t = host.CreateTransaction(string.Format(SR.TableLayoutPanelDesignerChangeSizeTypeUndoUnit, Table.Site.Name));

                try
                {
                    Table.SuspendLayout();

                    PropChanging(isRow ? _rowStyleProp : _colStyleProp);

                    switch (newType)
                    {
                        case SizeType.Absolute:
                            styles[index].SizeType = SizeType.Absolute;
                            if (isRow)
                            {
                                Table.RowStyles[index].Height = rh[index];
                            }
                            else
                            {
                                Table.ColumnStyles[index].Width = ch[index];
                            }

                            break;
                        case SizeType.Percent:
                            styles[index].SizeType = SizeType.Percent;
                            if (isRow)
                            {
                                Table.RowStyles[index].Height = DesignerUtils.s_minimumStylePercent;
                            }
                            else
                            {
                                Table.ColumnStyles[index].Width = DesignerUtils.s_minimumStylePercent;
                            }

                            break;
                        case SizeType.AutoSize:
                            styles[index].SizeType = SizeType.AutoSize;
                            break;
                        default:
                            Debug.Fail("Unknown SizeType!");
                            break;
                    }

                    PropChanged(isRow ? _rowStyleProp : _colStyleProp);

                    Table.ResumeLayout();
                    t.Commit();
                }
                catch (CheckoutException checkoutException)
                {
                    if (CheckoutException.Canceled.Equals(checkoutException))
                    {
                        t?.Cancel();
                    }
                    else
                    {
                        throw;
                    }
                }
            }
        }
        catch (InvalidOperationException ex)
        {
            IUIService uiService = (IUIService)GetService(typeof(IUIService));
            uiService.ShowError(ex.Message);
        }
    }

    private void OnAbsoluteClick(object sender, EventArgs e) => ChangeSizeType((bool)((ToolStripMenuItem)sender).Tag, SizeType.Absolute);

    private void OnPercentClick(object sender, EventArgs e) => ChangeSizeType((bool)((ToolStripMenuItem)sender).Tag, SizeType.Percent);

    private void OnAutoSizeClick(object sender, EventArgs e) => ChangeSizeType((bool)((ToolStripMenuItem)sender).Tag, SizeType.AutoSize);

    private void OnEdit()
    {
        try
        {
            EditorServiceContext.EditValue(this, Table, "ColumnStyles");
        }
        catch (InvalidOperationException ex)
        {
            IUIService uiService = (IUIService)GetService(typeof(IUIService));
            uiService.ShowError(ex.Message);
        }
    }

    private static string ReplaceText(string text) => text is null ? null : ParenthesisRegex().Replace(text, "");

    private void OnVerbRemove(object sender, EventArgs e)
    {
        // sniff the text of the verb to see if we're adding columns or rows
        bool isRow = ((DesignerVerb)sender).Text.Equals(ReplaceText(SR.TableLayoutPanelDesignerRemoveRow));
        OnRemove(isRow);
    }

    private void OnVerbAdd(object sender, EventArgs e)
    {
        // sniff the text of the verb to see if we're adding columns or rows
        bool isRow = ((DesignerVerb)sender).Text.Equals(ReplaceText(SR.TableLayoutPanelDesignerAddRow));
        OnAdd(isRow);
    }

    private void OnVerbEdit(object sender, EventArgs e) => OnEdit();

    protected override void PreFilterProperties(IDictionary properties)
    {
        base.PreFilterProperties(properties);

        // Handle shadowed properties
        string[] shadowProps =
        [
            "ColumnStyles",
            "RowStyles",
            "ColumnCount",
            "RowCount"
        ];

        // VSWhidbey 491088
        // To enable the PropertyGrid to work with the TableLayoutPanel at runtime (when no designer is available),
        // the above properties are marked browsable(false) and re-enabled when a designer is present.
        // Since so much of the logic for keeping the TLP in a valid Row/Column state is designer dependent,
        // these properties are not accessible by the PropertyGrid without a designer.
        Attribute[] attribs = [new BrowsableAttribute(true)];

        for (int i = 0; i < shadowProps.Length; i++)
        {
            PropertyDescriptor prop = (PropertyDescriptor)properties[shadowProps[i]];
            if (prop is not null)
            {
                properties[shadowProps[i]] = TypeDescriptor.CreateProperty(typeof(TableLayoutPanelDesigner), prop, attribs);
            }
        }

        // replace this one seperately because it is of a different type (DesignerTableLayoutControlCollection) than
        // the original property (TableLayoutControlCollection)
        //
        PropertyDescriptor controlsProp = (PropertyDescriptor)properties["Controls"];

        if (controlsProp is not null)
        {
            Attribute[] attrs = new Attribute[controlsProp.Attributes.Count];
            controlsProp.Attributes.CopyTo(attrs, 0);
            properties["Controls"] = TypeDescriptor.CreateProperty(typeof(TableLayoutPanelDesigner), "Controls", typeof(DesignerTableLayoutControlCollection), attrs);
        }
    }

    private void Refresh()
    {
        // refresh selection, glyphs, and adorners
        BehaviorService.SyncSelection();

        Table?.Invalidate(true);
    }

    private void PropChanging(PropertyDescriptor prop)
    {
        if (_compSvc is not null && prop is not null)
        {
            _compSvc.OnComponentChanging(Table, prop);
        }
    }

    private void PropChanged(PropertyDescriptor prop)
    {
        if (_compSvc is not null && prop is not null)
        {
            _compSvc.OnComponentChanged(Table, prop, null, null);
        }
    }

    [ListBindable(false)]
    [DesignerSerializer(typeof(DesignerTableLayoutControlCollectionCodeDomSerializer), typeof(CodeDomSerializer))]
    internal class DesignerTableLayoutControlCollection : TableLayoutControlCollection, IList
    {
        private readonly TableLayoutControlCollection _realCollection;

        public DesignerTableLayoutControlCollection(TableLayoutPanel owner) : base(owner) => _realCollection = owner.Controls;

        public override int Count => _realCollection.Count;

        object ICollection.SyncRoot => this;

        bool ICollection.IsSynchronized => false;

        bool IList.IsFixedSize => false;

        public new bool IsReadOnly => _realCollection.IsReadOnly;

        int IList.Add(object control) => ((IList)_realCollection).Add(control);

        public override void Add(Control c) => _realCollection.Add(c);

        public override void AddRange(Control[] controls) => _realCollection.AddRange(controls);

        bool IList.Contains(object control) => ((IList)_realCollection).Contains(control);

        public new void CopyTo(Array dest, int index) => _realCollection.CopyTo(dest, index);

        public override bool Equals(object other) => _realCollection.Equals(other);

        public new IEnumerator GetEnumerator() => _realCollection.GetEnumerator();

        public override int GetHashCode() => _realCollection.GetHashCode();

        int IList.IndexOf(object control) => ((IList)_realCollection).IndexOf(control);

        void IList.Insert(int index, object value) => ((IList)_realCollection).Insert(index, value);

        void IList.Remove(object control) => ((IList)_realCollection).Remove(control);

        void IList.RemoveAt(int index) => ((IList)_realCollection).RemoveAt(index);

        object IList.this[int index]
        {
            get
            {
                return ((IList)_realCollection)[index];
            }
            set
            {
                throw new NotSupportedException();
            }
        }

        public override void Add(Control control, int column, int row) => _realCollection.Add(control, column, row);

        public override int GetChildIndex(Control child, bool throwException) => _realCollection.GetChildIndex(child, throwException);

        public override void SetChildIndex(Control child, int newIndex) => _realCollection.SetChildIndex(child, newIndex);

        public override void Clear()
        {
            // only remove the sited non-inherited components
            for (int i = _realCollection.Count - 1; i >= 0; i--)
            {
                if (_realCollection[i] is not null &&
                    _realCollection[i].Site is not null &&
                    TypeDescriptor.GetAttributes(_realCollection[i]).Contains(InheritanceAttribute.NotInherited))
                {
                    _realCollection.RemoveAt(i);
                }
            }
        }
    }

    // Custom code dom serializer for the DesignerControlCollection. We need this so we can filter out controls
    // that aren't sited in the host's container.
    internal class DesignerTableLayoutControlCollectionCodeDomSerializer : TableLayoutControlCollectionCodeDomSerializer
    {
        protected override object SerializeCollection(IDesignerSerializationManager manager, CodeExpression targetExpression, Type targetType, ICollection originalCollection, ICollection valuesToSerialize)
        {
            List<IComponent> subset = [];

            if (valuesToSerialize is not null && valuesToSerialize.Count > 0)
            {
                foreach (object val in valuesToSerialize)
                {
                    if (val is IComponent { Site: not null and not INestedSite } comp)
                    {
                        subset.Add(comp);
                    }
                }
            }

            return base.SerializeCollection(manager, targetExpression, targetType, originalCollection, subset);
        }
    }

    [GeneratedRegex(@"\(\&.\)")]
    private static partial Regex ParenthesisRegex();
}
