package adminconsole

import (
	"fmt"
	"html/template"
	"strings"
	"time"
)

// ChartPoint is one move-number sample of the move-duration chart: the min, mean and
// max think time (seconds) the account took on its Ordinal-th move across its games.
type ChartPoint struct {
	Ordinal int
	Min     float64
	Max     float64
	Avg     float64
}

// FormatDuration renders a think-time in seconds as a compact human string
// ("45s", "3m", "1h5m"), for the user-list columns and the chart's Y labels.
func FormatDuration(secs float64) string {
	d := time.Duration(secs * float64(time.Second))
	switch {
	case d < time.Minute:
		return fmt.Sprintf("%ds", int(d.Seconds()+0.5))
	case d < time.Hour:
		return fmt.Sprintf("%dm", int(d.Minutes()+0.5))
	default:
		h := int(d.Hours())
		if m := int(d.Minutes()) - h*60; m > 0 {
			return fmt.Sprintf("%dh%dm", h, m)
		}
		return fmt.Sprintf("%dh", h)
	}
}

// MoveDurationChart renders the per-move-number think-time chart as a self-contained,
// script-free inline SVG with three series (min, mean, max). The coordinates and
// labels are all derived from numeric data, so the result is safe template.HTML.
// An empty series renders nothing.
func MoveDurationChart(points []ChartPoint) template.HTML {
	if len(points) == 0 {
		return ""
	}
	const (
		w, h = 640, 240
		padL = 46
		padR = 12
		padT = 10
		padB = 28
	)
	maxOrd := points[len(points)-1].Ordinal
	if maxOrd < 1 {
		maxOrd = 1
	}
	var maxY float64
	for _, p := range points {
		maxY = max(maxY, p.Max)
	}
	if maxY <= 0 {
		maxY = 1
	}
	xOf := func(ord int) float64 {
		if maxOrd == 1 {
			return padL
		}
		return padL + (float64(ord-1)/float64(maxOrd-1))*(w-padL-padR)
	}
	yOf := func(v float64) float64 { return padT + (1-v/maxY)*(h-padT-padB) }
	line := func(get func(ChartPoint) float64) string {
		pts := make([]string, len(points))
		for i, p := range points {
			pts[i] = fmt.Sprintf("%.1f,%.1f", xOf(p.Ordinal), yOf(get(p)))
		}
		return strings.Join(pts, " ")
	}

	var b strings.Builder
	fmt.Fprintf(&b, `<svg viewBox="0 0 %d %d" class="chart" role="img" aria-label="Move duration by move number">`, w, h)
	fmt.Fprintf(&b, `<line x1="%d" y1="%d" x2="%d" y2="%.1f" class="axis"/>`, padL, padT, padL, float64(h-padB))
	fmt.Fprintf(&b, `<line x1="%d" y1="%.1f" x2="%d" y2="%.1f" class="axis"/>`, padL, float64(h-padB), w-padR, float64(h-padB))
	for _, frac := range []float64{0, 0.5, 1} {
		v := maxY * frac
		y := yOf(v)
		fmt.Fprintf(&b, `<line x1="%d" y1="%.1f" x2="%d" y2="%.1f" class="grid"/>`, padL, y, w-padR, y)
		fmt.Fprintf(&b, `<text x="%d" y="%.1f" class="lbl" text-anchor="end">%s</text>`, padL-5, y+3, FormatDuration(v))
	}
	for _, ord := range xTicks(maxOrd) {
		fmt.Fprintf(&b, `<text x="%.1f" y="%d" class="lbl" text-anchor="middle">%d</text>`, xOf(ord), h-padB+15, ord)
	}
	fmt.Fprintf(&b, `<polyline points="%s" class="ln ln-max"/>`, line(func(p ChartPoint) float64 { return p.Max }))
	fmt.Fprintf(&b, `<polyline points="%s" class="ln ln-avg"/>`, line(func(p ChartPoint) float64 { return p.Avg }))
	fmt.Fprintf(&b, `<polyline points="%s" class="ln ln-min"/>`, line(func(p ChartPoint) float64 { return p.Min }))
	b.WriteString(`</svg>`)
	return template.HTML(b.String())
}

// xTicks returns up to three distinct ordinal labels for the chart's X axis.
func xTicks(maxOrd int) []int {
	if maxOrd <= 2 {
		out := make([]int, 0, maxOrd)
		for i := 1; i <= maxOrd; i++ {
			out = append(out, i)
		}
		return out
	}
	return []int{1, (maxOrd + 1) / 2, maxOrd}
}
