package ui

import (
	"strings"
	"time"
	"unicode/utf8"

	"github.com/charmbracelet/lipgloss"
)

// ToastType represents the type of toast notification.
type ToastType int

const (
	ToastInfo ToastType = iota
	ToastSuccess
	ToastWarning
	ToastError
)

// Toast represents a single toast notification.
type Toast struct {
	ID        int
	Type      ToastType
	Title     string
	Message   string
	Duration  time.Duration
	CreatedAt time.Time
	FadeOut   bool
	// Tag groups related toasts. When non-empty, ShowTagged replaces the
	// existing toast with the same tag instead of stacking a new one, so
	// repeated status updates (e.g. retry attempts) collapse to a single
	// live toast rather than spamming the user.
	Tag string
}

// IsExpired returns true if the toast should be removed.
func (t *Toast) IsExpired() bool {
	return time.Since(t.CreatedAt) > t.Duration
}

const maxToastHistory = 50

// ToastManager manages toast notifications.
// All methods are only called from the Bubble Tea event loop (single goroutine),
// so no mutex is needed.
type ToastManager struct {
	toasts    []Toast
	history   []Toast // ring buffer of expired toasts
	maxToasts int
	styles    *Styles
	nextID    int
}

// NewToastManager creates a new toast manager.
func NewToastManager(styles *Styles) *ToastManager {
	return &ToastManager{
		toasts:    make([]Toast, 0),
		maxToasts: 5, // Allow enough for error bursts
		styles:    styles,
		nextID:    1,
	}
}

// Show displays a new toast notification.
func (m *ToastManager) Show(toastType ToastType, title, message string, duration time.Duration) {
	toast := Toast{
		ID:        m.nextID,
		Type:      toastType,
		Title:     title,
		Message:   message,
		Duration:  duration,
		CreatedAt: time.Now(),
		FadeOut:   false,
	}
	m.nextID++

	// Add to the beginning (newest first)
	m.toasts = append([]Toast{toast}, m.toasts...)

	// Evict non-error toasts first when over limit
	if len(m.toasts) > m.maxToasts {
		for i := len(m.toasts) - 1; i >= 0 && len(m.toasts) > m.maxToasts; i-- {
			if m.toasts[i].Type != ToastError {
				m.toasts = append(m.toasts[:i], m.toasts[i+1:]...)
			}
		}
		// If still over limit, truncate oldest
		if len(m.toasts) > m.maxToasts {
			m.toasts = m.toasts[:m.maxToasts]
		}
	}
}

// ShowSuccess displays a success toast.
func (m *ToastManager) ShowSuccess(message string) {
	m.Show(ToastSuccess, "", message, 1500*time.Millisecond)
}

// ShowError displays an error toast. Errors stay longer than info/success
// so the user has time to read them (15s vs the old 6s).
func (m *ToastManager) ShowError(message string) {
	m.Show(ToastError, "", message, 15*time.Second)
}

// ShowErrorWithHint displays an error toast with optional actionable hint.
// If a matching error pattern is found, appends hint: "Error message → Hint"
func (m *ToastManager) ShowErrorWithHint(message string) {
	hint := GetCompactHint(message)
	if hint != "" {
		message = message + " → " + hint
	}
	m.Show(ToastError, "Error", message, 15*time.Second)
}

// ShowInfo displays an info toast.
func (m *ToastManager) ShowInfo(message string) {
	m.Show(ToastInfo, "", message, 1500*time.Millisecond)
}

// ShowWarning displays a warning toast.
func (m *ToastManager) ShowWarning(message string) {
	m.Show(ToastWarning, "", message, 4*time.Second)
}

// ShowTagged shows a toast that replaces any existing toast with the same tag.
// Use this for progress-style updates (retries, failover attempts, etc.) where
// a burst of near-identical messages would otherwise spam the stack — each new
// message updates the live toast in place.
func (m *ToastManager) ShowTagged(tag string, toastType ToastType, message string, duration time.Duration) {
	if tag != "" {
		for i := range m.toasts {
			if m.toasts[i].Tag == tag {
				m.toasts[i].Type = toastType
				m.toasts[i].Message = message
				m.toasts[i].Duration = duration
				m.toasts[i].CreatedAt = time.Now()
				return
			}
		}
	}
	toast := Toast{
		ID:        m.nextID,
		Type:      toastType,
		Message:   message,
		Duration:  duration,
		CreatedAt: time.Now(),
		Tag:       tag,
	}
	m.nextID++
	m.toasts = append([]Toast{toast}, m.toasts...)
	if len(m.toasts) > m.maxToasts {
		for i := len(m.toasts) - 1; i >= 0 && len(m.toasts) > m.maxToasts; i-- {
			if m.toasts[i].Type != ToastError {
				m.toasts = append(m.toasts[:i], m.toasts[i+1:]...)
			}
		}
		if len(m.toasts) > m.maxToasts {
			m.toasts = m.toasts[:m.maxToasts]
		}
	}
}

// Dismiss removes a toast by ID.
func (m *ToastManager) Dismiss(id int) {
	for i, toast := range m.toasts {
		if toast.ID == id {
			m.toasts = append(m.toasts[:i], m.toasts[i+1:]...)
			return
		}
	}
}

// Update removes expired toasts, archiving them to history.
func (m *ToastManager) Update() {
	var active []Toast
	for _, toast := range m.toasts {
		if !toast.IsExpired() {
			active = append(active, toast)
		} else {
			// Archive expired toasts (newest first)
			m.history = append([]Toast{toast}, m.history...)
			if len(m.history) > maxToastHistory {
				m.history = m.history[:maxToastHistory]
			}
		}
	}
	m.toasts = active
}

// History returns a copy of the toast history (newest first).
func (m *ToastManager) History() []Toast {
	result := make([]Toast, len(m.history))
	copy(result, m.history)
	return result
}

// Count returns the number of active toasts.
func (m *ToastManager) Count() int {
	return len(m.toasts)
}

// View renders all active toasts in the right upper corner.
func (m *ToastManager) View(width int) string {
	if len(m.toasts) == 0 {
		return ""
	}

	var lines []string

	for _, toast := range m.toasts {
		line := m.renderToast(toast, width)
		lines = append(lines, line)
	}

	return strings.Join(lines, "\n")
}

// renderToast renders a single toast as compact single line.
// Format: ✓ Message — fades to dim in last 500ms.
func (m *ToastManager) renderToast(toast Toast, width int) string {
	icon := ToastIcons[toast.Type]
	iconColor := ToastColors[toast.Type]

	// Fade entire toast when nearing expiration
	elapsed := time.Since(toast.CreatedAt)
	remaining := toast.Duration - elapsed
	fading := remaining < 500*time.Millisecond

	msgColor := ColorMuted
	if fading {
		iconColor = ColorDim
		msgColor = ColorDim
	}

	iconStyle := lipgloss.NewStyle().Foreground(iconColor)
	msgStyle := lipgloss.NewStyle().Foreground(msgColor)

	// Truncate message to fit width (head-only, not center-truncation)
	msg := toast.Message
	maxLen := width - 5
	if maxLen < 10 {
		maxLen = 10
	}
	if utf8.RuneCountInString(msg) > maxLen {
		runes := []rune(msg)
		msg = string(runes[:maxLen-1]) + "…"
	}

	return iconStyle.Render(icon) + " " + msgStyle.Render(msg)
}

// Clear removes all toasts.
func (m *ToastManager) Clear() {
	m.toasts = m.toasts[:0]
}
