package uv

import (
	"github.com/charmbracelet/x/ansi"
)

// MouseMode represents the mouse tracking mode for the terminal.
type MouseMode uint8

// Mouse tracking modes.
//
// These determine which mouse events the terminal reports.
const (
	MouseModeNone   MouseMode = iota // Disable mouse tracking.
	MouseModePress                   // Press only (DEC mode 9). Reports button press events.
	MouseModeClick                   // Click tracking (DEC mode 1000). Reports button press and release.
	MouseModeDrag                    // Drag tracking (DEC mode 1002). Reports press, release, and drag.
	MouseModeMotion                  // Motion tracking (DEC mode 1003). Reports all mouse events including motion.
)

// MouseEncoding represents the encoding used for mouse events.
type MouseEncoding uint8

// Mouse encodings.
//
// These determine how mouse coordinates and buttons are encoded in the
// terminal's escape sequences. The encoding is only meaningful when mouse
// tracking is enabled via [MouseMode].
const (
	MouseEncodingLegacy   MouseEncoding = iota // Legacy X10-compatible encoding. Coordinates limited to 223.
	MouseEncodingSGR                           // SGR encoding (DEC mode 1006). No coordinate limit, distinguishes press/release.
	MouseEncodingSGRPixel                      // SGR-pixel encoding (DEC mode 1016). Reports pixel coordinates.

	// TODO: support these additional encodings in the future.
	// MouseEncodingUTF8                          // UTF-8 encoding (DEC mode 1005). Coordinates limited to 223.
	// MouseEncodingUrxvt                         // urxvt encoding (DEC mode 1015). No coordinate limit.
	// MouseEncodingSGRPixel                      // SGR-pixel encoding (DEC mode 1016). Reports pixel coordinates.
)

// MouseButton represents the button that was pressed during a mouse message.
type MouseButton = ansi.MouseButton

// Mouse event buttons
//
// This is based on X11 mouse button codes.
//
//	1 = left button
//	2 = middle button (pressing the scroll wheel)
//	3 = right button
//	4 = turn scroll wheel up
//	5 = turn scroll wheel down
//	6 = push scroll wheel left
//	7 = push scroll wheel right
//	8 = 4th button (aka browser backward button)
//	9 = 5th button (aka browser forward button)
//	10
//	11
//
// Other buttons are not supported.
const (
	MouseNone       = ansi.MouseNone
	MouseLeft       = ansi.MouseLeft
	MouseMiddle     = ansi.MouseMiddle
	MouseRight      = ansi.MouseRight
	MouseWheelUp    = ansi.MouseWheelUp
	MouseWheelDown  = ansi.MouseWheelDown
	MouseWheelLeft  = ansi.MouseWheelLeft
	MouseWheelRight = ansi.MouseWheelRight
	MouseBackward   = ansi.MouseBackward
	MouseForward    = ansi.MouseForward
	MouseButton10   = ansi.MouseButton10
	MouseButton11   = ansi.MouseButton11
)

// Mouse represents a Mouse message. Use [MouseEvent] to represent all mouse
// messages.
//
// The X and Y coordinates are zero-based, with (0,0) being the upper left
// corner of the terminal. When using [MouseEncodingSGRPixel] (DEC mode 1016),
// X and Y are in pixel coordinates; otherwise they are in cell coordinates.
//
//	// Catch all mouse events
//	switch Event := Event.(type) {
//	case MouseEvent:
//	    m := Event.Mouse()
//	    fmt.Println("Mouse event:", m.X, m.Y, m)
//	}
//
//	// Only catch mouse click events
//	switch Event := Event.(type) {
//	case MouseClickEvent:
//	    fmt.Println("Mouse click event:", Event.X, Event.Y, Event)
//	}
type Mouse struct {
	X, Y   int
	Button MouseButton
	Mod    KeyMod
}

// String returns a string representation of the mouse message.
func (m Mouse) String() (s string) {
	if m.Mod.Contains(ModCtrl) {
		s += "ctrl+"
	}
	if m.Mod.Contains(ModAlt) {
		s += "alt+"
	}
	if m.Mod.Contains(ModShift) {
		s += "shift+"
	}

	str := m.Button.String()
	if str == "" {
		s += "unknown"
	} else if str != "none" { // motion events don't have a button
		s += str
	}

	return s
}

// MousePixelToCell converts a mouse event with pixel coordinates to cell
// coordinates.
//
// This is only meaningful when using [MouseEncodingSGRPixel] encoding, which
// reports mouse coordinates in pixels rather than cell units. The conversion
// is based on the terminal's reported pixel dimensions and cell dimensions.
//
// On Windows, or other platforms where [Terminal.GetWinsize] doesn't report
// pixel dimensions, you can query for the terminal window pixel dimensions via
// [ansi.WindowOp](4) and get back a [PixelSizeEvent] with the pixel
// dimensions. You can then construct a [Winsize] struct with the pixel
// dimensions and the cell dimensions from [Terminal.GetWinsize] and pass it to
// this function.
func MousePixelToCell(m Mouse, ws *Winsize) Mouse {
	var col, row int
	if ws.Xpixel > 0 {
		col = m.X * int(ws.Col) / int(ws.Xpixel)
	}
	if ws.Ypixel > 0 {
		row = m.Y * int(ws.Row) / int(ws.Ypixel)
	}

	return Mouse{
		X:      col,
		Y:      row,
		Button: m.Button,
		Mod:    m.Mod,
	}
}
