package main

import (
	"encoding/json"
	"fmt"
	"image/color"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	lipgloss "charm.land/lipgloss/v2"
)

type Token int

const (
	TokenBg Token = iota
	TokenFg
	TokenFgMuted
	TokenFgMostSubtle
	TokenPrimary
	TokenSecondary
	TokenAccent
	TokenSuccess
	TokenSuccessSubtle
	TokenUser
	TokenAssistant
	TokenBorder
	TokenCodeBlock
	TokenHeaderBg
	TokenHeaderFg
	TokenStreaming
	TokenBgSubtle
	TokenOnPrimary
)

func tokenKey(t Token) string {
	switch t {
	case TokenBg:
		return "bg"
	case TokenFg:
		return "fg"
	case TokenFgMuted:
		return "fg_muted"
	case TokenFgMostSubtle:
		return "fg_most_subtle"
	case TokenPrimary:
		return "primary"
	case TokenSecondary:
		return "secondary"
	case TokenAccent:
		return "accent"
	case TokenSuccess:
		return "success"
	case TokenSuccessSubtle:
		return "success_subtle"
	case TokenUser:
		return "user"
	case TokenAssistant:
		return "assistant"
	case TokenBorder:
		return "border"
	case TokenCodeBlock:
		return "code_block"
	case TokenHeaderBg:
		return "header_bg"
	case TokenHeaderFg:
		return "header_fg"
	case TokenStreaming:
		return "streaming"
	case TokenBgSubtle:
		return "bg_subtle"
	case TokenOnPrimary:
		return "on_primary"
	}
	return ""
}

type Theme map[Token]string

func SystemTheme() Theme {
	return Theme{
		TokenBg:            "#303030",
		TokenFg:            "#d7dae0",
		TokenFgMuted:       "#abb2bf",
		TokenFgMostSubtle:  "#565c64",
		TokenPrimary:       "#56b6c2",
		TokenSecondary:     "#e06c75",
		TokenAccent:        "#c678dd",
		TokenSuccess:       "#98c379",
		TokenSuccessSubtle: "#98c379",
		TokenUser:          "#56b6c2",
		TokenAssistant:     "#98c379",
		TokenBorder:        "#353b45",
		TokenCodeBlock:     "#353b45",
		TokenHeaderBg:      "#232326",
		TokenHeaderFg:      "#d7dae0",
		TokenStreaming:     "#e06c75",
		TokenBgSubtle:      "#232326",
		TokenOnPrimary:     "#c8ccd4",
	}
}

func LoadTheme(name, themesDirectory string) Theme {
	if name == "" || name == "system" {
		return SystemTheme()
	}

	path := filepath.Join(themesDirectory, name+".json")
	data, err := os.ReadFile(path)
	if err != nil {
		return SystemTheme()
	}

	type rawTheme struct {
		Colors map[string]string `json:"colors"`
	}
	var full rawTheme
	if err := json.Unmarshal(data, &full); err != nil {
		return SystemTheme()
	}

	t := SystemTheme()
	for k, v := range full.Colors {
		for tok := TokenBg; tok <= TokenOnPrimary; tok++ {
			if tokenKey(tok) == k {
				t[tok] = v
				break
			}
		}
	}

	return t
}

func themeColor(val string) color.Color {
	if val == "" {
		return lipgloss.NoColor{}
	}
	if n, err := strconv.Atoi(val); err == nil && n >= 0 && n <= 255 {
		return lipgloss.Color(strconv.Itoa(n))
	}
	if strings.HasPrefix(val, "#") && len(val) == 7 {
		return lipgloss.Color(val)
	}
	return lipgloss.NoColor{}
}

var thickLeftBorder = lipgloss.Border{Left: "▌"}

type Styles struct {
	Bg        color.Color
	BgSubtle  color.Color
	Fg        color.Color

	Logo              lipgloss.Style
	LogoDiagonals     lipgloss.Style
	LogoCharm         lipgloss.Style
	LogoVersion       lipgloss.Style
	LogoGradFrom      color.Color
	LogoGradTo        color.Color
	HeaderDiagonals   lipgloss.Style
	HeaderSep         lipgloss.Style
	HeaderWrap        lipgloss.Style
	UserFocused       lipgloss.Style
	UserBlurred       lipgloss.Style
	AsstFocused       lipgloss.Style
	AsstBlurred       lipgloss.Style
	ModelInfoIcon     lipgloss.Style
	ModelInfoName     lipgloss.Style
	ModelInfoProvider lipgloss.Style
	Help              lipgloss.Style
	Error             lipgloss.Style
	Streaming         lipgloss.Style
	CodeBlock         lipgloss.Style
	InlineBold        lipgloss.Style
	InlineItalic      lipgloss.Style
	InlineCode        lipgloss.Style
	Heading           lipgloss.Style
	H1                lipgloss.Style
	DialogTitle       lipgloss.Style
	DialogTitleAccent lipgloss.Style
	DialogView        lipgloss.Style
	DialogSelected    lipgloss.Style
	DialogNormal      lipgloss.Style
	DialogContentBg   lipgloss.Style
	InputBorder       lipgloss.Style
	ScrollbarThumb    lipgloss.Style
	ScrollbarTrack    lipgloss.Style
	ModelPickerBorder color.Color
	PromptFocused     lipgloss.Style
	PromptBlurred     lipgloss.Style
	BtnFocused        lipgloss.Style
	BtnBlurred        lipgloss.Style
	SelCursor         lipgloss.Style
	SelVisual         lipgloss.Style
	SelCursorBg       string
	SelVisualBg       string
}

func BuildStyles(t Theme) Styles {
	return Styles{
		Bg:       themeColor(t[TokenBg]),
		BgSubtle: themeColor(t[TokenBgSubtle]),
		Fg:       themeColor(t[TokenFg]),

		Logo:          lipgloss.NewStyle(),
		LogoDiagonals: lipgloss.NewStyle().Foreground(themeColor(t[TokenPrimary])),
		LogoCharm:     lipgloss.NewStyle().Foreground(themeColor(t[TokenPrimary])).Bold(true),
		LogoVersion:   lipgloss.NewStyle().Foreground(themeColor(t[TokenPrimary])),
		LogoGradFrom:  themeColor(t[TokenSecondary]),
		LogoGradTo:    themeColor(t[TokenPrimary]),

		HeaderDiagonals: lipgloss.NewStyle().Foreground(themeColor(t[TokenPrimary])),
		HeaderSep:       lipgloss.NewStyle().Foreground(themeColor(t[TokenFgMuted])),
		HeaderWrap: lipgloss.NewStyle().
			Background(themeColor(t[TokenHeaderBg])).
			Foreground(themeColor(t[TokenHeaderFg])).
			Padding(0, 1),

		UserFocused: lipgloss.NewStyle().
			Border(thickLeftBorder, false, false, false, true).
			BorderForeground(themeColor(t[TokenUser])).
			Foreground(themeColor(t[TokenFgMuted])).
			Padding(0, 0, 0, 1),
		UserBlurred: lipgloss.NewStyle().
			BorderLeft(true).
			BorderForeground(themeColor(t[TokenUser])).
			Foreground(themeColor(t[TokenFgMuted])).
			Padding(0, 0, 0, 1),

		AsstFocused: lipgloss.NewStyle().
			Border(thickLeftBorder, false, false, false, true).
			BorderForeground(themeColor(t[TokenAssistant])).
			Padding(0, 0, 0, 1),
		AsstBlurred: lipgloss.NewStyle().
			BorderLeft(true).
			BorderForeground(themeColor(t[TokenAssistant])).
			Padding(0, 0, 0, 1),

		ModelInfoIcon:     lipgloss.NewStyle().Foreground(themeColor(t[TokenFgMostSubtle])),
		ModelInfoName:     lipgloss.NewStyle().Foreground(themeColor(t[TokenFg])),
		ModelInfoProvider: lipgloss.NewStyle().Foreground(themeColor(t[TokenFgMuted])),

		Help:      lipgloss.NewStyle().Foreground(themeColor(t[TokenFgMuted])),
		Error:     lipgloss.NewStyle().Foreground(themeColor(t[TokenSecondary])),
		Streaming: lipgloss.NewStyle().Foreground(themeColor(t[TokenStreaming])),

		CodeBlock: lipgloss.NewStyle().
			Border(lipgloss.RoundedBorder()).
			BorderForeground(themeColor(t[TokenCodeBlock])).
			Background(themeColor(t[TokenBgSubtle])).
			Padding(0, 1),

		InlineBold: lipgloss.NewStyle().
			Bold(true).
			Foreground(themeColor(t[TokenFg])),

		InlineItalic: lipgloss.NewStyle().
			Foreground(themeColor(t[TokenSuccess])),

		InlineCode: lipgloss.NewStyle().
			Foreground(themeColor(t[TokenPrimary])).
			Background(themeColor(t[TokenCodeBlock])),

		Heading: lipgloss.NewStyle().
			Bold(true).
			Foreground(themeColor(t[TokenPrimary])),

		H1: lipgloss.NewStyle().
			Bold(true).
			Background(themeColor(t[TokenPrimary])).
			Foreground(themeColor(t[TokenOnPrimary])),

		DialogTitle:       lipgloss.NewStyle().Padding(0, 1).Foreground(themeColor(t[TokenPrimary])),
		DialogTitleAccent: lipgloss.NewStyle().Foreground(themeColor(t[TokenSuccess])).Bold(true),
		DialogView: lipgloss.NewStyle().
			Border(lipgloss.RoundedBorder()).
			BorderForeground(themeColor(t[TokenPrimary])),
		DialogSelected: lipgloss.NewStyle().Padding(0, 1).
			Background(themeColor(t[TokenPrimary])).
			Foreground(themeColor(t[TokenOnPrimary])),
		DialogNormal: lipgloss.NewStyle().Padding(0, 1).
			Foreground(themeColor(t[TokenFg])),
		DialogContentBg: lipgloss.NewStyle().
			Background(themeColor(t[TokenBgSubtle])).
			Foreground(themeColor(t[TokenFg])).
			Padding(1, 2),

		InputBorder: lipgloss.NewStyle().
			Background(themeColor(t[TokenBgSubtle])).
			Padding(2, 3),

		ScrollbarThumb: lipgloss.NewStyle().
			Foreground(themeColor(t[TokenSecondary])),

		ScrollbarTrack: lipgloss.NewStyle().
			Foreground(themeColor(t[TokenFgMuted])),

		ModelPickerBorder: themeColor(t[TokenPrimary]),

		PromptFocused: lipgloss.NewStyle().Foreground(themeColor(t[TokenAccent])),
		PromptBlurred: lipgloss.NewStyle().Foreground(themeColor(t[TokenFgMuted])),

		BtnFocused: lipgloss.NewStyle().Padding(0, 2).
			Foreground(themeColor(t[TokenOnPrimary])).
			Background(themeColor(t[TokenSecondary])),
		BtnBlurred: lipgloss.NewStyle().Padding(0, 2).
			Foreground(themeColor(t[TokenFg])).
			Background(themeColor(t[TokenBgSubtle])),

		SelCursor: lipgloss.NewStyle().
			Background(lipgloss.Color("#2d3f41")),

		SelVisual: lipgloss.NewStyle().
			Background(lipgloss.Color("#3a2540")),

		SelCursorBg: hexToAnsiBg("#2d3f41"),
		SelVisualBg: hexToAnsiBg("#3a2540"),
	}
}

func hexToAnsiBg(hex string) string {
	if len(hex) < 7 || hex[0] != '#' {
		return ""
	}
	r, _ := strconv.ParseUint(hex[1:3], 16, 8)
	g, _ := strconv.ParseUint(hex[3:5], 16, 8)
	b, _ := strconv.ParseUint(hex[5:7], 16, 8)
	return fmt.Sprintf("\033[48;2;%d;%d;%dm", r, g, b)
}

func foregroundGrad(base lipgloss.Style, input string, bold bool, c1, c2 color.Color) []string {
	if input == "" {
		return []string{""}
	}
	if len(input) == 1 {
		return []string{base.Foreground(c1).Bold(bold).Render(input)}
	}
	runes := []rune(input)
	ramp := lipgloss.Blend1D(len(runes), c1, c2)
	result := make([]string, len(runes))
	for i, c := range ramp {
		s := base.Foreground(c)
		if bold {
			s = s.Bold(true)
		}
		result[i] = s.Render(string(runes[i]))
	}
	return result
}

func applyForegroundGrad(base lipgloss.Style, input string, c1, c2 color.Color) string {
	clusters := foregroundGrad(base, input, false, c1, c2)
	return strings.Join(clusters, "")
}

func applyBoldGrad(base lipgloss.Style, input string, c1, c2 color.Color) string {
	clusters := foregroundGrad(base, input, true, c1, c2)
	return strings.Join(clusters, "")
}

func renderCompactLogo(sty Styles) string {
	name := applyBoldGrad(sty.Logo, "LUDMILA", sty.LogoGradFrom, sty.LogoGradTo)
	diag := sty.LogoDiagonals.Render("╱╱╱╱╱")
	return name + " " + diag
}

func renderHeader(sty Styles, model string, width int, mode string) string {
	logoPart := renderCompactLogo(sty)

	modelStyled := lipgloss.NewStyle().
		Foreground(sty.LogoGradTo).
		Bold(true).
		Render(" " + model + " ")

	fixed := lipgloss.Width(logoPart) + 1 + lipgloss.Width(modelStyled)

	var modePart string
	if mode != "" {
		modeBg := lipgloss.Color("#e06c75")
		modeFg := lipgloss.Color("#c8ccd4")
		if mode == "--VISUAL--" {
			modeBg = lipgloss.Color("#c678dd")
		}
		modePart = lipgloss.NewStyle().
			Background(modeBg).
			Foreground(modeFg).
			Bold(true).
			Padding(0, 1).
			Render(mode) + " "
		fixed += lipgloss.Width(modePart)
	}

	gap := width - fixed
	if gap < 0 {
		gap = 0
	}
	return sty.HeaderWrap.Width(width).Render(
		modePart + logoPart + " " + modelStyled + strings.Repeat(" ", gap),
	)
}

func wrapText(text string, width int) string {
	if width <= 0 {
		return text
	}
	var b strings.Builder
	lines := strings.Split(text, "\n")
	for li, line := range lines {
		if li > 0 {
			b.WriteByte('\n')
		}
		runes := []rune(line)
		pos := 0
		for pos < len(runes) {
			end := pos + width
			if end > len(runes) {
				end = len(runes)
			}
			b.WriteString(string(runes[pos:end]))
			pos = end
			if pos < len(runes) {
				b.WriteByte('\n')
			}
		}
	}
	return b.String()
}

func renderScrollbar(height, contentSize, viewportSize, offset int, thumb, track lipgloss.Style) string {
	if height <= 0 || contentSize <= viewportSize {
		return ""
	}
	thumbSize := max(1, height*viewportSize/contentSize)
	maxOffset := contentSize - viewportSize
	if maxOffset <= 0 {
		return ""
	}
	trackSpace := height - thumbSize
	thumbPos := 0
	if trackSpace > 0 && maxOffset > 0 {
		thumbPos = min(trackSpace, offset*trackSpace/maxOffset)
	}
	var sb strings.Builder
	for i := range height {
		if i > 0 {
			sb.WriteByte('\n')
		}
		if i >= thumbPos && i < thumbPos+thumbSize {
			sb.WriteString(thumb.Render("┃"))
		} else {
			sb.WriteString(track.Render("│"))
		}
	}
	return sb.String()
}

func dialogTitle(title string, gradientAccent lipgloss.Style, width int, from, to color.Color) string {
	titleRendered := lipgloss.NewStyle().Foreground(from).Render(title)
	remaining := width - lipgloss.Width(titleRendered) - 1
	if remaining < 0 {
		remaining = 0
	}
	diagLine := applyForegroundGrad(gradientAccent, strings.Repeat("╱", remaining), from, to)
	return titleRendered + " " + diagLine
}
