// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package comment

import (
	
	
	
	
	
)

// A textPrinter holds the state needed for printing a Doc as plain text.
type textPrinter struct {
	*Printer
	long       strings.Builder
	prefix     string
	codePrefix string
	width      int
}

// Text returns a textual formatting of the [Doc].
// See the [Printer] documentation for ways to customize the text output.
func ( *Printer) ( *Doc) []byte {
	 := &textPrinter{
		Printer:    ,
		prefix:     .TextPrefix,
		codePrefix: .TextCodePrefix,
		width:      .TextWidth,
	}
	if .codePrefix == "" {
		.codePrefix = .TextPrefix + "\t"
	}
	if .width == 0 {
		.width = 80 - utf8.RuneCountInString(.prefix)
	}

	var  bytes.Buffer
	for ,  := range .Content {
		if  > 0 && blankBefore() {
			.WriteString(.prefix)
			writeNL(&)
		}
		.block(&, )
	}
	 := false
	for ,  := range .Links {
		if .Used {
			 = true
			break
		}
	}
	if  {
		writeNL(&)
		for ,  := range .Links {
			if .Used {
				fmt.Fprintf(&, "[%s]: %s\n", .Text, .URL)
			}
		}
	}
	return .Bytes()
}

// writeNL calls out.WriteByte('\n')
// but first trims trailing spaces on the previous line.
func writeNL( *bytes.Buffer) {
	// Trim trailing spaces.
	 := .Bytes()
	 := 0
	for  < len() && ([len()--1] == ' ' || [len()--1] == '\t') {
		++
	}
	if  > 0 {
		.Truncate(len() - )
	}
	.WriteByte('\n')
}

// block prints the block x to out.
func ( *textPrinter) ( *bytes.Buffer,  Block) {
	switch x := .(type) {
	default:
		fmt.Fprintf(, "?%T\n", )

	case *Paragraph:
		.WriteString(.prefix)
		.text(, "", .Text)

	case *Heading:
		.WriteString(.prefix)
		.WriteString("# ")
		.text(, "", .Text)

	case *Code:
		 := .Text
		for  != "" {
			var  string
			, , _ = strings.Cut(, "\n")
			if  != "" {
				.WriteString(.codePrefix)
				.WriteString()
			}
			writeNL()
		}

	case *List:
		 := .BlankBetween()
		for ,  := range .Items {
			if  > 0 &&  {
				.WriteString(.prefix)
				writeNL()
			}
			.WriteString(.prefix)
			.WriteString(" ")
			if .Number == "" {
				.WriteString(" - ")
			} else {
				.WriteString(.Number)
				.WriteString(". ")
			}
			for ,  := range .Content {
				const  = "    "
				if  > 0 {
					writeNL()
					.WriteString(.prefix)
					.WriteString()
				}
				.text(, , .(*Paragraph).Text)
			}
		}
	}
}

// text prints the text sequence x to out.
func ( *textPrinter) ( *bytes.Buffer,  string,  []Text) {
	.oneLongLine(&.long, )
	 := strings.Fields(.long.String())
	.long.Reset()

	var  []int
	if .width < 0 || len() == 0 {
		 = []int{0, len()} // one long line
	} else {
		 = wrap(, .width-utf8.RuneCountInString())
	}
	for  := 0; +1 < len(); ++ {
		if  > 0 {
			.WriteString(.prefix)
			.WriteString()
		}
		for ,  := range [[]:[+1]] {
			if  > 0 {
				.WriteString(" ")
			}
			.WriteString()
		}
		writeNL()
	}
}

// oneLongLine prints the text sequence x to out as one long line,
// without worrying about line wrapping.
// Explicit links have the [ ] dropped to improve readability.
func ( *textPrinter) ( *strings.Builder,  []Text) {
	for ,  := range  {
		switch t := .(type) {
		case Plain:
			.WriteString(string())
		case Italic:
			.WriteString(string())
		case *Link:
			.(, .Text)
		case *DocLink:
			.(, .Text)
		}
	}
}

// wrap wraps words into lines of at most max runes,
// minimizing the sum of the squares of the leftover lengths
// at the end of each line (except the last, of course),
// with a preference for ending lines at punctuation (.,:;).
//
// The returned slice gives the indexes of the first words
// on each line in the wrapped text with a final entry of len(words).
// Thus the lines are words[seq[0]:seq[1]], words[seq[1]:seq[2]],
// ..., words[seq[len(seq)-2]:seq[len(seq)-1]].
//
// The implementation runs in O(n log n) time, where n = len(words),
// using the algorithm described in D. S. Hirschberg and L. L. Larmore,
// “[The least weight subsequence problem],” FOCS 1985, pp. 137-143.
//
// [The least weight subsequence problem]: https://doi.org/10.1109/SFCS.1985.60
func wrap( []string,  int) ( []int) {
	// The algorithm requires that our scoring function be concave,
	// meaning that for all i₀ ≤ i₁ < j₀ ≤ j₁,
	// weight(i₀, j₀) + weight(i₁, j₁) ≤ weight(i₀, j₁) + weight(i₁, j₀).
	//
	// Our weights are two-element pairs [hi, lo]
	// ordered by elementwise comparison.
	// The hi entry counts the weight for lines that are longer than max,
	// and the lo entry counts the weight for lines that are not.
	// This forces the algorithm to first minimize the number of lines
	// that are longer than max, which correspond to lines with
	// single very long words. Having done that, it can move on to
	// minimizing the lo score, which is more interesting.
	//
	// The lo score is the sum for each line of the square of the
	// number of spaces remaining at the end of the line and a
	// penalty of 64 given out for not ending the line in a
	// punctuation character (.,:;).
	// The penalty is somewhat arbitrarily chosen by trying
	// different amounts and judging how nice the wrapped text looks.
	// Roughly speaking, using 64 means that we are willing to
	// end a line with eight blank spaces in order to end at a
	// punctuation character, even if the next word would fit in
	// those spaces.
	//
	// We care about ending in punctuation characters because
	// it makes the text easier to skim if not too many sentences
	// or phrases begin with a single word on the previous line.

	// A score is the score (also called weight) for a given line.
	// add and cmp add and compare scores.
	type  struct {
		 int64
		 int64
	}
	 := func(,  )  { return {. + ., . + .} }
	 := func(,  ) int {
		switch {
		case . < .:
			return -1
		case . > .:
			return +1
		case . < .:
			return -1
		case . > .:
			return +1
		}
		return 0
	}

	// total[j] is the total number of runes
	// (including separating spaces) in words[:j].
	 := make([]int, len()+1)
	[0] = 0
	for ,  := range  {
		[1+] = [] + utf8.RuneCountInString() + 1
	}

	// weight returns weight(i, j).
	 := func(,  int)  {
		// On the last line, there is zero weight for being too short.
		 := [] - 1 - []
		if  == len() &&  <=  {
			return {0, 0}
		}

		// Otherwise the weight is the penalty plus the square of the number of
		// characters remaining on the line or by which the line goes over.
		// In the latter case, that value goes in the hi part of the score.
		// (See note above.)
		 := wrapPenalty([-1])
		 := int64(-) * int64(-)
		if  >  {
			return {, }
		}
		return {0,  + }
	}

	// The rest of this function is “The Basic Algorithm” from
	// Hirschberg and Larmore's conference paper,
	// using the same names as in the paper.
	 := []{{0, 0}}
	 := func(,  int)  { return ([], (, )) }

	 := func(, ,  int) bool {
		 :=  + sort.Search(len()+1-, func( int) bool {
			 += 
			return ((, ), (, )) > 0
		})
		if  > len() {
			return true
		}
		return ((, ), (, )) <= 0
	}

	// d is a one-ended deque implemented as a slice.
	 := make([]int, 1, len())
	[0] = 0
	 := make([]int, 1, len())
	[0] = -1
	for  := 1;  < len(); ++ {
		 = append(, ([0], ))
		 = append(, [0])
		for len() > 1 && (([1], +1), ([0], +1)) <= 0 {
			 = [1:] // “Retire”
		}
		for len() > 1 && ([len()-2], [len()-1], ) {
			 = [:len()-1] // “Fire”
		}
		if ((, len()), ([len()-1], len())) < 0 {
			 = append(, ) // “Hire”
			// The next few lines are not in the paper but are necessary
			// to handle two-word inputs correctly. It appears to be
			// just a bug in the paper's pseudocode.
			if len() == 2 && (([1], +1), ([0], +1)) <= 0 {
				 = [1:]
			}
		}
	}
	 = append(, [0])

	// Recover least weight sequence from bestleft.
	 := 1
	for  := len();  > 0;  = [] {
		++
	}
	 = make([]int, )
	for  := len();  > 0;  = [] {
		--
		[] = 
	}
	return 
}

// wrapPenalty is the penalty for inserting a line break after word s.
func wrapPenalty( string) int64 {
	switch [len()-1] {
	case '.', ',', ':', ';':
		return 0
	}
	return 64
}