// 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 Printer is a doc comment printer.
// The fields in the struct can be filled in before calling
// any of the printing methods
// in order to customize the details of the printing process.
type Printer struct {
	// HeadingLevel is the nesting level used for
	// HTML and Markdown headings.
	// If HeadingLevel is zero, it defaults to level 3,
	// meaning to use <h3> and ###.
	HeadingLevel int

	// HeadingID is a function that computes the heading ID
	// (anchor tag) to use for the heading h when generating
	// HTML and Markdown. If HeadingID returns an empty string,
	// then the heading ID is omitted.
	// If HeadingID is nil, h.DefaultID is used.
	HeadingID func(h *Heading) string

	// DocLinkURL is a function that computes the URL for the given DocLink.
	// If DocLinkURL is nil, then link.DefaultURL(p.DocLinkBaseURL) is used.
	DocLinkURL func(link *DocLink) string

	// DocLinkBaseURL is used when DocLinkURL is nil,
	// passed to [DocLink.DefaultURL] to construct a DocLink's URL.
	// See that method's documentation for details.
	DocLinkBaseURL string

	// TextPrefix is a prefix to print at the start of every line
	// when generating text output using the Text method.
	TextPrefix string

	// TextCodePrefix is the prefix to print at the start of each
	// preformatted (code block) line when generating text output,
	// instead of (not in addition to) TextPrefix.
	// If TextCodePrefix is the empty string, it defaults to TextPrefix+"\t".
	TextCodePrefix string

	// TextWidth is the maximum width text line to generate,
	// measured in Unicode code points,
	// excluding TextPrefix and the newline character.
	// If TextWidth is zero, it defaults to 80 minus the number of code points in TextPrefix.
	// If TextWidth is negative, there is no limit.
	TextWidth int
}

func ( *Printer) () int {
	if .HeadingLevel <= 0 {
		return 3
	}
	return .HeadingLevel
}

func ( *Printer) ( *Heading) string {
	if .HeadingID == nil {
		return .DefaultID()
	}
	return .HeadingID()
}

func ( *Printer) ( *DocLink) string {
	if .DocLinkURL != nil {
		return .DocLinkURL()
	}
	return .DefaultURL(.DocLinkBaseURL)
}

// DefaultURL constructs and returns the documentation URL for l,
// using baseURL as a prefix for links to other packages.
//
// The possible forms returned by DefaultURL are:
//   - baseURL/ImportPath, for a link to another package
//   - baseURL/ImportPath#Name, for a link to a const, func, type, or var in another package
//   - baseURL/ImportPath#Recv.Name, for a link to a method in another package
//   - #Name, for a link to a const, func, type, or var in this package
//   - #Recv.Name, for a link to a method in this package
//
// If baseURL ends in a trailing slash, then DefaultURL inserts
// a slash between ImportPath and # in the anchored forms.
// For example, here are some baseURL values and URLs they can generate:
//
//	"/pkg/" → "/pkg/math/#Sqrt"
//	"/pkg"  → "/pkg/math#Sqrt"
//	"/"     → "/math/#Sqrt"
//	""      → "/math#Sqrt"
func ( *DocLink) ( string) string {
	if .ImportPath != "" {
		 := ""
		if strings.HasSuffix(, "/") {
			 = "/"
		} else {
			 += "/"
		}
		switch {
		case .Name == "":
			return  + .ImportPath + 
		case .Recv != "":
			return  + .ImportPath +  + "#" + .Recv + "." + .Name
		default:
			return  + .ImportPath +  + "#" + .Name
		}
	}
	if .Recv != "" {
		return "#" + .Recv + "." + .Name
	}
	return "#" + .Name
}

// DefaultID returns the default anchor ID for the heading h.
//
// The default anchor ID is constructed by converting every
// rune that is not alphanumeric ASCII to an underscore
// and then adding the prefix “hdr-”.
// For example, if the heading text is “Go Doc Comments”,
// the default ID is “hdr-Go_Doc_Comments”.
func ( *Heading) () string {
	// Note: The “hdr-” prefix is important to avoid DOM clobbering attacks.
	// See https://pkg.go.dev/github.com/google/safehtml#Identifier.
	var  strings.Builder
	var  textPrinter
	.oneLongLine(&, .Text)
	 := strings.TrimSpace(.String())
	if  == "" {
		return ""
	}
	.Reset()
	.WriteString("hdr-")
	for ,  := range  {
		if  < 0x80 && isIdentASCII(byte()) {
			.WriteByte(byte())
		} else {
			.WriteByte('_')
		}
	}
	return .String()
}

type commentPrinter struct {
	*Printer
}

// Comment returns the standard Go formatting of the [Doc],
// without any comment markers.
func ( *Printer) ( *Doc) []byte {
	 := &commentPrinter{Printer: }
	var  bytes.Buffer
	for ,  := range .Content {
		if  > 0 && blankBefore() {
			.WriteString("\n")
		}
		.block(&, )
	}

	// Print one block containing all the link definitions that were used,
	// and then a second block containing all the unused ones.
	// This makes it easy to clean up the unused ones: gofmt and
	// delete the final block. And it's a nice visual signal without
	// affecting the way the comment formats for users.
	for  := 0;  < 2; ++ {
		 :=  == 0
		 := true
		for ,  := range .Links {
			if .Used ==  {
				if  {
					.WriteString("\n")
					 = false
				}
				.WriteString("[")
				.WriteString(.Text)
				.WriteString("]: ")
				.WriteString(.URL)
				.WriteString("\n")
			}
		}
	}

	return .Bytes()
}

// blankBefore reports whether the block x requires a blank line before it.
// All blocks do, except for Lists that return false from x.BlankBefore().
func blankBefore( Block) bool {
	if ,  := .(*List);  {
		return .BlankBefore()
	}
	return true
}

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

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

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

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

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

// text prints the text sequence x to out.
func ( *commentPrinter) ( *bytes.Buffer,  string,  []Text) {
	for ,  := range  {
		switch t := .(type) {
		case Plain:
			.indent(, , string())
		case Italic:
			.indent(, , string())
		case *Link:
			if .Auto {
				.(, , .Text)
			} else {
				.WriteString("[")
				.(, , .Text)
				.WriteString("]")
			}
		case *DocLink:
			.WriteString("[")
			.(, , .Text)
			.WriteString("]")
		}
	}
}

// indent prints s to out, indenting with the indent string
// after each newline in s.
func ( *commentPrinter) ( *bytes.Buffer, ,  string) {
	for  != "" {
		, ,  := strings.Cut(, "\n")
		.WriteString()
		if  {
			.WriteString("\n")
			.WriteString()
		}
		 = 
	}
}