package tui

import (
	"strings"

	"charm.land/lipgloss/v2"

	"github.com/smallnest/pigo/internal/cli/ui"
)

// Theme bundles the lipgloss styles for every visual element the TUI paints so
// the transcript, tool cards and status bar share one palette instead of each
// call site hand-rolling colors (see tasks/spec-tui-agent.md Sections 2.2, 5.1).
// The reference palette is: success green, error/warn red & yellow, file/accent
// blue, and gray for secondary chrome. Styles are plain value types, so a Theme
// is cheap to copy and safe to pass by value.
type Theme struct {
	// User styles the human's turns in the transcript.
	User lipgloss.Style
	// Assistant styles the model's turns in the transcript.
	Assistant lipgloss.Style
	// System styles system / meta notices (secondary gray).
	System lipgloss.Style
	// ToolHeader styles the title line of a tool invocation card.
	ToolHeader lipgloss.Style
	// ToolBody styles the body/output region of a tool card.
	ToolBody lipgloss.Style
	// StatusBar styles the persistent bottom status bar.
	StatusBar lipgloss.Style
	// Accent styles file names and other highlighted tokens (blue).
	Accent lipgloss.Style
	// Error styles failure messages (red).
	Error lipgloss.Style
	// Warn styles warnings (yellow).
	Warn lipgloss.Style
	// Success styles successful outcomes (green).
	Success lipgloss.Style
	// ScrollThumb styles the transcript scrollbar thumb (medium gray block).
	ScrollThumb lipgloss.Style
	// ScrollTrack styles the transcript scrollbar track (dim shaded column).
	ScrollTrack lipgloss.Style
	// Spinner styles the animated "working" indicator glyph + verb (warm coral).
	Spinner lipgloss.Style
}

// Palette color numbers use the ANSI 256-color cube so the theme renders
// consistently across terminals without depending on true-color support.
const (
	colorSuccess = "42"  // green
	colorError   = "196" // red
	colorWarn    = "214" // yellow/amber
	colorAccent  = "39"  // blue (file names, highlights)
	colorGray    = "245" // secondary / muted text
	colorScroll  = "250" // scrollbar thumb (bright gray pill, clearly visible)
	colorTrack   = "240" // scrollbar groove (dim gray, visible but recessive)
	colorUser    = "15"  // bright white
	colorAssist  = "252" // near-white
	colorStatus  = "62"  // status bar background (violet)
	colorSpinner = "173" // spinner glyph/verb (warm coral, matches Claude Code)
)

// DefaultTheme returns the built-in palette described in the SPEC: success
// green, error/warn red & yellow, file/accent blue, and gray for secondary
// chrome. It performs no I/O and never panics, so callers can construct it
// eagerly at startup.
func DefaultTheme() Theme {
	return Theme{
		User: lipgloss.NewStyle().
			Foreground(lipgloss.Color(colorUser)).
			Bold(true),
		Assistant: lipgloss.NewStyle().
			Foreground(lipgloss.Color(colorAssist)),
		System: lipgloss.NewStyle().
			Foreground(lipgloss.Color(colorGray)).
			Italic(true),
		ToolHeader: lipgloss.NewStyle().
			Foreground(lipgloss.Color(colorAccent)).
			Bold(true),
		ToolBody: lipgloss.NewStyle().
			Foreground(lipgloss.Color(colorGray)),
		StatusBar: lipgloss.NewStyle().
			Foreground(lipgloss.Color(colorUser)).
			Background(lipgloss.Color(colorStatus)),
		Accent: lipgloss.NewStyle().
			Foreground(lipgloss.Color(colorAccent)),
		Error: lipgloss.NewStyle().
			Foreground(lipgloss.Color(colorError)).
			Bold(true),
		Warn: lipgloss.NewStyle().
			Foreground(lipgloss.Color(colorWarn)),
		Success: lipgloss.NewStyle().
			Foreground(lipgloss.Color(colorSuccess)),
		ScrollThumb: lipgloss.NewStyle().
			Foreground(lipgloss.Color(colorScroll)),
		ScrollTrack: lipgloss.NewStyle().
			Foreground(lipgloss.Color(colorTrack)),
		Spinner: lipgloss.NewStyle().
			Foreground(lipgloss.Color(colorSpinner)).
			Bold(true),
	}
}

// ellipsis is the single rune appended to truncated strings. It is itself a
// single display column, so it is cheap to reserve room for.
const ellipsis = "…"

// WrapToWidth wraps s to at most width display columns per line, measuring width
// by terminal cells (CJK and emoji count as two) via ui.Width rather than byte
// length. It never splits inside a multi-byte rune or a double-width character:
// a rune that would overflow the current line starts a new line instead. Any
// existing newlines in s are preserved as hard breaks. A non-positive width is
// treated as "no wrapping" and s is returned unchanged.
func WrapToWidth(s string, width int) string {
	if width <= 0 {
		return s
	}

	var out strings.Builder
	lines := strings.Split(s, "\n")
	for li, line := range lines {
		if li > 0 {
			out.WriteByte('\n')
		}
		wrapLine(&out, line, width)
	}
	return out.String()
}

// wrapLine wraps a single newline-free line into out, breaking on rune
// boundaries so no double-width rune is ever cut in half.
func wrapLine(out *strings.Builder, line string, width int) {
	cur := 0 // display width accumulated on the current output line
	first := true
	for _, r := range line {
		rw := ui.Width(string(r))
		if !first && cur+rw > width {
			out.WriteByte('\n')
			cur = 0
		}
		out.WriteRune(r)
		cur += rw
		first = false
	}
}

// TruncateToWidth returns s clipped to at most width display columns, appending
// an ellipsis "…" when it removes content. Width is measured in terminal cells
// (CJK and emoji count as two) via ui.Width, and truncation happens on rune
// boundaries so a double-width character is never sliced. The returned string's
// display width is guaranteed to be <= width. A non-positive width yields the
// empty string.
func TruncateToWidth(s string, width int) string {
	if width <= 0 {
		return ""
	}
	if ui.Width(s) <= width {
		return s
	}

	// Reserve room for the ellipsis. If width is too small to even hold the
	// ellipsis plus one column, fall back to fitting bare runes into width.
	budget := width - ui.Width(ellipsis)
	if budget <= 0 {
		return fitRunes(s, width)
	}

	var b strings.Builder
	used := 0
	for _, r := range s {
		rw := ui.Width(string(r))
		if used+rw > budget {
			break
		}
		b.WriteRune(r)
		used += rw
	}
	b.WriteString(ellipsis)
	return b.String()
}

// fitRunes packs as many leading runes of s as fit within width columns without
// any ellipsis, breaking on rune boundaries.
func fitRunes(s string, width int) string {
	var b strings.Builder
	used := 0
	for _, r := range s {
		rw := ui.Width(string(r))
		if used+rw > width {
			break
		}
		b.WriteRune(r)
		used += rw
	}
	return b.String()
}
