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

using Windows.Win32.System.Com;
using Windows.Win32.Web.MsHtml;
namespace System.Windows.Forms;

public sealed unsafe partial class HtmlDocument
{
    /// <summary>
    ///  HtmlDocumentShim - this is the glue between the DOM eventing mechanisms
    ///  and our CLR callbacks.
    ///
    ///  There are two kinds of events: HTMLWindowEvents2 and IHtmlWindow3.AttachHandler style
    ///  HTMLDocumentEvents2: we create an IConnectionPoint (via ConnectionPointCookie) between us and MSHTML and it calls back
    ///  on our an instance of HTMLDocumentEvents2. The HTMLDocumentEvents2 class then fires the event.
    ///
    ///  IHTMLDocument3.AttachHandler: MSHTML calls back on an HtmlToClrEventProxy that we've created, looking
    ///  for a method named DISPID=0. For each event that's subscribed, we create
    ///  a new HtmlToClrEventProxy, detect the callback and fire the corresponding
    ///  CLR event.
    /// </summary>
    internal class HtmlDocumentShim : HtmlShim
    {
        private readonly AgileComPointer<IHTMLWindow2>? _associatedWindow;
        private AxHost.ConnectionPointCookie? _cookie;
        private HtmlDocument _htmlDocument;

        internal HtmlDocumentShim(HtmlDocument htmlDocument)
        {
            _htmlDocument = htmlDocument;

            // Snap our associated window so we know when to disconnect.
            HtmlWindow? window = htmlDocument.Window;
            if (window is not null)
            {
                _associatedWindow = window.NativeHtmlWindow;
            }
        }

        public override IHTMLWindow2.Interface? AssociatedWindow => (IHTMLWindow2.Interface?)_associatedWindow?.GetManagedObject();

        public IHTMLDocument2.Interface NativeHtmlDocument2 => (IHTMLDocument2.Interface)_htmlDocument.NativeHtmlDocument2.GetManagedObject();

        internal HtmlDocument Document => _htmlDocument;

        /// Support IHtmlDocument3.AttachHandler
        public override void AttachEventHandler(string eventName, EventHandler eventHandler)
        {
            // IE likes to call back on an IDispatch of DISPID=0 when it has an event,
            // the HtmlToClrEventProxy helps us fake out the CLR so that we can call back on
            // our EventHandler properly.

            HtmlToClrEventProxy proxy = AddEventProxy(eventName, eventHandler);
            using var htmlDoc3 = _htmlDocument.GetHtmlDocument<IHTMLDocument3>();
            using BSTR name = new(eventName);
            using var dispatch = ComHelpers.GetComScope<IDispatch>(proxy);
            VARIANT_BOOL result = default;
            htmlDoc3.Value->attachEvent(name, dispatch, &result).ThrowOnFailure();
        }

        //
        // Connect to standard events
        //
        public override void ConnectToEvents()
        {
            if (_cookie is null || !_cookie.Connected)
            {
                _cookie = new AxHost.ConnectionPointCookie(
                    NativeHtmlDocument2,
                    new HTMLDocumentEvents2(_htmlDocument),
                    typeof(Interop.Mshtml.DHTMLDocumentEvents2),
                    throwException: false);

                if (!_cookie.Connected)
                {
                    _cookie = null;
                }
            }
        }

        /// Support IHtmlDocument3.DetachHandler
        public override void DetachEventHandler(string eventName, EventHandler eventHandler)
        {
            HtmlToClrEventProxy? proxy = RemoveEventProxy(eventHandler);
            if (proxy is not null)
            {
                using var htmlDoc3 = _htmlDocument.GetHtmlDocument<IHTMLDocument3>();
                using BSTR name = new(eventName);
                using var dispatch = ComHelpers.GetComScope<IDispatch>(proxy);
                htmlDoc3.Value->detachEvent(name, dispatch).ThrowOnFailure();
            }
        }

        //
        // Disconnect from standard events
        //
        public override void DisconnectFromEvents()
        {
            _cookie?.Disconnect();
            _cookie = null;
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            if (disposing)
            {
                _htmlDocument?.NativeHtmlDocument2.Dispose();
                _htmlDocument = null!;
            }
        }

        protected override object GetEventSender() => _htmlDocument;
    }
}
