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
	LineHeight_pre       = 1.45
	FontSize_pre_code_em = 0.85

	PaddingTopBottom_code_em = 0.2
	PaddingLeftRight_code_em = 0.4

	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 {
	switch  {
	case "h1":
		return int(h1_em * float64())
	case "h2":
		return int(h2_em * float64())
	case "h3":
		return int(h3_em * float64())
	case "h4":
		return int(h4_em * float64())
	case "h5":
		return int(h5_em * float64())
	case "h6":
		return int(h6_em * float64())
	}
	return 0
}

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,  int) (,  int,  error) {
	,  := RenderMarkdown()
	if  != nil {
		return , , 
	}

	,  := goquery.NewDocumentFromReader(strings.NewReader())
	if  != nil {
		return , , 
	}

	{
		 := .LineHeightFactor
		.boundsWithDot = true
		.LineHeightFactor = MarkdownLineHeight
		defer func() {
			.LineHeightFactor = 
			.boundsWithDot = false
		}()
	}

	// TODO consider setting a max width + (manual) text wrapping
	 := .Find("body").First().Nodes[0]
	 := .measureNode(0, , , , d2fonts.FONT_STYLE_REGULAR)

	return int(math.Ceil(.width)), int(math.Ceil(.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{}
}

// measures node dimensions to match rendering with styles in github-markdown.css
func ( *Ruler) ( int,  *html.Node,  *d2fonts.FontFamily,  int,  d2fonts.FontStyle) 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"
		 := 0.

		if ! {
			 := .spaceWidth()
			// MeasurePrecise will not include leading or trailing whitespace, so we account for it here
			 = strings.ReplaceAll(, "\n", " ")
			 = strings.ReplaceAll(, "\t", " ")
			if strings.HasPrefix(, " ") {
				// consecutive leading/trailing spaces end up rendered as a single space
				 = strings.TrimPrefix(, " ")
				if hasPrev() {
					 += 
				}
			}
			if strings.HasSuffix(, " ") {
				 = strings.TrimSuffix(, " ")
				if hasNext() {
					 += 
				}
			}
		}

		if  == "pre" {
			 := .LineHeightFactor
			.LineHeightFactor = LineHeight_pre
			defer func() {
				.LineHeightFactor = 
			}()
		}
		,  := .MeasurePrecise(, )
		if  {
			 *= FontSize_pre_code_em
			 *= FontSize_pre_code_em
		} else {
			 = .scaleUnicode(, , )
		}
		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":
			 = HeaderToFontSize(, .Data)
			 = d2fonts.FONT_STYLE_SEMIBOLD
			 := .LineHeightFactor
			.LineHeightFactor = LineHeight_h
			defer func() {
				.LineHeightFactor = 
			}()
		case "em":
			 = d2fonts.FONT_STYLE_ITALIC
		case "b", "strong":
			 = d2fonts.FONT_STYLE_BOLD
		case "pre", "code":
			 = go2.Pointer(d2fonts.SourceCodePro)
			 = 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 && .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 "blockquote":
			.width += (2*PaddingLR_blockquote_em + BorderLeft_blockquote_em) * float64()
			.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*float64() + BorderBottom_h1_h2
			}
		case "li":
			.width += PaddingLeft_ul_ol_em * float64()
			if hasPrev() {
				.marginTop = go2.Max(.marginTop, MarginTop_li_em*float64())
			}
		case "ol", "ul":
			if hasAncestorElement(, "ul") || hasAncestorElement(, "ol") {
				.marginTop = 0
				.marginBottom = 0
			} else {
				.marginBottom = go2.Max(.marginBottom, MarginBottom_ul)
			}
		case "pre":
			.width += 2 * Padding_pre
			.height += 2 * Padding_pre
			.marginBottom = go2.Max(.marginBottom, MarginBottom_pre)
		case "code":
			if  != "pre" {
				.width += 2 * PaddingLeftRight_code_em * float64()
				.height += 2 * PaddingTopBottom_code_em * float64()
			}
		case "hr":
			.height += Height_hr_em * float64()
			.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 = 

		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":
			// Apply semibold style to header cells
			 := 
			if .Data == "th" {
				 = d2fonts.FONT_STYLE_SEMIBOLD
			}

			// Measure cell content with appropriate font style
			var ,  float64

			for  := .FirstChild;  != nil;  = .NextSibling {
				// Pass the header-specific font style to child measurements
				 := .(+1, , , , )
				 = 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
			}

			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
					}

					 := .(+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 mergeColumnWidths( []float64,  [][]float64) []float64 {
	for ,  := range  {
		for ,  := range  {
			if  >= len() {
				 = append(, )
			} else {
				[] = go2.Max([], )
			}
		}
	}
	return 
}