package cat

import (
	"fmt"
	"syscall"
	"unsafe"

	ole "github.com/go-ole/go-ole"
	"golang.org/x/sys/windows/registry"
)

// Last-resort activation path for OmniRig, used only when the normal
// CreateObject fails.
//
// OmniRig is a 32-bit program and its installer splits its COM identity across
// registry views — on a working machine the ProgID sits in the 64-bit view while
// the CLSID and its LocalServer32 exist only under WOW6432Node. That LOOKS like
// it should break a 64-bit client, and it was my first theory for "OmniRig not
// found"; measuring it on a machine with exactly that layout disproved it. COM
// resolves an out-of-process server across views by itself, and the plain
// CreateObject succeeds.
//
// What this still covers is the case where the ProgID is not visible to us at all
// (a partial or 32-bit-only registration), where CreateObject has nothing to
// resolve. Here we read the CLSID from BOTH views ourselves and activate the
// 32-bit local server explicitly. CLSCTX_ACTIVATE_32_BIT_SERVER is the documented
// flag for that; go-ole hard-codes CLSCTX_SERVER and keeps CoCreateInstance
// unexported, hence the direct call.
//
// It costs nothing when the normal path works, and the reason it ran at all is
// logged — so if it ever rescues a real installation we will see it.
const clsctxActivate32BitServer = 0x40000

var (
	modole32           = syscall.NewLazyDLL("ole32.dll")
	procCoCreateInst32 = modole32.NewProc("CoCreateInstance")
)

// omnirigCLSIDFromRegistry reads OmniRig's CLSID from the ProgID key, looking in
// the 64-bit view first and then the 32-bit one. Returned as a GUID ready for
// CoCreateInstance.
func omnirigCLSIDFromRegistry(progID string) (*ole.GUID, error) {
	for _, access := range []uint32{registry.QUERY_VALUE, registry.QUERY_VALUE | registry.WOW64_32KEY} {
		k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Classes\`+progID+`\CLSID`, access)
		if err != nil {
			continue
		}
		s, _, err := k.GetStringValue("")
		k.Close()
		if err != nil || s == "" {
			continue
		}
		if g := ole.NewGUID(s); g != nil {
			return g, nil
		}
	}
	return nil, fmt.Errorf("no CLSID registered for %s in either registry view", progID)
}

// createOmniRig32 activates OmniRig's 32-bit out-of-process server explicitly,
// bypassing the 64-bit registry lookup that CoCreateInstance would otherwise do.
func createOmniRig32(progID string) (*ole.IDispatch, error) {
	clsid, err := omnirigCLSIDFromRegistry(progID)
	if err != nil {
		return nil, err
	}
	var unk *ole.IUnknown
	hr, _, _ := procCoCreateInst32.Call(
		uintptr(unsafe.Pointer(clsid)),
		0, // no aggregation
		uintptr(ole.CLSCTX_LOCAL_SERVER|clsctxActivate32BitServer),
		uintptr(unsafe.Pointer(ole.IID_IUnknown)),
		uintptr(unsafe.Pointer(&unk)))
	if hr != 0 {
		return nil, fmt.Errorf("CoCreateInstance(32-bit local server) failed: %w", ole.NewError(hr))
	}
	disp, err := unk.QueryInterface(ole.IID_IDispatch)
	unk.Release()
	if err != nil {
		return nil, fmt.Errorf("query IDispatch: %w", err)
	}
	return disp, nil
}
