package username

import (
	"github.com/diamondburned/cchat"
	"github.com/diamondburned/cchat-gtk/internal/gts"
	"github.com/diamondburned/cchat-gtk/internal/ui/config"
	"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
	"github.com/diamondburned/cchat-gtk/internal/ui/rich"
	"github.com/diamondburned/cchat/text"
	"github.com/gotk3/gotk3/gtk"
)

const AvatarSize = 24

var showUser = true
var currentRevealer = func(bool) {} // noop by default

func init() {
	// Bind this revealer in settings.
	config.AppearanceAdd("Show Username in Input", config.Switch(
		&showUser,
		func(b bool) { currentRevealer(b) },
	))
}

type Container struct {
	*gtk.Revealer
	main   *gtk.Box
	avatar *rich.Icon
	label  *rich.Label
}

var (
	_ cchat.LabelContainer = (*Container)(nil)
	_ cchat.IconContainer  = (*Container)(nil)
)

var usernameCSS = primitives.PrepareCSS(`
	.username-view { margin: 0 5px }
`)

func NewContainer() *Container {
	avatar := rich.NewIcon(AvatarSize)
	avatar.SetPlaceholderIcon("user-available-symbolic", AvatarSize)
	avatar.Show()

	label := rich.NewLabel(text.Rich{})
	label.SetMaxWidthChars(35)
	label.Show()

	box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 5)
	box.PackStart(avatar, false, false, 0)
	box.PackStart(label, false, false, 0)
	box.Show()

	primitives.AddClass(box, "username-view")
	primitives.AttachCSS(box, usernameCSS)

	rev, _ := gtk.RevealerNew()
	rev.SetRevealChild(false)
	rev.SetTransitionType(gtk.REVEALER_TRANSITION_TYPE_SLIDE_RIGHT)
	rev.SetTransitionDuration(50)
	rev.Add(box)

	// Bind the current global revealer to this revealer for settings. This
	// operation should be thread-safe, as everything is being done in the main
	// thread.
	currentRevealer = rev.SetRevealChild

	return &Container{
		Revealer: rev,
		main:     box,
		avatar:   avatar,
		label:    label,
	}
}

func (u *Container) SetRevealChild(reveal bool) {
	// Only reveal if showUser is true.
	u.Revealer.SetRevealChild(reveal && showUser)
}

// shouldReveal returns whether or not the container should reveal.
func (u *Container) shouldReveal() bool {
	return !u.label.GetLabel().Empty() && showUser
}

func (u *Container) Reset() {
	u.SetRevealChild(false)
	u.avatar.Reset()
	u.label.Reset()
}

// Update is not thread-safe.
func (u *Container) Update(session cchat.Session, sender cchat.ServerMessageSender) {
	// Set the fallback username.
	u.label.SetLabelUnsafe(session.Name())
	// Reveal the name if it's not empty.
	u.SetRevealChild(u.shouldReveal())

	// Does sender (aka Server) implement ServerNickname? If yes, use it.
	if nicknamer, ok := sender.(cchat.ServerNickname); ok {
		u.label.AsyncSetLabel(nicknamer.Nickname, "Error fetching server nickname")
	}

	// Does session implement an icon? Update if yes.
	if iconer, ok := session.(cchat.Icon); ok {
		u.avatar.AsyncSetIconer(iconer, "Error fetching session icon URL")
	}
}

// GetLabel is not thread-safe.
func (u *Container) GetLabel() text.Rich {
	return u.label.GetLabel()
}

// SetLabel is thread-safe.
func (u *Container) SetLabel(content text.Rich) {
	gts.ExecAsync(func() {
		u.label.SetLabelUnsafe(content)

		// Reveal if the name is not empty.
		u.SetRevealChild(u.shouldReveal())
	})
}

// SetIcon is thread-safe.
func (u *Container) SetIcon(url string) {
	gts.ExecAsync(func() {
		u.avatar.SetIconUnsafe(url)

		// Reveal if the icon URL is not empty. We don't touch anything if the
		// URL is empty, as the name might not be.
		if url != "" {
			u.SetRevealChild(true)
		}
	})
}

// GetIconURL is not thread-safe.
func (u *Container) GetIconURL() string {
	return u.avatar.URL()
}
