//go:build windows

package platform

import (
	"fmt"
	"strconv"
	"strings"
	"sync"
	"unsafe"

	"github.com/stevec/fancywin/internal/config"
	"github.com/stevec/fancywin/internal/layout"
	"golang.org/x/sys/windows"
)

const (
	wmPaint           = 0x000F
	wmEraseBackground = 0x0014
	wmNcHitTest       = 0x0084
	wsPopup           = 0x80000000
	wsExTopmost       = 0x00000008
	wsExTransparent   = 0x00000020
	wsExToolWindow    = 0x00000080
	wsExLayered       = 0x00080000
	wsExNoActivate    = 0x08000000
	lwaColorKey       = 0x00000001
	lwaAlpha          = 0x00000002
	hwndTopmost       = ^uintptr(0)
	swpShowWindow     = 0x0040
	psSolid           = 0
	nullBrush         = 5
	bkModeTransparent = 1
	dtCenter          = 0x00000001
	dtVCenter         = 0x00000004
	dtSingleLine      = 0x00000020
	dtEndEllipsis     = 0x00008000
	fontWeightBold    = 700
	nonAntialiased    = 3
	overlayColorKey   = 0x00010001 // RGB(1,0,1), made fully transparent.
)

var (
	gdi32                          = windows.NewLazySystemDLL("gdi32.dll")
	procRegisterClassEx            = user32.NewProc("RegisterClassExW")
	procCreateWindowEx             = user32.NewProc("CreateWindowExW")
	procDestroyWindow              = user32.NewProc("DestroyWindow")
	procDefWindowProc              = user32.NewProc("DefWindowProcW")
	procBeginPaint                 = user32.NewProc("BeginPaint")
	procEndPaint                   = user32.NewProc("EndPaint")
	procGetClientRect              = user32.NewProc("GetClientRect")
	procFillRect                   = user32.NewProc("FillRect")
	procDrawText                   = user32.NewProc("DrawTextW")
	procInvalidateRect             = user32.NewProc("InvalidateRect")
	procUpdateWindow               = user32.NewProc("UpdateWindow")
	procSetLayeredWindowAttributes = user32.NewProc("SetLayeredWindowAttributes")
	procGetModuleHandle            = kernel32.NewProc("GetModuleHandleW")
	procCreateSolidBrush           = gdi32.NewProc("CreateSolidBrush")
	procCreatePen                  = gdi32.NewProc("CreatePen")
	procSelectObject               = gdi32.NewProc("SelectObject")
	procDeleteObject               = gdi32.NewProc("DeleteObject")
	procRectangle                  = gdi32.NewProc("Rectangle")
	procSetBkMode                  = gdi32.NewProc("SetBkMode")
	procSetTextColor               = gdi32.NewProc("SetTextColor")
	procCreateFont                 = gdi32.NewProc("CreateFontW")
	procGetStockObject             = gdi32.NewProc("GetStockObject")
	overlayClassName, _            = windows.UTF16PtrFromString("FancyWinZoneOverlay")
	overlayWindowProc              = windows.NewCallback(overlayWndProc)
	overlayViews                   = struct {
		sync.RWMutex
		items map[windows.Handle]*overlayView
	}{items: make(map[windows.Handle]*overlayView)}
)

type windowClassEx struct {
	Size       uint32
	Style      uint32
	WndProc    uintptr
	ClsExtra   int32
	WndExtra   int32
	Instance   windows.Handle
	Icon       windows.Handle
	Cursor     windows.Handle
	Background windows.Handle
	MenuName   *uint16
	ClassName  *uint16
	IconSmall  windows.Handle
}

type paintStruct struct {
	DC        windows.Handle
	Erase     int32
	Paint     rect
	Restore   int32
	IncUpdate int32
	Reserved  [32]byte
}

type overlayView struct {
	hwnd  windows.Handle
	work  layout.Rect
	zones []config.Zone
	gap   int
	style config.Overlay
}

type overlayManager struct {
	classReady bool
	hwnd       windows.Handle
	monitor    windows.Handle
	signature  string
}

func (m *overlayManager) show(mon windows.Handle, work layout.Rect, zones []config.Zone, gap int, style config.Overlay) error {
	signature := overlaySignature(mon, work, zones, gap, style)
	if m.hwnd != 0 && m.signature == signature {
		return nil
	}
	m.hide()
	if err := m.ensureClass(); err != nil {
		return err
	}

	instance, _, instanceErr := procGetModuleHandle.Call(0)
	if instance == 0 {
		return fmt.Errorf("GetModuleHandleW: %v", instanceErr)
	}
	w, h := work.Right-work.Left, work.Bottom-work.Top
	raw, _, createErr := procCreateWindowEx.Call(
		wsExTopmost|wsExTransparent|wsExToolWindow|wsExLayered|wsExNoActivate,
		uintptr(unsafe.Pointer(overlayClassName)),
		uintptr(unsafe.Pointer(overlayClassName)),
		wsPopup,
		uintptr(work.Left), uintptr(work.Top), uintptr(w), uintptr(h),
		0, 0, instance, 0,
	)
	if raw == 0 {
		return fmt.Errorf("CreateWindowExW: %v", createErr)
	}
	hwnd := windows.Handle(raw)
	view := &overlayView{hwnd: hwnd, work: work, zones: append([]config.Zone(nil), zones...), gap: gap, style: style}
	overlayViews.Lock()
	overlayViews.items[hwnd] = view
	overlayViews.Unlock()

	alpha := byte((style.Opacity*255 + 50) / 100)
	if ok, _, err := procSetLayeredWindowAttributes.Call(raw, overlayColorKey, uintptr(alpha), lwaColorKey|lwaAlpha); ok == 0 {
		m.destroy(hwnd)
		return fmt.Errorf("SetLayeredWindowAttributes: %v", err)
	}
	procInvalidateRect.Call(raw, 0, 1)
	procUpdateWindow.Call(raw)
	if ok, _, err := procSetWindowPos.Call(raw, hwndTopmost, uintptr(work.Left), uintptr(work.Top), uintptr(w), uintptr(h), swpNoActivate|swpNoOwnerZOrder|swpShowWindow); ok == 0 {
		m.destroy(hwnd)
		return fmt.Errorf("show overlay: %v", err)
	}
	m.hwnd = hwnd
	m.monitor = mon
	m.signature = signature
	return nil
}

func (m *overlayManager) hide() {
	if m.hwnd != 0 {
		m.destroy(m.hwnd)
	}
	m.hwnd = 0
	m.monitor = 0
	m.signature = ""
}

func (m *overlayManager) destroy(hwnd windows.Handle) {
	overlayViews.Lock()
	delete(overlayViews.items, hwnd)
	overlayViews.Unlock()
	procDestroyWindow.Call(uintptr(hwnd))
}

func (m *overlayManager) ensureClass() error {
	if m.classReady {
		return nil
	}
	instance, _, instanceErr := procGetModuleHandle.Call(0)
	if instance == 0 {
		return fmt.Errorf("GetModuleHandleW: %v", instanceErr)
	}
	wc := windowClassEx{
		Size:      uint32(unsafe.Sizeof(windowClassEx{})),
		WndProc:   overlayWindowProc,
		Instance:  windows.Handle(instance),
		ClassName: overlayClassName,
	}
	if atom, _, err := procRegisterClassEx.Call(uintptr(unsafe.Pointer(&wc))); atom == 0 {
		return fmt.Errorf("RegisterClassExW: %v", err)
	}
	m.classReady = true
	return nil
}

func overlayWndProc(rawHWND, message, wParam, lParam uintptr) uintptr {
	hwnd := windows.Handle(rawHWND)
	switch message {
	case wmNcHitTest:
		return ^uintptr(0) // HTTRANSPARENT: always pass pointer input through.
	case wmEraseBackground:
		return 1
	case wmPaint:
		overlayViews.RLock()
		view := overlayViews.items[hwnd]
		overlayViews.RUnlock()
		if view != nil {
			view.paint()
			return 0
		}
	}
	r, _, _ := procDefWindowProc.Call(rawHWND, message, wParam, lParam)
	return r
}

func (v *overlayView) paint() {
	var ps paintStruct
	dc, _, _ := procBeginPaint.Call(uintptr(v.hwnd), uintptr(unsafe.Pointer(&ps)))
	if dc == 0 {
		return
	}
	defer procEndPaint.Call(uintptr(v.hwnd), uintptr(unsafe.Pointer(&ps)))

	var client rect
	procGetClientRect.Call(uintptr(v.hwnd), uintptr(unsafe.Pointer(&client)))
	background, _, _ := procCreateSolidBrush.Call(overlayColorKey)
	if background != 0 {
		procFillRect.Call(dc, uintptr(unsafe.Pointer(&client)), background)
		procDeleteObject.Call(background)
	}

	color := colorRef(v.style.Color)
	pen, _, _ := procCreatePen.Call(psSolid, uintptr(v.style.BorderWidth), uintptr(color))
	nullFill, _, _ := procGetStockObject.Call(nullBrush)
	oldPen, _, _ := procSelectObject.Call(dc, pen)
	oldBrush, _, _ := procSelectObject.Call(dc, nullFill)
	defer func() {
		procSelectObject.Call(dc, oldPen)
		procSelectObject.Call(dc, oldBrush)
		if pen != 0 {
			procDeleteObject.Call(pen)
		}
	}()

	fontName, _ := windows.UTF16PtrFromString("Segoe UI")
	font, _, _ := procCreateFont.Call(^uintptr(55), 0, 0, 0, fontWeightBold, 0, 0, 0, 1, 0, 0, nonAntialiased, 0, uintptr(unsafe.Pointer(fontName)))
	oldFont := uintptr(0)
	if font != 0 {
		oldFont, _, _ = procSelectObject.Call(dc, font)
		defer func() { procSelectObject.Call(dc, oldFont); procDeleteObject.Call(font) }()
	}
	procSetBkMode.Call(dc, bkModeTransparent)

	for i, zone := range v.zones {
		r := layout.Resolve(v.work, zone, v.gap)
		r.Left -= v.work.Left
		r.Right -= v.work.Left
		r.Top -= v.work.Top
		r.Bottom -= v.work.Top
		procRectangle.Call(dc, uintptr(r.Left), uintptr(r.Top), uintptr(r.Right), uintptr(r.Bottom))
		name := strings.TrimSpace(zone.Name)
		if name == "" {
			name = fmt.Sprintf("Zone %d", i+1)
		}
		text, err := windows.UTF16FromString(name)
		if err != nil || len(text) == 0 {
			continue
		}
		shadow := rect{Left: r.Left + 2, Top: r.Top + 2, Right: r.Right + 2, Bottom: r.Bottom + 2}
		procSetTextColor.Call(dc, 0x00000000)
		procDrawText.Call(dc, uintptr(unsafe.Pointer(&text[0])), ^uintptr(0), uintptr(unsafe.Pointer(&shadow)), dtCenter|dtVCenter|dtSingleLine|dtEndEllipsis)
		label := rect{Left: r.Left, Top: r.Top, Right: r.Right, Bottom: r.Bottom}
		procSetTextColor.Call(dc, uintptr(color))
		procDrawText.Call(dc, uintptr(unsafe.Pointer(&text[0])), ^uintptr(0), uintptr(unsafe.Pointer(&label)), dtCenter|dtVCenter|dtSingleLine|dtEndEllipsis)
	}
}

func colorRef(value string) uint32 {
	rgb, err := strconv.ParseUint(strings.TrimPrefix(value, "#"), 16, 24)
	if err != nil {
		return 0x00EFAE00
	}
	r := uint32(rgb >> 16)
	g := uint32((rgb >> 8) & 0xff)
	b := uint32(rgb & 0xff)
	return r | g<<8 | b<<16
}

func overlaySignature(mon windows.Handle, work layout.Rect, zones []config.Zone, gap int, style config.Overlay) string {
	var b strings.Builder
	fmt.Fprintf(&b, "%x|%v|%d|%v", uintptr(mon), work, gap, style)
	for _, zone := range zones {
		fmt.Fprintf(&b, "|%s:%g:%g:%g:%g", zone.Name, zone.X, zone.Y, zone.Width, zone.Height)
	}
	return b.String()
}
