package html

import (
	
	
	
	
	
	
	

	
)

// Option sets an option of the HTML formatter.
type Option func(f *Formatter)

// Standalone configures the HTML formatter for generating a standalone HTML document.
func ( bool) Option { return func( *Formatter) { .standalone =  } }

// ClassPrefix sets the CSS class prefix.
func ( string) Option { return func( *Formatter) { .prefix =  } }

// WithClasses emits HTML using CSS classes, rather than inline styles.
func ( bool) Option { return func( *Formatter) { .Classes =  } }

// WithAllClasses disables an optimisation that omits redundant CSS classes.
func ( bool) Option { return func( *Formatter) { .allClasses =  } }

// WithCustomCSS sets user's custom CSS styles.
func ( map[chroma.TokenType]string) Option {
	return func( *Formatter) {
		.customCSS = 
	}
}

// TabWidth sets the number of characters for a tab. Defaults to 8.
func ( int) Option { return func( *Formatter) { .tabWidth =  } }

// PreventSurroundingPre prevents the surrounding pre tags around the generated code.
func ( bool) Option {
	return func( *Formatter) {
		.preventSurroundingPre = 

		if  {
			.preWrapper = nopPreWrapper
		} else {
			.preWrapper = defaultPreWrapper
		}
	}
}

// InlineCode creates inline code wrapped in a code tag.
func ( bool) Option {
	return func( *Formatter) {
		.inlineCode = 
		.preWrapper = preWrapper{
			start: func( bool,  string) string {
				if  {
					return fmt.Sprintf(`<code%s>`, )
				}

				return ``
			},
			end: func( bool) string {
				if  {
					return `</code>`
				}

				return ``
			},
		}
	}
}

// WithPreWrapper allows control of the surrounding pre tags.
func ( PreWrapper) Option {
	return func( *Formatter) {
		.preWrapper = 
	}
}

// WrapLongLines wraps long lines.
func ( bool) Option {
	return func( *Formatter) {
		.wrapLongLines = 
	}
}

// WithLineNumbers formats output with line numbers.
func ( bool) Option {
	return func( *Formatter) {
		.lineNumbers = 
	}
}

// LineNumbersInTable will, when combined with WithLineNumbers, separate the line numbers
// and code in table td's, which make them copy-and-paste friendly.
func ( bool) Option {
	return func( *Formatter) {
		.lineNumbersInTable = 
	}
}

// WithLinkableLineNumbers decorates the line numbers HTML elements with an "id"
// attribute so they can be linked.
func ( bool,  string) Option {
	return func( *Formatter) {
		.linkableLineNumbers = 
		.lineNumbersIDPrefix = 
	}
}

// HighlightLines higlights the given line ranges with the Highlight style.
//
// A range is the beginning and ending of a range as 1-based line numbers, inclusive.
func ( [][2]int) Option {
	return func( *Formatter) {
		.highlightRanges = 
		sort.Sort(.highlightRanges)
	}
}

// BaseLineNumber sets the initial number to start line numbering at. Defaults to 1.
func ( int) Option {
	return func( *Formatter) {
		.baseLineNumber = 
	}
}

// New HTML formatter.
func ( ...Option) *Formatter {
	 := &Formatter{
		baseLineNumber: 1,
		preWrapper:     defaultPreWrapper,
	}
	.styleCache = newStyleCache()
	for ,  := range  {
		()
	}
	return 
}

// PreWrapper defines the operations supported in WithPreWrapper.
type PreWrapper interface {
	// Start is called to write a start <pre> element.
	// The code flag tells whether this block surrounds
	// highlighted code. This will be false when surrounding
	// line numbers.
	Start(code bool, styleAttr string) string

	// End is called to write the end </pre> element.
	End(code bool) string
}

type preWrapper struct {
	start func(code bool, styleAttr string) string
	end   func(code bool) string
}

func ( preWrapper) ( bool,  string) string {
	return .start(, )
}

func ( preWrapper) ( bool) string {
	return .end()
}

var (
	nopPreWrapper = preWrapper{
		start: func( bool,  string) string { return "" },
		end:   func( bool) string { return "" },
	}
	defaultPreWrapper = preWrapper{
		start: func( bool,  string) string {
			if  {
				return fmt.Sprintf(`<pre%s><code>`, )
			}

			return fmt.Sprintf(`<pre%s>`, )
		},
		end: func( bool) string {
			if  {
				return `</code></pre>`
			}

			return `</pre>`
		},
	}
)

// Formatter that generates HTML.
type Formatter struct {
	styleCache            *styleCache
	standalone            bool
	prefix                string
	Classes               bool // Exported field to detect when classes are being used
	allClasses            bool
	customCSS             map[chroma.TokenType]string
	preWrapper            PreWrapper
	inlineCode            bool
	preventSurroundingPre bool
	tabWidth              int
	wrapLongLines         bool
	lineNumbers           bool
	lineNumbersInTable    bool
	linkableLineNumbers   bool
	lineNumbersIDPrefix   string
	highlightRanges       highlightRanges
	baseLineNumber        int
}

type highlightRanges [][2]int

func ( highlightRanges) () int           { return len() }
func ( highlightRanges) (,  int)      { [], [] = [], [] }
func ( highlightRanges) (,  int) bool { return [][0] < [][0] }

func ( *Formatter) ( io.Writer,  *chroma.Style,  chroma.Iterator) ( error) {
	return .writeHTML(, , .Tokens())
}

// We deliberately don't use html/template here because it is two orders of magnitude slower (benchmarked).
//
// OTOH we need to be super careful about correct escaping...
func ( *Formatter) ( io.Writer,  *chroma.Style,  []chroma.Token) ( error) { // nolint: gocyclo
	 := .styleCache.get(, true)
	if .standalone {
		fmt.Fprint(, "<html>\n")
		if .Classes {
			fmt.Fprint(, "<style type=\"text/css\">\n")
			 = .WriteCSS(, )
			if  != nil {
				return 
			}
			fmt.Fprintf(, "body { %s; }\n", [chroma.Background])
			fmt.Fprint(, "</style>")
		}
		fmt.Fprintf(, "<body%s>\n", .styleAttr(, chroma.Background))
	}

	 := .lineNumbers && .lineNumbersInTable

	 := chroma.SplitTokensIntoLines()
	 := len(strconv.Itoa(.baseLineNumber + len() - 1))
	 := 0

	if  {
		// List line numbers in its own <td>
		fmt.Fprintf(, "<div%s>\n", .styleAttr(, chroma.PreWrapper))
		fmt.Fprintf(, "<table%s><tr>", .styleAttr(, chroma.LineTable))
		fmt.Fprintf(, "<td%s>\n", .styleAttr(, chroma.LineTableTD))
		fmt.Fprintf(, "%s", .preWrapper.Start(false, .styleAttr(, chroma.PreWrapper)))
		for  := range  {
			 := .baseLineNumber + 
			,  := .shouldHighlight(, )
			if  {
				++
			}
			if  {
				fmt.Fprintf(, "<span%s>", .styleAttr(, chroma.LineHighlight))
			}

			fmt.Fprintf(, "<span%s%s>%s\n</span>", .styleAttr(, chroma.LineNumbersTable), .lineIDAttribute(), .lineTitleWithLinkIfNeeded(, , ))

			if  {
				fmt.Fprintf(, "</span>")
			}
		}
		fmt.Fprint(, .preWrapper.End(false))
		fmt.Fprint(, "</td>\n")
		fmt.Fprintf(, "<td%s>\n", .styleAttr(, chroma.LineTableTD, "width:100%"))
	}

	fmt.Fprintf(, "%s", .preWrapper.Start(true, .styleAttr(, chroma.PreWrapper)))

	 = 0
	for ,  := range  {
		// 1-based line number.
		 := .baseLineNumber + 
		,  := .shouldHighlight(, )
		if  {
			++
		}

		if !(.preventSurroundingPre || .inlineCode) {
			// Start of Line
			fmt.Fprint(, `<span`)

			if  {
				// Line + LineHighlight
				if .Classes {
					fmt.Fprintf(, ` class="%s %s"`, .class(chroma.Line), .class(chroma.LineHighlight))
				} else {
					fmt.Fprintf(, ` style="%s %s"`, [chroma.Line], [chroma.LineHighlight])
				}
				fmt.Fprint(, `>`)
			} else {
				fmt.Fprintf(, "%s>", .styleAttr(, chroma.Line))
			}

			// Line number
			if .lineNumbers && ! {
				fmt.Fprintf(, "<span%s%s>%s</span>", .styleAttr(, chroma.LineNumbers), .lineIDAttribute(), .lineTitleWithLinkIfNeeded(, , ))
			}

			fmt.Fprintf(, `<span%s>`, .styleAttr(, chroma.CodeLine))
		}

		for ,  := range  {
			 := html.EscapeString(.String())
			 := .styleAttr(, .Type)
			if  != "" {
				 = fmt.Sprintf("<span%s>%s</span>", , )
			}
			fmt.Fprint(, )
		}

		if !(.preventSurroundingPre || .inlineCode) {
			fmt.Fprint(, `</span>`) // End of CodeLine

			fmt.Fprint(, `</span>`) // End of Line
		}
	}
	fmt.Fprintf(, "%s", .preWrapper.End(true))

	if  {
		fmt.Fprint(, "</td></tr></table>\n")
		fmt.Fprint(, "</div>\n")
	}

	if .standalone {
		fmt.Fprint(, "\n</body>\n")
		fmt.Fprint(, "</html>\n")
	}

	return nil
}

func ( *Formatter) ( int) string {
	if !.linkableLineNumbers {
		return ""
	}
	return fmt.Sprintf(" id=\"%s\"", .lineID())
}

func ( *Formatter) ( map[chroma.TokenType]string, ,  int) string {
	 := fmt.Sprintf("%*d", , )
	if !.linkableLineNumbers {
		return 
	}
	return fmt.Sprintf("<a%s href=\"#%s\">%s</a>", .styleAttr(, chroma.LineLink), .lineID(), )
}

func ( *Formatter) ( int) string {
	return fmt.Sprintf("%s%d", .lineNumbersIDPrefix, )
}

func ( *Formatter) (,  int) (bool, bool) {
	 := false
	for  < len(.highlightRanges) &&  > .highlightRanges[][1] {
		++
		 = true
	}
	if  < len(.highlightRanges) {
		 := .highlightRanges[]
		if  >= [0] &&  <= [1] {
			return true, 
		}
	}
	return false, 
}

func ( *Formatter) ( chroma.TokenType) string {
	for  != 0 {
		if ,  := chroma.StandardTypes[];  {
			if  != "" {
				return .prefix + 
			}
			return ""
		}
		 = .Parent()
	}
	if  := chroma.StandardTypes[];  != "" {
		return .prefix + 
	}
	return ""
}

func ( *Formatter) ( map[chroma.TokenType]string,  chroma.TokenType,  ...string) string {
	if .Classes {
		 := .class()
		if  == "" {
			return ""
		}
		return fmt.Sprintf(` class="%s"`, )
	}
	if ,  := []; ! {
		 = .SubCategory()
		if ,  := []; ! {
			 = .Category()
			if ,  := []; ! {
				return ""
			}
		}
	}
	 := []string{[]}
	 = append(, ...)
	return fmt.Sprintf(` style="%s"`, strings.Join(, ";"))
}

func ( *Formatter) () string {
	if .tabWidth != 0 && .tabWidth != 8 {
		return fmt.Sprintf("-moz-tab-size: %[1]d; -o-tab-size: %[1]d; tab-size: %[1]d;", .tabWidth)
	}
	return ""
}

// WriteCSS writes CSS style definitions (without any surrounding HTML).
func ( *Formatter) ( io.Writer,  *chroma.Style) error {
	 := .styleCache.get(, false)
	// Special-case background as it is mapped to the outer ".chroma" class.
	if ,  := fmt.Fprintf(, "/* %s */ .%sbg { %s }\n", chroma.Background, .prefix, [chroma.Background]);  != nil {
		return 
	}
	// Special-case PreWrapper as it is the ".chroma" class.
	if ,  := fmt.Fprintf(, "/* %s */ .%schroma { %s }\n", chroma.PreWrapper, .prefix, [chroma.PreWrapper]);  != nil {
		return 
	}
	// Special-case code column of table to expand width.
	if .lineNumbers && .lineNumbersInTable {
		if ,  := fmt.Fprintf(, "/* %s */ .%schroma .%s:last-child { width: 100%%; }",
			chroma.LineTableTD, .prefix, .class(chroma.LineTableTD));  != nil {
			return 
		}
	}
	// Special-case line number highlighting when targeted.
	if .lineNumbers || .lineNumbersInTable {
		 := StyleEntryToCSS(.Get(chroma.LineHighlight))
		for ,  := range []chroma.TokenType{chroma.LineNumbers, chroma.LineNumbersTable} {
			fmt.Fprintf(, "/* %s targeted by URL anchor */ .%schroma .%s:target { %s }\n", , .prefix, .class(), )
		}
	}
	 := []int{}
	for  := range  {
		 = append(, int())
	}
	sort.Ints()
	for ,  := range  {
		 := chroma.TokenType()
		switch  {
		case chroma.Background, chroma.PreWrapper:
			continue
		}
		 := .class()
		if  == "" {
			continue
		}
		 := []
		if ,  := fmt.Fprintf(, "/* %s */ .%schroma .%s { %s }\n", , .prefix, , );  != nil {
			return 
		}
	}
	return nil
}

func ( *Formatter) ( *chroma.Style) map[chroma.TokenType]string {
	 := map[chroma.TokenType]string{}
	 := .Get(chroma.Background)
	// Convert the style.
	for  := range chroma.StandardTypes {
		 := .Get()
		if  != chroma.Background {
			 = .Sub()
		}

		// Inherit from custom CSS provided by user
		 := .Category()
		 := .SubCategory()
		if  !=  {
			if ,  := .customCSS[];  {
				[] = 
			}
		}
		if  !=  {
			if ,  := .customCSS[];  {
				[] += 
			}
		}
		// Add custom CSS provided by user
		if ,  := .customCSS[];  {
			[] += 
		}

		if !.allClasses && .IsZero() && [] == `` {
			continue
		}

		 := StyleEntryToCSS()
		if  != `` && [] != `` {
			 += `;`
		}
		[] =  + []
	}
	[chroma.Background] += `;` + .tabWidthStyle()
	[chroma.PreWrapper] += [chroma.Background]
	// Make PreWrapper a grid to show highlight style with full width.
	if len(.highlightRanges) > 0 && .customCSS[chroma.PreWrapper] == `` {
		[chroma.PreWrapper] += `display: grid;`
	}
	// Make PreWrapper wrap long lines.
	if .wrapLongLines {
		[chroma.PreWrapper] += `white-space: pre-wrap; word-break: break-word;`
	}
	 := `white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;`
	// All rules begin with default rules followed by user provided rules
	[chroma.Line] = `display: flex;` + [chroma.Line]
	[chroma.LineNumbers] =  + [chroma.LineNumbers]
	[chroma.LineNumbersTable] =  + [chroma.LineNumbersTable]
	[chroma.LineTable] = "border-spacing: 0; padding: 0; margin: 0; border: 0;" + [chroma.LineTable]
	[chroma.LineTableTD] = "vertical-align: top; padding: 0; margin: 0; border: 0;" + [chroma.LineTableTD]
	[chroma.LineLink] = "outline: none; text-decoration: none; color: inherit" + [chroma.LineLink]
	return 
}

// StyleEntryToCSS converts a chroma.StyleEntry to CSS attributes.
func ( chroma.StyleEntry) string {
	 := []string{}
	if .Colour.IsSet() {
		 = append(, "color: "+.Colour.String())
	}
	if .Background.IsSet() {
		 = append(, "background-color: "+.Background.String())
	}
	if .Bold == chroma.Yes {
		 = append(, "font-weight: bold")
	}
	if .Italic == chroma.Yes {
		 = append(, "font-style: italic")
	}
	if .Underline == chroma.Yes {
		 = append(, "text-decoration: underline")
	}
	return strings.Join(, "; ")
}

// Compress CSS attributes - remove spaces, transform 6-digit colours to 3.
func compressStyle( string) string {
	 := strings.Split(, ";")
	 := []string{}
	for ,  := range  {
		 = strings.Join(strings.Fields(), " ")
		 = strings.Replace(, ": ", ":", 1)
		if strings.Contains(, "#") {
			 := [len()-6:]
			if [0] == [1] && [2] == [3] && [4] == [5] {
				 = [:len()-6] + [0:1] + [2:3] + [4:5]
			}
		}
		 = append(, )
	}
	return strings.Join(, ";")
}

const styleCacheLimit = 32

type styleCacheEntry struct {
	style      *chroma.Style
	compressed bool
	cache      map[chroma.TokenType]string
}

type styleCache struct {
	mu sync.Mutex
	// LRU cache of compiled (and possibly compressed) styles. This is a slice
	// because the cache size is small, and a slice is sufficiently fast for
	// small N.
	cache []styleCacheEntry
	f     *Formatter
}

func newStyleCache( *Formatter) *styleCache {
	return &styleCache{f: }
}

func ( *styleCache) ( *chroma.Style,  bool) map[chroma.TokenType]string {
	.mu.Lock()
	defer .mu.Unlock()

	// Look for an existing entry.
	for  := len(.cache) - 1;  >= 0; -- {
		 := .cache[]
		if .style ==  && .compressed ==  {
			// Top of the cache, no need to adjust the order.
			if  == len(.cache)-1 {
				return .cache
			}
			// Move this entry to the end of the LRU
			copy(.cache[:], .cache[+1:])
			.cache[len(.cache)-1] = 
			return .cache
		}
	}

	// No entry, create one.
	 := .f.styleToCSS()
	if !.f.Classes {
		for ,  := range  {
			[] = compressStyle()
		}
	}
	if  {
		for ,  := range  {
			[] = compressStyle()
		}
	}
	// Evict the oldest entry.
	if len(.cache) >= styleCacheLimit {
		.cache = .cache[0:copy(.cache, .cache[1:])]
	}
	.cache = append(.cache, styleCacheEntry{style: , cache: , compressed: })
	return 
}