package highlight

import (
	
	

	
	
)

var sourceText = parse.SourceText

// Represents a region to be highlighted.
type region struct {
	begin int
	end   int
	// Regions can be lexical or semantic. Lexical regions always correspond to
	// a leaf node in the parse tree, either a parse.Primary node or a parse.Sep
	// node. Semantic regions may span several leaves and override all lexical
	// regions in it.
	kind regionKind
	// In lexical regions for Primary nodes, this field corresponds to the Type
	// field of the node (e.g. "bareword", "single-quoted"). In lexical regions
	// for Sep nodes, this field is simply the source text itself (e.g. "(",
	// "|"), except for comments, which have typ == "comment".
	//
	// In semantic regions, this field takes a value from a fixed list (see
	// below).
	typ string
}

type regionKind int

// Region kinds.
const (
	lexicalRegion regionKind = iota
	semanticRegion
)

// Lexical region types.
const (
	barewordRegion     = "bareword"
	singleQuotedRegion = "single-quoted"
	doubleQuotedRegion = "double-quoted"
	variableRegion     = "variable" // Could also be semantic.
	wildcardRegion     = "wildcard"
	tildeRegion        = "tilde"
	// A comment region. Note that this is the only type of Sep leaf node that
	// is not identified by its text.
	commentRegion = "comment"
)

// Semantic region types.
const (
	// A region when a string literal (bareword, single-quoted or double-quoted)
	// appears as a command.
	commandRegion = "command"
	// A region for keywords in special forms, like "else" in an "if" form.
	keywordRegion = "keyword"
	// A region of parse or compilation error.
	errorRegion = "error"
)

func ( parse.Node) []region {
	 := getRegionsInner()
	 = fixRegions()
	return 
}

func ( parse.Node) []region {
	var  []region
	emitRegions(, func( parse.Node,  regionKind,  string) {
		 = append(, region{.Range().From, .Range().To, , })
	})
	return 
}

func ( []region) []region {
	// Sort regions by the begin position, putting semantic regions before
	// lexical regions.
	sort.Slice(, func(,  int) bool {
		if [].begin < [].begin {
			return true
		}
		if [].begin == [].begin {
			return [].kind == semanticRegion && [].kind == lexicalRegion
		}
		return false
	})
	// Remove overlapping regions, preferring the ones that appear earlier.
	var  []region
	 := 0
	for ,  := range  {
		if .begin <  {
			continue
		}
		 = append(, )
		 = .end
	}
	return 
}

func ( parse.Node,  func(parse.Node, regionKind, string)) {
	switch n := .(type) {
	case *parse.Form:
		emitRegionsInForm(, )
	case *parse.Primary:
		emitRegionsInPrimary(, )
	case *parse.Sep:
		emitRegionsInSep(, )
	}
	for ,  := range parse.Children() {
		(, )
	}
}

func ( *parse.Form,  func(parse.Node, regionKind, string)) {
	// Left hands of temporary assignments.
	for ,  := range .Assignments {
		if .Left != nil && .Left.Head != nil {
			(.Left.Head, semanticRegion, variableRegion)
		}
	}
	if .Head == nil {
		return
	}
	// Special forms.
	// TODO: This only highlights bareword special commands, however currently
	// quoted special commands are also possible (e.g `"if" $true { }` is
	// accepted).
	 := sourceText(.Head)
	switch  {
	case "var", "set":
		emitRegionsInVarSet(, )
	case "if":
		emitRegionsInIf(, )
	case "for":
		emitRegionsInFor(, )
	case "try":
		emitRegionsInTry(, )
	}
	if !eval.IsBuiltinSpecial[] {
		for ,  := range .Args {
			if parse.SourceText() == "=" {
				// Highlight left hands of legacy assignment form.
				emitVariableRegion(.Head, )
				for  := 0;  < ; ++ {
					emitVariableRegion(.Args[], )
				}
				return
			}
		}
	}
	if isBarewordCompound(.Head) {
		(.Head, semanticRegion, commandRegion)
	}
}

func ( *parse.Form,  func(parse.Node, regionKind, string)) {
	// Highlight all LHS, and = as a keyword.
	for ,  := range .Args {
		if parse.SourceText() == "=" {
			(, semanticRegion, keywordRegion)
			break
		}
		emitVariableRegion(, )
	}
}

func ( *parse.Compound,  func(parse.Node, regionKind, string)) {
	// Only handle valid LHS here. Invalid LHS will result in a compile error
	// and highlighted as an error accordingly.
	if  != nil && len(.Indexings) == 1 && .Indexings[0].Head != nil {
		(.Indexings[0].Head, semanticRegion, variableRegion)
	}
}

func ( *parse.Compound) bool {
	return len(.Indexings) == 1 && len(.Indexings[0].Indicies) == 0 && .Indexings[0].Head.Type == parse.Bareword
}

func ( *parse.Form,  func(parse.Node, regionKind, string)) {
	// Highlight all "elif" and "else".
	for  := 2;  < len(.Args);  += 2 {
		 := .Args[]
		if  := sourceText();  == "elif" ||  == "else" {
			(, semanticRegion, keywordRegion)
		}
	}
}

func ( *parse.Form,  func(parse.Node, regionKind, string)) {
	// Highlight the iterating variable.
	if 0 < len(.Args) && len(.Args[0].Indexings) > 0 {
		(.Args[0].Indexings[0].Head, semanticRegion, variableRegion)
	}
	// Highlight "else".
	if 3 < len(.Args) && sourceText(.Args[3]) == "else" {
		(.Args[3], semanticRegion, keywordRegion)
	}
}

func ( *parse.Form,  func(parse.Node, regionKind, string)) {
	// Highlight "except", the exception variable after it, "else" and
	// "finally".
	 := 1
	 := func( string) bool {
		if  < len(.Args) && sourceText(.Args[]) ==  {
			(.Args[], semanticRegion, keywordRegion)
			return true
		}
		return false
	}
	if ("except") {
		if +1 < len(.Args) && len(.Args[+1].Indexings) > 0 {
			(.Args[+1], semanticRegion, variableRegion)
		}
		 += 3
	}
	if ("else") {
		 += 2
	}
	("finally")
}

func ( *parse.Primary,  func(parse.Node, regionKind, string)) {
	switch .Type {
	case parse.Bareword:
		(, lexicalRegion, barewordRegion)
	case parse.SingleQuoted:
		(, lexicalRegion, singleQuotedRegion)
	case parse.DoubleQuoted:
		(, lexicalRegion, doubleQuotedRegion)
	case parse.Variable:
		(, lexicalRegion, variableRegion)
	case parse.Wildcard:
		(, lexicalRegion, wildcardRegion)
	case parse.Tilde:
		(, lexicalRegion, tildeRegion)
	}
}

func ( *parse.Sep,  func(parse.Node, regionKind, string)) {
	 := sourceText()
	 := strings.TrimLeftFunc(, parse.IsWhitespace)
	switch {
	case  == "":
		// Don't do anything; whitespaces do not get highlighted.
	case strings.HasPrefix(, "#"):
		(, lexicalRegion, commentRegion)
	default:
		(, lexicalRegion, )
	}
}