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

using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Windows.Forms.VisualStyles;
using Windows.Win32.UI.Accessibility;

namespace System.Windows.Forms;

/// <summary>
///  Represents a Windows text box control.
/// </summary>
[Designer($"System.Windows.Forms.Design.TextBoxDesigner, {AssemblyRef.SystemDesign}")]
[SRDescription(nameof(SR.DescriptionTextBox))]
public partial class TextBox : TextBoxBase
{
    private static readonly object s_textAlignChangedEvent = new();

    /// <summary>
    ///  Controls whether or not the edit box consumes/respects ENTER key
    ///  presses. While this is typically desired by multiline edits, this
    ///  can interfere with normal key processing in a dialog.
    /// </summary>
    private bool _acceptsReturn;

    /// <summary>
    ///  Indicates what the current special password character is. This is
    ///  displayed instead of any other text the user might enter.
    /// </summary>
    private char _passwordChar;

    private bool _useSystemPasswordChar;

    /// <summary>
    ///  Controls whether or not the case of characters entered into the edit
    ///  box is forced to a specific case.
    /// </summary>
    private CharacterCasing _characterCasing = CharacterCasing.Normal;

    /// <summary>
    ///  Controls which scrollbars appear by default.
    /// </summary>
    private ScrollBars _scrollBars = ScrollBars.None;

    /// <summary>
    ///  Controls text alignment in the edit box.
    /// </summary>
    private HorizontalAlignment _textAlign = HorizontalAlignment.Left;

    /// <summary>
    ///  True if the selection has been set by the user. If the selection has
    ///  never been set and we get focus, we focus all the text in the control
    ///  so we mimic the Windows dialog manager.
    /// </summary>
    private bool _selectionSet;

    /// <summary>
    ///  This stores the value for the autocomplete mode which can be either
    ///  None, AutoSuggest, AutoAppend or AutoSuggestAppend.
    /// </summary>
    private AutoCompleteMode _autoCompleteMode = AutoCompleteMode.None;

    /// <summary>
    ///  This stores the value for the autoCompleteSource mode which can be one of the values
    ///  from AutoCompleteSource enum.
    /// </summary>
    private AutoCompleteSource _autoCompleteSource = AutoCompleteSource.None;

    /// <summary>
    ///  This stores the custom StringCollection required for the autoCompleteSource when its set to CustomSource.
    /// </summary>
    private AutoCompleteStringCollection? _autoCompleteCustomSource;
    private bool _fromHandleCreate;
    private StringSource? _stringSource;
    private string _placeholderText = string.Empty;

    public TextBox()
    {
    }

    /// <summary>
    ///  Gets or sets a value indicating whether pressing ENTER in a multiline <see cref="TextBox"/>
    ///  control creates a new line of text in the control or activates the default button
    ///  for the form.
    /// </summary>
    [SRCategory(nameof(SR.CatBehavior))]
    [DefaultValue(false)]
    [SRDescription(nameof(SR.TextBoxAcceptsReturnDescr))]
    public bool AcceptsReturn
    {
        get
        {
            return _acceptsReturn;
        }
        set
        {
            _acceptsReturn = value;
        }
    }

    protected override AccessibleObject CreateAccessibilityInstance()
        => new TextBoxAccessibleObject(this);

    /// <summary>
    ///  This is the AutoCompleteMode which can be either
    ///  None, AutoSuggest, AutoAppend or AutoSuggestAppend.
    ///  This property in conjunction with AutoCompleteSource enables the AutoComplete feature for TextBox.
    /// </summary>
    [DefaultValue(AutoCompleteMode.None)]
    [SRDescription(nameof(SR.TextBoxAutoCompleteModeDescr))]
    [Browsable(true)]
    [EditorBrowsable(EditorBrowsableState.Always)]
    public AutoCompleteMode AutoCompleteMode
    {
        get
        {
            return _autoCompleteMode;
        }
        set
        {
            SourceGenerated.EnumValidator.Validate(value);
            bool resetAutoComplete = false;
            if (_autoCompleteMode != AutoCompleteMode.None && value == AutoCompleteMode.None)
            {
                resetAutoComplete = true;
            }

            _autoCompleteMode = value;
            SetAutoComplete(resetAutoComplete);
        }
    }

    /// <summary>
    ///  This is the AutoCompleteSource which can be one of the values from AutoCompleteSource enumeration.
    ///  This property in conjunction with AutoCompleteMode enables the AutoComplete feature for TextBox.
    /// </summary>
    [DefaultValue(AutoCompleteSource.None)]
    [SRDescription(nameof(SR.TextBoxAutoCompleteSourceDescr))]
    [TypeConverter(typeof(TextBoxAutoCompleteSourceConverter))]
    [Browsable(true)]
    [EditorBrowsable(EditorBrowsableState.Always)]
    public AutoCompleteSource AutoCompleteSource
    {
        get
        {
            return _autoCompleteSource;
        }
        set
        {
            switch (value)
            {
                case AutoCompleteSource.None:
                case AutoCompleteSource.AllSystemSources:
                case AutoCompleteSource.AllUrl:
                case AutoCompleteSource.CustomSource:
                case AutoCompleteSource.FileSystem:
                case AutoCompleteSource.FileSystemDirectories:
                case AutoCompleteSource.HistoryList:
                case AutoCompleteSource.RecentlyUsedList:
                    _autoCompleteSource = value;
                    SetAutoComplete(false);
                    break;
                case AutoCompleteSource.ListItems:
                    throw new NotSupportedException(SR.TextBoxAutoCompleteSourceNoItems);
                default:
                    throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(AutoCompleteSource));
            }
        }
    }

    /// <summary>
    ///  This is the AutoCompleteCustomSource which is custom StringCollection used when the
    ///  AutoCompleteSource is CustomSource.
    /// </summary>
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    [Localizable(true)]
    [SRDescription(nameof(SR.TextBoxAutoCompleteCustomSourceDescr))]
    [Editor($"System.Windows.Forms.Design.ListControlStringCollectionEditor, {AssemblyRef.SystemDesign}", typeof(UITypeEditor))]
    [Browsable(true)]
    [EditorBrowsable(EditorBrowsableState.Always)]
    [AllowNull]
    public AutoCompleteStringCollection AutoCompleteCustomSource
    {
        get
        {
            if (_autoCompleteCustomSource is null)
            {
                _autoCompleteCustomSource = [];
                _autoCompleteCustomSource.CollectionChanged += OnAutoCompleteCustomSourceChanged;
            }

            return _autoCompleteCustomSource;
        }
        set
        {
            if (_autoCompleteCustomSource != value)
            {
                if (_autoCompleteCustomSource is not null)
                {
                    _autoCompleteCustomSource.CollectionChanged -= OnAutoCompleteCustomSourceChanged;
                }

                _autoCompleteCustomSource = value;

                if (_autoCompleteCustomSource is not null)
                {
                    _autoCompleteCustomSource.CollectionChanged += OnAutoCompleteCustomSourceChanged;
                }

                SetAutoComplete(false);
            }
        }
    }

    /// <summary>
    ///  Gets or sets whether the TextBox control
    ///  modifies the case of characters as they are typed.
    /// </summary>
    [SRCategory(nameof(SR.CatBehavior))]
    [DefaultValue(CharacterCasing.Normal)]
    [SRDescription(nameof(SR.TextBoxCharacterCasingDescr))]
    public CharacterCasing CharacterCasing
    {
        get
        {
            return _characterCasing;
        }
        set
        {
            if (_characterCasing != value)
            {
                SourceGenerated.EnumValidator.Validate(value);

                _characterCasing = value;
                RecreateHandle();
            }
        }
    }

    public override bool Multiline
    {
        get => base.Multiline;
        set
        {
            if (Multiline != value)
            {
                base.Multiline = value;
                if (value && AutoCompleteMode != AutoCompleteMode.None)
                {
                    RecreateHandle();
                }
            }
        }
    }

    /// <summary>
    ///  Determines if the control is in password protect mode.
    /// </summary>
    private protected override bool PasswordProtect
        => PasswordChar != '\0';

    /// <summary>
    ///  Returns the parameters needed to create the handle. Inheriting classes
    ///  can override this to provide extra functionality. They should not,
    ///  however, forget to call base.getCreateParams() first to get the struct
    ///  filled up with the basic info.
    /// </summary>
    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;
            switch (_characterCasing)
            {
                case CharacterCasing.Lower:
                    cp.Style |= PInvoke.ES_LOWERCASE;
                    break;
                case CharacterCasing.Upper:
                    cp.Style |= PInvoke.ES_UPPERCASE;
                    break;
            }

            // Translate for Rtl if necessary
            HorizontalAlignment align = RtlTranslateHorizontal(_textAlign);

            // WS_EX_RIGHT overrides the ES_XXXX alignment styles
            cp.ExStyle &= ~(int)WINDOW_EX_STYLE.WS_EX_RIGHT;

            switch (align)
            {
                case HorizontalAlignment.Left:
                    cp.Style |= PInvoke.ES_LEFT;
                    break;
                case HorizontalAlignment.Center:
                    cp.Style |= PInvoke.ES_CENTER;
                    break;
                case HorizontalAlignment.Right:
                    cp.Style |= PInvoke.ES_RIGHT;
                    break;
            }

            if (Multiline)
            {
                // Don't show horizontal scroll bars which won't do anything
                if ((_scrollBars & ScrollBars.Horizontal) == ScrollBars.Horizontal
                    && _textAlign == HorizontalAlignment.Left
                    && !WordWrap)
                {
                    cp.Style |= (int)WINDOW_STYLE.WS_HSCROLL;
                }

                if ((_scrollBars & ScrollBars.Vertical) == ScrollBars.Vertical)
                {
                    cp.Style |= (int)WINDOW_STYLE.WS_VSCROLL;
                }
            }

            if (_useSystemPasswordChar)
            {
                cp.Style |= PInvoke.ES_PASSWORD;
            }

            return cp;
        }
    }

    /// <summary>
    ///  Gets or sets the character used to mask characters in a single-line text box
    ///  control used to enter passwords.
    /// </summary>
    [SRCategory(nameof(SR.CatBehavior))]
    [DefaultValue((char)0)]
    [Localizable(true)]
    [SRDescription(nameof(SR.TextBoxPasswordCharDescr))]
    [RefreshProperties(RefreshProperties.Repaint)]
    public char PasswordChar
    {
        get
        {
            if (!IsHandleCreated)
            {
                CreateHandle();
            }

            return (char)PInvokeCore.SendMessage(this, PInvokeCore.EM_GETPASSWORDCHAR);
        }
        set
        {
            _passwordChar = value;
            if (!_useSystemPasswordChar)
            {
                if (IsHandleCreated)
                {
                    if (PasswordChar != value)
                    {
                        // Set the password mode.
                        PInvokeCore.SendMessage(this, PInvokeCore.EM_SETPASSWORDCHAR, (WPARAM)value);

                        // Disable IME if setting the control to password mode.
                        VerifyImeRestrictedModeChanged();

                        ResetAutoComplete(false);
                        Invalidate();
                    }
                }
            }
        }
    }

    /// <summary>
    ///  Gets or sets which scroll bars should
    ///  appear in a multiline <see cref="TextBox"/>
    ///  control.
    /// </summary>
    [SRCategory(nameof(SR.CatAppearance))]
    [Localizable(true)]
    [DefaultValue(ScrollBars.None)]
    [SRDescription(nameof(SR.TextBoxScrollBarsDescr))]
    public ScrollBars ScrollBars
    {
        get
        {
            return _scrollBars;
        }
        set
        {
            if (_scrollBars != value)
            {
                SourceGenerated.EnumValidator.Validate(value);

                _scrollBars = value;
                RecreateHandle();
            }
        }
    }

    internal override bool SupportsUiaProviders => true;

    internal override Size GetPreferredSizeCore(Size proposedConstraints)
    {
        Size scrollBarPadding = Size.Empty;

        if (Multiline && !WordWrap && (ScrollBars & ScrollBars.Horizontal) != 0)
        {
            scrollBarPadding.Height += SystemInformation.GetHorizontalScrollBarHeightForDpi(_deviceDpi);
        }

        if (Multiline && (ScrollBars & ScrollBars.Vertical) != 0)
        {
            scrollBarPadding.Width += SystemInformation.GetVerticalScrollBarWidthForDpi(_deviceDpi);
        }

        // Subtract the scroll bar padding before measuring
        proposedConstraints -= scrollBarPadding;

        Size prefSize = base.GetPreferredSizeCore(proposedConstraints);

        return prefSize + scrollBarPadding;
    }

    /// <summary>
    ///  Gets or sets the current text in the text box.
    /// </summary>
    [AllowNull]
    public override string Text
    {
        get => base.Text;
        set
        {
            base.Text = value;
            _selectionSet = false;
        }
    }

    /// <summary>
    ///  Gets or sets how text is aligned in a <see cref="TextBox"/> control.
    ///  Note: This code is duplicated in MaskedTextBox for simplicity.
    /// </summary>
    [Localizable(true)]
    [SRCategory(nameof(SR.CatAppearance))]
    [DefaultValue(HorizontalAlignment.Left)]
    [SRDescription(nameof(SR.TextBoxTextAlignDescr))]
    public HorizontalAlignment TextAlign
    {
        get
        {
            return _textAlign;
        }
        set
        {
            if (_textAlign != value)
            {
                SourceGenerated.EnumValidator.Validate(value);

                _textAlign = value;
                RecreateHandle();
                OnTextAlignChanged(EventArgs.Empty);
            }
        }
    }

    /// <summary>
    ///  Indicates if the text in the edit control should appear as
    ///  the default password character. This property has precedence
    ///  over the PasswordChar property. Whenever the UseSystemPasswordChar
    ///  is set to true, the default system password character is used,
    ///  any character set into PasswordChar is ignored.
    /// </summary>
    [SRCategory(nameof(SR.CatBehavior))]
    [DefaultValue(false)]
    [SRDescription(nameof(SR.TextBoxUseSystemPasswordCharDescr))]
    [RefreshProperties(RefreshProperties.Repaint)]
    public bool UseSystemPasswordChar
    {
        get
        {
            return _useSystemPasswordChar;
        }
        set
        {
            if (value != _useSystemPasswordChar)
            {
                _useSystemPasswordChar = value;

                // RecreateHandle will update IME restricted mode.
                RecreateHandle();

                if (value)
                {
                    ResetAutoComplete(false);
                }
            }
        }
    }

    [SRCategory(nameof(SR.CatPropertyChanged))]
    [SRDescription(nameof(SR.RadioButtonOnTextAlignChangedDescr))]
    public event EventHandler? TextAlignChanged
    {
        add => Events.AddHandler(s_textAlignChangedEvent, value);
        remove => Events.RemoveHandler(s_textAlignChangedEvent, value);
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            // Reset this just in case, because the SHAutoComplete stuff
            // will subclass this guys wndproc (and nativewindow can't know about it).
            // so this will undo it, but on a dispose we'll be Destroying the window anyway.

            ResetAutoComplete(true);
            if (_autoCompleteCustomSource is not null)
            {
                _autoCompleteCustomSource.CollectionChanged -= OnAutoCompleteCustomSourceChanged;
            }

            if (_stringSource is not null)
            {
                _stringSource.ReleaseAutoComplete();
                _stringSource = null;
            }
        }

        base.Dispose(disposing);
    }

    /// <summary>
    ///  Overridden to handle RETURN key.
    /// </summary>
    protected override bool IsInputKey(Keys keyData)
    {
        if (Multiline && (keyData & Keys.Alt) == 0)
        {
            switch (keyData & Keys.KeyCode)
            {
                case Keys.Return:
                    return _acceptsReturn;
            }
        }

        return base.IsInputKey(keyData);
    }

    private void OnAutoCompleteCustomSourceChanged(object? sender, CollectionChangeEventArgs e)
    {
        if (AutoCompleteSource == AutoCompleteSource.CustomSource)
        {
            SetAutoComplete(true);
        }
    }

    protected override unsafe void OnBackColorChanged(EventArgs e)
    {
        base.OnBackColorChanged(e);

        // Force repainting of the entire window frame.
        if (Application.RenderWithVisualStyles && IsHandleCreated && BorderStyle == BorderStyle.Fixed3D)
        {
            PInvoke.RedrawWindow(this, lprcUpdate: null, HRGN.Null, REDRAW_WINDOW_FLAGS.RDW_INVALIDATE | REDRAW_WINDOW_FLAGS.RDW_FRAME);
        }
    }

    protected override void OnFontChanged(EventArgs e)
    {
        base.OnFontChanged(e);
        if (AutoCompleteMode != AutoCompleteMode.None)
        {
            // Always recreate the handle when autocomplete mode is on
            RecreateHandle();
        }
    }

    /// <summary>
    ///  Overridden to focus the text on first focus.
    /// </summary>
    protected override void OnGotFocus(EventArgs e)
    {
        base.OnGotFocus(e);
        if (!_selectionSet)
        {
            // We get one shot at selecting when we first get focus. If we don't
            // do it, we still want to act like the selection was set.
            _selectionSet = true;

            // If the user didn't provide a selection, force one in.
            if (SelectionLength == 0 && MouseButtons == MouseButtons.None)
            {
                SelectAll();
            }
        }

        if (IsAccessibilityObjectCreated)
        {
            AccessibilityObject.SetFocus();
        }
    }

    /// <summary>
    ///  Overridden to update the newly created handle with the settings of the
    ///  PasswordChar properties.
    /// </summary>
    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);
        if (!IsHandleCreated)
        {
            return;
        }

        SetSelectionOnHandle();

        if (_passwordChar != 0)
        {
            if (!_useSystemPasswordChar)
            {
                PInvokeCore.SendMessage(this, PInvokeCore.EM_SETPASSWORDCHAR, (WPARAM)_passwordChar);
            }
        }

        VerifyImeRestrictedModeChanged();

        if (AutoCompleteMode != AutoCompleteMode.None)
        {
            try
            {
                _fromHandleCreate = true;
                SetAutoComplete(false);
            }
            finally
            {
                _fromHandleCreate = false;
            }
        }
    }

    protected override void OnHandleDestroyed(EventArgs e)
    {
        if (_stringSource is not null)
        {
            _stringSource.ReleaseAutoComplete();
            _stringSource = null;
        }

        base.OnHandleDestroyed(e);
    }

    protected override void OnKeyUp(KeyEventArgs e)
    {
        base.OnKeyUp(e);

        if (IsHandleCreated && IsAccessibilityObjectCreated && ContainsNavigationKeyCode(e.KeyCode))
        {
            AccessibilityObject.RaiseAutomationEvent(UIA_EVENT_ID.UIA_Text_TextSelectionChangedEventId);
        }
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
        base.OnMouseDown(e);

        if (IsHandleCreated && IsAccessibilityObjectCreated)
        {
            // As there is no corresponding windows notification
            // about text selection changed for TextBox assuming
            // that any mouse down on textbox leads to change of
            // the caret position and thereby change the selection.
            AccessibilityObject.RaiseAutomationEvent(UIA_EVENT_ID.UIA_Text_TextSelectionChangedEventId);
        }
    }

    protected virtual void OnTextAlignChanged(EventArgs e)
    {
        if (Events[s_textAlignChangedEvent] is EventHandler eh)
        {
            eh(this, e);
        }
    }

    /// <summary>
    ///  Process a command key.
    ///  Native "EDIT" control does not support "Select All" shortcut represented by Ctrl-A keys, when in multiline mode,
    ///  WinForms TextBox supports this in .NET.
    /// </summary>
    /// <param name="m">The current windows message.</param>
    /// <param name="keyData">The bitmask containing one or more keys.</param>
    protected override bool ProcessCmdKey(ref Message m, Keys keyData)
    {
        bool returnValue = base.ProcessCmdKey(ref m, keyData);
        if (!returnValue && ShortcutsEnabled && (keyData == (Keys.Control | Keys.A)))
        {
            SelectAll();
            return true;
        }

        return returnValue;
    }

    /// <summary>
    ///  Replaces the portion of the text specified by startPos and length with the one passed in,
    ///  without resetting the undo buffer (if any).
    ///  This method is provided as an alternative to SelectedText which clears the undo buffer.
    ///  Observe that this method does not honor the MaxLength property as the parameter-less base's
    ///  Paste does
    /// </summary>
    public void Paste(string? text)
    {
        base.SetSelectedTextInternal(text, false);
    }

    /// <summary>
    ///  Performs the actual select without doing arg checking.
    /// </summary>
    private protected override void SelectInternal(int start, int length, int textLen)
    {
        // If user set selection into text box, mark it so we don't
        // clobber it when we get focus.
        _selectionSet = true;
        base.SelectInternal(start, length, textLen);
    }

    /// <summary>
    ///  Sets the AutoComplete mode in TextBox.
    /// </summary>
    private unsafe void SetAutoComplete(bool reset)
    {
        // Autocomplete Not Enabled for Password enabled and MultiLine Textboxes.
        if (Multiline || _passwordChar != 0 || _useSystemPasswordChar || AutoCompleteSource == AutoCompleteSource.None)
        {
            return;
        }

        if (AutoCompleteMode != AutoCompleteMode.None)
        {
            if (!_fromHandleCreate)
            {
                // RecreateHandle to avoid Leak.
                // notice the use of member variable to avoid reentrancy
                AutoCompleteMode backUpMode = AutoCompleteMode;
                _autoCompleteMode = AutoCompleteMode.None;
                RecreateHandle();
                _autoCompleteMode = backUpMode;
            }

            if (AutoCompleteSource == AutoCompleteSource.CustomSource)
            {
                if (IsHandleCreated && AutoCompleteCustomSource is not null)
                {
                    if (AutoCompleteCustomSource.Count == 0)
                    {
                        ResetAutoComplete(true);
                    }
                    else
                    {
                        if (_stringSource is null)
                        {
                            _stringSource = new StringSource(AutoCompleteCustomSource.ToArray());
                            if (!_stringSource.Bind(this, (AUTOCOMPLETEOPTIONS)AutoCompleteMode))
                            {
                                throw new ArgumentException(SR.AutoCompleteFailure);
                            }
                        }
                        else
                        {
                            _stringSource.RefreshList(AutoCompleteCustomSource.ToArray());
                        }
                    }
                }
            }
            else
            {
                if (IsHandleCreated)
                {
                    SHELL_AUTOCOMPLETE_FLAGS mode = SHELL_AUTOCOMPLETE_FLAGS.SHACF_DEFAULT;
                    if (AutoCompleteMode == AutoCompleteMode.Suggest)
                    {
                        mode |= SHELL_AUTOCOMPLETE_FLAGS.SHACF_AUTOSUGGEST_FORCE_ON | SHELL_AUTOCOMPLETE_FLAGS.SHACF_AUTOAPPEND_FORCE_OFF;
                    }

                    if (AutoCompleteMode == AutoCompleteMode.Append)
                    {
                        mode |= SHELL_AUTOCOMPLETE_FLAGS.SHACF_AUTOAPPEND_FORCE_ON | SHELL_AUTOCOMPLETE_FLAGS.SHACF_AUTOSUGGEST_FORCE_OFF;
                    }

                    if (AutoCompleteMode == AutoCompleteMode.SuggestAppend)
                    {
                        mode |= SHELL_AUTOCOMPLETE_FLAGS.SHACF_AUTOSUGGEST_FORCE_ON;
                        mode |= SHELL_AUTOCOMPLETE_FLAGS.SHACF_AUTOAPPEND_FORCE_ON;
                    }

                    PInvoke.SHAutoComplete(this, (SHELL_AUTOCOMPLETE_FLAGS)AutoCompleteSource | mode);
                }
            }
        }
        else if (reset)
        {
            ResetAutoComplete(true);
        }
    }

    /// <summary>
    ///  Resets the AutoComplete mode in TextBox.
    /// </summary>
    private void ResetAutoComplete(bool force)
    {
        if ((AutoCompleteMode != AutoCompleteMode.None || force) && IsHandleCreated)
        {
            PInvoke.SHAutoComplete(this, (SHELL_AUTOCOMPLETE_FLAGS)AutoCompleteSource.AllSystemSources | SHELL_AUTOCOMPLETE_FLAGS.SHACF_AUTOSUGGEST_FORCE_OFF | SHELL_AUTOCOMPLETE_FLAGS.SHACF_AUTOAPPEND_FORCE_OFF);
        }
    }

    private void ResetAutoCompleteCustomSource()
    {
        AutoCompleteCustomSource = null;
    }

    private void WmPrint(ref Message m)
    {
        base.WndProc(ref m);
        if (((nint)m.LParamInternal & PInvoke.PRF_NONCLIENT) != 0 && Application.RenderWithVisualStyles
            && BorderStyle == BorderStyle.Fixed3D)
        {
            using Graphics g = Graphics.FromHdc((HDC)m.WParamInternal);
            Rectangle rect = new(0, 0, Size.Width - 1, Size.Height - 1);
            using var pen = VisualStyleInformation.TextControlBorder.GetCachedPenScope();
            g.DrawRectangle(pen, rect);
            rect.Inflate(-1, -1);
            g.DrawRectangle(SystemPens.Window, rect);
        }
    }

    /// <summary>
    ///  Gets or sets the text that is displayed when the control has no text and does not have the focus.
    /// </summary>
    /// <value>The text that is displayed when the control has no text and does not have the focus.</value>
    [Localizable(true)]
    [DefaultValue("")]
    [SRDescription(nameof(SR.TextBoxPlaceholderTextDescr))]
    [AllowNull]
    public virtual string PlaceholderText
    {
        get
        {
            return _placeholderText;
        }
        set
        {
            value ??= string.Empty;

            if (_placeholderText != value)
            {
                _placeholderText = value;
                if (IsHandleCreated)
                {
                    Invalidate();
                }
            }
        }
    }

    /// <summary>
    ///  Draws the <see cref="PlaceholderText"/> in the client area of the <see cref="TextBox"/> using the default font and color.
    /// </summary>
    private void DrawPlaceholderText(HDC hdc)
    {
        TextFormatFlags flags = TextFormatFlags.NoPadding | TextFormatFlags.Top | TextFormatFlags.EndEllipsis;
        Rectangle rectangle = ClientRectangle;

        if (RightToLeft == RightToLeft.Yes)
        {
            flags |= TextFormatFlags.RightToLeft;
            switch (TextAlign)
            {
                case HorizontalAlignment.Center:
                    flags |= TextFormatFlags.HorizontalCenter;
                    rectangle.Offset(0, 1);
                    break;
                case HorizontalAlignment.Left:
                    flags |= TextFormatFlags.Right;
                    rectangle.Offset(1, 1);
                    break;
                case HorizontalAlignment.Right:
                    flags |= TextFormatFlags.Left;
                    rectangle.Offset(0, 1);
                    break;
            }
        }
        else
        {
            flags &= ~TextFormatFlags.RightToLeft;
            switch (TextAlign)
            {
                case HorizontalAlignment.Center:
                    flags |= TextFormatFlags.HorizontalCenter;
                    rectangle.Offset(0, 1);
                    break;
                case HorizontalAlignment.Left:
                    flags |= TextFormatFlags.Left;
                    rectangle.Offset(1, 1);
                    break;
                case HorizontalAlignment.Right:
                    flags |= TextFormatFlags.Right;
                    rectangle.Offset(0, 1);
                    break;
            }
        }

        TextRenderer.DrawTextInternal(hdc, PlaceholderText, Font, rectangle, SystemColors.GrayText, TextRenderer.DefaultQuality, flags);
    }

    protected override unsafe void WndProc(ref Message m)
    {
        switch (m.MsgInternal)
        {
            // Work around a very obscure Windows issue.
            case PInvokeCore.WM_LBUTTONDOWN:
                MouseButtons realState = MouseButtons;
                bool wasValidationCancelled = ValidationCancelled;
                Focus();
                if (realState == MouseButtons &&
                   (!ValidationCancelled || wasValidationCancelled))
                {
                    base.WndProc(ref m);
                }

                break;

            case PInvokeCore.WM_PAINT:
                {
                    // The native control tracks its own state, so it is get into a state Where either the native control invalidates
                    // itself, and thus blastsover the placeholder text
                    // - or -
                    // The placeholder text is written multiple times without being cleared first.
                    //
                    // To avoid either of the above we need the following operations.
                    //
                    // NOTE: there is still an observable flicker with this implementations. We're getting a second WM_PAINT after we
                    // do our begin/end paint. Something is apparently invalidating the control again. Explicitly calling ValidateRect
                    // should, in theory, prevent this second call but it clearly isn't happening.

                    // Invalidate the whole control to make sure the native control doesn't make any assumptions over what it has to paint
                    if (ShouldRenderPlaceHolderText())
                    {
                        PInvoke.InvalidateRect(this, lpRect: null, bErase: true);
                    }

                    // Let the native implementation draw the background and animate the frame
                    base.WndProc(ref m);

                    if (ShouldRenderPlaceHolderText())
                    {
                        // Invalidate again because the native WM_PAINT already validated everything by calling BeginPaint itself.
                        PInvoke.InvalidateRect(this, lpRect: null, bErase: true);

                        // Use BeginPaint instead of GetDC to prevent flicker and support print-to-image scenarios.
                        using BeginPaintScope paintScope = new(HWND);
                        DrawPlaceholderText(paintScope);

                        PInvoke.ValidateRect(this, lpRect: null);
                    }
                }

                break;

            case PInvokeCore.WM_PRINT:
                WmPrint(ref m);
                break;

            default:
                base.WndProc(ref m);
                break;
        }
    }

    private bool ShouldRenderPlaceHolderText() =>
        !string.IsNullOrEmpty(PlaceholderText) &&
        !GetStyle(ControlStyles.UserPaint) &&
        !Focused &&
        TextLength == 0;
}
