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

namespace System.Windows.Forms.Design;

public partial class ControlDesigner
{
    private class ChildWindowTarget : IWindowTarget, IDesignerTarget
    {
        private readonly ControlDesigner _designer;
        private readonly Control _childControl;
        private HWND _handle = HWND.Null;

        public ChildWindowTarget(ControlDesigner designer, Control childControl, IWindowTarget oldWindowTarget)
        {
            _designer = designer;
            _childControl = childControl;
            OldWindowTarget = oldWindowTarget;
        }

        public IWindowTarget OldWindowTarget { get; }

        public void DefWndProc(ref Message m) => OldWindowTarget.OnMessage(ref m);

        public void Dispose()
        {
            // Do nothing. We will pick this up through a null DesignerTarget property when we come out of the message loop.
        }

        public void OnHandleChange(IntPtr newHandle)
        {
            _handle = (HWND)newHandle;
            OldWindowTarget.OnHandleChange(newHandle);
        }

        public void OnMessage(ref Message m)
        {
            // If the designer has jumped ship, the continue partying on messages, but send them back to the original control.
            if (_designer.Component is null)
            {
                OldWindowTarget.OnMessage(ref m);
                return;
            }

            // We want these messages to go through the designer's WndProc method, and we want people to be able
            // to do default processing with the designer's DefWndProc. So, we stuff the old window target into
            // the designer's target and then call their WndProc.
            IDesignerTarget? designerTarget = _designer.DesignerTarget;
            _designer.DesignerTarget = this;

            try
            {
                _designer.WndProc(ref m);
            }
            catch (Exception ex)
            {
                _designer.SetUnhandledException(_childControl, ex);
            }
            finally
            {
                // If the designer disposed us, then we should follow suit.
                if (_designer.DesignerTarget is null)
                {
                    designerTarget?.Dispose();
                }
                else
                {
                    _designer.DesignerTarget = designerTarget;
                }

                // Controls (primarily RichEdit) will register themselves as drag-drop source/targets when they
                // are instantiated. Normally, when they are being designed, we will RevokeDragDrop() in their
                // designers. The problem occurs when these controls are inside a UserControl. At that time, we
                // do not have a designer for these controls, and they prevent the ParentControlDesigner's
                // drag-drop from working. What we do is to loop through all child controls that do not have a
                // designer (in HookChildControls()), and RevokeDragDrop() after their handles have been created.
                if (m.Msg == (int)PInvokeCore.WM_CREATE)
                {
                    Debug.Assert(_handle != IntPtr.Zero, "Handle for control not created");
                    PInvokeCore.RevokeDragDrop(_handle);
                }
            }
        }
    }
}
