package tui

import (
	"strings"
	"time"
)

// Key is a parsed keypress.
type Key struct {
	Kind  KeyKind
	Rune  rune   // for KeyRune
	Paste string // for KeyPaste
	Ctrl  bool
	Alt   bool
}

type KeyKind int

const (
	KeyRune KeyKind = iota
	KeyEnter
	KeyBackspace
	KeyTab
	KeyShiftTab
	KeyEsc
	KeyUp
	KeyDown
	KeyLeft
	KeyRight
	KeyHome
	KeyEnd
	KeyPageUp
	KeyPageDown
	KeyDelete
	KeyCtrlC
	KeyCtrlD
	KeyCtrlL
	KeyCtrlU
	KeyCtrlK
	KeyCtrlA
	KeyCtrlE
	KeyCtrlW
	KeyCtrlO
	KeyPaste
	KeyUnknown
)

// Reader parses a byte stream into Key events. It understands basic
// xterm escape sequences and bracketed paste.
type Reader struct {
	src  func() (byte, error)
	peek func(time.Duration) (byte, bool, error) // optional; may be nil
}

// NewReader returns a Reader that pulls bytes from read.
func NewReader(read func() (byte, error)) *Reader { return &Reader{src: read} }

// NewReaderWithPeek returns a Reader that pulls bytes from read and uses
// peek to disambiguate bare Esc from the start of an escape sequence.
func NewReaderWithPeek(read func() (byte, error), peek func(time.Duration) (byte, bool, error)) *Reader {
	return &Reader{src: read, peek: peek}
}

// Read returns the next parsed Key.
func (r *Reader) Read() (Key, error) {
	b, err := r.src()
	if err != nil {
		return Key{}, err
	}
	switch {
	case b == 0x03:
		return Key{Kind: KeyCtrlC}, nil
	case b == 0x04:
		return Key{Kind: KeyCtrlD}, nil
	case b == 0x0c:
		return Key{Kind: KeyCtrlL}, nil
	case b == 0x15:
		return Key{Kind: KeyCtrlU}, nil
	case b == 0x0b:
		return Key{Kind: KeyCtrlK}, nil
	case b == 0x01:
		return Key{Kind: KeyCtrlA}, nil
	case b == 0x05:
		return Key{Kind: KeyCtrlE}, nil
	case b == 0x17:
		return Key{Kind: KeyCtrlW}, nil
	case b == 0x0f:
		return Key{Kind: KeyCtrlO}, nil
	case b == '\r', b == '\n':
		return Key{Kind: KeyEnter}, nil
	case b == '\t':
		return Key{Kind: KeyTab}, nil
	case b == 0x7f, b == 0x08:
		return Key{Kind: KeyBackspace}, nil
	case b == 0x1b:
		return r.readEscape()
	case b < 0x20:
		return Key{Kind: KeyUnknown}, nil
	}
	// UTF-8 multibyte?
	if b < 0x80 {
		return Key{Kind: KeyRune, Rune: rune(b)}, nil
	}
	// Decode UTF-8 (up to 4 bytes).
	n := utf8Len(b)
	buf := []byte{b}
	for i := 1; i < n; i++ {
		bb, err := r.src()
		if err != nil {
			return Key{}, err
		}
		buf = append(buf, bb)
	}
	rn, _ := decodeRune(buf)
	return Key{Kind: KeyRune, Rune: rn}, nil
}

func utf8Len(b byte) int {
	switch {
	case b&0xe0 == 0xc0:
		return 2
	case b&0xf0 == 0xe0:
		return 3
	case b&0xf8 == 0xf0:
		return 4
	}
	return 1
}

func decodeRune(b []byte) (rune, int) {
	// Minimal decoder; invalid runes become U+FFFD.
	if len(b) == 1 {
		return rune(b[0]), 1
	}
	var r rune
	switch len(b) {
	case 2:
		r = rune(b[0]&0x1f)<<6 | rune(b[1]&0x3f)
	case 3:
		r = rune(b[0]&0x0f)<<12 | rune(b[1]&0x3f)<<6 | rune(b[2]&0x3f)
	case 4:
		r = rune(b[0]&0x07)<<18 | rune(b[1]&0x3f)<<12 | rune(b[2]&0x3f)<<6 | rune(b[3]&0x3f)
	default:
		r = 0xFFFD
	}
	return r, len(b)
}

// readEscape handles sequences starting with 0x1b.
func (r *Reader) readEscape() (Key, error) {
	// Bare ESC: maybe followed by another byte within a short window.
	b, have, err := r.readEscapeNext(50 * time.Millisecond)
	if err != nil || !have {
		return Key{Kind: KeyEsc}, nil
	}
	switch b {
	case '[':
		return r.readCSI()
	case 'O':
		// SS3 sequences (function keys in some terminals).
		c, err := r.src()
		if err != nil {
			return Key{}, err
		}
		switch c {
		case 'H':
			return Key{Kind: KeyHome}, nil
		case 'F':
			return Key{Kind: KeyEnd}, nil
		}
		return Key{Kind: KeyUnknown}, nil
	case 0x7f, 0x08:
		// Alt+Backspace (Option+Delete on macOS) — most terminals send
		// ESC + DEL for this. Surface as a dedicated "alt backspace"
		// so the editor can map it to delete-word.
		return Key{Kind: KeyBackspace, Alt: true}, nil
	case 'b':
		// Emacs-style word-left, also emitted by some terminals for
		// Option+LeftArrow.
		return Key{Kind: KeyLeft, Alt: true}, nil
	case 'f':
		// Emacs-style word-right, also emitted for Option+RightArrow.
		return Key{Kind: KeyRight, Alt: true}, nil
	default:
		// Alt+<char>
		if b < 0x80 {
			return Key{Kind: KeyRune, Rune: rune(b), Alt: true}, nil
		}
	}
	return Key{Kind: KeyUnknown}, nil
}

// readEscapeNext tries to read one byte within d. If peek is available
// we use it (true non-blocking). Otherwise we fall back to a blocking
// read, which means bare Esc is only detected after the next keystroke.
func (r *Reader) readEscapeNext(d time.Duration) (byte, bool, error) {
	if r.peek != nil {
		return r.peek(d)
	}
	b, err := r.src()
	if err != nil {
		return 0, false, err
	}
	return b, true, nil
}

// readCSI parses a CSI sequence after ESC [.
func (r *Reader) readCSI() (Key, error) {
	var params []byte
	for {
		c, err := r.src()
		if err != nil {
			return Key{}, err
		}
		if c >= 0x30 && c <= 0x3f {
			params = append(params, c)
			continue
		}
		// Final byte.
		return r.dispatchCSI(string(params), c), nil
	}
}

func (r *Reader) dispatchCSI(params string, final byte) Key {
	// Modified arrow keys come in as CSI 1;<mod><final>. Modifier values
	// we care about: 2=Shift, 3=Alt/Option, 5=Ctrl. We only extract Alt.
	var alt bool
	if params != "" {
		if i := strings.IndexByte(params, ';'); i >= 0 {
			mod := params[i+1:]
			if mod == "3" || mod == "4" || mod == "7" || mod == "8" {
				// 3=Alt, 4=Shift+Alt, 7=Ctrl+Alt, 8=Ctrl+Shift+Alt
				alt = true
			}
		}
	}
	switch final {
	case 'A':
		return Key{Kind: KeyUp, Alt: alt}
	case 'B':
		return Key{Kind: KeyDown, Alt: alt}
	case 'C':
		return Key{Kind: KeyRight, Alt: alt}
	case 'D':
		return Key{Kind: KeyLeft, Alt: alt}
	case 'H':
		return Key{Kind: KeyHome}
	case 'F':
		return Key{Kind: KeyEnd}
	case 'Z':
		return Key{Kind: KeyShiftTab}
	case '~':
		switch params {
		case "3":
			return Key{Kind: KeyDelete}
		case "5":
			return Key{Kind: KeyPageUp}
		case "6":
			return Key{Kind: KeyPageDown}
		case "200":
			// Start of bracketed paste.
			return r.readPaste()
		}
	}
	return Key{Kind: KeyUnknown}
}

// readPaste reads until ESC [ 2 0 1 ~ and returns the pasted text.
func (r *Reader) readPaste() Key {
	var sb strings.Builder
	const end = "\x1b[201~"
	tail := make([]byte, 0, len(end))
	for {
		b, err := r.src()
		if err != nil {
			break
		}
		tail = append(tail, b)
		if len(tail) > len(end) {
			sb.WriteByte(tail[0])
			tail = tail[1:]
		}
		if string(tail) == end {
			break
		}
	}
	return Key{Kind: KeyPaste, Paste: sb.String()}
}
