package d2scenebuild

import (
	
	
	
	
	

	
	
	

	
	
	
	libcolor 
	
	
	
)

const (
	// Markdown is already bounded by the compiler's source input in ordinary
	// CLI use, but scene construction is also a public API. These ceilings keep
	// a single rich label from turning into unbounded Goldmark/layout work or an
	// arbitrarily large retained scene.
	maxMarkdownSourceBytes = 1 << 20
	maxMarkdownPrimitives  = 100_000
)

func validateMarkdownLabel(,  string, , ,  int) error {
	if !utf8.ValidString() {
		return invalidField(, "label", nil, "must be valid UTF-8")
	}
	if len() > maxMarkdownSourceBytes {
		return invalidField(, "label", len(), fmt.Sprintf("Markdown source bytes must not exceed %d", maxMarkdownSourceBytes))
	}
	if  > 0 &&  > 0 &&  <= 0 {
		return invalidField(, "fontSize", , "must be positive for a Markdown label")
	}
	return nil
}

func ( *builder) ( d2target.Shape) ([]*d2scene.Node, error) {
	 := markdownShapeLabelTopLeft()
	if  == nil {
		return nil, invalidField(fmt.Sprintf("shape %q", .ID), "labelPosition", .LabelPosition, "does not resolve to a finite Markdown viewport")
	}
	,  := .buildMarkdownLabel(
		fmt.Sprintf("shape %q", .ID), .ID+":markdown",
		.Label, .FontFamily, .FontSize,
		.LabelWidth, .LabelHeight,
		.GetFontColor(), .Fill,
		.Link != "", .Underline, ,
	)
	if  != nil {
		return nil, 
	}
	return []*d2scene.Node{}, nil
}

func ( *builder) ( d2target.Connection,  *geo.Point) (*d2scene.Node, error) {
	return .buildMarkdownLabel(
		fmt.Sprintf("connection %q", .ID), .ID+":markdown",
		.Label, .FontFamily, .FontSize,
		.LabelWidth, .LabelHeight,
		.GetFontColor(), .Fill,
		.Link != "", .Underline, ,
	)
}

func markdownShapeLabelTopLeft( d2target.Shape) *geo.Point {
	 := label.FromString(.LabelPosition)
	if  == label.Unset {
		 = label.InsideMiddleCenter
	}
	 := targetGeometry()
	var  *geo.Box
	if .IsOutside() || .IsBorder() {
		 = .GetBox().Copy()
		if .ThreeDee {
			 := d2target.THREE_DEE_OFFSET
			if .Type == d2target.ShapeHexagon {
				 /= 2
			}
			.TopLeft.Y -= float64()
			.Height += float64()
			.Width += float64(d2target.THREE_DEE_OFFSET)
		} else if .Multiple {
			.TopLeft.Y -= float64(d2target.MULTIPLE_OFFSET)
			.Height += float64(d2target.MULTIPLE_OFFSET)
			.Width += float64(d2target.MULTIPLE_OFFSET)
		}
	} else {
		 = .GetInnerBox()
	}
	return .GetPointOnBox(, label.PADDING, float64(.LabelWidth), float64(.LabelHeight))
}

func ( *builder) (
	, , ,  string,
	, ,  int,
	,  string,
	,  bool,
	 *geo.Point,
) (*d2scene.Node, error) {
	if  := .ctx.Err();  != nil {
		return nil, 
	}
	 := d2scene.NewNode(nil)
	.ID = 
	.Transform = d2scene.Translate(.X, .Y)
	,  := .markdownFontFamilies()
	,  := .markdownLayoutRuler()
	if  != nil {
		return nil, fmt.Errorf("scene: %s Markdown ruler: %w", , )
	}
	 := 
	if  <= 0 {
		// Zero-sized empty Markdown labels may carry a zero font size in frozen
		// targets. Still parse and validate their source with D2's
		// normal body size before discarding their non-painting viewport.
		 = d2fonts.FONT_SIZE_M
	}
	,  := textmeasure.LayoutMarkdown(, , &, &, )
	if  != nil {
		return nil, fmt.Errorf("scene: %s Markdown layout: %w", , )
	}
	if len(.Primitives) > maxMarkdownPrimitives {
		return nil, fmt.Errorf("scene: %s Markdown primitive count %d exceeds limit %d", , len(.Primitives), maxMarkdownPrimitives)
	}
	if  := .ctx.Err();  != nil {
		return nil, 
	}
	// A zero-sized Markdown viewport cannot paint or expose interactive hit
	// regions. Represent it as an empty group without a degenerate clip.
	if  == 0 ||  == 0 {
		return , nil
	}

	,  := .markdownRolePaints()
	if  != nil {
		return nil, fmt.Errorf("scene: %s Markdown paints: %w", , )
	}
	 := d2scene.Box{Width: float64(), Height: float64()}
	.Clip = boxClip()

	if  != "" && !strings.EqualFold(, libcolor.None) && !strings.EqualFold(, "transparent") {
		,  := .paint(, +" Markdown background")
		if  != nil {
			return nil, 
		}
		if  != nil {
			 := d2scene.NewNode(d2scene.Rect{Box: , Fill: })
			.ID =  + ":background"
			.Children = append(.Children, )
		}
	}

	for ,  := range .Primitives {
		if &255 == 0 {
			if  := .ctx.Err();  != nil {
				return nil, 
			}
		}
		, ,  := .buildMarkdownPrimitive(, , , , , , )
		if  != nil {
			return nil, fmt.Errorf("scene: %s Markdown primitive %d: %w", , , )
		}
		if  != nil {
			.Children = append(.Children, )
		}
		 := textmeasure.SafeMarkdownLink(.Link)
		if  ||  == "" || !.Valid {
			continue
		}
		 = intersectMarkdownBounds(, .Bounds())
		if !.Valid {
			continue
		}
		if  := .addMarkdownLinkRegion(, , , .LinkTitle, .Translate(d2scene.Point{X: .X, Y: .Y}).Box());  != nil {
			return nil, 
		}
	}
	return , nil
}

func ( *builder) () (*textmeasure.Ruler, error) {
	if .markdownRuler == nil && .markdownRulerErr == nil {
		.markdownRuler, .markdownRulerErr = textmeasure.NewRuler()
	}
	return .markdownRuler, .markdownRulerErr
}

func ( *builder) ( string) (d2fonts.FontFamily, d2fonts.FontFamily) {
	 := d2fonts.SourceSansPro
	if .diagram.FontFamily != nil {
		 = *.diagram.FontFamily
	}
	 := d2fonts.SourceCodePro
	if .diagram.MonoFontFamily != nil {
		 = *.diagram.MonoFontFamily
	}
	if strings.EqualFold(, "mono") {
		 = 
	} else if  != "" && !strings.EqualFold(, "default") {
		if ,  := d2fonts.D2_FONT_TO_FAMILY[strings.ToLower()];  {
			 = 
		}
	}
	return , 
}

func ( *builder) ( string) (map[textmeasure.MarkdownColorRole]d2scene.Paint, error) {
	 := map[textmeasure.MarkdownColorRole]string{
		textmeasure.MarkdownColorForeground:       ,
		textmeasure.MarkdownColorForegroundStroke: ,
		textmeasure.MarkdownColorMuted:            libcolor.N2,
		textmeasure.MarkdownColorMutedStroke:      libcolor.N2,
		textmeasure.MarkdownColorAccent:           libcolor.B2,
		textmeasure.MarkdownColorBorder:           libcolor.B1,
		textmeasure.MarkdownColorBorderMuted:      libcolor.B2,
		textmeasure.MarkdownColorCanvas:           libcolor.N7,
		textmeasure.MarkdownColorCanvasSubtle:     libcolor.N6,
		textmeasure.MarkdownColorNeutralMuted:     libcolor.N6,
	}
	 := make(map[textmeasure.MarkdownColorRole]d2scene.Paint, len())
	for ,  := range  {
		,  := .paint(, "Markdown "+string())
		if  != nil {
			return nil, 
		}
		[] = 
	}
	return , nil
}

func ( *builder) (
	 string,
	 int,
	 textmeasure.MarkdownPrimitive,
	,  d2fonts.FontFamily,
	 map[textmeasure.MarkdownColorRole]d2scene.Paint,
	 bool,
) (*d2scene.Node, d2scene.Bounds, error) {
	 := [.FillRole]
	 := [.StrokeRole]
	 := fmt.Sprintf("%s:primitive:%d", , )
	switch .Kind {
	case textmeasure.MarkdownRectPrimitive:
		 := d2scene.Box{X: .X, Y: .Y, Width: .Width, Height: .Height}
		var  *d2scene.Stroke
		if  != nil && .StrokeWidth > 0 {
			 = &d2scene.Stroke{Paint: , Width: .StrokeWidth, Cap: d2scene.CapButt, Join: d2scene.JoinMiter, MiterLimit: 4}
		}
		 := d2scene.NewNode(d2scene.Rect{
			Box: , RadiusX: .Radius, RadiusY: .Radius,
			Fill: , Stroke: ,
		})
		.ID = 
		return , .Bounds(), nil
	case textmeasure.MarkdownLinePrimitive:
		if  == nil || .StrokeWidth <= 0 {
			return nil, d2scene.Bounds{}, nil
		}
		 := &d2scene.Stroke{Paint: , Width: .StrokeWidth, Cap: d2scene.CapButt, Join: d2scene.JoinMiter, MiterLimit: 4}
		 := d2scene.Path{Commands: []d2scene.PathCommand{
			d2scene.MoveTo(.X, .Y),
			d2scene.LineTo(.X2, .Y2),
		}, Stroke: }
		 := d2scene.NewNode()
		.ID = 
		 := d2scene.BoundsFromPoints(
			d2scene.Point{X: .X, Y: .Y},
			d2scene.Point{X: .X2, Y: .Y2},
		).Expand(.StrokeWidth/2, .StrokeWidth/2)
		return , , nil
	case textmeasure.MarkdownTextPrimitive:
		,  := .markdownFont(.Font, , , .FontSize)
		if  != nil {
			return nil, d2scene.Bounds{}, 
		}
		 := .Width
		if  <= 0 {
			 = markdownEstimatedAdvance(.Text, .FontSize)
		}
		 := .Height
		if  <= 0 {
			 = .FontSize
		}
		 := d2scene.NewBounds(
			.X, .Y-,
			.X+, .Y+.FontSize*.25,
		)
		var  *d2scene.Stroke
		if .SyntheticBold &&  != nil {
			 = &d2scene.Stroke{
				Paint: , Width: math.Max(.35, .FontSize/32),
				Cap: d2scene.CapRound, Join: d2scene.JoinRound, MiterLimit: 4,
			}
		}
		 := d2scene.TextRun{
			Text: .Text, Origin: d2scene.Point{X: .X, Y: .Y},
			Anchor: d2scene.AnchorStart, Font: , Fill: , Stroke: ,
			Underline: , Strike: .Decoration == textmeasure.MarkdownTextDecorationLineThrough,
			Ink: ,
		}
		 := d2scene.NewNode()
		.ID = 
		 := d2scene.Identity()
		if .TextLength && .Width > 0 {
			,  := .fontFace(.Asset)
			if  != nil {
				return nil, d2scene.Bounds{}, fmt.Errorf("measure textLength: %w", )
			}
			,  := markdownFontAdvance(.ctx, .Outline, .Text, .FontSize)
			if  != nil {
				return nil, d2scene.Bounds{}, fmt.Errorf("measure textLength: %w", )
			}
			if  <= 0 {
				return nil, d2scene.Bounds{}, fmt.Errorf("textLength source has zero advance")
			}
			 := .Width / 
			if !finite() ||  <= 0 {
				return nil, d2scene.Bounds{}, fmt.Errorf("textLength produces invalid horizontal scale %v", )
			}
			 = d2scene.Translate(.X, .Y).
				Mul(d2scene.Scale(, 1)).
				Mul(d2scene.Translate(-.X, -.Y))
		}
		if .SyntheticItalic {
			 := d2scene.Translate(.X, .Y).
				Mul(d2scene.SkewX(-12 * math.Pi / 180)).
				Mul(d2scene.Translate(-.X, -.Y))
			 = .Mul()
		}
		.Transform = 
		return , .Transform(), nil
	default:
		return nil, d2scene.Bounds{}, fmt.Errorf("unknown primitive kind %q", .Kind)
	}
}

func ( *builder) ( textmeasure.MarkdownFontRole, ,  d2fonts.FontFamily,  float64) (d2scene.Font, error) {
	 := 
	 := d2fonts.FONT_STYLE_REGULAR
	switch  {
	case textmeasure.MarkdownFontRegular:
	case textmeasure.MarkdownFontSemibold:
		 = d2fonts.FONT_STYLE_SEMIBOLD
	case textmeasure.MarkdownFontBold:
		 = d2fonts.FONT_STYLE_BOLD
	case textmeasure.MarkdownFontItalic:
		 = d2fonts.FONT_STYLE_ITALIC
	case textmeasure.MarkdownFontMono:
		 = 
	case textmeasure.MarkdownFontMonoSemibold:
		,  = , d2fonts.FONT_STYLE_SEMIBOLD
	case textmeasure.MarkdownFontMonoBold:
		,  = , d2fonts.FONT_STYLE_BOLD
	case textmeasure.MarkdownFontMonoItalic:
		,  = , d2fonts.FONT_STYLE_ITALIC
	default:
		return d2scene.Font{}, fmt.Errorf("unknown font role %q", )
	}
	 := d2fonts.Font{Family: , Style: }
	,  := d2fonts.FontFaces.Lookup()
	if ! || len() == 0 {
		return d2scene.Font{}, fmt.Errorf("font %s/%s is not loaded", , )
	}
	 := d2scene.AssetID("font:" + string() + ":" + string())
	if ,  := .assets[]; ! {
		.assets[] = d2scene.FontAsset{MIMEType: "font/ttf", Data: retainedFontBytes()}
	}
	 := 400
	if  == d2fonts.FONT_STYLE_BOLD {
		 = 700
	} else if  == d2fonts.FONT_STYLE_SEMIBOLD {
		 = 600
	}
	return d2scene.Font{
		Family: string(), Style: string(), Weight: ,
		Size: , Asset: ,
	}, nil
}

func markdownFontAdvance( interface{ () error },  *sfnt.Font,  string,  float64) (float64, error) {
	if  == nil {
		return 0, fmt.Errorf("nil font")
	}
	 := fixed.Int26_6(math.Round( * 64))
	var  sfnt.Buffer
	 := fixed.Int26_6(0)
	var  sfnt.GlyphIndex
	 := false
	for ,  := range  {
		if  := .();  != nil {
			return 0, 
		}
		,  := .GlyphIndex(&, )
		if  != nil {
			return 0, fmt.Errorf("look up U+%04X: %w", , )
		}
		if  == 0 {
			return 0, fmt.Errorf("missing glyph U+%04X", )
		}
		if  {
			,  := .Kern(&, , , , font.HintingNone)
			if  != nil && !errors.Is(, sfnt.ErrNotFound) {
				return 0, fmt.Errorf("kern U+%04X: %w", , )
			}
			if  == nil {
				 += 
			}
		}
		,  := .GlyphAdvance(&, , , font.HintingNone)
		if  != nil {
			return 0, fmt.Errorf("advance U+%04X: %w", , )
		}
		 += 
		,  = , true
	}
	return float64() / 64, nil
}

func markdownEstimatedAdvance( string,  float64) float64 {
	 := utf8.RuneCountInString()
	if  == 0 {
		return math.Max(1, *.25)
	}
	return math.Max(1, float64()**.6)
}

func ( *builder) ( string,  int, ,  string,  d2scene.Box) error {
	if .options.LinkBudget.MaxRegions <= 0 || .options.LinkBudget.MaxStringBytes <= 0 {
		return invalidField("options", "linkBudget", .options.LinkBudget, "must provide positive MaxRegions and MaxStringBytes for Markdown links")
	}
	 := fmt.Sprintf("%s Markdown primitive %d", , )
	if  := .validateLinkFields(, , );  != nil {
		return 
	}
	,  := len(.links), .linkBytes
	// Markdown destinations originate as URLs, including relative URLs such as
	// root.html. Only typed D2 object and connection links carry board-target
	// provenance and may be classified by linkDestination.
	if  := .addLinkRegion(, d2scene.LinkRegion{Box: , URL: , Tooltip: });  != nil {
		return 
	}
	if  := .addAppendixItem(, "linkTitle", );  != nil {
		.links = .links[:]
		.linkBytes = 
		return 
	}
	return nil
}

func intersectMarkdownBounds(,  d2scene.Bounds) d2scene.Bounds {
	if !.Valid || !.Valid {
		return d2scene.Bounds{}
	}
	 := math.Max(.Min.X, .Min.X)
	 := math.Max(.Min.Y, .Min.Y)
	 := math.Min(.Max.X, .Max.X)
	 := math.Min(.Max.Y, .Max.Y)
	if  <=  ||  <=  {
		return d2scene.Bounds{}
	}
	return d2scene.NewBounds(, , , )
}