// 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.

package terminfo

import (
	
	
	
	
	
	
	
	
	
)

var (
	// ErrTermNotFound indicates that a suitable terminal entry could
	// not be found.  This can result from either not having TERM set,
	// or from the TERM failing to support certain minimal functionality,
	// in particular absolute cursor addressability (the cup capability)
	// is required.  For example, legacy "adm3" lacks this capability,
	// whereas the slightly newer "adm3a" supports it.  This failure
	// occurs most often with "dumb".
	ErrTermNotFound = errors.New("terminal entry not found")
)

// Terminfo represents a terminfo entry.  Note that we use friendly names
// in Go, but when we write out JSON, we use the same names as terminfo.
// The name, aliases and smous, rmous fields do not come from terminfo directly.
type Terminfo struct {
	Name        string
	Aliases     []string
	Columns     int    // cols
	Lines       int    // lines
	Colors      int    // colors
	Clear       string // clear
	EnterCA     string // smcup
	ExitCA      string // rmcup
	ShowCursor  string // cnorm
	HideCursor  string // civis
	AttrOff     string // sgr0
	Underline   string // smul
	Bold        string // bold
	Blink       string // blink
	Reverse     string // rev
	Dim         string // dim
	Italic      string // sitm
	EnterKeypad string // smkx
	ExitKeypad  string // rmkx
	SetFg       string // setaf
	SetBg       string // setab
	ResetFgBg   string // op
	SetCursor   string // cup
	PadChar     string // pad
	Mouse       string // kmous
	AltChars    string // acsc
	EnterAcs    string // smacs
	ExitAcs     string // rmacs
	EnableAcs   string // enacs

	// These are non-standard extensions to terminfo.  This includes
	// true color support, and some additional keys.  Its kind of bizarre
	// that shifted variants of left and right exist, but not up and down.
	// Terminal support for these are going to vary amongst XTerm
	// emulations, so don't depend too much on them in your application.

	StrikeThrough     string // smxx
	SetFgBg           string // setfgbg
	SetFgBgRGB        string // setfgbgrgb
	SetFgRGB          string // setfrgb
	SetBgRGB          string // setbrgb
	InsertChar        string // string to insert a character (ich1)
	AutoMargin        bool   // true if writing to last cell in line advances
	TrueColor         bool   // true if the terminal supports direct color
	DisableAutoMargin string // smam
	EnableAutoMargin  string // rmam
	XTermLike         bool   // (XT) has XTerm extensions
}

type stack []any

func ( stack) ( any) stack {
	if ,  := .(bool);  {
		if  {
			return append(, 1)
		} else {
			return append(, 0)
		}
	}
	return append(, )
}

func ( stack) () (string, stack) {
	if len() > 0 {
		 := [len()-1]
		var  string
		switch v := .(type) {
		case int:
			 = strconv.Itoa()
		case string:
			 = 
		}
		return , [:len()-1]
	}
	return "", 

}
func ( stack) () (int, stack) {
	if len() > 0 {
		 := [len()-1]
		var  int
		switch v := .(type) {
		case int:
			 = 
		case string:
			, _ = strconv.Atoi()
		}
		return , [:len()-1]
	}
	return 0, 
}

// static vars
var svars [26]string

type paramsBuffer struct {
	out bytes.Buffer
	buf bytes.Buffer
}

// Start initializes the params buffer with the initial string data.
// It also locks the paramsBuffer.  The caller must call End() when
// finished.
func ( *paramsBuffer) ( string) {
	.out.Reset()
	.buf.Reset()
	.buf.WriteString()
}

// End returns the final output from TParam, but it also releases the lock.
func ( *paramsBuffer) () string {
	 := .out.String()
	return 
}

// NextCh returns the next input character to the expander.
func ( *paramsBuffer) () (byte, error) {
	return .buf.ReadByte()
}

// PutCh "emits" (rather schedules for output) a single byte character.
func ( *paramsBuffer) ( byte) {
	.out.WriteByte()
}

// PutString schedules a string for output.
func ( *paramsBuffer) ( string) {
	.out.WriteString()
}

// TParm takes a terminfo parameterized string, such as setaf or cup, and
// evaluates the string, and returns the result with the parameter
// applied.
func ( *Terminfo) ( string,  ...any) string {
	var  stack
	var  string
	var ,  int
	var  [26]string
	var  [9]any
	var  = &paramsBuffer{}

	.Start()

	// make sure we always have 9 parameters -- makes it easier
	// later to skip checks
	for  := 0;  < len() &&  < len(); ++ {
		[] = []
	}

	const (
		 = iota
		
		
	)

	 := 

	for {

		,  := .NextCh()
		if  != nil {
			break
		}

		if  != '%' {
			if  ==  {
				.PutCh()
			}
			continue
		}

		,  = .NextCh()
		if  != nil {
			// XXX Error
			break
		}
		if  ==  {
			if  == ';' {
				 = 
			}
			continue
		} else if  ==  {
			if  == 'e' ||  == ';' {
				 = 
			}
			continue
		}

		switch  {
		case '%': // quoted %
			.PutCh()

		case 'i': // increment both parameters (ANSI cup support)
			if ,  := [0].(int);  {
				[0] =  + 1
			}
			if ,  := [1].(int);  {
				[1] =  + 1
			}

		case 's':
			// NB: 's', 'c', and 'd' below are special cased for
			// efficiency.  They could be handled by the richer
			// format support below, less efficiently.
			,  = .PopString()
			.PutString()

		case 'c':
			// Integer as special character.
			,  = .PopInt()
			.PutCh(byte())

		case 'd':
			,  = .PopInt()
			.PutString(strconv.Itoa())

		case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'x', 'X', 'o', ':':
			// This is pretty suboptimal, but this is rarely used.
			// None of the mainstream terminals use any of this,
			// and it would surprise me if this code is ever
			// executed outside test cases.
			 := "%"
			if  == ':' {
				, _ = .NextCh()
			}
			 += string()
			for  == '+' ||  == '-' ||  == '#' ||  == ' ' {
				, _ = .NextCh()
				 += string()
			}
			for ( >= '0' &&  <= '9') ||  == '.' {
				, _ = .NextCh()
				 += string()
			}
			switch  {
			case 'd', 'x', 'X', 'o':
				,  = .PopInt()
				.PutString(fmt.Sprintf(, ))
			case 's':
				,  = .PopString()
				.PutString(fmt.Sprintf(, ))
			case 'c':
				,  = .PopInt()
				.PutString(fmt.Sprintf(, ))
			}

		case 'p': // push parameter
			, _ = .NextCh()
			 = int( - '1')
			if  >= 0 &&  < len() {
				 = .Push([])
			} else {
				 = .Push(0)
			}

		case 'P': // pop & store variable
			, _ = .NextCh()
			if  >= 'A' &&  <= 'Z' {
				svars[int(-'A')],  = .PopString()
			} else if  >= 'a' &&  <= 'z' {
				[int(-'a')],  = .PopString()
			}

		case 'g': // recall & push variable
			, _ = .NextCh()
			if  >= 'A' &&  <= 'Z' {
				 = .Push(svars[int(-'A')])
			} else if  >= 'a' &&  <= 'z' {
				 = .Push([int(-'a')])
			}

		case '\'': // push(char) - the integer value of it
			, _ = .NextCh()
			_, _ = .NextCh() // must be ' but we don't check
			 = .Push(int())

		case '{': // push(int)
			 = 0
			, _ = .NextCh()
			for  >= '0' &&  <= '9' {
				 *= 10
				 += int( - '0')
				, _ = .NextCh()
			}
			// ch must be '}' but no verification
			 = .Push()

		case 'l': // push(strlen(pop))
			,  = .PopString()
			 = .Push(len())

		case '+':
			,  = .PopInt()
			,  = .PopInt()
			 = .Push( + )

		case '-':
			,  = .PopInt()
			,  = .PopInt()
			 = .Push( - )

		case '*':
			,  = .PopInt()
			,  = .PopInt()
			 = .Push( * )

		case '/':
			,  = .PopInt()
			,  = .PopInt()
			if  != 0 {
				 = .Push( / )
			} else {
				 = .Push(0)
			}

		case 'm': // push(pop mod pop)
			,  = .PopInt()
			,  = .PopInt()
			if  != 0 {
				 = .Push( % )
			} else {
				 = .Push(0)
			}

		case '&': // AND
			,  = .PopInt()
			,  = .PopInt()
			 = .Push( & )

		case '|': // OR
			,  = .PopInt()
			,  = .PopInt()
			 = .Push( | )

		case '^': // XOR
			,  = .PopInt()
			,  = .PopInt()
			 = .Push( ^ )

		case '~': // bit complement
			,  = .PopInt()
			 = .Push( ^ -1)

		case '!': // logical NOT
			,  = .PopInt()
			 = .Push( == 0)

		case '=': // numeric compare
			,  = .PopInt()
			,  = .PopInt()
			 = .Push( == )

		case '>': // greater than, numeric
			,  = .PopInt()
			,  = .PopInt()
			 = .Push( > )

		case '<': // less than, numeric
			,  = .PopInt()
			,  = .PopInt()
			 = .Push( < )

		case '?': // start conditional

		case ';':
			 = 

		case 't':
			,  = .PopInt()
			if  == 0 {
				 = 
			}

		case 'e':
			 = 

		default:
			.PutString("%" + string())
		}
	}

	return .End()
}

// TPuts emits the string to the writer, but expands inline padding
// indications (of the form $<[delay]> where [delay] is msec) to
// a suitable time (unless the terminfo string indicates this isn't needed
// by specifying npc - no padding).  All Terminfo based strings should be
// emitted using this function.
func ( *Terminfo) ( io.Writer,  string) {
	for {
		 := strings.Index(, "$<")
		if  < 0 {
			// Most strings don't need padding, which is good news!
			_, _ = io.WriteString(, )
			return
		}
		_, _ = io.WriteString(, [:])
		 = [+2:]
		 := strings.Index(, ">")
		if  < 0 {
			// unterminated.. just emit bytes unadulterated
			_, _ = io.WriteString(, "$<"+)
			return
		}
		 := [:]
		 = [+1:]
		 := 0
		 := time.Millisecond
		 := false
	:
		for  := range  {
			switch [] {
			case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
				 *= 10
				 += int([] - '0')
				if  {
					 /= 10
				}
			case '.':
				if ! {
					 = true
				} else {
					break 
				}
			default:
				break 
			}
		}

		// Curses historically uses padding to achieve "fine grained"
		// delays. We have much better clocks these days, and so we
		// do not rely on padding but simply sleep a bit.
		if len(.PadChar) > 0 {
			time.Sleep( * time.Duration())
		}
	}
}

// TGoto returns a string suitable for addressing the cursor at the given
// row and column.  The origin 0, 0 is in the upper left corner of the screen.
func ( *Terminfo) (,  int) string {
	return .TParm(.SetCursor, , )
}

// TColor returns a string corresponding to the given foreground and background
// colors.  Either fg or bg can be set to -1 to elide.
func ( *Terminfo) (,  int) string {
	 := ""
	// As a special case, we map bright colors to lower versions if the
	// color table only holds 8.  For the remaining 240 colors, the user
	// is out of luck.  Someday we could create a mapping table, but its
	// not worth it.
	if .Colors == 8 {
		if  > 7 &&  < 16 {
			 -= 8
		}
		if  > 7 &&  < 16 {
			 -= 8
		}
	}
	if .Colors >  &&  >= 0 {
		 += .TParm(.SetFg, )
	}
	if .Colors >  &&  >= 0 {
		 += .TParm(.SetBg, )
	}
	return 
}

var (
	dblock    sync.Mutex
	terminfos = make(map[string]*Terminfo)
)

// AddTerminfo can be called to register a new Terminfo entry.
func ( *Terminfo) {
	dblock.Lock()

	terminfos[.Name] = 
	for ,  := range .Aliases {
		terminfos[] = 
	}
	dblock.Unlock()
}

// LookupTerminfo attempts to find a definition for the named $TERM.
func ( string) (*Terminfo, error) {
	if  == "" {
		// else on windows: index out of bounds
		// on the name[0] reference below
		return nil, ErrTermNotFound
	}

	 := false
	 := false
	switch os.Getenv("COLORTERM") {
	case "truecolor", "24bit", "24-bit":
		 = true
	}
	dblock.Lock()
	 := terminfos[]
	dblock.Unlock()

	// If the name ends in -truecolor, then fabricate an entry
	// from the corresponding -256color, -color, or bare terminal.
	if  != nil && .TrueColor {
		 = true
	} else if  == nil && strings.HasSuffix(, "-truecolor") {

		 := []string{
			"-256color",
			"-88color",
			"-color",
			"",
		}
		 := [:len()-len("-truecolor")]
		for ,  := range  {
			if , _ = ( + );  != nil {
				 = true
				break
			}
		}
	}

	// If the name ends in -256color, maybe fabricate using the xterm 256 color sequences
	if  == nil && strings.HasSuffix(, "-256color") {
		 := []string{
			"-88color",
			"-color",
		}
		 := [:len()-len("-256color")]
		for ,  := range  {
			if , _ = ( + );  != nil {
				 = true
				break
			}
		}
	}

	if  == nil {
		return nil, ErrTermNotFound
	}

	switch os.Getenv("TCELL_TRUECOLOR") {
	case "":
	case "disable":
		 = false
	default:
		 = true
	}

	// If the user has requested 24-bit color with $COLORTERM, then
	// amend the value (unless already present).  This means we don't
	// need to have a value present.
	if  &&
		.SetFgBgRGB == "" &&
		.SetFgRGB == "" &&
		.SetBgRGB == "" {

		// Supply vanilla ISO 8613-6:1994 24-bit color sequences.
		.SetFgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%dm"
		.SetBgRGB = "\x1b[48;2;%p1%d;%p2%d;%p3%dm"
		.SetFgBgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%d;" +
			"48;2;%p4%d;%p5%d;%p6%dm"
	}

	if  {
		.Colors = 256
		.SetFg = "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m"
		.SetBg = "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m"
		.SetFgBg = "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m"
		.ResetFgBg = "\x1b[39;49m"
	}

	return , nil
}

func () []string {
	 := make([]string, 0, len(terminfos))
	for  := range terminfos {
		 = append(, )
	}
	return 
}