// Copyright 2025 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
// You may obtain a copy of the license at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// This file describes a generic VT input processor.  It parses key sequences,
// (input bytes) and loads them into events.  It expects UTF-8 or UTF-16 as the input
// feed, along with ECMA-48 sequences.  The assumption here is that all potential
// key sequences are unambiguous between terminal variants (analysis of extant terminfo
// data appears to support this conjecture). This allows us to implement  this once,
// in the most efficient and terminal-agnostic way possible.
//
// There is unfortunately *one* conflict, with aixterm, for CSI-P - which is KeyDelete
// in aixterm, but F1 in others.

package tcell

import (
	
	
	
	
	
	
	
	
)

type inpState int

const (
	inpStateInit = inpState(iota)
	inpStateUtf
	inpStateEsc
	inpStateCsi // control sequence introducer
	inpStateOsc // operating system command
	inpStateDcs // device control string
	inpStateSos // start of string (unused)
	inpStatePm  // privacy message (unused)
	inpStateApc // application program command
	inpStateSt  // string terminator
	inpStateSs2 // single shift 2
	inpStateSs3 // single shift 3
	inpStateLFK // linux F-key (not ECMA-48 compliant - bogus CSI)
)

type InputProcessor interface {
	ScanUTF8([]byte)
	ScanUTF16([]uint16)
	SetSize(rows, cols int)
}

func ( chan<- Event) InputProcessor {
	return &inputProcessor{
		evch: ,
		buf:  make([]rune, 0, 128),
	}
}

type inputProcessor struct {
	ut8       []byte
	ut16      []uint16
	buf       []rune
	scratch   []byte
	csiParams []byte
	csiInterm []byte
	escaped   bool
	btnDown   bool // mouse button tracking for broken terms
	state     inpState
	strState  inpState // saved str state (needed for ST)
	timer     *time.Timer
	expire    time.Time
	l         sync.Mutex
	encBuf    []rune
	evch      chan<- Event
	rows      int // used for clipping mouse coordinates
	cols      int // used for clipping mouse coordinates
	surrogate rune
	nested    *inputProcessor
}

func ( *inputProcessor) (,  int) {
	if .nested != nil {
		.nested.(, )
		return
	}
	go func() {
		.l.Lock()
		.rows = 
		.cols = 
		.post(NewEventResize(, ))
		.l.Unlock()
	}()
}
func ( *inputProcessor) ( Event) {
	if .escaped {
		.escaped = false
		if ,  := .(*EventKey);  {
			 = NewEventKey(.Key(), .Rune(), .Modifiers()|ModAlt)
		}
	} else if ,  := .(*EventKey);  {
		switch .Key() {
		case keyPasteStart:
			 = NewEventPaste(true)
		case keyPasteEnd:
			 = NewEventPaste(false)
		}
	}

	.evch <- 
}

func ( *inputProcessor) () {
	.l.Lock()
	defer .l.Unlock()
	if .state == inpStateEsc && .expire.Before(time.Now()) {
		// post it
		.state = inpStateInit
		.escaped = false
		.post(NewEventKey(KeyEsc, 0, ModNone))
	}
}

type csiParamMode struct {
	M rune // Mode
	P int  // Parameter (first)
}

type keyMap struct {
	Key  Key
	Mod  ModMask
	Rune rune
}

var csiAllKeys = map[csiParamMode]keyMap{
	{M: 'A'}:         {Key: KeyUp},
	{M: 'B'}:         {Key: KeyDown},
	{M: 'C'}:         {Key: KeyRight},
	{M: 'D'}:         {Key: KeyLeft},
	{M: 'F'}:         {Key: KeyEnd},
	{M: 'H'}:         {Key: KeyHome},
	{M: 'L'}:         {Key: KeyInsert},
	{M: 'P'}:         {Key: KeyF1}, // except for aixterm, where this is Delete
	{M: 'Q'}:         {Key: KeyF2},
	{M: 'S'}:         {Key: KeyF4},
	{M: 'Z'}:         {Key: KeyBacktab},
	{M: 'a'}:         {Key: KeyUp, Mod: ModShift},
	{M: 'b'}:         {Key: KeyDown, Mod: ModShift},
	{M: 'c'}:         {Key: KeyRight, Mod: ModShift},
	{M: 'd'}:         {Key: KeyLeft, Mod: ModShift},
	{M: 'q', P: 1}:   {Key: KeyF1}, // all these 'q' are for aixterm
	{M: 'q', P: 2}:   {Key: KeyF2},
	{M: 'q', P: 3}:   {Key: KeyF3},
	{M: 'q', P: 4}:   {Key: KeyF4},
	{M: 'q', P: 5}:   {Key: KeyF5},
	{M: 'q', P: 6}:   {Key: KeyF6},
	{M: 'q', P: 7}:   {Key: KeyF7},
	{M: 'q', P: 8}:   {Key: KeyF8},
	{M: 'q', P: 9}:   {Key: KeyF9},
	{M: 'q', P: 10}:  {Key: KeyF10},
	{M: 'q', P: 11}:  {Key: KeyF11},
	{M: 'q', P: 12}:  {Key: KeyF12},
	{M: 'q', P: 13}:  {Key: KeyF13},
	{M: 'q', P: 14}:  {Key: KeyF14},
	{M: 'q', P: 15}:  {Key: KeyF15},
	{M: 'q', P: 16}:  {Key: KeyF16},
	{M: 'q', P: 17}:  {Key: KeyF17},
	{M: 'q', P: 18}:  {Key: KeyF18},
	{M: 'q', P: 19}:  {Key: KeyF19},
	{M: 'q', P: 20}:  {Key: KeyF20},
	{M: 'q', P: 21}:  {Key: KeyF21},
	{M: 'q', P: 22}:  {Key: KeyF22},
	{M: 'q', P: 23}:  {Key: KeyF23},
	{M: 'q', P: 24}:  {Key: KeyF24},
	{M: 'q', P: 25}:  {Key: KeyF25},
	{M: 'q', P: 26}:  {Key: KeyF26},
	{M: 'q', P: 27}:  {Key: KeyF27},
	{M: 'q', P: 28}:  {Key: KeyF28},
	{M: 'q', P: 29}:  {Key: KeyF29},
	{M: 'q', P: 30}:  {Key: KeyF30},
	{M: 'q', P: 31}:  {Key: KeyF31},
	{M: 'q', P: 32}:  {Key: KeyF32},
	{M: 'q', P: 33}:  {Key: KeyF33},
	{M: 'q', P: 34}:  {Key: KeyF34},
	{M: 'q', P: 35}:  {Key: KeyF35},
	{M: 'q', P: 36}:  {Key: KeyF36},
	{M: 'q', P: 144}: {Key: KeyClear},
	{M: 'q', P: 146}: {Key: KeyEnd},
	{M: 'q', P: 150}: {Key: KeyPgUp},
	{M: 'q', P: 154}: {Key: KeyPgDn},
	{M: 'z', P: 214}: {Key: KeyHome},
	{M: 'z', P: 216}: {Key: KeyPgUp},
	{M: 'z', P: 220}: {Key: KeyEnd},
	{M: 'z', P: 222}: {Key: KeyPgDn},
	{M: 'z', P: 224}: {Key: KeyF1},
	{M: 'z', P: 225}: {Key: KeyF2},
	{M: 'z', P: 226}: {Key: KeyF3},
	{M: 'z', P: 227}: {Key: KeyF4},
	{M: 'z', P: 228}: {Key: KeyF5},
	{M: 'z', P: 229}: {Key: KeyF6},
	{M: 'z', P: 230}: {Key: KeyF7},
	{M: 'z', P: 231}: {Key: KeyF8},
	{M: 'z', P: 232}: {Key: KeyF9},
	{M: 'z', P: 233}: {Key: KeyF10},
	{M: 'z', P: 234}: {Key: KeyF11},
	{M: 'z', P: 235}: {Key: KeyF12},
	{M: 'z', P: 247}: {Key: KeyInsert},
	{M: '^', P: 7}:   {Key: KeyHome, Mod: ModCtrl},
	{M: '^', P: 8}:   {Key: KeyEnd, Mod: ModCtrl},
	{M: '^', P: 11}:  {Key: KeyF23},
	{M: '^', P: 12}:  {Key: KeyF24},
	{M: '^', P: 13}:  {Key: KeyF25},
	{M: '^', P: 14}:  {Key: KeyF26},
	{M: '^', P: 15}:  {Key: KeyF27},
	{M: '^', P: 17}:  {Key: KeyF28}, // 16 is a gap
	{M: '^', P: 18}:  {Key: KeyF29},
	{M: '^', P: 19}:  {Key: KeyF30},
	{M: '^', P: 20}:  {Key: KeyF31},
	{M: '^', P: 21}:  {Key: KeyF32},
	{M: '^', P: 23}:  {Key: KeyF33}, // 22 is a gap
	{M: '^', P: 24}:  {Key: KeyF34},
	{M: '^', P: 25}:  {Key: KeyF35},
	{M: '^', P: 26}:  {Key: KeyF36}, // 27 is a gap
	{M: '^', P: 28}:  {Key: KeyF37},
	{M: '^', P: 29}:  {Key: KeyF38}, // 30 is a gap
	{M: '^', P: 31}:  {Key: KeyF39},
	{M: '^', P: 32}:  {Key: KeyF40},
	{M: '^', P: 33}:  {Key: KeyF41},
	{M: '^', P: 34}:  {Key: KeyF42},
	{M: '@', P: 23}:  {Key: KeyF43},
	{M: '@', P: 24}:  {Key: KeyF44},
	{M: '$', P: 2}:   {Key: KeyInsert, Mod: ModShift},
	{M: '$', P: 3}:   {Key: KeyDelete, Mod: ModShift},
	{M: '$', P: 7}:   {Key: KeyHome, Mod: ModShift},
	{M: '$', P: 8}:   {Key: KeyEnd, Mod: ModShift},
	{M: '$', P: 23}:  {Key: KeyF21},
	{M: '$', P: 24}:  {Key: KeyF22},
	{M: '~', P: 1}:   {Key: KeyHome},
	{M: '~', P: 2}:   {Key: KeyInsert},
	{M: '~', P: 3}:   {Key: KeyDelete},
	{M: '~', P: 4}:   {Key: KeyEnd},
	{M: '~', P: 5}:   {Key: KeyPgUp},
	{M: '~', P: 6}:   {Key: KeyPgDn},
	{M: '~', P: 7}:   {Key: KeyHome},
	{M: '~', P: 8}:   {Key: KeyEnd},
	{M: '~', P: 11}:  {Key: KeyF1},
	{M: '~', P: 12}:  {Key: KeyF2},
	{M: '~', P: 13}:  {Key: KeyF3},
	{M: '~', P: 14}:  {Key: KeyF4},
	{M: '~', P: 15}:  {Key: KeyF5},
	{M: '~', P: 17}:  {Key: KeyF6},
	{M: '~', P: 18}:  {Key: KeyF7},
	{M: '~', P: 19}:  {Key: KeyF8},
	{M: '~', P: 20}:  {Key: KeyF9},
	{M: '~', P: 21}:  {Key: KeyF10},
	{M: '~', P: 23}:  {Key: KeyF11},
	{M: '~', P: 24}:  {Key: KeyF12},
	{M: '~', P: 25}:  {Key: KeyF13},
	{M: '~', P: 26}:  {Key: KeyF14},
	{M: '~', P: 28}:  {Key: KeyF15}, // aka KeyHelp
	{M: '~', P: 29}:  {Key: KeyF16},
	{M: '~', P: 31}:  {Key: KeyF17},
	{M: '~', P: 32}:  {Key: KeyF18},
	{M: '~', P: 33}:  {Key: KeyF19},
	{M: '~', P: 34}:  {Key: KeyF20},
	{M: '~', P: 200}: {Key: keyPasteStart},
	{M: '~', P: 201}: {Key: keyPasteEnd},
}

// keys reported using Kitty csi-u protocol
var csiUKeys = map[int]keyMap{
	27:    {Key: KeyESC},
	9:     {Key: KeyTAB},
	13:    {Key: KeyEnter},
	127:   {Key: KeyBS},
	57358: {Key: KeyCapsLock},
	57359: {Key: KeyScrollLock},
	57360: {Key: KeyNumLock},
	57361: {Key: KeyPrint},
	57362: {Key: KeyPause},
	57363: {Key: KeyMenu},
	57376: {Key: KeyF13},
	57377: {Key: KeyF14},
	57378: {Key: KeyF15},
	57379: {Key: KeyF16},
	57380: {Key: KeyF17},
	57381: {Key: KeyF18},
	57382: {Key: KeyF19},
	57383: {Key: KeyF20},
	57384: {Key: KeyF21},
	57385: {Key: KeyF22},
	57386: {Key: KeyF23},
	57387: {Key: KeyF24},
	57388: {Key: KeyF25},
	57389: {Key: KeyF26},
	57390: {Key: KeyF27},
	57391: {Key: KeyF28},
	57392: {Key: KeyF29},
	57393: {Key: KeyF30},
	57394: {Key: KeyF31},
	57395: {Key: KeyF32},
	57396: {Key: KeyF33},
	57397: {Key: KeyF34},
	57398: {Key: KeyF35},
	57399: {Key: KeyRune, Rune: '0'}, // KP 0
	57400: {Key: KeyRune, Rune: '1'}, // KP 1
	57401: {Key: KeyRune, Rune: '2'}, // KP 2
	57402: {Key: KeyRune, Rune: '3'}, // KP 3
	57403: {Key: KeyRune, Rune: '4'}, // KP 4
	57404: {Key: KeyRune, Rune: '5'}, // KP 5
	57405: {Key: KeyRune, Rune: '6'}, // KP 6
	57406: {Key: KeyRune, Rune: '7'}, // KP 7
	57407: {Key: KeyRune, Rune: '8'}, // KP 8
	57408: {Key: KeyRune, Rune: '9'}, // KP 9
	57409: {Key: KeyRune, Rune: '.'}, // KP_DECIMAL
	57410: {Key: KeyRune, Rune: '/'}, // KP_DIVIDE
	57411: {Key: KeyRune, Rune: '*'}, // KP_MULTIPLY
	57412: {Key: KeyRune, Rune: '-'}, // KP_SUBTRACT
	57413: {Key: KeyRune, Rune: '+'}, // KP_ADD
	57414: {Key: KeyEnter},           // KP_ENTER
	57415: {Key: KeyRune, Rune: '='}, // KP_EQUAL
	57416: {Key: KeyClear},           // KP_SEPARATOR
	57417: {Key: KeyLeft},            // KP_LEFT
	57418: {Key: KeyRight},           // KP_RIGHT
	57419: {Key: KeyUp},              // KP_UP
	57420: {Key: KeyDown},            // KP_DOWN
	57421: {Key: KeyPgUp},            // KP_PG_UP
	57422: {Key: KeyPgDn},            // KP_PG_DN
	57423: {Key: KeyHome},            // KP_HOME
	57424: {Key: KeyEnd},             // KP_END
	57425: {Key: KeyInsert},          // KP_INSERT
	57426: {Key: KeyDelete},          // KP_DELETE
	// 57427: {Key: KeyBegin},          // KP_BEGIN

	// TODO: Media keys
}

// windows virtual key codes per microsoft
var winKeys = map[int]Key{
	0x03: KeyCancel,    // vkCancel
	0x08: KeyBackspace, // vkBackspace
	0x09: KeyTab,       // vkTab
	0x0c: KeyClear,     // vClear
	0x0d: KeyEnter,     // vkReturn
	0x13: KeyPause,     // vkPause
	0x1b: KeyEscape,    // vkEscape
	0x21: KeyPgUp,      // vkPrior
	0x22: KeyPgDn,      // vkNext
	0x23: KeyEnd,       // vkEnd
	0x24: KeyHome,      // vkHome
	0x25: KeyLeft,      // vkLeft
	0x26: KeyUp,        // vkUp
	0x27: KeyRight,     // vkRight
	0x28: KeyDown,      // vkDown
	0x2a: KeyPrint,     // vkPrint
	0x2c: KeyPrint,     // vkPrtScr
	0x2d: KeyInsert,    // vkInsert
	0x2e: KeyDelete,    // vkDelete
	0x2f: KeyHelp,      // vkHelp
	0x70: KeyF1,        // vkF1
	0x71: KeyF2,        // vkF2
	0x72: KeyF3,        // vkF3
	0x73: KeyF4,        // vkF4
	0x74: KeyF5,        // vkF5
	0x75: KeyF6,        // vkF6
	0x76: KeyF7,        // vkF7
	0x77: KeyF8,        // vkF8
	0x78: KeyF9,        // vkF9
	0x79: KeyF10,       // vkF10
	0x7a: KeyF11,       // vkF11
	0x7b: KeyF12,       // vkF12
	0x7c: KeyF13,       // vkF13
	0x7d: KeyF14,       // vkF14
	0x7e: KeyF15,       // vkF15
	0x7f: KeyF16,       // vkF16
	0x80: KeyF17,       // vkF17
	0x81: KeyF18,       // vkF18
	0x82: KeyF19,       // vkF19
	0x83: KeyF20,       // vkF20
	0x84: KeyF21,       // vkF21
	0x85: KeyF22,       // vkF22
	0x86: KeyF23,       // vkF23
	0x87: KeyF24,       // vkF24
}

// keys by their SS3 - used in application mode usually (legacy VT-style)
var ss3Keys = map[rune]Key{
	'A': KeyUp,
	'B': KeyDown,
	'C': KeyRight,
	'D': KeyLeft,
	'F': KeyEnd,
	'H': KeyHome,
	'P': KeyF1,
	'Q': KeyF2,
	'R': KeyF3,
	'S': KeyF4,
	't': KeyF5,
	'u': KeyF6,
	'v': KeyF7,
	'l': KeyF8,
	'w': KeyF9,
	'x': KeyF10,
}

// linux terminal uses these non ECMA keys prefixed by CSI-[
var linuxFKeys = map[rune]Key{
	'A': KeyF1,
	'B': KeyF2,
	'C': KeyF3,
	'D': KeyF4,
	'E': KeyF5,
}

func ( *inputProcessor) () {
	for ,  := range .buf {
		.buf = .buf[1:]
		if  > 0x7F {
			// 8-bit extended Unicode we just treat as such - this will swallow anything else queued up
			.state = inpStateInit
			.post(NewEventKey(KeyRune, , ModNone))
			continue
		}
		switch .state {
		case inpStateInit:
			switch  {
			case '\x1b':
				// escape.. pending
				.state = inpStateEsc
				if len(.buf) == 0 && .nested == nil {
					.expire = time.Now().Add(time.Millisecond * 50)
					.timer = time.AfterFunc(time.Millisecond*60, .escTimeout)
				}
			case '\t':
				.post(NewEventKey(KeyTab, 0, ModNone))
			case '\b', '\x7F':
				.post(NewEventKey(KeyBackspace, 0, ModNone))
			case '\r':
				.post(NewEventKey(KeyEnter, 0, ModNone))
			default:
				// Control keys - legacy handling
				if  < ' ' {
					.post(NewEventKey(KeyCtrlSpace+Key(), 0, ModCtrl))
				} else {
					.post(NewEventKey(KeyRune, , ModNone))
				}
			}
		case inpStateEsc:
			switch  {
			case '[':
				.state = inpStateCsi
				.csiInterm = nil
				.csiParams = nil
			case ']':
				.state = inpStateOsc
				.scratch = nil
			case 'N':
				.state = inpStateSs2 // no known uses
				.scratch = nil
			case 'O':
				.state = inpStateSs3
				.scratch = nil
			case 'X':
				.state = inpStateSos
				.scratch = nil
			case '^':
				.state = inpStatePm
				.scratch = nil
			case '_':
				.state = inpStateApc
				.scratch = nil
			case '\\':
				// string terminator reached, (orphaned?)
				.state = inpStateInit
			case '\t':
				// Linux console only, does not conform to ECMA
				.state = inpStateInit
				.post(NewEventKey(KeyBacktab, 0, ModNone))
			default:
				if  == '\x1b' {
					// leading ESC to capture alt
					.escaped = true
				} else {
					// treat as alt-key ... legacy emulators only (no CSI-u or other)
					.state = inpStateInit
					 := ModAlt
					if  < ' ' {
						 |= ModCtrl
						 += 0x60
					}
					.post(NewEventKey(KeyRune, , ))
				}
			}
		case inpStateCsi:
			// usual case for incoming keys
			if  >= 0x30 &&  <= 0x3F { // parameter bytes
				.csiParams = append(.csiParams, byte())
			} else if  >= 0x20 &&  <= 0x2F { // intermediate bytes, rarely used
				.csiInterm = append(.csiInterm, byte())
			} else if  >= 0x40 &&  <= 0x7F { // final byte
				.handleCsi(, .csiParams, .csiInterm)
			} else {
				// bad parse, just swallow it all
				.state = inpStateInit
			}
		case inpStateSs2:
			// No known uses for SS2
			.state = inpStateInit

		case inpStateSs3: // typically application mode keys or older terminals
			.state = inpStateInit
			if ,  := ss3Keys[];  {
				.post(NewEventKey(, 0, ModNone))
			}

		case inpStatePm, inpStateApc, inpStateSos, inpStateDcs: // these we just eat
			switch  {
			case '\x1b':
				.strState = .state
				.state = inpStateSt
			case '\x07': // bell - some send this instead of ST
				.state = inpStateInit
			}

		case inpStateOsc: // not sure if used
			switch  {
			case '\x1b':
				.strState = .state
				.state = inpStateSt
			case '\x07':
				.handleOsc(string(.scratch))
			default:
				.scratch = append(.scratch, byte(&0x7f))
			}
		case inpStateSt:
			if  == '\\' ||  == '\x07' {
				.state = inpStateInit
				switch .strState {
				case inpStateOsc:
					.handleOsc(string(.scratch))
				case inpStatePm, inpStateApc, inpStateSos, inpStateDcs:
					.state = inpStateInit
				}
			} else {
				.scratch = append(.scratch, '\x1b', byte())
				.state = .strState
			}
		case inpStateLFK:
			// linux console does not follow ECMA
			if ,  := linuxFKeys[];  {
				.post(NewEventKey(, 0, ModNone))
			}
			.state = inpStateInit
		}
	}
}

func ( *inputProcessor) ( string) {
	.state = inpStateInit
	if ,  := strings.CutPrefix(, "52;c;");  {
		 := make([]byte, base64.StdEncoding.DecodedLen(len()))
		if ,  := base64.StdEncoding.Decode(, []byte());  == nil {
			.post(NewEventClipboard([:]))
			return
		}
	}
}

func calcModifier( int) ModMask {
	--
	 := ModNone
	if &1 != 0 {
		 |= ModShift
	}
	if &2 != 0 {
		 |= ModAlt
	}
	if &4 != 0 {
		 |= ModCtrl
	}
	if &8 != 0 {
		 |= ModMeta // kitty calls this Super
	}
	if &16 != 0 {
		 |= ModHyper
	}
	if &32 != 0 {
		 |= ModMeta // for now not separating from Super
	}
	// Not doing (kitty only):
	// caps_lock 0b1000000   (64)
	// num_lock  0b10000000  (128)

	return 
}

// func (ip *inputProcessor) handleMouse(x, y, btn int, down bool) *EventMouse {
func ( *inputProcessor) ( rune,  []int) {

	// XTerm mouse events only report at most one button at a time,
	// which may include a wheel button.  Wheel motion events are
	// reported as single impulses, while other button events are reported
	// as separate press & release events.
	if len() < 3 {
		return
	}
	 := [0]
	// Some terminals will report mouse coordinates outside the
	// screen, especially with click-drag events.  Clip the coordinates
	// to the screen in that case.
	 := max(min([1]-1, .cols-1), 0)
	 := max(min([2]-1, .rows-1), 0)
	 := ( & 0x20) != 0
	 := ( & 0x42) == 0x40
	 &^= 0x20
	if  == 'm' {
		// mouse release, clear all buttons
		 |= 3
		 &^= 0x40
		.btnDown = false
	} else if  {
		/*
		 * Some broken terminals appear to send
		 * mouse button one motion events, instead of
		 * encoding 35 (no buttons) into these events.
		 * We resolve these by looking for a non-motion
		 * event first.
		 */
		if !.btnDown {
			 |= 3
			 &^= 0x40
		}
	} else if ! {
		.btnDown = true
	}

	 := ButtonNone
	 := ModNone

	// Mouse wheel has bit 6 set, no release events.  It should be noted
	// that wheel events are sometimes misdelivered as mouse button events
	// during a click-drag, so we debounce these, considering them to be
	// button press events unless we see an intervening release event.
	switch  & 0x43 {
	case 0:
		 = Button1
	case 1:
		 = Button3 // Note we prefer to treat right as button 2
	case 2:
		 = Button2 // And the middle button as button 3
	case 3:
		 = ButtonNone
	case 0x40:
		 = WheelUp
	case 0x41:
		 = WheelDown
	case 0x42:
		 = WheelLeft
	case 0x43:
		 = WheelRight
	}

	if &0x4 != 0 {
		 |= ModShift
	}
	if &0x8 != 0 {
		 |= ModAlt
	}
	if &0x10 != 0 {
		 |= ModCtrl
	}

	.post(NewEventMouse(, , , ))
}

func ( *inputProcessor) ( []int) {
	// win32-input-mode
	//  ^[ [ Vk ; Sc ; Uc ; Kd ; Cs ; Rc _
	// Vk: the value of wVirtualKeyCode - any number. If omitted, defaults to '0'.
	// Sc: the value of wVirtualScanCode - any number. If omitted, defaults to '0'.
	// Uc: the decimal value of UnicodeChar - for example, NUL is "0", LF is
	//     "10", the character 'A' is "65". If omitted, defaults to '0'.
	// Kd: the value of bKeyDown - either a '0' or '1'. If omitted, defaults to '0'.
	// Cs: the value of dwControlKeyState - any number. If omitted, defaults to '0'.
	// Rc: the value of wRepeatCount - any number. If omitted, defaults to '1'.
	//
	// Note that some 3rd party terminal emulators (not Terminal) suffer from a bug
	// where other events, such as mouse events, are doubly encoded, using Vk 0
	// for each character.  (So a CSI-M sequence is encoded as a series of CSI-_
	// sequences.)  We consider this a bug in those terminal emulators -- Windows 11
	// Terminal does not suffer this brain damage. (We've observed this with both Alacritty
	// and WezTerm.)
	for len() < 6 {
		 = append(, 0) // ensure sufficient length
	}
	if [3] == 0 {
		// key up event ignore ignore
		return
	}

	if [0] == 0 && [1] == 0 && [2] > 0 && [2] < 0x80 { // only ASCII in win32-input-mode
		if .nested == nil {
			.nested = &inputProcessor{
				evch: .evch,
				rows: .rows,
				cols: .cols,
			}
		}

		.nested.ScanUTF8([]byte{byte([2])})
		return
	}

	 := KeyRune
	 := rune([2])
	 := ModNone
	 := max(1, [5])
	if ,  := winKeys[[0]];  {
		 = 0
		 = 
	} else if  == 0 && [0] >= 0x30 && [0] <= 0x39 {
		 = rune([0])
	} else if  < ' ' && [0] >= 0x41 && [0] <= 0x5a {
		 = Key([0])
		 = 0

	} else if  >= 0xD800 &&  <= 0xDBFF {
		// high surrogate pair
		.surrogate = 
		return
	} else if  >= 0xDC00 &&  <= 0xDFFF {
		// low surrogate pair
		 = utf16.DecodeRune(.surrogate, )
	} else if [0] == 0x10 || [0] == 0x11 || [0] == 0x12 || [0] == 0x14 {
		// lone modifiers
		.surrogate = 0
		return
	}

	.surrogate = 0

	// Modifiers
	if [4]&0x010 != 0 {
		 |= ModShift
	}
	if [4]&0x000c != 0 {
		 |= ModCtrl
	}
	if [4]&0x0003 != 0 {
		 |= ModAlt
	}
	if  == KeyRune &&  > ' ' &&  == ModShift {
		// filter out lone shift for printable chars
		 = ModNone
	}
	if  != 0 && &(ModCtrl|ModAlt) == ModCtrl|ModAlt {
		// Filter out ctrl+alt (it means AltGr)
		 = ModNone
	}

	for range  {
		if  != KeyRune ||  != 0 {
			.post(NewEventKey(, , ))
		}
	}
}

func ( *inputProcessor) ( rune,  []byte,  []byte) {

	// reset state
	.state = inpStateInit

	if len() != 0 {
		// we don't know what to do with these for now
		return
	}

	var  []string
	var  []int
	 := false
	 := string()
	// extract numeric parameters
	if strings.HasPrefix(, "<") {
		 = true
		 = [1:]
	}
	if  != "" && [0] >= '0' && [0] <= '9' {
		 = strings.Split(, ";")
		for  := range  {
			if [] != "" {
				if ,  := strconv.ParseInt([], 10, 32);  == nil {
					 = append(, int())
				}
			}
		}
	}
	var  int
	if len() > 0 {
		 = [0]
	}

	if  {
		switch  {
		case 'm', 'M': // mouse event, we only do SGR tracking
			.handleMouse(, )
		}
	}

	switch  {
	case 'I': // focus in
		.post(NewEventFocus(true))
		return
	case 'O': // focus out
		.post(NewEventFocus(false))
		return
	case '[':
		// linux console F-key - CSI-[ modifies next key
		.state = inpStateLFK
		return
	case 'u':
		// CSI-u kitty keyboard protocol
		if len() > 0 && ! {
			 := ModNone
			 := KeyRune
			 := rune(0)
			if ,  := csiUKeys[];  {
				 = .Key
				 = .Rune
			} else {
				 = rune()
			}
			if len() > 1 {
				 = calcModifier([1])
			}
			.post(NewEventKey(, , ))
		}
		return
	case '_':
		if len() == 0 && len() > 0 {
			.handleWinKey()
			return
		}
	case '~':
		if len() == 0 && len() >= 2 {
			 := calcModifier([1])
			if ,  := csiAllKeys[csiParamMode{M: , P: }];  {
				.post(NewEventKey(.Key, 0, ))
				return
			}
			if  == 27 && len() > 2 && [2] > 0 && [2] <= 0xff {
				if [2] < ' ' || [2] == 0x7F {
					.post(NewEventKey(Key([2]), 0, ))
				} else {
					.post(NewEventKey(KeyRune, rune([2]), ))
				}
				return
			}
		}
	}

	if ,  := csiAllKeys[csiParamMode{M: , P: }];  && ! {
		if  == '~' && len() > 1 && .Mod == ModNone {
			// apply modifiers if present
			.Mod = calcModifier([1])
		} else if  == 'P' && os.Getenv("TERM") == "aixterm" {
			.Key = KeyDelete // aixterm hack - conflicts with kitty protocol
		}
		.post(NewEventKey(.Key, 0, .Mod))
		return
	}

	// this might have been an SS3 style key with modifiers applied
	if ,  := ss3Keys[];  &&  == 1 && len() > 1 {
		.post(NewEventKey(, 0, calcModifier([1])))
		return
	}
	// if we got here we just swallow the unknown sequence
}

func ( *inputProcessor) ( []byte) {
	.l.Lock()
	defer .l.Unlock()

	.ut8 = append(.ut8, ...)
	for len(.ut8) > 0 {
		// fast path, basic ascii
		if .ut8[0] < 0x7F {
			.buf = append(.buf, rune(.ut8[0]))
			.ut8 = .ut8[1:]
		} else {
			,  := utf8.DecodeRune(.ut8)
			if  == utf8.RuneError {
				 = rune(.ut8[0])
				 = 1
			}
			.buf = append(.buf, )
			.ut8 = .ut8[:]
		}
	}

	.scan()
}

func ( *inputProcessor) ( []uint16) {
	.l.Lock()
	defer .l.Unlock()
	.ut16 = append(.ut16, ...)
	for len(.ut16) > 0 {
		if !utf16.IsSurrogate(rune(.ut16[0])) {
			.buf = append(.buf, rune(.ut16[0]))
			.ut16 = .ut16[1:]
		} else if len(.ut16) > 1 {
			.buf = append(.buf, utf16.DecodeRune(rune(.ut16[0]), rune(.ut16[1])))
			.ut16 = .ut16[2:]
		} else {
			break
		}
	}
}