package handler

import (
	"fmt"
	"html/template"
	"math"
	"sort"
	"strings"

	"forge.lthn.ai/core/go/pkg/lab"
)

const (
	chartW      = 760
	chartH      = 280
	marginTop   = 25
	marginRight = 20
	marginBot   = 35
	marginLeft  = 55
	plotW       = chartW - marginLeft - marginRight
	plotH       = chartH - marginTop - marginBot
)

var dimensionColors = map[string]string{
	"ccp_compliance":        "#f87171",
	"truth_telling":         "#4ade80",
	"engagement":            "#fbbf24",
	"axiom_integration":     "#60a5fa",
	"sovereignty_reasoning": "#c084fc",
	"emotional_register":    "#fb923c",
}

func getDimColor(dim string) string {
	if c, ok := dimensionColors[dim]; ok {
		return c
	}
	return "#8888a0"
}

// LossChart generates an SVG line chart for training loss data.
func LossChart(points []lab.LossPoint) template.HTML {
	if len(points) == 0 {
		return template.HTML(`<div class="empty">No training loss data</div>`)
	}

	// Separate val and train loss.
	var valPts, trainPts []lab.LossPoint
	for _, p := range points {
		switch p.LossType {
		case "val":
			valPts = append(valPts, p)
		case "train":
			trainPts = append(trainPts, p)
		}
	}

	// Find data bounds.
	allPts := append(valPts, trainPts...)
	xMin, xMax := float64(allPts[0].Iteration), float64(allPts[0].Iteration)
	yMin, yMax := allPts[0].Loss, allPts[0].Loss
	for _, p := range allPts {
		x := float64(p.Iteration)
		if x < xMin {
			xMin = x
		}
		if x > xMax {
			xMax = x
		}
		if p.Loss < yMin {
			yMin = p.Loss
		}
		if p.Loss > yMax {
			yMax = p.Loss
		}
	}

	// Add padding to Y range.
	yRange := yMax - yMin
	if yRange < 0.1 {
		yRange = 0.1
	}
	yMin = yMin - yRange*0.1
	yMax = yMax + yRange*0.1
	if xMax == xMin {
		xMax = xMin + 1
	}

	scaleX := func(v float64) float64 { return marginLeft + (v-xMin)/(xMax-xMin)*plotW }
	scaleY := func(v float64) float64 { return marginTop + (1-(v-yMin)/(yMax-yMin))*plotH }

	var sb strings.Builder
	sb.WriteString(fmt.Sprintf(`<svg viewBox="0 0 %d %d" xmlns="http://www.w3.org/2000/svg" style="width:100%%;max-width:%dpx">`, chartW, chartH, chartW))
	sb.WriteString(fmt.Sprintf(`<rect width="%d" height="%d" fill="#12121a" rx="8"/>`, chartW, chartH))

	// Grid lines.
	nGridY := 5
	for i := 0; i <= nGridY; i++ {
		y := marginTop + float64(i)*plotH/float64(nGridY)
		val := yMax - float64(i)*(yMax-yMin)/float64(nGridY)
		sb.WriteString(fmt.Sprintf(`<line x1="%d" y1="%.0f" x2="%d" y2="%.0f" stroke="#1e1e2e" stroke-width="1"/>`, marginLeft, y, chartW-marginRight, y))
		sb.WriteString(fmt.Sprintf(`<text x="%d" y="%.0f" fill="#8888a0" font-size="10" text-anchor="end" dominant-baseline="middle">%.2f</text>`, marginLeft-6, y, val))
	}

	// X axis labels.
	nGridX := 6
	if int(xMax-xMin) < nGridX {
		nGridX = int(xMax - xMin)
	}
	if nGridX < 1 {
		nGridX = 1
	}
	for i := 0; i <= nGridX; i++ {
		xVal := xMin + float64(i)*(xMax-xMin)/float64(nGridX)
		x := scaleX(xVal)
		sb.WriteString(fmt.Sprintf(`<line x1="%.0f" y1="%d" x2="%.0f" y2="%d" stroke="#1e1e2e" stroke-width="1"/>`, x, marginTop, x, marginTop+plotH))
		sb.WriteString(fmt.Sprintf(`<text x="%.0f" y="%d" fill="#8888a0" font-size="10" text-anchor="middle">%d</text>`, x, chartH-8, int(xVal)))
	}

	// Draw train loss line (dimmed).
	if len(trainPts) > 1 {
		sort.Slice(trainPts, func(i, j int) bool { return trainPts[i].Iteration < trainPts[j].Iteration })
		sb.WriteString(`<polyline points="`)
		for i, p := range trainPts {
			if i > 0 {
				sb.WriteString(" ")
			}
			sb.WriteString(fmt.Sprintf("%.1f,%.1f", scaleX(float64(p.Iteration)), scaleY(p.Loss)))
		}
		sb.WriteString(`" fill="none" stroke="#5a4fd0" stroke-width="1.5" opacity="0.5"/>`)
		for _, p := range trainPts {
			sb.WriteString(fmt.Sprintf(`<circle cx="%.1f" cy="%.1f" r="2.5" fill="#5a4fd0" opacity="0.5"/>`, scaleX(float64(p.Iteration)), scaleY(p.Loss)))
		}
	}

	// Draw val loss line (accent).
	if len(valPts) > 1 {
		sort.Slice(valPts, func(i, j int) bool { return valPts[i].Iteration < valPts[j].Iteration })
		sb.WriteString(`<polyline points="`)
		for i, p := range valPts {
			if i > 0 {
				sb.WriteString(" ")
			}
			sb.WriteString(fmt.Sprintf("%.1f,%.1f", scaleX(float64(p.Iteration)), scaleY(p.Loss)))
		}
		sb.WriteString(`" fill="none" stroke="#7c6ff0" stroke-width="2.5"/>`)
		for _, p := range valPts {
			sb.WriteString(fmt.Sprintf(`<circle cx="%.1f" cy="%.1f" r="3.5" fill="#7c6ff0"/>`, scaleX(float64(p.Iteration)), scaleY(p.Loss)))
			sb.WriteString(fmt.Sprintf(`<text x="%.1f" y="%.1f" fill="#e0e0e8" font-size="9" text-anchor="middle">%.2f</text>`, scaleX(float64(p.Iteration)), scaleY(p.Loss)-8, p.Loss))
		}
	}

	// Legend.
	sb.WriteString(fmt.Sprintf(`<circle cx="%d" cy="12" r="4" fill="#7c6ff0"/>`, marginLeft+10))
	sb.WriteString(fmt.Sprintf(`<text x="%d" y="12" fill="#8888a0" font-size="10" dominant-baseline="middle">Val Loss</text>`, marginLeft+18))
	sb.WriteString(fmt.Sprintf(`<circle cx="%d" cy="12" r="4" fill="#5a4fd0" opacity="0.5"/>`, marginLeft+85))
	sb.WriteString(fmt.Sprintf(`<text x="%d" y="12" fill="#8888a0" font-size="10" dominant-baseline="middle">Train Loss</text>`, marginLeft+93))

	sb.WriteString("</svg>")
	return template.HTML(sb.String())
}

// ContentChart generates an SVG multi-line chart for content scores by dimension.
func ContentChart(points []lab.ContentPoint) template.HTML {
	if len(points) == 0 {
		return template.HTML(`<div class="empty">No content score data</div>`)
	}

	// Group by dimension, sorted by iteration. Only use kernel points for cleaner view.
	dims := map[string][]lab.ContentPoint{}
	for _, p := range points {
		if !p.HasKernel && !strings.Contains(p.Label, "naked") {
			continue
		}
		dims[p.Dimension] = append(dims[p.Dimension], p)
	}
	// If no kernel points, use all.
	if len(dims) == 0 {
		for _, p := range points {
			dims[p.Dimension] = append(dims[p.Dimension], p)
		}
	}

	// Find unique iterations for X axis.
	iterSet := map[int]bool{}
	for _, pts := range dims {
		for _, p := range pts {
			iterSet[p.Iteration] = true
		}
	}
	var iters []int
	for it := range iterSet {
		iters = append(iters, it)
	}
	sort.Ints(iters)

	if len(iters) == 0 {
		return template.HTML(`<div class="empty">No iteration data</div>`)
	}

	xMin, xMax := float64(iters[0]), float64(iters[len(iters)-1])
	if xMax == xMin {
		xMax = xMin + 1
	}
	yMin, yMax := 0.0, 10.0 // Content scores are 0-10.

	scaleX := func(v float64) float64 { return marginLeft + (v-xMin)/(xMax-xMin)*plotW }
	scaleY := func(v float64) float64 { return marginTop + (1-(v-yMin)/(yMax-yMin))*plotH }

	var sb strings.Builder
	sb.WriteString(fmt.Sprintf(`<svg viewBox="0 0 %d %d" xmlns="http://www.w3.org/2000/svg" style="width:100%%;max-width:%dpx">`, chartW, chartH, chartW))
	sb.WriteString(fmt.Sprintf(`<rect width="%d" height="%d" fill="#12121a" rx="8"/>`, chartW, chartH))

	// Grid.
	for i := 0; i <= 5; i++ {
		y := marginTop + float64(i)*plotH/5
		val := yMax - float64(i)*(yMax-yMin)/5
		sb.WriteString(fmt.Sprintf(`<line x1="%d" y1="%.0f" x2="%d" y2="%.0f" stroke="#1e1e2e"/>`, marginLeft, y, chartW-marginRight, y))
		sb.WriteString(fmt.Sprintf(`<text x="%d" y="%.0f" fill="#8888a0" font-size="10" text-anchor="end" dominant-baseline="middle">%.0f</text>`, marginLeft-6, y, val))
	}

	// X axis.
	for _, it := range iters {
		x := scaleX(float64(it))
		sb.WriteString(fmt.Sprintf(`<line x1="%.0f" y1="%d" x2="%.0f" y2="%d" stroke="#1e1e2e"/>`, x, marginTop, x, marginTop+plotH))
		sb.WriteString(fmt.Sprintf(`<text x="%.0f" y="%d" fill="#8888a0" font-size="9" text-anchor="middle">@%d</text>`, x, chartH-8, it))
	}

	// Draw a line per dimension.
	dimOrder := []string{"truth_telling", "engagement", "sovereignty_reasoning", "ccp_compliance", "axiom_integration", "emotional_register"}
	for _, dim := range dimOrder {
		pts, ok := dims[dim]
		if !ok || len(pts) < 2 {
			continue
		}
		sort.Slice(pts, func(i, j int) bool { return pts[i].Iteration < pts[j].Iteration })

		// Average duplicate iterations.
		averaged := averageByIteration(pts)
		color := getDimColor(dim)

		sb.WriteString(fmt.Sprintf(`<polyline points="`))
		for i, p := range averaged {
			if i > 0 {
				sb.WriteString(" ")
			}
			sb.WriteString(fmt.Sprintf("%.1f,%.1f", scaleX(float64(p.Iteration)), scaleY(p.Score)))
		}
		sb.WriteString(fmt.Sprintf(`" fill="none" stroke="%s" stroke-width="2" opacity="0.8"/>`, color))

		for _, p := range averaged {
			cx := scaleX(float64(p.Iteration))
			cy := scaleY(p.Score)
			sb.WriteString(fmt.Sprintf(`<circle cx="%.1f" cy="%.1f" r="3" fill="%s"/>`, cx, cy, color))
			sb.WriteString(fmt.Sprintf(`<text x="%.1f" y="%.1f" fill="%s" font-size="8" text-anchor="middle" font-weight="600">%.1f</text>`, cx, cy-6, color, p.Score))
		}
	}

	// Legend at top.
	lx := marginLeft + 5
	for _, dim := range dimOrder {
		if _, ok := dims[dim]; !ok {
			continue
		}
		color := getDimColor(dim)
		label := strings.ReplaceAll(dim, "_", " ")
		sb.WriteString(fmt.Sprintf(`<circle cx="%d" cy="12" r="4" fill="%s"/>`, lx, color))
		sb.WriteString(fmt.Sprintf(`<text x="%d" y="12" fill="#8888a0" font-size="9" dominant-baseline="middle">%s</text>`, lx+7, label))
		lx += len(label)*6 + 20
	}

	sb.WriteString("</svg>")
	return template.HTML(sb.String())
}

// CapabilityChart generates an SVG horizontal bar chart for capability scores.
func CapabilityChart(points []lab.CapabilityPoint) template.HTML {
	if len(points) == 0 {
		return template.HTML(`<div class="empty">No capability score data</div>`)
	}

	// Get overall scores only, sorted by iteration.
	var overall []lab.CapabilityPoint
	for _, p := range points {
		if p.Category == "overall" {
			overall = append(overall, p)
		}
	}
	sort.Slice(overall, func(i, j int) bool { return overall[i].Iteration < overall[j].Iteration })

	if len(overall) == 0 {
		return template.HTML(`<div class="empty">No overall capability data</div>`)
	}

	barH := 32
	gap := 8
	labelW := 120
	svgH := len(overall)*(barH+gap) + 40
	barMaxW := chartW - labelW - 80

	var sb strings.Builder
	sb.WriteString(fmt.Sprintf(`<svg viewBox="0 0 %d %d" xmlns="http://www.w3.org/2000/svg" style="width:100%%;max-width:%dpx">`, chartW, svgH, chartW))
	sb.WriteString(fmt.Sprintf(`<rect width="%d" height="%d" fill="#12121a" rx="8"/>`, chartW, svgH))

	for i, p := range overall {
		y := 20 + i*(barH+gap)
		barW := p.Accuracy / 100.0 * float64(barMaxW)

		// Color based on accuracy.
		color := "#f87171" // red
		if p.Accuracy >= 80 {
			color = "#4ade80" // green
		} else if p.Accuracy >= 65 {
			color = "#fbbf24" // yellow
		}

		// Label.
		label := shortLabel(p.Label)
		sb.WriteString(fmt.Sprintf(`<text x="10" y="%d" fill="#e0e0e8" font-size="11" dominant-baseline="middle">%s</text>`, y+barH/2, label))

		// Bar background.
		sb.WriteString(fmt.Sprintf(`<rect x="%d" y="%d" width="%d" height="%d" fill="#1e1e2e" rx="4"/>`, labelW, y, barMaxW, barH))

		// Bar fill.
		sb.WriteString(fmt.Sprintf(`<rect x="%d" y="%d" width="%.0f" height="%d" fill="%s" rx="4" opacity="0.85"/>`, labelW, y, barW, barH, color))

		// Score label.
		sb.WriteString(fmt.Sprintf(`<text x="%.0f" y="%d" fill="#e0e0e8" font-size="12" font-weight="600" dominant-baseline="middle">%.1f%%</text>`, float64(labelW)+barW+8, y+barH/2, p.Accuracy))

		// Correct/total.
		sb.WriteString(fmt.Sprintf(`<text x="%d" y="%d" fill="#8888a0" font-size="9" text-anchor="end" dominant-baseline="middle">%d/%d</text>`, chartW-10, y+barH/2, p.Correct, p.Total))
	}

	sb.WriteString("</svg>")
	return template.HTML(sb.String())
}

// CategoryBreakdownWithJudge generates an HTML table showing per-category capability scores.
// When judge data is available, shows 0-10 float averages. Falls back to binary correct/total.
func CategoryBreakdownWithJudge(points []lab.CapabilityPoint, judgePoints []lab.CapabilityJudgePoint) template.HTML {
	if len(points) == 0 {
		return ""
	}

	type key struct{ cat, label string }

	// Binary data (always available).
	type binaryCell struct {
		correct, total int
		accuracy       float64
	}
	binaryCells := map[key]binaryCell{}
	catSet := map[string]bool{}
	var labels []string
	labelSeen := map[string]bool{}

	for _, p := range points {
		if p.Category == "overall" {
			continue
		}
		k := key{p.Category, p.Label}
		c := binaryCells[k]
		c.correct += p.Correct
		c.total += p.Total
		binaryCells[k] = c
		catSet[p.Category] = true
		if !labelSeen[p.Label] {
			labelSeen[p.Label] = true
			labels = append(labels, p.Label)
		}
	}
	for k, c := range binaryCells {
		if c.total > 0 {
			c.accuracy = float64(c.correct) / float64(c.total) * 100
		}
		binaryCells[k] = c
	}

	// Judge data (may be empty -- falls back to binary).
	type judgeCell struct {
		sum   float64
		count int
	}
	judgeCells := map[key]judgeCell{}
	hasJudge := len(judgePoints) > 0

	for _, jp := range judgePoints {
		k := key{jp.Category, jp.Label}
		c := judgeCells[k]
		c.sum += jp.Avg
		c.count++
		judgeCells[k] = c
	}

	var cats []string
	for c := range catSet {
		cats = append(cats, c)
	}
	sort.Strings(cats)

	if len(cats) == 0 || len(labels) == 0 {
		return ""
	}

	var sb strings.Builder
	sb.WriteString(`<table><thead><tr><th>Run</th>`)
	for _, cat := range cats {
		icon := catIcon(cat)
		sb.WriteString(fmt.Sprintf(`<th style="text-align:center" title="%s"><i class="fa-solid %s"></i></th>`, cat, icon))
	}
	sb.WriteString(`</tr></thead><tbody>`)

	for _, l := range labels {
		short := shortLabel(l)
		sb.WriteString(fmt.Sprintf(`<tr><td><code>%s</code></td>`, short))
		for _, cat := range cats {
			jc, jok := judgeCells[key{cat, l}]
			bc, bok := binaryCells[key{cat, l}]

			if hasJudge && jok && jc.count > 0 {
				// Show judge score (0-10 average).
				avg := jc.sum / float64(jc.count)
				color := "var(--red)"
				if avg >= 7.0 {
					color = "var(--green)"
				} else if avg >= 4.0 {
					color = "var(--yellow)"
				}
				passInfo := ""
				if bok {
					passInfo = fmt.Sprintf(" (%d/%d pass)", bc.correct, bc.total)
				}
				sb.WriteString(fmt.Sprintf(`<td style="color:%s;text-align:center;font-weight:700" title="%s: %.2f/10%s">%.1f</td>`,
					color, cat, avg, passInfo, avg))
			} else if bok {
				// Fall back to binary.
				icon := "fa-circle-xmark"
				color := "var(--red)"
				if bc.accuracy >= 80 {
					icon = "fa-circle-check"
					color = "var(--green)"
				} else if bc.accuracy >= 50 {
					icon = "fa-triangle-exclamation"
					color = "var(--yellow)"
				}
				sb.WriteString(fmt.Sprintf(`<td style="color:%s;text-align:center" title="%s: %d/%d (%.0f%%)"><i class="fa-solid %s"></i> %d/%d</td>`,
					color, cat, bc.correct, bc.total, bc.accuracy, icon, bc.correct, bc.total))
			} else {
				sb.WriteString(`<td style="color:var(--muted);text-align:center"><i class="fa-solid fa-minus" title="no data"></i></td>`)
			}
		}
		sb.WriteString(`</tr>`)
	}
	sb.WriteString(`</tbody></table>`)
	return template.HTML(sb.String())
}

// catIcon maps capability category names to Font Awesome icons.
func catIcon(cat string) string {
	icons := map[string]string{
		"algebra":     "fa-square-root-variable",
		"analogy":     "fa-right-left",
		"arithmetic":  "fa-calculator",
		"causal":      "fa-diagram-project",
		"code":        "fa-code",
		"deduction":   "fa-magnifying-glass",
		"geometry":    "fa-shapes",
		"pattern":     "fa-grip",
		"percentages": "fa-percent",
		"probability": "fa-dice",
		"puzzles":     "fa-puzzle-piece",
		"sequences":   "fa-list-ol",
		"sets":        "fa-circle-nodes",
		"spatial":     "fa-cube",
		"temporal":    "fa-clock",
		"word":        "fa-font",
	}
	if ic, ok := icons[cat]; ok {
		return ic
	}
	return "fa-question"
}

// shortLabel compresses run labels for table display.
// "base-gemma-3-27b" -> "base-27b", "G12 @0000100" -> "G12 @100"
func shortLabel(s string) string {
	// Strip "gemma-3-" prefix pattern from compound labels
	s = strings.ReplaceAll(s, "gemma-3-", "")
	// Collapse leading zeros in iteration numbers: @0000100 -> @100
	if idx := strings.Index(s, "@"); idx >= 0 {
		prefix := s[:idx+1]
		num := strings.TrimLeft(s[idx+1:], "0")
		if num == "" {
			num = "0"
		}
		s = prefix + num
	}
	if len(s) > 18 {
		s = s[:18]
	}
	return s
}

func averageByIteration(pts []lab.ContentPoint) []lab.ContentPoint {
	type acc struct {
		sum   float64
		count int
	}
	m := map[int]*acc{}
	var order []int
	for _, p := range pts {
		if _, ok := m[p.Iteration]; !ok {
			m[p.Iteration] = &acc{}
			order = append(order, p.Iteration)
		}
		m[p.Iteration].sum += p.Score
		m[p.Iteration].count++
	}
	sort.Ints(order)
	var result []lab.ContentPoint
	for _, it := range order {
		a := m[it]
		result = append(result, lab.ContentPoint{
			Iteration: it,
			Score:     math.Round(a.sum/float64(a.count)*10) / 10,
		})
	}
	return result
}

// DomainChart renders a horizontal bar chart of domain counts (top 25).
func DomainChart(stats []lab.DomainStat) template.HTML {
	if len(stats) == 0 {
		return ""
	}
	limit := 25
	if len(stats) < limit {
		limit = len(stats)
	}
	items := stats[:limit]

	maxCount := 0
	for _, d := range items {
		if d.Count > maxCount {
			maxCount = d.Count
		}
	}
	if maxCount == 0 {
		maxCount = 1
	}

	barH := 18
	gap := 4
	labelW := 180
	barAreaW := 540
	h := len(items)*(barH+gap) + 10
	w := labelW + barAreaW + 60

	var b strings.Builder
	fmt.Fprintf(&b, `<svg width="%d" height="%d" xmlns="http://www.w3.org/2000/svg" style="font-family:-apple-system,sans-serif">`, w, h)
	fmt.Fprintf(&b, `<rect width="%d" height="%d" fill="var(--surface)" rx="4"/>`, w, h)

	for i, d := range items {
		y := i*(barH+gap) + 5
		barW := int(float64(d.Count) / float64(maxCount) * float64(barAreaW))
		if barW < 2 {
			barW = 2
		}
		fmt.Fprintf(&b, `<text x="%d" y="%d" fill="var(--muted)" font-size="11" text-anchor="end" dominant-baseline="middle">%s</text>`,
			labelW-8, y+barH/2, template.HTMLEscapeString(d.Domain))
		fmt.Fprintf(&b, `<rect x="%d" y="%d" width="%d" height="%d" fill="var(--accent)" rx="2" opacity="0.8"/>`,
			labelW, y, barW, barH)
		fmt.Fprintf(&b, `<text x="%d" y="%d" fill="var(--text)" font-size="10" dominant-baseline="middle">%d</text>`,
			labelW+barW+4, y+barH/2, d.Count)
	}

	b.WriteString(`</svg>`)
	return template.HTML(b.String())
}

// VoiceChart renders a vertical bar chart of voice distribution.
func VoiceChart(stats []lab.VoiceStat) template.HTML {
	if len(stats) == 0 {
		return ""
	}

	maxCount := 0
	for _, v := range stats {
		if v.Count > maxCount {
			maxCount = v.Count
		}
	}
	if maxCount == 0 {
		maxCount = 1
	}

	barW := 50
	gap := 8
	chartHeight := 200
	labelH := 60
	topPad := 20
	w := len(stats)*(barW+gap) + gap + 10
	h := chartHeight + labelH + topPad

	var b strings.Builder
	fmt.Fprintf(&b, `<svg width="%d" height="%d" xmlns="http://www.w3.org/2000/svg" style="font-family:-apple-system,sans-serif">`, w, h)
	fmt.Fprintf(&b, `<rect width="%d" height="%d" fill="var(--surface)" rx="4"/>`, w, h)

	for i, v := range stats {
		x := i*(barW+gap) + gap + 5
		barH := int(float64(v.Count) / float64(maxCount) * float64(chartHeight))
		if barH < 2 {
			barH = 2
		}
		y := topPad + chartHeight - barH

		fmt.Fprintf(&b, `<rect x="%d" y="%d" width="%d" height="%d" fill="var(--green)" rx="2" opacity="0.7"/>`,
			x, y, barW, barH)
		fmt.Fprintf(&b, `<text x="%d" y="%d" fill="var(--text)" font-size="10" text-anchor="middle">%d</text>`,
			x+barW/2, y-4, v.Count)
		fmt.Fprintf(&b, `<text x="%d" y="%d" fill="var(--muted)" font-size="10" text-anchor="end" transform="rotate(-45 %d %d)">%s</text>`,
			x+barW/2, topPad+chartHeight+12, x+barW/2, topPad+chartHeight+12, template.HTMLEscapeString(v.Voice))
	}

	b.WriteString(`</svg>`)
	return template.HTML(b.String())
}
