//go:build windows
// +build windows

package uv

import (
	"bytes"
	"context"
	"fmt"
	"strings"
	"time"
	"unicode/utf16"

	"github.com/charmbracelet/x/ansi"
	xwindows "github.com/charmbracelet/x/windows"
	"github.com/muesli/cancelreader"
	"golang.org/x/sys/windows"
)

// streamData sends data from the input stream to the event channel.
func (d *TerminalReader) streamData(ctx context.Context, readc chan []byte) error {
	cc, ok := d.r.(*conInputReader)
	if !ok {
		d.logf("streamData: reader is not a conInputReader, falling back to default implementation")
		return d.sendBytes(ctx, readc)
	}

	// Store the value of VT Input Mode for later use.
	d.vtInput = cc.newMode&windows.ENABLE_VIRTUAL_TERMINAL_INPUT != 0

	var buf bytes.Buffer
	var records []xwindows.InputRecord
	var err error
	for {
		for {
			records, err = peekNConsoleInputs(cc.conin, readBufSize)
			if cc.isCanceled() {
				return cancelreader.ErrCanceled
			}
			if err != nil {
				return err
			}
			if len(records) > 0 {
				break
			}

			// Sleep for a bit to avoid busy waiting.
			time.Sleep(10 * time.Millisecond)
		}

		records, err = readNConsoleInputs(cc.conin, uint32(len(records))) //nolint:gosec
		if cc.isCanceled() {
			return cancelreader.ErrCanceled
		}
		if err != nil {
			return err
		}

		// We convert Windows Input Records to VT input sequences for easier
		// processing especially when dealing with UTF-16 decoding and
		// Win32-Input-Mode processing.
		d.serializeWin32InputRecords(records, &buf)

		select {
		case <-ctx.Done():
			return nil
		case readc <- buf.Bytes():
		}

		buf.Reset()
	}
}

// serializeWin32InputRecords serializes the Win32 input events converting them
// to valid VT input sequences. It will also encode any UTF-16 pairs that might
// be present in the input buffer. The resulting byte slice can be sent to the
// terminal as input.
func (d *TerminalReader) serializeWin32InputRecords(records []xwindows.InputRecord, buf *bytes.Buffer) {
	for _, record := range records {
		switch record.EventType {
		case xwindows.KEY_EVENT:
			kevent := record.KeyEvent()
			// d.logf("key event: %s", keyEventString(kevent.VirtualKeyCode, kevent.VirtualScanCode, kevent.Char, kevent.KeyDown, kevent.ControlKeyState, kevent.RepeatCount))

			var kd int
			if kevent.KeyDown {
				kd = 1
			}
			if d.vtInput { //nolint:nestif
				// In VT Input Mode, we only capture the Unicode characters
				// decoding them along the way.
				// This is similar to [TerminalReader.storeGraphemeRune] except
				// that we need to write the events directly to the buffer.
				if d.utf16Half[kd] {
					// We have a half pair that needs to be decoded.
					d.utf16Half[kd] = false
					d.utf16Buf[kd][1] = kevent.Char
					r := utf16.DecodeRune(d.utf16Buf[kd][0], d.utf16Buf[kd][1])
					buf.WriteRune(r)
				} else if utf16.IsSurrogate(kevent.Char) {
					// This is the first half of a UTF-16 surrogate pair.
					d.utf16Half[kd] = true
					d.utf16Buf[kd][0] = kevent.Char
				} else if kevent.KeyDown {
					// Just a regular key press character encoded in VT.
					buf.WriteRune(kevent.Char)
				}
			} else {
				// We encode the key to Win32 Input Mode if it is a known key.
				if kevent.VirtualKeyCode == 0 {
					d.eventScanner.storeGraphemeRune(kd, kevent.Char)
				} else {
					buf.Write(d.eventScanner.encodeGraphemeBufs())
					fmt.Fprintf(buf,
						"\x1b[%d;%d;%d;%d;%d;%d_",
						kevent.VirtualKeyCode,
						kevent.VirtualScanCode,
						kevent.Char,
						kd,
						kevent.ControlKeyState,
						kevent.RepeatCount)
				}
			}

		case xwindows.MOUSE_EVENT:
			if d.MouseMode == nil || *d.MouseMode == 0 {
				continue
			}
			mouseMode := *d.MouseMode
			mevent := record.MouseEvent()

			var isRelease bool
			var isMotion bool
			var button MouseButton
			alt := mevent.ControlKeyState&(xwindows.LEFT_ALT_PRESSED|xwindows.RIGHT_ALT_PRESSED) != 0
			ctrl := mevent.ControlKeyState&(xwindows.LEFT_CTRL_PRESSED|xwindows.RIGHT_CTRL_PRESSED) != 0
			shift := mevent.ControlKeyState&(xwindows.SHIFT_PRESSED) != 0
			wheelDirection := int16(highWord(mevent.ButtonState)) //nolint:gosec
			switch mevent.EventFlags {
			case 0, xwindows.DOUBLE_CLICK:
				button, isRelease = mouseEventButton(d.lastMouseBtns, mevent.ButtonState)
			case xwindows.MOUSE_WHEELED:
				if wheelDirection > 0 {
					button = MouseWheelUp
				} else {
					button = MouseWheelDown
				}
			case xwindows.MOUSE_HWHEELED:
				if wheelDirection > 0 {
					button = MouseWheelRight
				} else {
					button = MouseWheelLeft
				}
			case xwindows.MOUSE_MOVED:
				button, _ = mouseEventButton(d.lastMouseBtns, mevent.ButtonState)
				isMotion = true
			}

			// We emulate mouse mode levels on Windows. This is because Windows
			// doesn't have a concept of different mouse modes. We use the mouse mode to determine
			if button == MouseNone && mouseMode&MouseModeMotion == 0 ||
				(button != MouseNone && mouseMode&MouseModeDrag == 0) {
				continue
			}

			// Encode mouse events as SGR mouse sequences that can be read by [EventDecoder].
			buf.WriteString(ansi.MouseSgr(
				ansi.EncodeMouseButton(button, isMotion, shift, alt, ctrl),
				int(mevent.MousePositon.X), int(mevent.MousePositon.Y), isRelease,
			))

			d.lastMouseBtns = mevent.ButtonState

		case xwindows.WINDOW_BUFFER_SIZE_EVENT:
			wevent := record.WindowBufferSizeEvent()
			if wevent.Size.X != d.lastWinsizeX || wevent.Size.Y != d.lastWinsizeY {
				d.lastWinsizeX, d.lastWinsizeY = wevent.Size.X, wevent.Size.Y
				// We encode window resize events as CSI 4 ; height ; width t
				// sequence which the [EventDecoder] understands.
				buf.WriteString(
					ansi.WindowOp(
						8,                  // Terminal window size in cells
						int(wevent.Size.Y), // height
						int(wevent.Size.X), // width
					),
				)
			}

		case xwindows.FOCUS_EVENT:
			fevent := record.FocusEvent()
			if fevent.SetFocus {
				buf.WriteString(ansi.Focus)
			} else {
				buf.WriteString(ansi.Blur)
			}

		case xwindows.MENU_EVENT:
			// ignore
		}
	}

	// Flush any remaining grapheme buffers.
	buf.Write(d.eventScanner.encodeGraphemeBufs())
}

func mouseEventButton(p, s uint32) (MouseButton, bool) {
	var isRelease bool
	button := MouseNone
	btn := p ^ s
	if btn&s == 0 {
		isRelease = true
	}

	if btn == 0 {
		switch {
		case s&xwindows.FROM_LEFT_1ST_BUTTON_PRESSED > 0:
			button = MouseLeft
		case s&xwindows.FROM_LEFT_2ND_BUTTON_PRESSED > 0:
			button = MouseMiddle
		case s&xwindows.RIGHTMOST_BUTTON_PRESSED > 0:
			button = MouseRight
		case s&xwindows.FROM_LEFT_3RD_BUTTON_PRESSED > 0:
			button = MouseBackward
		case s&xwindows.FROM_LEFT_4TH_BUTTON_PRESSED > 0:
			button = MouseForward
		}
		return button, isRelease
	}

	switch btn {
	case xwindows.FROM_LEFT_1ST_BUTTON_PRESSED: // left button
		button = MouseLeft
	case xwindows.RIGHTMOST_BUTTON_PRESSED: // right button
		button = MouseRight
	case xwindows.FROM_LEFT_2ND_BUTTON_PRESSED: // middle button
		button = MouseMiddle
	case xwindows.FROM_LEFT_3RD_BUTTON_PRESSED: // unknown (possibly mouse backward)
		button = MouseBackward
	case xwindows.FROM_LEFT_4TH_BUTTON_PRESSED: // unknown (possibly mouse forward)
		button = MouseForward
	}

	return button, isRelease
}

func highWord(data uint32) uint16 {
	return uint16((data & 0xFFFF0000) >> 16) //nolint:gosec
}

func readNConsoleInputs(console windows.Handle, maxEvents uint32) ([]xwindows.InputRecord, error) {
	if maxEvents == 0 {
		return nil, fmt.Errorf("maxEvents cannot be zero")
	}

	records := make([]xwindows.InputRecord, maxEvents)
	n, err := readConsoleInput(console, records)
	return records[:n], err
}

func readConsoleInput(console windows.Handle, inputRecords []xwindows.InputRecord) (uint32, error) {
	if len(inputRecords) == 0 {
		return 0, fmt.Errorf("size of input record buffer cannot be zero")
	}

	var read uint32

	err := xwindows.ReadConsoleInput(console, &inputRecords[0], uint32(len(inputRecords)), &read) //nolint:gosec

	return read, err //nolint:wrapcheck
}

func peekConsoleInput(console windows.Handle, inputRecords []xwindows.InputRecord) (uint32, error) {
	if len(inputRecords) == 0 {
		return 0, fmt.Errorf("size of input record buffer cannot be zero")
	}

	var read uint32

	err := xwindows.PeekConsoleInput(console, &inputRecords[0], uint32(len(inputRecords)), &read) //nolint:gosec

	return read, err //nolint:wrapcheck
}

func peekNConsoleInputs(console windows.Handle, maxEvents uint32) ([]xwindows.InputRecord, error) {
	if maxEvents == 0 {
		return nil, fmt.Errorf("maxEvents cannot be zero")
	}

	records := make([]xwindows.InputRecord, maxEvents)
	n, err := peekConsoleInput(console, records)
	return records[:n], err
}

//nolint:unused
func keyEventString(vkc, sc uint16, r rune, keyDown bool, cks uint32, repeatCount uint16) string {
	var s strings.Builder
	s.WriteString("vkc: ")
	fmt.Fprintf(&s, "%d, 0x%02x", vkc, vkc)
	s.WriteString(", sc: ")
	fmt.Fprintf(&s, "%d, 0x%02x", sc, sc)
	s.WriteString(", r: ")
	fmt.Fprintf(&s, "%q 0x%x", r, r)
	s.WriteString(", down: ")
	fmt.Fprintf(&s, "%v", keyDown)
	s.WriteString(", cks: [")
	if cks&xwindows.LEFT_ALT_PRESSED != 0 {
		s.WriteString("left alt, ")
	}
	if cks&xwindows.RIGHT_ALT_PRESSED != 0 {
		s.WriteString("right alt, ")
	}
	if cks&xwindows.LEFT_CTRL_PRESSED != 0 {
		s.WriteString("left ctrl, ")
	}
	if cks&xwindows.RIGHT_CTRL_PRESSED != 0 {
		s.WriteString("right ctrl, ")
	}
	if cks&xwindows.SHIFT_PRESSED != 0 {
		s.WriteString("shift, ")
	}
	if cks&xwindows.CAPSLOCK_ON != 0 {
		s.WriteString("caps lock, ")
	}
	if cks&xwindows.NUMLOCK_ON != 0 {
		s.WriteString("num lock, ")
	}
	if cks&xwindows.SCROLLLOCK_ON != 0 {
		s.WriteString("scroll lock, ")
	}
	if cks&xwindows.ENHANCED_KEY != 0 {
		s.WriteString("enhanced key, ")
	}
	s.WriteString("], repeat count: ")
	fmt.Fprintf(&s, "%d", repeatCount)
	return s.String()
}
