using System;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using Microsoft.Win32;
using Vanara.Extensions;
using Vanara.InteropServices;
using Vanara.PInvoke;
using Vanara.Resources;
using static Vanara.PInvoke.ComCtl32;
using static Vanara.PInvoke.Kernel32;
using static Vanara.PInvoke.Shell32;
using static Vanara.PInvoke.User32;

namespace Vanara.Windows.Forms
{
	/// <summary>
	/// </summary>
	public enum FolderBrowserDialogOptions
	{
		/// <summary></summary>
		Folders,
		/// <summary></summary>
		FoldersAndFiles,
		/// <summary></summary>
		Computers,
		/// <summary></summary>
		Printers
	}

	/// <summary>
	/// Standard folders registered with the system as Known Folders. A computer will have only folders appropriate to it installed.
	/// </summary>
	public enum KnownFolder
	{
		/// <summary>Account Pictures</summary>
		AccountPictures = KNOWNFOLDERID.FOLDERID_AccountPictures,

		/// <summary>Get Programs</summary>
		AddNewPrograms = KNOWNFOLDERID.FOLDERID_AddNewPrograms,

		/// <summary>Admin tools</summary>
		AdminTools = KNOWNFOLDERID.FOLDERID_AdminTools,

		/// <summary>Application shortcuts</summary>
		ApplicationShortcuts = KNOWNFOLDERID.FOLDERID_ApplicationShortcuts,

		/// <summary>Applications</summary>
		AppsFolder = KNOWNFOLDERID.FOLDERID_AppsFolder,

		/// <summary>Installed Updates</summary>
		AppUpdates = KNOWNFOLDERID.FOLDERID_AppUpdates,

		/// <summary>Camera Roll</summary>
		CameraRoll = KNOWNFOLDERID.FOLDERID_CameraRoll,

		/// <summary>Temporary Burn Folder</summary>
		CDBurning = KNOWNFOLDERID.FOLDERID_CDBurning,

		/// <summary>Programs and Features</summary>
		ChangeRemovePrograms = KNOWNFOLDERID.FOLDERID_ChangeRemovePrograms,

		/// <summary>Administrative Tools</summary>
		CommonAdminTools = KNOWNFOLDERID.FOLDERID_CommonAdminTools,

		/// <summary>OEM Links</summary>
		CommonOEMLinks = KNOWNFOLDERID.FOLDERID_CommonOEMLinks,

		/// <summary>Programs</summary>
		CommonPrograms = KNOWNFOLDERID.FOLDERID_CommonPrograms,

		/// <summary>Start Menu</summary>
		CommonStartMenu = KNOWNFOLDERID.FOLDERID_CommonStartMenu,

		/// <summary>Startup</summary>
		CommonStartup = KNOWNFOLDERID.FOLDERID_CommonStartup,

		/// <summary>Templates</summary>
		CommonTemplates = KNOWNFOLDERID.FOLDERID_CommonTemplates,

		/// <summary>Computer</summary>
		ComputerFolder = KNOWNFOLDERID.FOLDERID_ComputerFolder,

		/// <summary>Conflicts</summary>
		ConflictFolder = KNOWNFOLDERID.FOLDERID_ConflictFolder,

		/// <summary>Network Connections</summary>
		ConnectionsFolder = KNOWNFOLDERID.FOLDERID_ConnectionsFolder,

		/// <summary>Contacts</summary>
		Contacts = KNOWNFOLDERID.FOLDERID_Contacts,

		/// <summary>Control Panel</summary>
		ControlPanelFolder = KNOWNFOLDERID.FOLDERID_ControlPanelFolder,

		/// <summary>Cookies</summary>
		Cookies = KNOWNFOLDERID.FOLDERID_Cookies,

		/// <summary>Desktop</summary>
		Desktop = KNOWNFOLDERID.FOLDERID_Desktop,

		/// <summary>DeviceMetadataStore</summary>
		DeviceMetadataStore = KNOWNFOLDERID.FOLDERID_DeviceMetadataStore,

		/// <summary>Documents</summary>
		Documents = KNOWNFOLDERID.FOLDERID_Documents,

		/// <summary>Documents</summary>
		DocumentsLibrary = KNOWNFOLDERID.FOLDERID_DocumentsLibrary,

		/// <summary>Downloads</summary>
		Downloads = KNOWNFOLDERID.FOLDERID_Downloads,

		/// <summary>Favorites</summary>
		Favorites = KNOWNFOLDERID.FOLDERID_Favorites,

		/// <summary>Fonts</summary>
		Fonts = KNOWNFOLDERID.FOLDERID_Fonts,

		/// <summary>Games</summary>
		Games = KNOWNFOLDERID.FOLDERID_Games,

		/// <summary>GameExplorer</summary>
		GameTasks = KNOWNFOLDERID.FOLDERID_GameTasks,

		/// <summary>History</summary>
		History = KNOWNFOLDERID.FOLDERID_History,

		/// <summary>Homegroup</summary>
		HomeGroup = KNOWNFOLDERID.FOLDERID_HomeGroup,

		/// <summary>The user's username (%USERNAME%)</summary>
		HomeGroupCurrentUser = KNOWNFOLDERID.FOLDERID_HomeGroupCurrentUser,

		/// <summary>ImplicitAppShortcuts</summary>
		ImplicitAppShortcuts = KNOWNFOLDERID.FOLDERID_ImplicitAppShortcuts,

		/// <summary>Temporary Internet Files</summary>
		InternetCache = KNOWNFOLDERID.FOLDERID_InternetCache,

		/// <summary>The Internet</summary>
		InternetFolder = KNOWNFOLDERID.FOLDERID_InternetFolder,

		/// <summary>Libraries</summary>
		Libraries = KNOWNFOLDERID.FOLDERID_Libraries,

		/// <summary>Links</summary>
		Links = KNOWNFOLDERID.FOLDERID_Links,

		/// <summary>Local</summary>
		LocalAppData = KNOWNFOLDERID.FOLDERID_LocalAppData,

		/// <summary>LocalLow</summary>
		LocalAppDataLow = KNOWNFOLDERID.FOLDERID_LocalAppDataLow,

		/// <summary>None</summary>
		LocalizedResourcesDir = KNOWNFOLDERID.FOLDERID_LocalizedResourcesDir,

		/// <summary>Music</summary>
		Music = KNOWNFOLDERID.FOLDERID_Music,

		/// <summary>Music</summary>
		MusicLibrary = KNOWNFOLDERID.FOLDERID_MusicLibrary,

		/// <summary>Network Shortcuts</summary>
		NetHood = KNOWNFOLDERID.FOLDERID_NetHood,

		/// <summary>Network</summary>
		NetworkFolder = KNOWNFOLDERID.FOLDERID_NetworkFolder,

		/// <summary>Original Images</summary>
		OriginalImages = KNOWNFOLDERID.FOLDERID_OriginalImages,

		/// <summary>Slide Shows</summary>
		PhotoAlbums = KNOWNFOLDERID.FOLDERID_PhotoAlbums,

		/// <summary>Pictures</summary>
		PicturesLibrary = KNOWNFOLDERID.FOLDERID_PicturesLibrary,

		/// <summary>Pictures</summary>
		Pictures = KNOWNFOLDERID.FOLDERID_Pictures,

		/// <summary>Playlists</summary>
		Playlists = KNOWNFOLDERID.FOLDERID_Playlists,

		/// <summary>Printers</summary>
		PrintersFolder = KNOWNFOLDERID.FOLDERID_PrintersFolder,

		/// <summary>Printer Shortcuts</summary>
		PrintHood = KNOWNFOLDERID.FOLDERID_PrintHood,

		/// <summary>The user's username (%USERNAME%)</summary>
		Profile = KNOWNFOLDERID.FOLDERID_Profile,

		/// <summary>ProgramData</summary>
		ProgramData = KNOWNFOLDERID.FOLDERID_ProgramData,

		/// <summary>Program Files</summary>
		ProgramFiles = KNOWNFOLDERID.FOLDERID_ProgramFiles,

		/// <summary>Program Files</summary>
		ProgramFilesX64 = KNOWNFOLDERID.FOLDERID_ProgramFilesX64,

		/// <summary>Program Files</summary>
		ProgramFilesX86 = KNOWNFOLDERID.FOLDERID_ProgramFilesX86,

		/// <summary>Common Files</summary>
		ProgramFilesCommon = KNOWNFOLDERID.FOLDERID_ProgramFilesCommon,

		/// <summary>Common Files</summary>
		ProgramFilesCommonX64 = KNOWNFOLDERID.FOLDERID_ProgramFilesCommonX64,

		/// <summary>Common Files</summary>
		ProgramFilesCommonX86 = KNOWNFOLDERID.FOLDERID_ProgramFilesCommonX86,

		/// <summary>Programs</summary>
		Programs = KNOWNFOLDERID.FOLDERID_Programs,

		/// <summary>Public</summary>
		Public = KNOWNFOLDERID.FOLDERID_Public,

		/// <summary>Public Desktop</summary>
		PublicDesktop = KNOWNFOLDERID.FOLDERID_PublicDesktop,

		/// <summary>Public Documents</summary>
		PublicDocuments = KNOWNFOLDERID.FOLDERID_PublicDocuments,

		/// <summary>Public Downloads</summary>
		PublicDownloads = KNOWNFOLDERID.FOLDERID_PublicDownloads,

		/// <summary>GameExplorer</summary>
		PublicGameTasks = KNOWNFOLDERID.FOLDERID_PublicGameTasks,

		/// <summary>Libraries</summary>
		PublicLibraries = KNOWNFOLDERID.FOLDERID_PublicLibraries,

		/// <summary>Public Music</summary>
		PublicMusic = KNOWNFOLDERID.FOLDERID_PublicMusic,

		/// <summary>Public Pictures</summary>
		PublicPictures = KNOWNFOLDERID.FOLDERID_PublicPictures,

		/// <summary>Ringtones</summary>
		PublicRingtones = KNOWNFOLDERID.FOLDERID_PublicRingtones,

		/// <summary>Public Account Pictures</summary>
		PublicUserTiles = KNOWNFOLDERID.FOLDERID_PublicUserTiles,

		/// <summary>Public Videos</summary>
		PublicVideos = KNOWNFOLDERID.FOLDERID_PublicVideos,

		/// <summary>Quick Launch</summary>
		QuickLaunch = KNOWNFOLDERID.FOLDERID_QuickLaunch,

		/// <summary>Recent Items</summary>
		Recent = KNOWNFOLDERID.FOLDERID_Recent,

		/// <summary>Recorded TV</summary>
		RecordedTVLibrary = KNOWNFOLDERID.FOLDERID_RecordedTVLibrary,

		/// <summary>Recycle Bin</summary>
		RecycleBinFolder = KNOWNFOLDERID.FOLDERID_RecycleBinFolder,

		/// <summary>Resources</summary>
		ResourceDir = KNOWNFOLDERID.FOLDERID_ResourceDir,

		/// <summary>Ringtones</summary>
		Ringtones = KNOWNFOLDERID.FOLDERID_Ringtones,

		/// <summary>Roaming</summary>
		RoamingAppData = KNOWNFOLDERID.FOLDERID_RoamingAppData,

		/// <summary>RoamedTileImages</summary>
		RoamedTileImages = KNOWNFOLDERID.FOLDERID_RoamedTileImages,

		/// <summary>RoamingTiles</summary>
		RoamingTiles = KNOWNFOLDERID.FOLDERID_RoamingTiles,

		/// <summary>Sample Music</summary>
		SampleMusic = KNOWNFOLDERID.FOLDERID_SampleMusic,

		/// <summary>Sample Pictures</summary>
		SamplePictures = KNOWNFOLDERID.FOLDERID_SamplePictures,

		/// <summary>Sample Playlists</summary>
		SamplePlaylists = KNOWNFOLDERID.FOLDERID_SamplePlaylists,

		/// <summary>Sample Videos</summary>
		SampleVideos = KNOWNFOLDERID.FOLDERID_SampleVideos,

		/// <summary>Saved Games</summary>
		SavedGames = KNOWNFOLDERID.FOLDERID_SavedGames,

		/// <summary>Saved Pictures</summary>
		SavedPictures = KNOWNFOLDERID.FOLDERID_SavedPictures,

		/// <summary>Saved Pictures Library</summary>
		SavedPicturesLibrary = KNOWNFOLDERID.FOLDERID_SavedPicturesLibrary,

		/// <summary>Searches</summary>
		SavedSearches = KNOWNFOLDERID.FOLDERID_SavedSearches,

		/// <summary>Screenshots</summary>
		Screenshots = KNOWNFOLDERID.FOLDERID_Screenshots,

		/// <summary>Offline Files</summary>
		SEARCH_CSC = KNOWNFOLDERID.FOLDERID_SEARCH_CSC,

		/// <summary>History</summary>
		SearchHistory = KNOWNFOLDERID.FOLDERID_SearchHistory,

		/// <summary>Search Results</summary>
		SearchHome = KNOWNFOLDERID.FOLDERID_SearchHome,

		/// <summary>Microsoft Office Outlook</summary>
		SEARCH_MAPI = KNOWNFOLDERID.FOLDERID_SEARCH_MAPI,

		/// <summary>Templates</summary>
		SearchTemplates = KNOWNFOLDERID.FOLDERID_SearchTemplates,

		/// <summary>SendTo</summary>
		SendTo = KNOWNFOLDERID.FOLDERID_SendTo,

		/// <summary>Gadgets</summary>
		SidebarDefaultParts = KNOWNFOLDERID.FOLDERID_SidebarDefaultParts,

		/// <summary>Gadgets</summary>
		SidebarParts = KNOWNFOLDERID.FOLDERID_SidebarParts,

		/// <summary>OneDrive</summary>
		SkyDrive = KNOWNFOLDERID.FOLDERID_SkyDrive,

		/// <summary>Camera Roll</summary>
		SkyDriveCameraRoll = KNOWNFOLDERID.FOLDERID_SkyDriveCameraRoll,

		/// <summary>Documents</summary>
		SkyDriveDocuments = KNOWNFOLDERID.FOLDERID_SkyDriveDocuments,

		/// <summary>Pictures</summary>
		SkyDrivePictures = KNOWNFOLDERID.FOLDERID_SkyDrivePictures,

		/// <summary>Start Menu</summary>
		StartMenu = KNOWNFOLDERID.FOLDERID_StartMenu,

		/// <summary>Startup</summary>
		Startup = KNOWNFOLDERID.FOLDERID_Startup,

		/// <summary>Sync Center</summary>
		SyncManagerFolder = KNOWNFOLDERID.FOLDERID_SyncManagerFolder,

		/// <summary>Sync Results</summary>
		SyncResultsFolder = KNOWNFOLDERID.FOLDERID_SyncResultsFolder,

		/// <summary>Sync Setup</summary>
		SyncSetupFolder = KNOWNFOLDERID.FOLDERID_SyncSetupFolder,

		/// <summary>System32</summary>
		System = KNOWNFOLDERID.FOLDERID_System,

		/// <summary>System32</summary>
		SystemX86 = KNOWNFOLDERID.FOLDERID_SystemX86,

		/// <summary>Templates</summary>
		Templates = KNOWNFOLDERID.FOLDERID_Templates,

		/// <summary>User Pinned</summary>
		UserPinned = KNOWNFOLDERID.FOLDERID_UserPinned,

		/// <summary>Users</summary>
		UserProfiles = KNOWNFOLDERID.FOLDERID_UserProfiles,

		/// <summary>Programs</summary>
		UserProgramFiles = KNOWNFOLDERID.FOLDERID_UserProgramFiles,

		/// <summary>Programs</summary>
		UserProgramFilesCommon = KNOWNFOLDERID.FOLDERID_UserProgramFilesCommon,

		/// <summary>The user's full name (for instance, Jean Philippe Bagel) entered when the user account was created.</summary>
		UsersFiles = KNOWNFOLDERID.FOLDERID_UsersFiles,

		/// <summary>Libraries</summary>
		UsersLibraries = KNOWNFOLDERID.FOLDERID_UsersLibraries,

		/// <summary>Videos</summary>
		Videos = KNOWNFOLDERID.FOLDERID_Videos,

		/// <summary>Videos</summary>
		VideosLibrary = KNOWNFOLDERID.FOLDERID_VideosLibrary,

		/// <summary>Windows</summary>
		Windows = KNOWNFOLDERID.FOLDERID_Windows,

		/// <summary>Undefined</summary>
		Undefined = 0xFFFF,
	}

	/// <summary>Class to let the user browse for a folder.</summary>
	[ToolboxBitmap(typeof(FolderBrowserDialog), "Dialog"), Description("Dialog that browses network computers.")]
	public class FolderBrowserDialog : CommonDialog
	{
		private const KnownFolder defaultComputersFolder = KnownFolder.NetworkFolder;
		private const KnownFolder defaultFolderFolder = KnownFolder.ComputerFolder;
		private const KnownFolder defaultPrintersFolder = KnownFolder.PrintersFolder;

		private FolderBrowserDialogOptions browseOption;
		private bool initialized;
		private KnownFolder rootFolder;
		private PIDL rootPidl;

		/// <summary>Initializes a new instance of the <see cref="FolderBrowserDialog"/> class.</summary>
		public FolderBrowserDialog()
		{
			Reset();
			RootFolder = defaultFolderFolder;
		}

		/// <summary>Occurs when dialog box has been initialized and primary values have been set.</summary>
		public event EventHandler<FolderBrowserDialogInitializedEventArgs> Initialized;

		/// <summary>Event that is raised when the user selects an invalid folder.</summary>
		public event EventHandler<InvalidFolderEventArgs> InvalidFolderSelected;

		/// <summary>Occurs when <see cref="SelectedItem"/> property has changed.</summary>
		public event PropertyChangedEventHandler SelectedItemChanged;

		/// <summary>Gets or sets the types of items to browse.</summary>
		[DefaultValue(typeof(FolderBrowserDialogOptions), "Folders"), Localizable(false), Category("Behavior"),
		 Description("The types of items to browse")]
		public FolderBrowserDialogOptions BrowseOption
		{
			get => browseOption; set
			{
				if (browseOption != value)
				{
					browseOption = value;
					switch (browseOption)
					{
						case FolderBrowserDialogOptions.Folders:
						case FolderBrowserDialogOptions.FoldersAndFiles:
							if (RootFolder == defaultComputersFolder || RootFolder == defaultPrintersFolder)
								RootFolder = defaultFolderFolder;
							break;
						case FolderBrowserDialogOptions.Computers:
							RootFolder = defaultComputersFolder;
							break;
						case FolderBrowserDialogOptions.Printers:
							RootFolder = defaultPrintersFolder;
							break;
						default:
							throw new ArgumentOutOfRangeException();
					}
				}
			}
		}

		/// <summary>Gets or sets the caption of the dialog box.</summary>
		[DefaultValue(""), Category("Appearance"), Localizable(true), Description("Caption of the dialog box.")]
		public string Caption { get; set; } = "";

		/// <summary>Gets or sets the description shown to the user.</summary>
		[DefaultValue(""), Category("Appearance"), Localizable(true), Description("Description shown to the user.")]
		public string Description { get; set; } = "";

		/// <summary>Gets or sets whether to automatically expand the tree when shown.</summary>
		[DefaultValue(true), Localizable(false), Category("Behavior"), Description("Whether to automatically expand the tree when shown.")]
		public bool Expanded { get; set; } = true;

		/// <summary>Gets or sets whether to hide network folders below the domain level in the tree.</summary>
		[DefaultValue(false), Localizable(false), Category("Behavior"), Description("Whether to hide network folders below the domain.")]
		public bool HideDomainFolders { get; set; }

		/// <summary>Gets or sets whether to return only file system folders.</summary>
		[DefaultValue(false), Localizable(false), Category("Behavior"), Description("Whether to return only file system folders.")]
		public bool LocalFileSystemOnly { get; set; }

		/// <summary>Gets or sets the text on the OK button.</summary>
		[DefaultValue(""), Category("Appearance"), Localizable(true), Description("Text on the OK button.")]
		public string OkText { get; set; } = "";

		/// <summary>Gets or sets the root folder.</summary>
		[Localizable(false), Category("Data"), Description("Root folder of tree."), DefaultValue(defaultFolderFolder)]
		public KnownFolder RootFolder
		{
			get => rootFolder; set
			{
				rootFolder = value;
				try { rootPidl = ((KNOWNFOLDERID)RootFolder).PIDL(); }
				catch
				{
					System.Diagnostics.Debug.WriteLine($"The known folder '{RootFolder}' is not supported for this OS or application configuration.");
					ResetRootFolder();
				}
			}
		}

		/// <summary>Gets or sets the PIDL associated with the root folder. This can be used to specify a non-known folder as the root.</summary>
		/// <value>The root folder's PIDL.</value>
		[DefaultValue(0), Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		// ReSharper disable once InconsistentNaming
		public PIDL RootFolderPIDL
		{
			get => rootPidl; set
			{
				rootPidl = value;
				rootFolder = KnownFolder.Undefined;
			}
		}

		/// <summary>Gets or sets the path or name of the folder selected by the user.</summary>
		[DefaultValue(""), Category("Data"), Localizable(true), Description("The path or name of the selected folder")]
		public string SelectedItem { get; set; } = "";

		/// <summary>Gets the image from the system image list associated with the selected item.</summary>
		/// <value>The selected item's image.</value>
		[DefaultValue(null), Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public Icon SelectedItemImage => SelectedItemPIDL.GetIcon(IconSize.Small);

		/// <summary>Gets the PIDL associated with the selected item.</summary>
		/// <value>The selected item's PIDL.</value>
		[DefaultValue(0), Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		// ReSharper disable once InconsistentNaming
		public PIDL SelectedItemPIDL { get; private set; }

		/// <summary>Gets or sets whether or not to show file junctions, such as a library or compressed file. This only is available in Windows 7 and later.</summary>
		[DefaultValue(false), Localizable(false), Category("Behavior"), Description("Whether or not to show file junctions.")]
		public bool ShowFileJunctions { get; set; }

		/// <summary>Gets or sets whether or not to show an edit box for the folder path.</summary>
		[DefaultValue(false), Localizable(false), Category("Behavior"), Description("Whether or not to show an edit box for the folder.")]
		public bool ShowFolderPathEditBox { get; set; }

		/// <summary>Gets or sets whether or not to show the new folder button.</summary>
		[DefaultValue(false), Localizable(false), Category("Behavior"), Description("Whether or not to show the new folder button.")]
		public bool ShowNewFolderButton { get; set; }

		/// <summary>When overridden in a derived class, resets the properties of a common dialog box to their default values.</summary>
		public override void Reset()
		{
			BrowseOption = FolderBrowserDialogOptions.Folders;
			Caption = Description = OkText = SelectedItem = string.Empty;
			Expanded = true;
			RootFolder = defaultFolderFolder;
			HideDomainFolders = ShowFileJunctions = LocalFileSystemOnly = ShowFolderPathEditBox = ShowNewFolderButton = initialized = false;
			SelectedItemPIDL = null;
		}

		/// <summary>Shows the dialog box to let the user browse for and select a folder.</summary>
		/// <param name="parentWindowHandle">The HWND of the parent window.</param>
		/// <returns>The selected folder or <c>null</c> if no folder was selected by the user.</returns>
		protected override bool RunDialog(IntPtr parentWindowHandle)
		{
			// Setup BROWSEINFO.dwFlag value
			EnumFlagIndexer<BrowseInfoFlag> browseInfoFlag = BrowseInfoFlag.BIF_SHAREABLE;
			browseInfoFlag[BrowseInfoFlag.BIF_NEWDIALOGSTYLE] = Application.OleRequired() == ApartmentState.STA;
			browseInfoFlag[BrowseInfoFlag.BIF_DONTGOBELOWDOMAIN] = HideDomainFolders;
			browseInfoFlag[BrowseInfoFlag.BIF_BROWSEFILEJUNCTIONS] = ShowFileJunctions;
			browseInfoFlag[BrowseInfoFlag.BIF_RETURNONLYFSDIRS] = LocalFileSystemOnly;
			browseInfoFlag[BrowseInfoFlag.BIF_NONEWFOLDERBUTTON] = !ShowNewFolderButton;
			browseInfoFlag[BrowseInfoFlag.BIF_EDITBOX | BrowseInfoFlag.BIF_VALIDATE] = ShowFolderPathEditBox;
			switch (BrowseOption)
			{
				case FolderBrowserDialogOptions.Folders:
					break;
				case FolderBrowserDialogOptions.FoldersAndFiles:
					browseInfoFlag |= BrowseInfoFlag.BIF_BROWSEINCLUDEFILES;
					break;
				case FolderBrowserDialogOptions.Computers:
					browseInfoFlag |= BrowseInfoFlag.BIF_BROWSEFORCOMPUTER;
					RootFolder = defaultComputersFolder;
					break;
				case FolderBrowserDialogOptions.Printers:
					browseInfoFlag |= BrowseInfoFlag.BIF_BROWSEFORPRINTER;
					RootFolder = defaultPrintersFolder;
					break;
				default:
					throw new ArgumentOutOfRangeException();
			}

			// Setup the BROWSEINFO structure
			var dn = new SafeCoTaskMemString(MAX_PATH);
			var bi = new BROWSEINFO(parentWindowHandle, rootPidl.DangerousGetHandle(), Description, browseInfoFlag, OnBrowseEvent, dn);

			// Show the dialog
			SelectedItemPIDL = SHBrowseForFolder(bi);
			if (SelectedItemPIDL.IsInvalid) return false;
			if (browseInfoFlag[BrowseInfoFlag.BIF_BROWSEFORPRINTER] || browseInfoFlag[BrowseInfoFlag.BIF_BROWSEFORCOMPUTER])
				SelectedItem = bi.DisplayName;
			else
				SelectedItem = GetNameForPidl(SelectedItemPIDL);
			return true;
		}

		/// <summary>Enables or disables the OK button in the dialog box.</summary>
		/// <param name="hwnd">The hwnd of the dialog box.</param>
		/// <param name="isEnabled">Whether or not the OK button should be enabled.</param>
		private static void EnableOk(HWND hwnd, bool isEnabled) => SendMessage(hwnd, (uint)BrowseForFolderMessages.BFFM_ENABLEOK, (IntPtr)0, (IntPtr)(isEnabled ? 1 : 0));

		private static string GetNameForPidl(PIDL pidl)
		{
			try { SHGetNameFromIDList(pidl, SIGDN.SIGDN_FILESYSPATH, out var mStr); return mStr; } catch { }
			try { SHGetNameFromIDList(pidl, SIGDN.SIGDN_DESKTOPABSOLUTEEDITING, out var mStr); return mStr; } catch { }
			try { SHGetNameFromIDList(pidl, SIGDN.SIGDN_NORMALDISPLAY, out var mStr); return mStr; } catch { }
			return string.Empty;
		}

		private static Icon GetSystemImageListIcon(int idx)
		{
			// Test first for overridden icon images
			var regVal = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Icons", idx.ToString(), null);
			if (regVal != null)
				return ResourceFile.GetResourceIcon(regVal.ToString());

			// Get the image from the system image list
			try
			{
				return IconExtension.GetSystemIcon(System.IO.Path.GetPathRoot(Environment.SystemDirectory), IconSize.Small);
			}
			catch
			{
				return null;
			}
		}

		/// <summary>Callback for Windows.</summary>
		/// <param name="hwnd">Window handle of the browse dialog box.</param>
		/// <param name="uMsg">Dialog box event that generated the statusMessage.</param>
		/// <param name="lParam">Value whose meaning depends on the event specified in uMsg.</param>
		/// <param name="lpData">Application-defined value that was specified in the lParam member of the BROWSEINFO structure used in the call to SHBrowseForFolder.</param>
		/// <returns>
		/// Returns 0 except in the case of BFFM_VALIDATEFAILED. For that flag, returns 0 to dismiss the dialog or nonzero to keep the dialog displayed.
		/// </returns>
		//[CLSCompliant(false)]
		private int OnBrowseEvent(HWND hwnd, BrowseForFolderMessages uMsg, IntPtr lParam, IntPtr lpData)
		{
			var messsage = uMsg;
			switch (messsage)
			{
				case BrowseForFolderMessages.BFFM_INITIALIZED:
					// Dialog is being initialized, so set the initial parameters
					if (!string.IsNullOrEmpty(Caption))
						SetWindowText(hwnd, Caption);

					if (!string.IsNullOrEmpty(SelectedItem))
						SendMessage(hwnd, (uint)BrowseForFolderMessages.BFFM_SETSELECTIONW, (IntPtr)1, SelectedItem);

					if (Expanded)
						SendMessage(hwnd, (uint)BrowseForFolderMessages.BFFM_SETEXPANDED, (IntPtr)1, rootPidl.DangerousGetHandle());

					if (!string.IsNullOrEmpty(OkText))
						SendMessage(hwnd, (uint)BrowseForFolderMessages.BFFM_SETOKTEXT, (IntPtr)0, OkText);

					Initialized?.Invoke(this, new FolderBrowserDialogInitializedEventArgs(hwnd));
					initialized = true;
					return 0;

				case BrowseForFolderMessages.BFFM_SELCHANGED:
					try
					{
						if (!initialized || SelectedItemPIDL?.DangerousGetHandle() == lParam) return 0;
						var tmpPidl = new PIDL(lParam, false, false);
						var str = GetNameForPidl(tmpPidl);
						if (string.IsNullOrEmpty(str))
							return 0;
						SelectedItem = str;
						SelectedItemPIDL = tmpPidl;
					}
					catch
					{
						return 0;
					}

					SelectedItemChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItem)));
					return 0;

				case BrowseForFolderMessages.BFFM_VALIDATEFAILEDA:
				case BrowseForFolderMessages.BFFM_VALIDATEFAILEDW:
					if (InvalidFolderSelected != null)
					{
						var folderName = messsage == BrowseForFolderMessages.BFFM_VALIDATEFAILEDA ? Marshal.PtrToStringAnsi(lParam) : Marshal.PtrToStringUni(lParam);
						var e = new InvalidFolderEventArgs(folderName, true);
						InvalidFolderSelected?.Invoke(this, e);
						return e.DismissDialog ? 0 : 1;
					}
					return 0;

				default:
					return 0;
			}
		}

		private void ResetRootFolder()
		{
			switch (BrowseOption)
			{
				case FolderBrowserDialogOptions.Folders:
				case FolderBrowserDialogOptions.FoldersAndFiles:
					RootFolder = defaultFolderFolder;
					break;
				case FolderBrowserDialogOptions.Computers:
					RootFolder = defaultComputersFolder;
					break;
				case FolderBrowserDialogOptions.Printers:
					RootFolder = defaultPrintersFolder;
					break;
				default:
					throw new ArgumentOutOfRangeException();
			}
		}

		private bool ShouldSerializeRootFolder()
		{
			switch (BrowseOption)
			{
				case FolderBrowserDialogOptions.Folders:
				case FolderBrowserDialogOptions.FoldersAndFiles:
					return RootFolder != KnownFolder.ComputerFolder;
				case FolderBrowserDialogOptions.Computers:
					return RootFolder != KnownFolder.NetworkFolder;
				case FolderBrowserDialogOptions.Printers:
					return RootFolder != KnownFolder.PrintersFolder;
				default:
					throw new ArgumentOutOfRangeException();
			}
		}
	}

	/// <summary>Event arguments for when the <see cref="FolderBrowserDialog"/> has been initialized.</summary>
	public class FolderBrowserDialogInitializedEventArgs : EventArgs
	{
		/// <summary>The HWND of the dialog box.</summary>
		public readonly HWND hwnd;

		/// <summary>Initializes a new instance of the <see cref="FolderBrowserDialogInitializedEventArgs"/> class.</summary>
		/// <param name="hwnd">The HWND of the dialog box.</param>
		public FolderBrowserDialogInitializedEventArgs(HWND hwnd) { this.hwnd = hwnd; }
	}

	/// <summary>Event arguments for when an invalid folder is selected.</summary>
	public class InvalidFolderEventArgs : EventArgs
	{
		/// <summary>Constructs an instance.</summary>
		/// <param name="folderName">The name of the invalid folder.</param>
		/// <param name="dismissDialog">Whether or not to dismiss the dialog.</param>
		public InvalidFolderEventArgs(string folderName, bool dismissDialog)
		{
			FolderName = folderName;
			DismissDialog = dismissDialog;
		}

		/// <summary>Gets or sets whether or not to dismiss the dialog box.</summary>
		public bool DismissDialog { get; set; }

		/// <summary>Gets the name of the invalid folder.</summary>
		public string FolderName { get; }
	}
}