package app

import (
	"bytes"
	"sync"
	"time"

	"github.com/mattn/go-runewidth"

	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib/log"
	"git.sr.ht/~rjarry/aerc/lib/state"
	"git.sr.ht/~rjarry/aerc/lib/templates"
	"git.sr.ht/~rjarry/aerc/lib/ui"
	"git.sr.ht/~rockorager/vaxis"
)

type StatusLine struct {
	sync.Mutex
	stack []*StatusMessage
	acct  *AccountView
	err   string
}

type StatusMessage struct {
	style   vaxis.Style
	message string
}

func (status *StatusLine) Invalidate() {
	ui.Invalidate()
}

func (status *StatusLine) Draw(ctx *ui.Context) {
	status.Lock()
	defer status.Unlock()
	style := status.uiConfig().GetStyle(config.STYLE_STATUSLINE_DEFAULT)
	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', style)
	switch {
	case len(status.stack) != 0:
		line := status.stack[len(status.stack)-1]
		msg := runewidth.Truncate(line.message, ctx.Width(), "")
		msg = runewidth.FillRight(msg, ctx.Width())
		ctx.Printf(0, 0, line.style, "%s", msg)
	case status.err != "":
		msg := runewidth.Truncate(status.err, ctx.Width(), "")
		msg = runewidth.FillRight(msg, ctx.Width())
		style := status.uiConfig().GetStyle(config.STYLE_STATUSLINE_ERROR)
		ctx.Printf(0, 0, style, "%s", msg)
	case status.acct != nil:
		data := state.NewDataSetter()
		data.SetPendingKeys(aerc.pendingKeys)
		data.SetState(&status.acct.state)
		data.SetAccount(status.acct.acct)
		data.SetFolder(status.acct.Directories().SelectedDirectory())
		msg, _ := status.acct.SelectedMessage()
		data.SetInfo(msg, 0, false)
		data.SetRUE(status.acct.dirlist.List(), status.acct.dirlist.GetRUECount)
		if store := status.acct.Store(); store != nil {
			data.SetVisual(store.Marker().IsVisualMark())
		}
		table := ui.NewTable(
			ctx.Height(),
			config.Statusline.StatusColumns,
			config.Statusline.ColumnSeparator,
			nil,
			func(*ui.Table, int) vaxis.Style { return style },
		)
		var buf bytes.Buffer
		cells := make([]string, len(table.Columns))
		for c, col := range table.Columns {
			err := templates.Render(col.Def.Template, &buf,
				data.Data())
			if err != nil {
				log.Errorf("%s", err)
				cells[c] = err.Error()
			} else {
				cells[c] = buf.String()
			}
			buf.Reset()
		}
		table.AddRow(cells, nil)
		table.Draw(ctx)
	}
}

func (status *StatusLine) Update(acct *AccountView) {
	status.acct = acct
	status.Invalidate()
}

func (status *StatusLine) SetError(err string) {
	prev := status.err
	status.err = err
	if prev != status.err {
		status.Invalidate()
	}
}

func (status *StatusLine) Clear() {
	status.SetError("")
	status.acct = nil
}

func (status *StatusLine) Push(text string, expiry time.Duration) *StatusMessage {
	status.Lock()
	defer status.Unlock()
	log.Debugf(text)
	msg := &StatusMessage{
		style:   status.uiConfig().GetStyle(config.STYLE_STATUSLINE_DEFAULT),
		message: text,
	}
	status.stack = append(status.stack, msg)
	go (func() {
		defer log.PanicHandler()

		time.Sleep(expiry)
		status.Lock()
		defer status.Unlock()
		for i, m := range status.stack {
			if m == msg {
				status.stack = append(status.stack[:i], status.stack[i+1:]...)
				break
			}
		}
		status.Invalidate()
	})()
	status.Invalidate()
	return msg
}

func (status *StatusLine) PushError(text string) *StatusMessage {
	log.Errorf(text)
	msg := status.Push(text, 10*time.Second)
	msg.Color(status.uiConfig().GetStyle(config.STYLE_STATUSLINE_ERROR))
	return msg
}

func (status *StatusLine) PushWarning(text string) *StatusMessage {
	log.Warnf(text)
	msg := status.Push(text, 10*time.Second)
	msg.Color(status.uiConfig().GetStyle(config.STYLE_STATUSLINE_WARNING))
	return msg
}

func (status *StatusLine) PushSuccess(text string) *StatusMessage {
	log.Tracef(text)
	msg := status.Push(text, 10*time.Second)
	msg.Color(status.uiConfig().GetStyle(config.STYLE_STATUSLINE_SUCCESS))
	return msg
}

func (status *StatusLine) Expire() {
	status.Lock()
	defer status.Unlock()
	status.stack = nil
}

func (status *StatusLine) uiConfig() *config.UIConfig {
	return SelectedAccountUiConfig()
}

func (msg *StatusMessage) Color(style vaxis.Style) {
	msg.style = style
}
