using System.Runtime.InteropServices;
using WinRT.Interop;

namespace MBN_STOCK_WEBVIEW.LegacyParityApp;

public sealed partial class MainWindow
{
    private Task<string?> PickBackgroundFileAsync(string trustedBackgroundDirectory)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(trustedBackgroundDirectory);
        var initialDirectory = Path.GetFullPath(trustedBackgroundDirectory);
        var completion = new TaskCompletionSource<string?>(
            TaskCreationOptions.RunContinuationsAsynchronously);

        void ShowPicker()
        {
            try
            {
                completion.TrySetResult(
                    LegacyFileDialog.ShowBackgroundFile(
                        WindowNative.GetWindowHandle(this),
                        initialDirectory));
            }
            catch (Exception exception)
            {
                completion.TrySetException(exception);
            }
        }

        if (DispatcherQueue.HasThreadAccess)
        {
            ShowPicker();
        }
        else if (!DispatcherQueue.TryEnqueue(ShowPicker))
        {
            completion.TrySetException(
                new InvalidOperationException("The background picker could not reach the UI thread."));
        }

        return completion.Task;
    }

    private Task<string?> PickOperatorFolderAsync(
        string? initialDirectory,
        string title)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(title);
        var canonicalInitialDirectory = initialDirectory is null
            ? null
            : Path.GetFullPath(initialDirectory);
        var completion = new TaskCompletionSource<string?>(
            TaskCreationOptions.RunContinuationsAsynchronously);

        void ShowPicker()
        {
            try
            {
                completion.TrySetResult(
                    LegacyFileDialog.ShowFolder(
                        WindowNative.GetWindowHandle(this),
                        canonicalInitialDirectory,
                        title));
            }
            catch (Exception exception)
            {
                completion.TrySetException(exception);
            }
        }

        if (DispatcherQueue.HasThreadAccess)
        {
            ShowPicker();
        }
        else if (!DispatcherQueue.TryEnqueue(ShowPicker))
        {
            completion.TrySetException(
                new InvalidOperationException("The folder picker could not reach the UI thread."));
        }

        return completion.Task;
    }

    private static class LegacyFileDialog
    {
        private const int CancelledHResult = unchecked((int)0x800704C7);
        private const uint FileSystemPathDisplayName = 0x80058000;
        private static readonly Guid FileOpenDialogClassId =
            new("DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7");

        public static string? ShowBackgroundFile(nint owner, string initialDirectory)
        {
            if (owner == nint.Zero || !Directory.Exists(initialDirectory))
            {
                throw new DirectoryNotFoundException(
                    "The trusted background directory is unavailable.");
            }

            IFileDialog? dialog = null;
            IShellItem? initialFolder = null;
            IShellItem? result = null;
            try
            {
                var dialogType = Type.GetTypeFromCLSID(
                    FileOpenDialogClassId,
                    throwOnError: true) ??
                    throw new InvalidOperationException(
                        "The Windows file-open dialog is unavailable.");
                dialog = (IFileDialog)(Activator.CreateInstance(dialogType) ??
                    throw new InvalidOperationException(
                        "The Windows file-open dialog could not be created."));
                dialog.GetOptions(out var existingOptions);
                dialog.SetOptions(
                    existingOptions |
                    FileOpenOptions.NoChangeDirectory |
                    FileOpenOptions.ForceFileSystem |
                    FileOpenOptions.PathMustExist |
                    FileOpenOptions.FileMustExist |
                    FileOpenOptions.NoDereferenceLinks |
                    FileOpenOptions.DoNotAddToRecent);
                dialog.SetTitle("다음 장면 배경 선택");
                dialog.SetOkButtonLabel("선택");
                dialog.SetDefaultExtension("vrv");
                dialog.SetFileTypes(
                    3,
                    new[]
                    {
                        new DialogFilter("VRV/JPG/PNG 배경", "*.vrv;*.jpg;*.jpeg;*.png"),
                        new DialogFilter("VRV 영상", "*.vrv"),
                        new DialogFilter("JPG/PNG 이미지", "*.jpg;*.jpeg;*.png")
                    });
                dialog.SetFileTypeIndex(1);

                var shellItemId = typeof(IShellItem).GUID;
                var createResult = SHCreateItemFromParsingName(
                    initialDirectory,
                    nint.Zero,
                    ref shellItemId,
                    out initialFolder);
                Marshal.ThrowExceptionForHR(createResult);
                dialog.SetFolder(initialFolder);
                dialog.SetDefaultFolder(initialFolder);

                var showResult = dialog.Show(owner);
                if (showResult == CancelledHResult)
                {
                    return null;
                }

                Marshal.ThrowExceptionForHR(showResult);
                dialog.GetResult(out result);
                result.GetDisplayName(FileSystemPathDisplayName, out var pathPointer);
                try
                {
                    return Marshal.PtrToStringUni(pathPointer) ??
                        throw new IOException("The selected background path is unavailable.");
                }
                finally
                {
                    Marshal.FreeCoTaskMem(pathPointer);
                }
            }
            finally
            {
                ReleaseComObject(result);
                ReleaseComObject(initialFolder);
                ReleaseComObject(dialog);
            }
        }

        public static string? ShowFolder(
            nint owner,
            string? initialDirectory,
            string title)
        {
            if (owner == nint.Zero)
            {
                throw new InvalidOperationException(
                    "The folder picker requires an owner window.");
            }

            if (initialDirectory is not null &&
                !Directory.Exists(initialDirectory))
            {
                throw new DirectoryNotFoundException(
                    "The initial folder-picker directory is unavailable.");
            }

            IFileDialog? dialog = null;
            IShellItem? initialFolder = null;
            IShellItem? result = null;
            try
            {
                var dialogType = Type.GetTypeFromCLSID(
                    FileOpenDialogClassId,
                    throwOnError: true) ??
                    throw new InvalidOperationException(
                        "The Windows folder dialog is unavailable.");
                dialog = (IFileDialog)(Activator.CreateInstance(dialogType) ??
                    throw new InvalidOperationException(
                        "The Windows folder dialog could not be created."));
                dialog.GetOptions(out var existingOptions);
                dialog.SetOptions(
                    existingOptions |
                    FileOpenOptions.NoChangeDirectory |
                    FileOpenOptions.PickFolders |
                    FileOpenOptions.ForceFileSystem |
                    FileOpenOptions.PathMustExist |
                    FileOpenOptions.NoDereferenceLinks |
                    FileOpenOptions.DoNotAddToRecent);
                dialog.SetTitle(title);
                dialog.SetOkButtonLabel("폴더 선택");

                if (initialDirectory is not null)
                {
                    var shellItemId = typeof(IShellItem).GUID;
                    var createResult = SHCreateItemFromParsingName(
                        initialDirectory,
                        nint.Zero,
                        ref shellItemId,
                        out initialFolder);
                    Marshal.ThrowExceptionForHR(createResult);
                    dialog.SetFolder(initialFolder);
                    dialog.SetDefaultFolder(initialFolder);
                }

                var showResult = dialog.Show(owner);
                if (showResult == CancelledHResult)
                {
                    return null;
                }

                Marshal.ThrowExceptionForHR(showResult);
                dialog.GetResult(out result);
                result.GetDisplayName(FileSystemPathDisplayName, out var pathPointer);
                try
                {
                    return Marshal.PtrToStringUni(pathPointer) ??
                        throw new IOException("The selected folder path is unavailable.");
                }
                finally
                {
                    Marshal.FreeCoTaskMem(pathPointer);
                }
            }
            finally
            {
                ReleaseComObject(result);
                ReleaseComObject(initialFolder);
                ReleaseComObject(dialog);
            }
        }

        private static void ReleaseComObject(object? value)
        {
            if (value is not null && Marshal.IsComObject(value))
            {
                _ = Marshal.FinalReleaseComObject(value);
            }
        }

        [Flags]
        private enum FileOpenOptions : uint
        {
            NoChangeDirectory = 0x00000008,
            PickFolders = 0x00000020,
            ForceFileSystem = 0x00000040,
            PathMustExist = 0x00000800,
            FileMustExist = 0x00001000,
            NoDereferenceLinks = 0x00100000,
            DoNotAddToRecent = 0x02000000
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        private readonly struct DialogFilter(string name, string specification)
        {
            [MarshalAs(UnmanagedType.LPWStr)]
            public readonly string Name = name;

            [MarshalAs(UnmanagedType.LPWStr)]
            public readonly string Specification = specification;
        }

        [ComImport]
        [Guid("42F85136-DB7E-439C-85F1-E4075D135FC8")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        private interface IFileDialog
        {
            [PreserveSig]
            int Show(nint owner);

            void SetFileTypes(
                uint count,
                [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]
                DialogFilter[] filters);

            void SetFileTypeIndex(uint index);

            void GetFileTypeIndex(out uint index);

            void Advise(nint events, out uint cookie);

            void Unadvise(uint cookie);

            void SetOptions(FileOpenOptions options);

            void GetOptions(out FileOpenOptions options);

            void SetDefaultFolder(IShellItem folder);

            void SetFolder(IShellItem folder);

            void GetFolder(out IShellItem folder);

            void GetCurrentSelection(out IShellItem item);

            void SetFileName([MarshalAs(UnmanagedType.LPWStr)] string name);

            void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string name);

            void SetTitle([MarshalAs(UnmanagedType.LPWStr)] string title);

            void SetOkButtonLabel([MarshalAs(UnmanagedType.LPWStr)] string text);

            void SetFileNameLabel([MarshalAs(UnmanagedType.LPWStr)] string label);

            void GetResult(out IShellItem item);

            void AddPlace(IShellItem item, uint placement);

            void SetDefaultExtension([MarshalAs(UnmanagedType.LPWStr)] string extension);

            void Close(int result);

            void SetClientGuid(ref Guid guid);

            void ClearClientData();

            void SetFilter(nint filter);
        }

        [ComImport]
        [Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        private interface IShellItem
        {
            void BindToHandler(
                nint bindContext,
                ref Guid handlerId,
                ref Guid interfaceId,
                out nint result);

            void GetParent(out IShellItem parent);

            void GetDisplayName(uint displayName, out nint name);

            void GetAttributes(uint mask, out uint attributes);

            void Compare(IShellItem item, uint hint, out int order);
        }

        [DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = true)]
        private static extern int SHCreateItemFromParsingName(
            [MarshalAs(UnmanagedType.LPWStr)] string path,
            nint bindContext,
            ref Guid interfaceId,
            [MarshalAs(UnmanagedType.Interface)] out IShellItem shellItem);
    }
}
