// TODO(doc): document TUI lifecycle
// TODO(test): write unittests for types.go
package tui

import (
	"fmt"
	"slices"
	"time"

	"github.com/NimbleMarkets/ntcharts/linechart/streamlinechart"
	"github.com/charmbracelet/bubbles/viewport"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
)

// Bubbletea model
type Model struct {
	width       int
	Addresses   []Address // as defined in internal/tui/types.go
	viewport    viewport.Model
	UpdateSpeed time.Duration
	ChartHeight int
}

func InitialModel(addresses []string, speed time.Duration, chartHeight int) Model {
	var model Model
	model.viewport = viewport.New(0, 0)
	model.viewport.MouseWheelEnabled = true
	model.UpdateSpeed = speed
	model.ChartHeight = chartHeight

	for _, address := range addresses {
		var addr Address
		addr.max_results = 80
		addr.Address = address
		model.Addresses = append(model.Addresses, addr)
	}
	return model
}

func (m Model) Init() tea.Cmd {
	return m.Tick()
}

func (m Model) Tick() tea.Cmd {
	return tea.Tick(time.Millisecond*m.UpdateSpeed, func(t time.Time) tea.Msg {
		return tickMsg(t)
	})
}

func (m Model) content() string {
	var blockStyle = lipgloss.NewStyle().
		Width(m.width).
		Align(lipgloss.Center)

	output := "\n"
	for _, address := range m.Addresses {
		if len(address.results) == 0 {
			output = output + fmt.Sprintf("\n%s\tloading...", headerStyle.Render(address.Address))
		} else if m.viewport.Width != 0 && m.viewport.Height != 0 {
			if slices.Contains(address.results, -1) {
				output = output + fmt.Sprintf("\n%s",
					blockStyle.Render(headerStyle.Render(
						fmt.Sprintf("%s\t%s",
							secondaryColor.Render(address.Address),
							infoStyle.Render("(connection unstable)"),
						),
					)))
			} else {
				output = output + fmt.Sprintf("\n%s",
					blockStyle.Render(headerStyle.Render(address.Address)))
			}

			// Linechart
			viewportHeight := m.viewport.Height - 4
			var slc streamlinechart.Model
			if m.ChartHeight == 0 && len(m.Addresses) == 1 {
				slc = streamlinechart.New(m.width, viewportHeight)
			} else if m.ChartHeight == 0 && len(m.Addresses) > 1 {
				slc = streamlinechart.New(m.width, viewportHeight-5)
			} else {
				slc = streamlinechart.New(m.width, m.ChartHeight)
			}

			for _, v := range address.results {
				slc.Push(v)
			}
			slc.Draw()
			output = output + blockStyle.Render(fmt.Sprintf("\n%s\n", slc.View()))
		}
	}

	return output
}

func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	var cmd tea.Cmd
	var cmds []tea.Cmd

	switch msg := msg.(type) {
	// if case is KeyMsg (keypress)
	case tea.WindowSizeMsg:
		m.width = msg.Width
		for i, address := range m.Addresses {
			address.max_results = m.width
			m.Addresses[i] = address
		}
		m.viewport.Height = msg.Height - 4
		m.viewport.Width = msg.Width
		m.viewport.YPosition = 1

	case tea.KeyMsg:
		if k := msg.String(); k == "j" { // scroll down
			m.viewport.LineDown(8)
		} else if k == "k" { // scroll up
			m.viewport.LineUp(8)
		} else {
			return m, tea.Quit
		}

	case tickMsg:
		cmds = append(cmds, m.Tick())
		cmds = append(cmds, m.Poll)
	}

	m.viewport, cmd = m.viewport.Update(msg)
	m.viewport.SetContent(m.content())
	cmds = append(cmds, cmd)
	// cmds = append(cmds, m.Poll)

	return m, tea.Batch(cmds...)
}

func (m Model) View() string {
	var headerStyle = lipgloss.NewStyle().
		Width(m.width).
		Align(lipgloss.Center).
		Italic(true).
		Faint(true)

	var footerStyle = lipgloss.NewStyle().
		Width(m.width).
		Align(lipgloss.Center).
		Italic(true).
		Faint(true)

	header := headerStyle.Render("pingo v0")
	footer := footerStyle.Render("\nj/k: down/up\t|\tq/ctrl-c/esc: quit\n")
	return fmt.Sprintf("\n%s\n%s\n%s", header, m.viewport.View(), footer)
}

// A wrapper for the underlying [tui.Address.Poll] function. For each address in
// [tui.Model.Addresses], run its respective Poll function and update [tui.Model]
//
// NOTE(async): this function fully blocks execution of the current thread.
func (m Model) Poll() tea.Msg {
	for i, element := range m.Addresses {
		element.Poll()
		// element.results = append(element.results, -1)
		m.Addresses[i] = element
	}
	return nil
}
