package textmeasure

import (
	
	
	
	

	
	
	goldmarkHtml 
	

	

	
)

var markdownRenderer goldmark.Markdown

// these are css values from github-markdown.css so we can accurately compute the rendered dimensions
const (
	MarkdownFontSize   = d2fonts.FONT_SIZE_M
	MarkdownLineHeight = 1.5

	PaddingLeft_ul_ol_em = 2.
	MarginBottom_ul      = 16.

	MarginTop_li_p  = 16.
	MarginTop_li_em = 0.25
	MarginBottom_p  = 16.

	LineHeight_h           = 1.25
	MarginTop_h            = 24
	MarginBottom_h         = 16
	PaddingBottom_h1_h2_em = 0.3
	BorderBottom_h1_h2     = 1

	Height_hr_em       = 0.25
	MarginTopBottom_hr = 24

	Padding_pre          = 16
	MarginBottom_pre     = 16
	MarginBottom_table   = 16
	LineHeight_pre       = 1.45
	FontSize_pre_code_em = 0.85

	PaddingTopBottom_code_em         = 0.2
	PaddingLeftRight_code_em         = 0.4
	PaddingLeftRight_heading_code_em = 0.2

	PaddingLR_blockquote_em  = 1.
	MarginBottom_blockquote  = 16
	BorderLeft_blockquote_em = 0.25

	h1_em = 2.
	h2_em = 1.5
	h3_em = 1.25
	h4_em = 1.
	h5_em = 0.875
	h6_em = 0.85
)

func ( int,  string) int {
	if !isMarkdownHeading() {
		return 0
	}
	return int(HeaderToFontScale() * float64())
}

// HeaderToFontScale returns github-markdown.css's exact heading size. The
// legacy measurement path keeps HeaderToFontSize's integer truncation so graph
// dimensions remain stable, while native SVG painting uses this scale and can
// reproduce fractional CSS sizes such as h6's 13.6px at a 16px base.
func ( string) float64 {
	switch  {
	case "h1":
		return h1_em
	case "h2":
		return h2_em
	case "h3":
		return h3_em
	case "h4":
		return h4_em
	case "h5":
		return h5_em
	case "h6":
		return h6_em
	default:
		return 1
	}
}

func isMarkdownHeading( string) bool {
	switch  {
	case "h1", "h2", "h3", "h4", "h5", "h6":
		return true
	default:
		return false
	}
}

func hasMarkdownHeadingAncestor( *html.Node) bool {
	for  := .Parent;  != nil;  = .Parent {
		if .Type == html.ElementNode && isMarkdownHeading(.Data) {
			return true
		}
	}
	return false
}

func ( string) (string, error) {
	var  bytes.Buffer
	if  := markdownRenderer.Convert([]byte(), &);  != nil {
		return "", 
	}
	,  := sanitizeLinks(.String())
	if  != nil {
		return "", 
	}
	return , nil
}

func init() {
	markdownRenderer = goldmark.New(
		goldmark.WithRendererOptions(
			goldmarkHtml.WithUnsafe(),
			goldmarkHtml.WithXHTML(),
		),
		goldmark.WithExtensions(
			extension.Strikethrough,
			extension.Table,
		),
	)
}

func ( string,  *Ruler,  *d2fonts.FontFamily,  *d2fonts.FontFamily,  int) (,  int,  error) {
	,  := LayoutMarkdown(, , , , )
	if  != nil {
		return 0, 0, 
	}
	return .Width, .Height, nil
}

func hasPrev( *html.Node) bool {
	if .PrevSibling == nil {
		return false
	}
	if strings.TrimSpace(.PrevSibling.Data) == "" {
		return (.PrevSibling)
	}
	return true
}

func hasNext( *html.Node) bool {
	if .NextSibling == nil {
		return false
	}
	// skip over empty text nodes
	if strings.TrimSpace(.NextSibling.Data) == "" {
		return (.NextSibling)
	}
	return true
}

func getPrev( *html.Node) *html.Node {
	if  == nil {
		return nil
	}
	if strings.TrimSpace(.Data) == "" {
		if  := getNext(.PrevSibling);  != nil {
			return 
		}
	}
	return 
}

func getNext( *html.Node) *html.Node {
	if  == nil {
		return nil
	}
	if strings.TrimSpace(.Data) == "" {
		if  := (.NextSibling);  != nil {
			return 
		}
	}
	return 
}

func isBlockElement( string) bool {
	switch  {
	case "blockquote",
		"div",
		"h1", "h2", "h3", "h4", "h5", "h6",
		"hr",
		"li",
		"ol",
		"p",
		"pre",
		"ul",
		"table", "thead", "tbody", "tr", "td", "th": // Added table elements here
		return true
	default:
		return false
	}
}

func hasAncestorElement( *html.Node,  string) bool {
	if .Parent == nil {
		return false
	}
	if .Parent.Type == html.ElementNode && .Parent.Data ==  {
		return true
	}
	return (.Parent, )
}

type blockAttrs struct {
	width, height, marginTop, marginBottom float64
	extraData                              interface{}
}

func ( *blockAttrs) () bool {
	return  != nil && * != blockAttrs{}
}

func isCollapsibleMarkdownWhitespace( rune) bool {
	switch  {
	case ' ', '\t', '\n', '\r', '\f':
		return true
	default:
		return false
	}
}

// normalizeMarkdownWhitespaceTree applies CSS white-space: normal across
// inline element boundaries. A per-text-node pass is insufficient: HTML such
// as a<a> b</a> must preserve one space, while whitespace next to a block or
// explicit line break must disappear. Preformatted code is the sole Markdown
// context that retains source whitespace.
func normalizeMarkdownWhitespaceTree( *html.Node) {
	type  struct {
		 bool
		     *strings.Builder
	}

	 := make(map[*html.Node]*strings.Builder)
	 := func( *html.Node) *strings.Builder {
		if  := [];  != nil {
			return 
		}
		 := &strings.Builder{}
		.Grow(len(.Data))
		[] = 
		return 
	}
	 := func( *) {
		if . != nil && !. {
			..WriteByte(' ')
		}
		. = nil
	}

	var  func(*html.Node)
	var  func(*html.Node, *)
	 = func( *html.Node,  *) {
		switch .Type {
		case html.TextNode:
			 := ()
			for ,  := range .Data {
				if isCollapsibleMarkdownWhitespace() {
					if . == nil {
						. = 
					}
					continue
				}
				()
				.WriteRune()
				. = false
			}
		case html.ElementNode:
			switch {
			case .Data == "br":
				. = nil
				. = true
			case isBlockElement(.Data):
				. = nil
				()
				. = true
			case .Data == "img":
				// Markdown images are outside native SVG's supported subset.
				// Ignore the node and let whitespace collapse across it.
			default:
				for  := .FirstChild;  != nil;  = .NextSibling {
					(, )
				}
			}
		}
	}
	 = func( *html.Node) {
		if .Type == html.ElementNode && .Data == "pre" {
			return
		}
		 := {: true}
		for  := .FirstChild;  != nil;  = .NextSibling {
			(, &)
		}
		// A pending space at the end of a block or line is not painted.
		. = nil
	}

	()
	for ,  := range  {
		.Data = .String()
	}
}

// measures node dimensions to match rendering with styles in github-markdown.css
func ( *Ruler) ( int,  *html.Node,  *d2fonts.FontFamily,  *d2fonts.FontFamily,  int,  d2fonts.FontStyle, ,  bool) blockAttrs {
	return .measureMarkdownNode(, , , , , 1, , , , false)
}

// measureCSSNode uses CSS line boxes and fractional heading scales for native
// SVG painting. measureNode above intentionally retains D2's historical box
// dimensions, which are part of graph layout compatibility.
func ( *Ruler) ( int,  *html.Node,  *d2fonts.FontFamily,  *d2fonts.FontFamily,  int,  float64,  d2fonts.FontStyle, ,  bool) blockAttrs {
	return .measureMarkdownNode(, , , , , , , , , true)
}

func ( *Ruler) ( int,  *html.Node,  *d2fonts.FontFamily,  *d2fonts.FontFamily,  int,  float64,  d2fonts.FontStyle, , ,  bool) blockAttrs {
	if  == nil {
		 = go2.Pointer(d2fonts.SourceSansPro)
	}
	 := .Font(, )

	var  string
	if .Parent != nil && .Parent.Type == html.ElementNode {
		 = .Parent.Data
	}

	 := false
	var  string
	if  {
		if  == 0 {
			fmt.Println()
		}
		 = "┌"
		for  := 0;  < ; ++ {
			 += "-"
		}
	}

	switch .Type {
	case html.TextNode:
		if ! && strings.Trim(.Data, "\n\t\b") == "" {
			return blockAttrs{}
		}
		 := .Data
		 :=  == "pre" ||  == "code"
		 :=  == "code" && hasMarkdownHeadingAncestor()
		 := 0.

		if ! {
			 := .spaceWidth() * 
			if  {
				if ,  := .measureFontAdvance(, " ");  {
					 =  * 
				}
			}
			if  {
				 = renderableMarkdownText()
				if  == "" {
					return blockAttrs{}
				}
				// MeasurePrecise omits edge whitespace, so account for it.
				if strings.HasPrefix(, " ") {
					 = strings.TrimPrefix(, " ")
					 += 
				}
				if strings.HasSuffix(, " ") {
					 = strings.TrimSuffix(, " ")
					 += 
				}
			} else {
				 = strings.ReplaceAll(, "\n", " ")
				 = strings.ReplaceAll(, "\t", " ")
				if strings.HasPrefix(, " ") {
					 = strings.TrimPrefix(, " ")
					if hasPrev() {
						 += 
					}
				}
				if strings.HasSuffix(, " ") {
					 = strings.TrimSuffix(, " ")
					if hasNext() {
						 += 
					}
				}
			}
		} else if  == "" {
			return blockAttrs{}
		}

		if  == "pre" {
			 := .LineHeightFactor
			.LineHeightFactor = LineHeight_pre
			defer func() {
				.LineHeightFactor = 
			}()
		}
		,  := .MeasurePrecise(, )
		if  {
			// CSS lays out adjacent inline runs by cursor advance, not glyph ink
			// bounds. Use the same advance as the SVG painter so table columns,
			// centered cells, and styled spans share one coordinate system.
			if ,  := .measureFontAdvance(, );  {
				 = 
			} else {
				 = .scaleUnicodeCSS(, , )
			}
		} else if ! {
			// The browser-era Markdown measurer applied its Unicode fallback
			// correction only to proportional text. Preserve the raw mono
			// measurement for legacy graph dimensions; native painting still
			// reserves browser-like fallback advances above.
			 = .scaleUnicodeLegacy(, , )
		}
		 *= 
		 *= 
		if  && (! || !) {
			 *= FontSize_pre_code_em
			 *= FontSize_pre_code_em
		}
		if  {
			fmt.Printf("%stext(%v,%v)\n", , , )
		}
		return blockAttrs{ + , , 0, 0, 0}
	case html.ElementNode:
		 := false
		switch .Data {
		case "h1", "h2", "h3", "h4", "h5", "h6":
			if  {
				 *= HeaderToFontScale(.Data)
			} else {
				 = HeaderToFontSize(, .Data)
			}
			 = d2fonts.FONT_STYLE_SEMIBOLD
			 = false
			 := .LineHeightFactor
			.LineHeightFactor = LineHeight_h
			defer func() {
				.LineHeightFactor = 
			}()
		case "em":
			 = d2fonts.FONT_STYLE_ITALIC
			 = true
		case "b", "strong":
			 = d2fonts.FONT_STYLE_BOLD
			 = true
		case "th":
			 = d2fonts.FONT_STYLE_SEMIBOLD
			 = true
		case "pre", "code":
			if  != nil {
				 = 
			} else {
				 = go2.Pointer(d2fonts.SourceCodePro)
			}
			// .md code selects the regular mono family, while CSS weight/style
			// continue to inherit from surrounding strong/em elements.
			 = d2fonts.FONT_STYLE_REGULAR
			 = true
		}

		 := blockAttrs{}
		 := float64() * 
		 :=  * .LineHeightFactor

		if .FirstChild != nil {
			 := getNext(.FirstChild)
			 := getPrev(.LastChild)

			var  []blockAttrs
			var  *blockAttrs
			// first create blocks from combined inline elements, then combine all blocks
			// inlineBlock will be non-nil while inline elements are being combined into a block
			 := func() {
				if ! && .height > 0 {
					if  {
						.height = 
					} else if .height <  {
						.height = 
					}
				}
				 = append(, *)
				 = nil
			}
			for  := .FirstChild;  != nil;  = .NextSibling {
				 := .(+1, , , , , , , , , )

				if .Type == html.ElementNode && isBlockElement(.Data) {
					if  != nil {
						()
					}
					 := &blockAttrs{}
					.width = .width
					.height = .height
					if  ==  && .Data == "blockquote" {
						.marginTop = 0.
					} else {
						.marginTop = .marginTop
					}
					if  ==  && .Data == "blockquote" {
						.marginBottom = 0.
					} else {
						.marginBottom = .marginBottom
					}

					 = append(, *)
				} else if .Type == html.ElementNode && .Data == "br" {
					if  != nil {
						()
					} else {
						.height += 
					}
				} else if .isNotEmpty() {
					if  == nil {
						// start inline block with child
						 = &
					} else {
						// stack inline element dimensions horizontally
						.width += .width
						.height = go2.Max(.height, .height)

						.marginTop = go2.Max(.marginTop, .marginTop)
						.marginBottom = go2.Max(.marginBottom, .marginBottom)
					}
				}
			}
			if  != nil {
				()
			}

			var  float64
			for ,  := range  {
				if  == 0 {
					.marginTop = go2.Max(.marginTop, .marginTop)
				} else {
					 := .marginTop - 
					if  > 0 {
						.height += 
					}
				}
				if  == len()-1 {
					.marginBottom = go2.Max(.marginBottom, .marginBottom)
				} else {
					.height += .marginBottom
					 = .marginBottom
				}

				.height += .height
				.width = go2.Max(.width, .width)
			}
		}

		switch .Data {
		case "img":
			// Preserve the browser-era measurement contract: image nodes have no
			// intrinsic dimensions here. Native SVG omits them rather than fetching.
		case "blockquote":
			 := BorderLeft_blockquote_em * 
			if  {
				 = go2.Max(1, math.Floor())
			}
			.width += 2*PaddingLR_blockquote_em* + 
			.marginBottom = go2.Max(.marginBottom, MarginBottom_blockquote)
		case "p":
			if  == "li" {
				.marginTop = go2.Max(.marginTop, MarginTop_li_p)
			}
			.marginBottom = go2.Max(.marginBottom, MarginBottom_p)
		case "h1", "h2", "h3", "h4", "h5", "h6":
			.marginTop = go2.Max(.marginTop, MarginTop_h)
			.marginBottom = go2.Max(.marginBottom, MarginBottom_h)
			switch .Data {
			case "h1", "h2":
				.height += PaddingBottom_h1_h2_em* + BorderBottom_h1_h2
			}
		case "li":
			.width += PaddingLeft_ul_ol_em * 
			// CSS paints an empty list marker in a line box, but D2's historical
			// graph-layout measurement intentionally gives an empty item no height.
			// Preserve that legacy viewport while letting the CSS-only paint pass
			// position the (ultimately clipped) marker like Chromium.
			if  && .height == 0 {
				.height = 
			}
			if hasPrev() {
				.marginTop = go2.Max(.marginTop, MarginTop_li_em*)
			}
		case "ol", "ul":
			if hasAncestorElement(, "ul") || hasAncestorElement(, "ol") {
				.marginTop = 0
				.marginBottom = 0
			} else {
				.marginBottom = go2.Max(.marginBottom, MarginBottom_ul)
			}
		case "pre":
			// CSS white-space: pre creates one line box per logical line, except
			// that a final newline does not create an extra empty line box. Glyph
			// bounds alone undercount leading and blank lines. The compatibility
			// measurement keeps its historical glyph-bound height; only native
			// painting uses the exact CSS content height.
			if  := markdownPreLineCount(markdownNodeText());  &&  > 0 {
				 := float64() * LineHeight_pre * FontSize_pre_code_em * 
				.height = go2.Max(.height, )
			}
			.width += 2 * Padding_pre
			.height += 2 * Padding_pre
			.marginBottom = go2.Max(.marginBottom, MarginBottom_pre)
		case "code":
			if  != "pre" {
				if  && hasMarkdownHeadingAncestor() {
					.width += 2 * PaddingLeftRight_heading_code_em * 
					.height = 
				} else if  {
					 := FontSize_pre_code_em * 
					.width += 2 * PaddingLeftRight_code_em * 
					if , ,  := .cssFontBoxMetrics(.Font(, ), );  {
						.height =  + 
					}
					.height += 2 * PaddingTopBottom_code_em * 
				} else {
					.width += 2 * PaddingLeftRight_code_em * 
					.height += 2 * PaddingTopBottom_code_em * 
				}
			}
		case "hr":
			.height += Height_hr_em * 
			.marginTop = go2.Max(.marginTop, MarginTopBottom_hr)
			.marginBottom = go2.Max(.marginBottom, MarginTopBottom_hr)
		case "table":
			var  []float64
			var  float64

			// Border width for table (outer border)
			 := 1.0

			// Iterate over child nodes (tbody, thead, tr)
			for  := .FirstChild;  != nil;  = .NextSibling {
				if .Type == html.ElementNode && (.Data == "tbody" || .Data == "thead" || .Data == "tfoot") {
					 := .(+1, , , , , , , , , )
					 += .height

					if ,  := .extraData.([][]float64);  {
						 = mergeColumnWidths(, )
					}
				} else if .Type == html.ElementNode && .Data == "tr" {
					 := .(+1, , , , , , , , , )
					 += .height

					if ,  := .extraData.([]float64);  {
						 = mergeColumnWidths(, [][]float64{})
					}
				}
			}

			// Calculate total table width including ALL borders
			 := 0.0
			if len() > 0 {
				// Add widths of all columns
				for ,  := range  {
					 += 
				}

				// Add border for every column division (including outer borders)
				 += float64(len()+1) * 
			}

			// Add outer borders to height
			 += 2 * 

			.width = 
			.height = 
			if  {
				.marginBottom = go2.Max(.marginBottom, MarginBottom_table)
			}

		case "thead", "tbody", "tfoot":
			var ,  float64
			var  [][]float64

			// Iterate over tr elements
			for  := .FirstChild;  != nil;  = .NextSibling {
				if .Type == html.ElementNode && .Data == "tr" {
					 := .(+1, , , , , , , , , )
					 += .height
					 = go2.Max(, .width)

					if ,  := .extraData.([]float64);  {
						 = append(, )
					}
				}
			}

			.width = 
			.height = 
			.extraData =  // Pass column widths back to table

		case "td", "th":
			if ! {
				// Preserve D2's historical graph-layout measurement for mixed
				// inline table-cell children: the widest child determines width,
				// while child heights accumulate. Browser-style painting uses the
				// generic inline aggregation calculated above.
				var ,  float64
				for  := .FirstChild;  != nil;  = .NextSibling {
					 := .(
						+1, , , , ,
						, , , , false,
					)
					 = go2.Max(, .width)
					 += .height
				}
				.width = 
				.height = 
			}

		case "tr":
			var ,  float64
			var  []float64

			 := 1.0
			 := 1.0

			 := 0.0
			 := 0

			// Check if this row is in a thead to determine default font style for cells
			 := hasAncestorElement(, "thead")
			 := 
			 := 
			if  {
				 = d2fonts.FONT_STYLE_SEMIBOLD
				 = true
			}

			for  := .FirstChild;  != nil;  = .NextSibling {
				if .Type == html.ElementNode && (.Data == "td" || .Data == "th") {
					++

					// Use semibold for th elements regardless of location
					 := 
					 := 
					if .Data == "th" {
						 = d2fonts.FONT_STYLE_SEMIBOLD
						 = true
					}

					 := .(+1, , , , , , , , , )
					 := 13.0 * 2
					 := 6.0 * 2

					 := .width + 
					 := .height + 

					 = append(, )
					 = go2.Max(, )
				}
			}

			if  > 0 {
				for ,  := range  {
					 += 
				}
				 += float64(+1) * 
			}

			 =  + 

			.width = 
			.height = 
			.extraData = 
		}
		if ! && .height > 0 && .height <  {
			.height = 
		}
		if  {
			fmt.Printf("%s%s(%v,%v) mt:%v mb:%v\n", , .Data, .width, .height, .marginTop, .marginBottom)
		}
		return 
	}
	return blockAttrs{}
}

func markdownPreLineCount( string) int {
	if  == "" {
		return 0
	}
	 := strings.Count(, "\n") + 1
	if strings.HasSuffix(, "\n") {
		--
	}
	return 
}

func mergeColumnWidths( []float64,  [][]float64) []float64 {
	for ,  := range  {
		for ,  := range  {
			if  >= len() {
				 = append(, )
			} else {
				[] = go2.Max([], )
			}
		}
	}
	return 
}