//go:build windows

package main

import (
	"fmt"
	"runtime"
	"syscall"
	"unsafe"
)

var (
	kernel32                  = syscall.NewLazyDLL("kernel32.dll")
	procGetLogicalDrives      = kernel32.NewProc("GetLogicalDrives")
	procGetDriveTypeW         = kernel32.NewProc("GetDriveTypeW")
	procGetVolumeInformationW = kernel32.NewProc("GetVolumeInformationW")
	procGetModuleHandleW      = kernel32.NewProc("GetModuleHandleW")

	user32                = syscall.NewLazyDLL("user32.dll")
	procRegisterClassExW  = user32.NewProc("RegisterClassExW")
	procCreateWindowExW   = user32.NewProc("CreateWindowExW")
	procDefWindowProcW    = user32.NewProc("DefWindowProcW")
	procGetMessageW       = user32.NewProc("GetMessageW")
	procTranslateMessage  = user32.NewProc("TranslateMessage")
	procDispatchMessageW  = user32.NewProc("DispatchMessageW")
	procRegisterDevNotify = user32.NewProc("RegisterDeviceNotificationW")
)

const (
	driveRemovable = 2 // GetDriveTypeW: cikarilabilir surucu (USB bellek, SD kart)

	wmDeviceChange          = 0x0219
	dbtDeviceArrival        = 0x8000
	dbtDeviceRemoveComplete = 0x8004
	dbtDevtypDeviceInterface = 0x00000005
	deviceNotifyWindowHandle = 0x00000000
)

// GUID_DEVINTERFACE_VOLUME: birim (surucu) arayuzu. Takma ve cikarmalar bu arayuzden bildirilir.
var guidDevInterfaceVolume = guid{0x53f5630d, 0xb6bf, 0x11d0, [8]byte{0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b}}

type guid struct {
	data1 uint32
	data2 uint16
	data3 uint16
	data4 [8]byte
}

// devBroadcastDeviceInterface: RegisterDeviceNotificationW filtresi (DEV_BROADCAST_DEVICEINTERFACE_W).
type devBroadcastDeviceInterface struct {
	dbccSize       uint32
	dbccDevicetype uint32
	dbccReserved   uint32
	dbccClassguid  guid
	dbccName       [1]uint16
}

type wndClassEx struct {
	cbSize        uint32
	style         uint32
	lpfnWndProc   uintptr
	cbClsExtra    int32
	cbWndExtra    int32
	hInstance     uintptr
	hIcon         uintptr
	hCursor       uintptr
	hbrBackground uintptr
	lpszMenuName  *uint16
	lpszClassName *uint16
	hIconSm       uintptr
}

type point struct{ x, y int32 }

type msg struct {
	hwnd    uintptr
	message uint32
	wParam  uintptr
	lParam  uintptr
	time    uint32
	pt      point
}

// olayGeriCagirim: takma ya da cikarma bildirimi geldiginde calisir (mesaj dongusu thread'inde).
var olayGeriCagirim func()

var wndProc = syscall.NewCallback(func(hwnd, mesaj, wParam, lParam uintptr) uintptr {
	if uint32(mesaj) == wmDeviceChange && (wParam == dbtDeviceArrival || wParam == dbtDeviceRemoveComplete) {
		if olayGeriCagirim != nil {
			olayGeriCagirim()
		}
		return 1
	}
	r, _, _ := procDefWindowProcW.Call(hwnd, mesaj, wParam, lParam)
	return r
})

// dinleyiciBaslat: gizli bir (normal, ekrana cizilmeyen) pencere acip birim arayuzu bildirimlerine abone olur.
// Message-only pencereler sistem yayinlarini (WM_DEVICECHANGE) almadigi icin normal pencere kullanilir.
func dinleyiciBaslat(geriCagirim func()) error {
	olayGeriCagirim = geriCagirim
	hazir := make(chan error, 1)
	go func() {
		runtime.LockOSThread()
		hInstance, _, _ := procGetModuleHandleW.Call(0)
		sinif, err := syscall.UTF16PtrFromString("fikoytUsbDinleyici")
		if err != nil {
			hazir <- err
			return
		}
		wc := wndClassEx{
			cbSize:        uint32(unsafe.Sizeof(wndClassEx{})),
			lpfnWndProc:   wndProc,
			hInstance:     hInstance,
			lpszClassName: sinif,
		}
		if r, _, _ := procRegisterClassExW.Call(uintptr(unsafe.Pointer(&wc))); r == 0 {
			hazir <- fmt.Errorf("pencere sınıfı kaydedilemedi")
			return
		}
		// Normal, gorunmez pencere: ust pencere yok, gosterilmiyor (WS_POPUP olmadan, boyut 0).
		hwnd, _, _ := procCreateWindowExW.Call(0, uintptr(unsafe.Pointer(sinif)), 0, 0,
			0, 0, 0, 0, 0, 0, hInstance, 0)
		if hwnd == 0 {
			hazir <- fmt.Errorf("gizli pencere açılamadı")
			return
		}
		filtre := devBroadcastDeviceInterface{
			dbccDevicetype: dbtDevtypDeviceInterface,
			dbccClassguid:  guidDevInterfaceVolume,
		}
		filtre.dbccSize = uint32(unsafe.Sizeof(filtre))
		if r, _, _ := procRegisterDevNotify.Call(hwnd, uintptr(unsafe.Pointer(&filtre)), deviceNotifyWindowHandle); r == 0 {
			hazir <- fmt.Errorf("birim bildirimine abone olunamadı")
			return
		}
		hazir <- nil
		var m msg
		for {
			r, _, _ := procGetMessageW.Call(uintptr(unsafe.Pointer(&m)), 0, 0, 0)
			if int32(r) <= 0 {
				return
			}
			procTranslateMessage.Call(uintptr(unsafe.Pointer(&m)))
			procDispatchMessageW.Call(uintptr(unsafe.Pointer(&m)))
		}
	}()
	return <-hazir
}

// taraSuruculer: bagli cikarilabilir suruculeri (harf ve etiket) doner.
func taraSuruculer() ([]Surucu, error) {
	maske, _, _ := procGetLogicalDrives.Call()
	if maske == 0 {
		return nil, fmt.Errorf("sürücü listesi alınamadı")
	}
	var liste []Surucu
	for i := 0; i < 26; i++ {
		if maske&(1<<uint(i)) == 0 {
			continue
		}
		harf := string(rune('A' + i))
		kok, err := syscall.UTF16PtrFromString(harf + ":\\")
		if err != nil {
			continue
		}
		tip, _, _ := procGetDriveTypeW.Call(uintptr(unsafe.Pointer(kok)))
		if tip != driveRemovable {
			continue
		}
		liste = append(liste, Surucu{Harf: harf, Etiket: surucuEtiketi(kok)})
	}
	return liste, nil
}

// surucuEtiketi: birim etiketi. Ortam yoksa (ornegin bos kart okuyucu) bos doner.
func surucuEtiketi(kok *uint16) string {
	var ad [261]uint16
	r, _, _ := procGetVolumeInformationW.Call(
		uintptr(unsafe.Pointer(kok)),
		uintptr(unsafe.Pointer(&ad[0])), uintptr(len(ad)),
		0, 0, 0, 0, 0,
	)
	if r == 0 {
		return ""
	}
	return syscall.UTF16ToString(ad[:])
}
