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

using System.Diagnostics.CodeAnalysis;
using System.Private.Windows.Nrbf;
using System.Reflection.Metadata;
using Windows.Win32.Foundation;
using Windows.Win32.System.Com;

namespace System.Private.Windows.Ole;

internal unsafe class TestDataObject<TOleServices> :
    IComVisibleDataObject,
    ITestDataObject,
    IDataObjectInternal<TestDataObject<TOleServices>, ITestDataObject>
    where TOleServices : IOleServices, new()
{
    private readonly Composition<TOleServices, CoreNrbfSerializer, TestFormat> _innerData;

#if NET
    static
#endif
    TestDataObject<TOleServices> IDataObjectInternal<TestDataObject<TOleServices>, ITestDataObject>.Create() =>
        new();

#if NET
    static
#endif
    TestDataObject<TOleServices> IDataObjectInternal<TestDataObject<TOleServices>, ITestDataObject>.Create(IDataObject* dataObject) =>
        new(dataObject);

#if NET
    static
#endif
    TestDataObject<TOleServices> IDataObjectInternal<TestDataObject<TOleServices>, ITestDataObject>.Create(object data) =>
        new(data);

#if NET
    static
#endif
    IDataObjectInternal IDataObjectInternal<TestDataObject<TOleServices>, ITestDataObject>.Wrap(ITestDataObject data) =>
        new TestDataObjectAdapter(data);

    bool IDataObjectInternal<TestDataObject<TOleServices>, ITestDataObject>.TryUnwrapUserDataObject([NotNullWhen(true)] out ITestDataObject? dataObject)
        => TryUnwrapUserDataObject(out dataObject);

    internal TestDataObject(IDataObject* data) =>
        _innerData = Composition<TOleServices, CoreNrbfSerializer, TestFormat>.Create(data);

    public TestDataObject() =>
        _innerData = Composition<TOleServices, CoreNrbfSerializer, TestFormat>.Create();

    internal TestDataObject(object data) =>
        _innerData = Composition<TOleServices, CoreNrbfSerializer, TestFormat>
            .Create<TestDataObject<TOleServices>, ITestDataObject>(data);

    internal TestDataObject(string format, object data) : this() => SetData(format, data);

    internal TestDataObject(string format, bool autoConvert, object data) : this() => SetData(format, autoConvert, data);

    internal virtual bool TryUnwrapUserDataObject([NotNullWhen(true)] out ITestDataObject? dataObject)
    {
        dataObject = _innerData.ManagedDataObject switch
        {
            TestDataObject<TOleServices> data => data,
            TestDataObjectAdapter adapter => adapter.DataObject,
            DataStore<TOleServices> => this,
            _ => null
        };

        return dataObject is not null;
    }

    public virtual object? GetData(string format) => GetData(format, autoConvert: true);
    public virtual object? GetData(Type format) => format is null ? null : GetData(format.FullName!);
    public virtual object? GetData(string format, bool autoConvert) => _innerData.GetData(format, autoConvert);
    public virtual bool GetDataPresent(string format, bool autoConvert) => _innerData.GetDataPresent(format, autoConvert);
    public virtual bool GetDataPresent(string format) => GetDataPresent(format, autoConvert: true);
    public virtual bool GetDataPresent(Type format) => format is not null && GetDataPresent(format.FullName!);
    public virtual string[] GetFormats(bool autoConvert) => _innerData.GetFormats(autoConvert);
    public virtual string[] GetFormats() => GetFormats(autoConvert: true);
    public virtual void SetData(string format, bool autoConvert, object? data) => _innerData.SetData(format, autoConvert, data);
    public virtual void SetData(string format, object? data) => _innerData.SetData(format, data);
    public virtual void SetData(Type format, object? data) => _innerData.SetData(format, data);
    public virtual void SetData(object? data) => _innerData.SetData(data);

    public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(
        [MaybeNullWhen(false), NotNullWhen(true)] out T data) =>
        TryGetDataInternal(typeof(T).FullName!, resolver: null, autoConvert: true, out data);

    public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(
        string format,
        [MaybeNullWhen(false), NotNullWhen(true)] out T data) =>
        TryGetDataInternal(format, resolver: null, autoConvert: true, out data);

    public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(
        string format,
        bool autoConvert,
        [MaybeNullWhen(false), NotNullWhen(true)] out T data) =>
        TryGetDataInternal(format, resolver: null, autoConvert: autoConvert, out data);

    public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(
        string format,
        Func<TypeName, Type?> resolver,
        bool autoConvert,
        [MaybeNullWhen(false), NotNullWhen(true)] out T data) =>
        TryGetDataInternal(typeof(T).FullName!, resolver: null, autoConvert: autoConvert, out data);

    internal bool TryGetDataInternal<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(
        string format,
        Func<TypeName, Type?>? resolver,
        bool autoConvert,
        [NotNullWhen(true), MaybeNullWhen(false)] out T data)
    {
        data = default;

        if (!ClipboardCore<TOleServices>.IsValidTypeForFormat(typeof(T), format))
        {
            // Resolver implementation is specific to the overridden TryGetDataCore method,
            // can't validate if a non-null resolver is required for unbounded types.
            return false;
        }

        // Invoke the appropriate overload so we don't fail a null check on a nested object if the resolver is null.
        return resolver is null
            ? _innerData.TryGetData(format, autoConvert, out data)
            : _innerData.TryGetData(format, resolver, autoConvert, out data);
    }

    // Native callbacks follow

    public HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) => _innerData.GetData(pformatetcIn, pmedium);
    public HRESULT GetDataHere(FORMATETC* pformatetc, STGMEDIUM* pmedium) => _innerData.GetDataHere(pformatetc, pmedium);
    public HRESULT QueryGetData(FORMATETC* pformatetc) => _innerData.QueryGetData(pformatetc);
    public HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) => _innerData.GetCanonicalFormatEtc(pformatectIn, pformatetcOut);
    public HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) => _innerData.SetData(pformatetc, pmedium, fRelease);
    public HRESULT EnumFormatEtc(uint dwDirection, IEnumFORMATETC** ppenumFormatEtc) => _innerData.EnumFormatEtc(dwDirection, ppenumFormatEtc);
    public HRESULT DAdvise(FORMATETC* pformatetc, uint advf, IAdviseSink* pAdvSink, uint* pdwConnection) => _innerData.DAdvise(pformatetc, advf, pAdvSink, pdwConnection);
    public HRESULT DUnadvise(uint dwConnection) => _innerData.DUnadvise(dwConnection);
    public HRESULT EnumDAdvise(IEnumSTATDATA** ppenumAdvise) => _innerData.EnumDAdvise(ppenumAdvise);
}
