// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>

package loop

import (
	"fmt"
	"math"
	"strconv"
	"strings"
	"unicode/utf8"

	"github.com/kovidgoyal/kitty"
)

// key encoding mappings {{{
// start csi mapping (auto generated by gen-key-constants.py do not edit)
var functional_key_number_to_name_map = map[int]string{57344: "ESCAPE", 57345: "ENTER", 57346: "TAB", 57347: "BACKSPACE", 57348: "INSERT", 57349: "DELETE", 57350: "LEFT", 57351: "RIGHT", 57352: "UP", 57353: "DOWN", 57354: "PAGE_UP", 57355: "PAGE_DOWN", 57356: "HOME", 57357: "END", 57358: "CAPS_LOCK", 57359: "SCROLL_LOCK", 57360: "NUM_LOCK", 57361: "PRINT_SCREEN", 57362: "PAUSE", 57363: "MENU", 57364: "F1", 57365: "F2", 57366: "F3", 57367: "F4", 57368: "F5", 57369: "F6", 57370: "F7", 57371: "F8", 57372: "F9", 57373: "F10", 57374: "F11", 57375: "F12", 57376: "F13", 57377: "F14", 57378: "F15", 57379: "F16", 57380: "F17", 57381: "F18", 57382: "F19", 57383: "F20", 57384: "F21", 57385: "F22", 57386: "F23", 57387: "F24", 57388: "F25", 57389: "F26", 57390: "F27", 57391: "F28", 57392: "F29", 57393: "F30", 57394: "F31", 57395: "F32", 57396: "F33", 57397: "F34", 57398: "F35", 57399: "KP_0", 57400: "KP_1", 57401: "KP_2", 57402: "KP_3", 57403: "KP_4", 57404: "KP_5", 57405: "KP_6", 57406: "KP_7", 57407: "KP_8", 57408: "KP_9", 57409: "KP_DECIMAL", 57410: "KP_DIVIDE", 57411: "KP_MULTIPLY", 57412: "KP_SUBTRACT", 57413: "KP_ADD", 57414: "KP_ENTER", 57415: "KP_EQUAL", 57416: "KP_SEPARATOR", 57417: "KP_LEFT", 57418: "KP_RIGHT", 57419: "KP_UP", 57420: "KP_DOWN", 57421: "KP_PAGE_UP", 57422: "KP_PAGE_DOWN", 57423: "KP_HOME", 57424: "KP_END", 57425: "KP_INSERT", 57426: "KP_DELETE", 57427: "KP_BEGIN", 57428: "MEDIA_PLAY", 57429: "MEDIA_PAUSE", 57430: "MEDIA_PLAY_PAUSE", 57431: "MEDIA_REVERSE", 57432: "MEDIA_STOP", 57433: "MEDIA_FAST_FORWARD", 57434: "MEDIA_REWIND", 57435: "MEDIA_TRACK_NEXT", 57436: "MEDIA_TRACK_PREVIOUS", 57437: "MEDIA_RECORD", 57438: "LOWER_VOLUME", 57439: "RAISE_VOLUME", 57440: "MUTE_VOLUME", 57441: "LEFT_SHIFT", 57442: "LEFT_CONTROL", 57443: "LEFT_ALT", 57444: "LEFT_SUPER", 57445: "LEFT_HYPER", 57446: "LEFT_META", 57447: "RIGHT_SHIFT", 57448: "RIGHT_CONTROL", 57449: "RIGHT_ALT", 57450: "RIGHT_SUPER", 57451: "RIGHT_HYPER", 57452: "RIGHT_META", 57453: "ISO_LEVEL3_SHIFT", 57454: "ISO_LEVEL5_SHIFT"}

var csi_number_to_functional_number_map = map[int]int{2: 57348, 3: 57349, 5: 57354, 6: 57355, 7: 57356, 8: 57357, 9: 57346, 11: 57364, 12: 57365, 13: 57345, 14: 57367, 15: 57368, 17: 57369, 18: 57370, 19: 57371, 20: 57372, 21: 57373, 23: 57374, 24: 57375, 27: 57344, 127: 57347}

var letter_trailer_to_csi_number_map = map[string]int{"A": 57352, "B": 57353, "C": 57351, "D": 57350, "E": 57427, "F": 8, "H": 7, "P": 11, "Q": 12, "S": 14}

var tilde_trailers = map[int]bool{57348: true, 57349: true, 57354: true, 57355: true, 57366: true, 57368: true, 57369: true, 57370: true, 57371: true, 57372: true, 57373: true, 57374: true, 57375: true}

// end csi mapping
// }}}

var name_to_functional_number_map map[string]int
var functional_to_csi_number_map map[int]int
var csi_number_to_letter_trailer_map map[int]string

type KeyEventType uint8
type KeyModifiers uint16

const (
	PRESS   KeyEventType = 1
	REPEAT  KeyEventType = 2
	RELEASE KeyEventType = 4
)

const (
	SHIFT     KeyModifiers = 1
	ALT       KeyModifiers = 2
	CTRL      KeyModifiers = 4
	SUPER     KeyModifiers = 8
	HYPER     KeyModifiers = 16
	META      KeyModifiers = 32
	CAPS_LOCK KeyModifiers = 64
	NUM_LOCK  KeyModifiers = 128
)

func (self KeyModifiers) WithoutLocks() KeyModifiers {
	return self & ^(CAPS_LOCK | NUM_LOCK)
}

func (self KeyEventType) String() string {
	switch self {
	case PRESS:
		return "PRESS"
	case REPEAT:
		return "REPEAT"
	case RELEASE:
		return "RELEASE"
	default:
		return fmt.Sprintf("KeyEventType:%d", int(self))
	}
}

func (self KeyModifiers) String() string {
	ans := make([]string, 0)
	if self&SHIFT != 0 {
		ans = append(ans, "shift")
	}
	if self&ALT != 0 {
		ans = append(ans, "alt")
	}
	if self&CTRL != 0 {
		ans = append(ans, "ctrl")
	}
	if self&SUPER != 0 {
		ans = append(ans, "super")
	}
	if self&HYPER != 0 {
		ans = append(ans, "hyper")
	}
	if self&META != 0 {
		ans = append(ans, "meta")
	}
	if self&CAPS_LOCK != 0 {
		ans = append(ans, "caps_lock")
	}
	if self&NUM_LOCK != 0 {
		ans = append(ans, "num_lock")
	}
	return strings.Join(ans, "+")
}

func (self KeyModifiers) HasCapsLock() bool {
	return self&CAPS_LOCK != 0
}

type KeyEvent struct {
	Type         KeyEventType
	Mods         KeyModifiers
	Key          string
	ShiftedKey   string
	AlternateKey string
	Text         string
	Handled      bool

	// The CSI string this key event was decoded from. Empty if not decoded from CSI.
	CSI string
}

func (self *KeyEvent) String() string {
	key := self.Key
	if self.Mods > 0 {
		key = self.Mods.String() + "+" + key
	}
	ans := fmt.Sprint(self.Type, "{ ", key, " ")
	if self.Text != "" {
		ans += "Text: " + self.Text + " "
	}
	if self.ShiftedKey != "" {
		ans += "ShiftedKey: " + self.ShiftedKey + " "
	}
	if self.AlternateKey != "" {
		ans += "AlternateKey: " + self.AlternateKey + " "
	}
	return ans + "}"
}

func (self *KeyEvent) HasCapsLock() bool {
	return self.Mods.HasCapsLock()
}

func KeyEventFromCSI(csi string) *KeyEvent {
	if len(csi) == 0 {
		return nil
	}
	orig_csi := csi
	last_char := csi[len(csi)-1:]
	if !strings.Contains("u~ABCDEHFPQS", last_char) || (last_char == "~" && (csi == "200~" || csi == "201~")) {
		return nil
	}
	csi = csi[:len(csi)-1]
	sections := strings.Split(csi, ";")

	get_sub_sections := func(section string, missing int32) []int32 {
		p := strings.Split(section, ":")
		ans := make([]int32, len(p))
		for i, x := range p {
			if x == "" {
				ans[i] = missing
			} else {
				q, err := strconv.ParseUint(x, 10, 32)
				if err != nil || q > math.MaxInt32 {
					return nil
				}
				ans[i] = int32(q)
			}
		}
		return ans
	}
	first_section := get_sub_sections(sections[0], 0)
	second_section := []int32{}
	third_section := []int32{}
	if len(sections) > 1 {
		second_section = get_sub_sections(sections[1], 1)
	}
	if len(sections) > 2 {
		third_section = get_sub_sections(sections[2], 0)
	}
	var ans = KeyEvent{Type: PRESS, CSI: orig_csi}
	var keynum int32
	if val, ok := letter_trailer_to_csi_number_map[last_char]; ok {
		keynum = int32(val)
	} else {
		if len(first_section) == 0 {
			return nil
		}
		keynum = first_section[0]
	}

	key_name := func(keynum int32) string {
		switch keynum {
		case 0:
			return ""
		case 13:
			if last_char == "u" {
				return "ENTER"
			}
			return "F3"
		default:
			if val, ok := csi_number_to_functional_number_map[int(keynum)]; ok {
				keynum = int32(val)
			}
			ans := ""
			if val, ok := functional_key_number_to_name_map[int(keynum)]; ok {
				ans = val
			} else {
				ans = string(rune(keynum))
			}
			return ans
		}
	}

	ans.Key = key_name(keynum)
	if len(first_section) > 1 {
		ans.ShiftedKey = key_name(first_section[1])
	}
	if len(first_section) > 2 {
		ans.AlternateKey = key_name(first_section[2])
	}
	if len(second_section) > 0 {
		ans.Mods = KeyModifiers(second_section[0] - 1)
	}
	if len(second_section) > 1 {
		switch second_section[1] {
		case 2:
			ans.Type = REPEAT
		case 3:
			ans.Type = RELEASE
		}
	}
	if len(third_section) > 0 {
		runes := make([]rune, len(third_section))
		for i, ch := range third_section {
			runes[i] = rune(ch)
		}
		ans.Text = string(runes)
	}
	return &ans
}

type ParsedShortcut struct {
	Mods    KeyModifiers
	KeyName string
}

func (self *ParsedShortcut) String() string {
	ans := self.KeyName
	if self.Mods > 0 {
		ans = self.Mods.String() + "+" + ans
	}
	return ans
}

var parsed_shortcut_cache map[string]*ParsedShortcut

func ParseShortcut(spec string) *ParsedShortcut {
	if parsed_shortcut_cache == nil {
		parsed_shortcut_cache = make(map[string]*ParsedShortcut, 128)
	}
	if val, ok := parsed_shortcut_cache[spec]; ok {
		return val
	}
	ospec := spec
	if strings.HasSuffix(spec, "+") {
		ospec = spec[:len(spec)-1] + "plus"
	}
	parts := strings.Split(ospec, "+")
	key_name := parts[len(parts)-1]
	if val, ok := kitty.FunctionalKeyNameAliases[strings.ToUpper(key_name)]; ok {
		key_name = val
	}
	if _, is_functional_key := name_to_functional_number_map[strings.ToUpper(key_name)]; is_functional_key {
		key_name = strings.ToUpper(key_name)
	} else {
		if val, ok := kitty.CharacterKeyNameAliases[strings.ToUpper(key_name)]; ok {
			key_name = val
		}
	}
	ans := ParsedShortcut{KeyName: key_name}
	if len(parts) > 1 {
		for _, q := range parts[:len(parts)-1] {
			val, ok := kitty.ConfigModMap[strings.ToUpper(q)]
			if ok {
				ans.Mods |= KeyModifiers(val)
			} else {
				ans.Mods |= META << 8
			}
		}
	}
	parsed_shortcut_cache[spec] = &ans
	return &ans
}

func isNonASCIIKey(key string) bool {
	r, size := utf8.DecodeRuneInString(key)
	return size > 0 && size == len(key) && r > 127 && r < 0xE000 && r != utf8.RuneError
}

func (self *KeyEvent) MatchesParsedShortcutWithFallback(ps *ParsedShortcut, event_type KeyEventType, allowFallback string) bool {
	return self.MatchesParsedShortcutPriorityWithFallback(ps, event_type, allowFallback) >= 0
}

// MatchesParsedShortcutPriorityWithFallback returns the match priority for the given shortcut:
//   - returns -1 if the event does not match
//   - returns 0 for a direct match (no fallback needed)
//   - returns the 1-based position of the matching fallback type in allowFallback for a fallback match
//     (e.g., "shifted" at position 0 in "shifted,ascii" returns 1; "ascii" at position 1 returns 2)
//
// Lower values indicate higher priority, so callers should prefer matches with smaller return values.
func (self *KeyEvent) MatchesParsedShortcutPriorityWithFallback(ps *ParsedShortcut, event_type KeyEventType, allowFallback string) int {
	if self.Type&event_type == 0 {
		return -1
	}
	mods := self.Mods.WithoutLocks()
	if mods == ps.Mods && self.Key == ps.KeyName {
		return 0
	}
	canShifted := self.ShiftedKey != "" && mods&SHIFT != 0 && (mods & ^SHIFT) == ps.Mods && self.ShiftedKey == ps.KeyName
	canASCII := self.AlternateKey != "" && isNonASCIIKey(self.Key) && mods == ps.Mods && self.AlternateKey == ps.KeyName
	for i, part := range strings.Split(allowFallback, ",") {
		switch strings.TrimSpace(part) {
		case "shifted":
			if canShifted {
				return i + 1
			}
		case "ascii":
			if canASCII {
				return i + 1
			}
		}
	}
	return -1
}

// MatchesPressOrRepeatPriorityWithFallback returns the match priority (see MatchesParsedShortcutPriorityWithFallback).
func (self *KeyEvent) MatchesPressOrRepeatPriorityWithFallback(spec string, allowFallback string) int {
	return self.MatchesParsedShortcutPriorityWithFallback(ParseShortcut(spec), PRESS|REPEAT, allowFallback)
}

func (self *KeyEvent) MatchesParsedShortcut(ps *ParsedShortcut, event_type KeyEventType) bool {
	return self.MatchesParsedShortcutWithFallback(ps, event_type, "shifted,ascii")
}

func (self *KeyEvent) Matches(spec string, event_type KeyEventType) bool {
	return self.MatchesParsedShortcut(ParseShortcut(spec), event_type)
}

func (self *KeyEvent) MatchesPressOrRepeat(spec string) bool {
	return self.MatchesParsedShortcut(ParseShortcut(spec), PRESS|REPEAT)
}

func (self *KeyEvent) MatchesPressOrRepeatWithFallback(spec string, allowFallback string) bool {
	return self.MatchesParsedShortcutWithFallback(ParseShortcut(spec), PRESS|REPEAT, allowFallback)
}

func (self *KeyEvent) MatchesCaseSensitiveTextOrKey(spec string) bool {
	if self.MatchesParsedShortcut(ParseShortcut(spec), PRESS|REPEAT) {
		return true
	}
	return self.Text == spec
}

func (self *KeyEvent) MatchesCaseInsensitiveTextOrKey(spec string) bool {
	if self.MatchesParsedShortcut(ParseShortcut(spec), PRESS|REPEAT) {
		return true
	}
	return strings.EqualFold(self.Text, spec)
}

func (self *KeyEvent) MatchesRelease(spec string) bool {
	return self.MatchesParsedShortcut(ParseShortcut(spec), RELEASE)
}

func (self *KeyEvent) AsCSI() string {
	key := csi_number_for_name(self.Key)
	shifted_key := csi_number_for_name(self.ShiftedKey)
	alternate_key := csi_number_for_name(self.AlternateKey)
	trailer, found := csi_number_to_letter_trailer_map[key]
	if !found {
		trailer = "u"
	}
	if self.Key == "ENTER" {
		trailer = "u"
	}
	if trailer != "u" {
		key = 1
	}
	ans := strings.Builder{}
	ans.Grow(32)
	ans.WriteString("\033[")
	if key != 1 || self.Mods != 0 || shifted_key != 0 || alternate_key != 0 || self.Text != "" {
		fmt.Fprint(&ans, key)
	}
	if shifted_key != 0 || alternate_key != 0 {
		ans.WriteString(":")
		if shifted_key != 0 {
			fmt.Fprint(&ans, shifted_key)
		}
		if alternate_key != 0 {
			fmt.Fprint(&ans, ":", alternate_key)
		}
	}
	action := 1
	switch self.Type {
	case REPEAT:
		action = 2
	case RELEASE:
		action = 3
	}
	if self.Mods != 0 || action > 1 || self.Text != "" {
		m := uint(self.Mods)
		if action > 1 || m != 0 {
			fmt.Fprintf(&ans, ";%d", m+1)
			if action > 1 {
				fmt.Fprintf(&ans, ":%d", action)
			}
		} else if self.Text != "" {
			ans.WriteString(";")
		}
	}
	if self.Text != "" {
		runes := []rune(self.Text)
		codes := make([]string, len(runes))
		for i, r := range runes {
			codes[i] = strconv.Itoa(int(r))
		}
		ans.WriteString(";")
		ans.WriteString(strings.Join(codes, ":"))
	}
	fn, found := name_to_functional_number_map[self.Key]
	if found && tilde_trailers[fn] {
		trailer = "~"
	}
	ans.WriteString(trailer)
	return ans.String()
}

func csi_number_for_name(key_name string) int {
	if key_name == "" {
		return 0
	}
	if key_name == "F3" || key_name == "ENTER" {
		return 13
	}
	fn, ok := name_to_functional_number_map[key_name]
	if !ok {
		return int(rune(key_name[0]))
	}
	ans, ok := functional_to_csi_number_map[fn]
	if ok {
		return ans
	}
	return fn
}

func init() {
	name_to_functional_number_map = make(map[string]int, len(functional_key_number_to_name_map))
	for k, v := range functional_key_number_to_name_map {
		name_to_functional_number_map[v] = k
	}
	functional_to_csi_number_map = make(map[int]int, len(csi_number_to_functional_number_map))
	for k, v := range csi_number_to_functional_number_map {
		functional_to_csi_number_map[v] = k
	}
	csi_number_to_letter_trailer_map = make(map[int]string, len(letter_trailer_to_csi_number_map))
	for k, v := range letter_trailer_to_csi_number_map {
		csi_number_to_letter_trailer_map[v] = k
	}
}
