//go:build windows
// +build windows

package cfd

import (
	"github.com/go-ole/go-ole"
	"github.com/google/uuid"
	"syscall"
	"unsafe"
)

var (
	fileOpenDialogCLSID = ole.NewGUID("{DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7}")
	fileOpenDialogIID   = ole.NewGUID("{d57c7288-d4ad-4768-be02-9d969532d960}")
)

type iFileOpenDialog struct {
	vtbl               *iFileOpenDialogVtbl
	parentWindowHandle uintptr
}

type iFileOpenDialogVtbl struct {
	iFileDialogVtbl

	GetResults       uintptr // func (ppenum **IShellItemArray) HRESULT
	GetSelectedItems uintptr
}

func newIFileOpenDialog() (*iFileOpenDialog, error) {
	if unknown, err := ole.CreateInstance(fileOpenDialogCLSID, fileOpenDialogIID); err == nil {
		return (*iFileOpenDialog)(unsafe.Pointer(unknown)), nil
	} else {
		return nil, err
	}
}

func (fileOpenDialog *iFileOpenDialog) Show() error {
	return fileOpenDialog.vtbl.show(unsafe.Pointer(fileOpenDialog), fileOpenDialog.parentWindowHandle)
}

func (fileOpenDialog *iFileOpenDialog) SetParentWindowHandle(hwnd uintptr) {
	fileOpenDialog.parentWindowHandle = hwnd
}

func (fileOpenDialog *iFileOpenDialog) ShowAndGetResult() (string, error) {
	isMultiselect, err := fileOpenDialog.isMultiselect()
	if err != nil {
		return "", err
	}
	if isMultiselect {
		// We should panic as this error is caused by the developer using the library
		panic("use ShowAndGetResults for open multiple files dialog")
	}
	if err := fileOpenDialog.Show(); err != nil {
		return "", err
	}
	return fileOpenDialog.GetResult()
}

func (fileOpenDialog *iFileOpenDialog) ShowAndGetResults() ([]string, error) {
	isMultiselect, err := fileOpenDialog.isMultiselect()
	if err != nil {
		return nil, err
	}
	if !isMultiselect {
		// We should panic as this error is caused by the developer using the library
		panic("use ShowAndGetResult for open single file dialog")
	}
	if err := fileOpenDialog.Show(); err != nil {
		return nil, err
	}
	return fileOpenDialog.GetResults()
}

func (fileOpenDialog *iFileOpenDialog) SetTitle(title string) error {
	return fileOpenDialog.vtbl.setTitle(unsafe.Pointer(fileOpenDialog), title)
}

func (fileOpenDialog *iFileOpenDialog) GetResult() (string, error) {
	isMultiselect, err := fileOpenDialog.isMultiselect()
	if err != nil {
		return "", err
	}
	if isMultiselect {
		// We should panic as this error is caused by the developer using the library
		panic("use GetResults for open multiple files dialog")
	}
	return fileOpenDialog.vtbl.getResultString(unsafe.Pointer(fileOpenDialog))
}

func (fileOpenDialog *iFileOpenDialog) Release() error {
	return fileOpenDialog.vtbl.release(unsafe.Pointer(fileOpenDialog))
}

func (fileOpenDialog *iFileOpenDialog) SetDefaultFolder(defaultFolderPath string) error {
	return fileOpenDialog.vtbl.setDefaultFolder(unsafe.Pointer(fileOpenDialog), defaultFolderPath)
}

func (fileOpenDialog *iFileOpenDialog) SetFolder(defaultFolderPath string) error {
	return fileOpenDialog.vtbl.setFolder(unsafe.Pointer(fileOpenDialog), defaultFolderPath)
}

func (fileOpenDialog *iFileOpenDialog) SetFileFilters(filter []FileFilter) error {
	return fileOpenDialog.vtbl.setFileTypes(unsafe.Pointer(fileOpenDialog), filter)
}

func (fileOpenDialog *iFileOpenDialog) SetRole(role string) error {
	return fileOpenDialog.vtbl.setClientGuid(unsafe.Pointer(fileOpenDialog), StringToUUID(role))
}

// This should only be callable when the user asks for a multi select because
// otherwise they will be given the Dialog interface which does not expose this function.
func (fileOpenDialog *iFileOpenDialog) GetResults() ([]string, error) {
	isMultiselect, err := fileOpenDialog.isMultiselect()
	if err != nil {
		return nil, err
	}
	if !isMultiselect {
		// We should panic as this error is caused by the developer using the library
		panic("use GetResult for open single file dialog")
	}
	return fileOpenDialog.vtbl.getResultsStrings(unsafe.Pointer(fileOpenDialog))
}

func (fileOpenDialog *iFileOpenDialog) SetDefaultExtension(defaultExtension string) error {
	return fileOpenDialog.vtbl.setDefaultExtension(unsafe.Pointer(fileOpenDialog), defaultExtension)
}

func (fileOpenDialog *iFileOpenDialog) SetFileName(initialFileName string) error {
	return fileOpenDialog.vtbl.setFileName(unsafe.Pointer(fileOpenDialog), initialFileName)
}

func (fileOpenDialog *iFileOpenDialog) SetSelectedFileFilterIndex(index uint) error {
	return fileOpenDialog.vtbl.setSelectedFileFilterIndex(unsafe.Pointer(fileOpenDialog), index)
}

func (fileOpenDialog *iFileOpenDialog) setPickFolders(pickFolders bool) error {
	const FosPickfolders = 0x20
	if pickFolders {
		return fileOpenDialog.vtbl.addOption(unsafe.Pointer(fileOpenDialog), FosPickfolders)
	} else {
		return fileOpenDialog.vtbl.removeOption(unsafe.Pointer(fileOpenDialog), FosPickfolders)
	}
}

const FosAllowMultiselect = 0x200

func (fileOpenDialog *iFileOpenDialog) isMultiselect() (bool, error) {
	options, err := fileOpenDialog.vtbl.getOptions(unsafe.Pointer(fileOpenDialog))
	if err != nil {
		return false, err
	}
	return options&FosAllowMultiselect != 0, nil
}

func (fileOpenDialog *iFileOpenDialog) setIsMultiselect(isMultiselect bool) error {
	if isMultiselect {
		return fileOpenDialog.vtbl.addOption(unsafe.Pointer(fileOpenDialog), FosAllowMultiselect)
	} else {
		return fileOpenDialog.vtbl.removeOption(unsafe.Pointer(fileOpenDialog), FosAllowMultiselect)
	}
}

func (vtbl *iFileOpenDialogVtbl) getResults(objPtr unsafe.Pointer) (*iShellItemArray, error) {
	var shellItemArray *iShellItemArray
	ret, _, _ := syscall.SyscallN(vtbl.GetResults,
		uintptr(objPtr),
		uintptr(unsafe.Pointer(&shellItemArray)),
		0)
	return shellItemArray, hresultToError(ret)
}

func (vtbl *iFileOpenDialogVtbl) getResultsStrings(objPtr unsafe.Pointer) ([]string, error) {
	shellItemArray, err := vtbl.getResults(objPtr)
	if err != nil {
		return nil, err
	}
	if shellItemArray == nil {
		return nil, ErrCancelled
	}
	defer shellItemArray.vtbl.release(unsafe.Pointer(shellItemArray))
	count, err := shellItemArray.vtbl.getCount(unsafe.Pointer(shellItemArray))
	if err != nil {
		return nil, err
	}
	var results []string
	for i := uintptr(0); i < count; i++ {
		newItem, err := shellItemArray.vtbl.getItemAt(unsafe.Pointer(shellItemArray), i)
		if err != nil {
			return nil, err
		}
		results = append(results, newItem)
	}
	return results, nil
}

func StringToUUID(str string) *ole.GUID {
	return ole.NewGUID(uuid.NewSHA1(uuid.Nil, []byte(str)).String())
}
