package ui

import (
	"fmt"
	"io"
	"log"
	"strings"

	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/bubbles/list"
	"github.com/charmbracelet/lipgloss"

	"github.com/gmgauthier/gostations/internal/data"
	"github.com/gmgauthier/gostations/internal/radio"
)

// item wraps a station for the bubbles list.
type item struct {
	station    radio.Station
	isFavorite bool
}

func (i item) Title() string { return i.station.Name }
func (i item) Description() string {
	return fmt.Sprintf("%s • %s kbps • %s", i.station.Codec, i.station.Bitrate, truncate(i.station.Url, 50))
}
func (i item) FilterValue() string {
	return i.station.Name + " " + i.station.Tags + " " + i.station.Codec + " " + i.station.Url
}

func truncate(s string, n int) string {
	if len(s) <= n {
		return s
	}
	return s[:n] + "…"
}

// listDelegate for nice rendering.
type listDelegate struct{}

func (d listDelegate) Height() int                               { return 2 }
func (d listDelegate) Spacing() int                              { return 1 }
func (d listDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil }
func (d listDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
	i, ok := listItem.(item)
	if !ok {
		return
	}
	title := i.station.Name
	if i.isFavorite {
		title = "★ " + title
	}
	if m.Index() == index {
		title = lipgloss.NewStyle().Bold(true).Render("▶ " + title)
	} else {
		title = "  " + title
	}
	desc := fmt.Sprintf("   %s • %s kbps • %s", i.station.Codec, i.station.Bitrate, truncate(i.station.Url, 45))
	fmt.Fprintf(w, "%s\n%s", title, desc)
}

// App is the root model. Currently focused on selection (playback integration later).
type App struct {
	list     list.Model
	quitting bool
}

func NewApp(initial []radio.Station) *App {
	favs, err := data.NewFavorites()
	if err != nil {
		log.Printf("warning: could not load favorites: %v", err)
	}
	favSet := map[string]bool{}
	if favs != nil {
		for _, s := range favs.List() {
			favSet[s.Url] = true
		}
	}

	items := make([]list.Item, len(initial))
	for i, s := range initial {
		isFav := favSet[s.Url]
		items[i] = item{station: s, isFavorite: isFav}
	}

	l := list.New(items, listDelegate{}, 60, 20)
	l.Title = "GoStations - Radio Browser (new TUI • ★ = favorite)"
	l.SetShowStatusBar(true)
	l.SetFilteringEnabled(true)
	l.Styles.Title = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("63"))

	// Use a custom substring/AND filter instead of the default fuzzy (which matches
	// any combination of individual letters, e.g. "WFMT" matching stations with W or F or M or T).
	// This makes "/WFMT" find stations containing the substring "wfmt" (case-insensitive),
	// and multi-word searches require all words to appear somewhere in the target.
	l.Filter = func(term string, targets []string) []list.Rank {
		term = strings.TrimSpace(term)
		if term == "" {
			ranks := make([]list.Rank, len(targets))
			for i := range targets {
				ranks[i] = list.Rank{Index: i}
			}
			return ranks
		}
		words := strings.Fields(strings.ToLower(term))
		var ranks []list.Rank
		for i, t := range targets {
			tl := strings.ToLower(t)
			matches := true
			for _, w := range words {
				if !strings.Contains(tl, w) {
					matches = false
					break
				}
			}
			if matches {
				ranks = append(ranks, list.Rank{Index: i})
			}
		}
		return ranks
	}

	return &App{list: l}
}

func (a *App) Init() tea.Cmd {
	return nil
}

func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.KeyMsg:
		switch msg.String() {
		case "q", "ctrl+c":
			a.quitting = true
			return a, tea.Quit
		case "enter":
			if i, ok := a.list.SelectedItem().(item); ok {
				// Placeholder: for now just show what would be played.
				// Later: switch to playback model + use player.Play
				a.list.Title = "Would play: " + i.station.Name + " (press q)"
			}
			return a, nil
		}

		// Auto-start filter on first alphanumeric character (better UX than requiring / first)
		s := msg.String()
		if len(s) == 1 {
			r := rune(s[0])
			if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') {
				if a.list.FilterState() != list.Filtering {
					// Simulate pressing the filter key to activate, then feed the char
					slash := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'/'}}
					var c1 tea.Cmd
					a.list, c1 = a.list.Update(slash)
					var c2 tea.Cmd
					a.list, c2 = a.list.Update(msg)
					return a, tea.Batch(c1, c2)
				}
			}
		}
	case tea.WindowSizeMsg:
		a.list.SetSize(msg.Width-4, msg.Height-4)
	}

	var cmd tea.Cmd
	a.list, cmd = a.list.Update(msg)
	return a, cmd
}

func (a *App) View() string {
	if a.quitting {
		return "Thanks for using GoStations!\n"
	}
	return "\n" + a.list.View() + "\n\n(type to filter • enter=play stub • / or letters for filter • q quit • --legacy for old UI)\n"
}

// Run starts the TUI (alt screen).
func Run(initial []radio.Station) error {
	p := tea.NewProgram(NewApp(initial), tea.WithAltScreen())
	_, err := p.Run()
	return err
}

// Short is a small helper (duplicated from old for TUI list desc; can be shared later).
func Short(s string, i int) string {
	runes := []rune(s)
	if len(runes) > i {
		return string(runes[:i])
	}
	return s
}
