package util

import (
	"fmt"
	"image"
	"image/color"
	"math"
	"strconv"
	"strings"
	"sync"
	"time"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/layout"
	"fyne.io/fyne/v2/theme"
	"fyne.io/fyne/v2/widget"
	"github.com/dweymouth/supersonic/backend/mediaprovider"
	"github.com/dweymouth/supersonic/res"
	"github.com/dweymouth/supersonic/sharedutil"
	"github.com/dweymouth/supersonic/ui/layouts"
	myTheme "github.com/dweymouth/supersonic/ui/theme"
	"golang.org/x/net/html"
)

var BoldRichTextStyle = widget.RichTextStyle{TextStyle: fyne.TextStyle{Bold: true}, Inline: true}

func MakeOpaque(c color.Color) color.Color {
	if nrgba, ok := c.(color.NRGBA); ok {
		nrgba.A = 255
		return nrgba
	}
	return c
}

func SecondsToTimeString(s float64) string {
	if s < 0 {
		s = 0
	}
	sec := int(math.Round(s))
	min := sec / 60
	sec -= min * 60

	return fmt.Sprintf("%d:%02d", min, sec)
}

func BytesToSizeString(bytes int64) string {
	var num float64
	var suffix string
	switch b := float64(bytes); {
	case b > 1000000000:
		suffix = "GB"
		num = b / 1000000000
	case b > 1000000:
		suffix = "MB"
		num = b / 1000000
	case b > 1000:
		suffix = "KB"
		num = b / 1000
	default:
		suffix = "B"
		num = b
	}
	return fmt.Sprintf(fmtStringForThreeSigFigs(num)+" %s", num, suffix)
}

func fmtStringForThreeSigFigs(num float64) string {
	switch {
	case num >= 100:
		return "%0.0f"
	case num >= 10:
		return "%0.1f"
	default:
		return "%0.2f"
	}
}

func ImageAspect(im image.Image) float32 {
	b := im.Bounds()
	return float32(b.Max.X-b.Min.X) / float32(b.Max.Y-b.Min.Y)
}

// Debouncer returns a function that will call callOnDone when
// it has not been invoked since the last dur interval.
func NewDebouncer(dur time.Duration, callOnDone func()) func() {
	var mu sync.Mutex
	var timer *time.Timer
	return func() {
		mu.Lock()
		defer mu.Unlock()

		if timer != nil {
			timer.Stop()
		}
		timer = time.AfterFunc(dur, callOnDone)
	}
}

func PlaintextFromHTMLString(s string) string {
	tokr := html.NewTokenizer(strings.NewReader(s))

	var text string
	var isLink bool
	var done bool
	for !done {
		tt := tokr.Next()
		switch {
		case tt == html.ErrorToken:
			done = true
		case tt == html.StartTagToken:
			t := tokr.Token()
			isLink = t.Data == "a"
		case tt == html.EndTagToken:
			isLink = false
		case tt == html.TextToken:
			t := tokr.Token()
			// for now, skip displaying Navidrome's "Read more on Last.FM" link
			if !isLink {
				text = text + t.Data
			}
		}
	}
	return text
}

func DisplayReleaseType(releaseTypes mediaprovider.ReleaseTypes) string {
	baseType := "Album"
	switch {
	case releaseTypes&mediaprovider.ReleaseTypeAudiobook > 0:
		baseType = "Audiobook"
	case releaseTypes&mediaprovider.ReleaseTypeAudioDrama > 0:
		baseType = "Audio Drama"
	case releaseTypes&mediaprovider.ReleaseTypeBroadcast > 0:
		baseType = "Broadcast"
	case releaseTypes&mediaprovider.ReleaseTypeDJMix > 0:
		baseType = "DJ-Mix"
	case releaseTypes&mediaprovider.ReleaseTypeEP > 0:
		baseType = "EP"
	case releaseTypes&mediaprovider.ReleaseTypeFieldRecording > 0:
		baseType = "Field Recording"
	case releaseTypes&mediaprovider.ReleaseTypeInterview > 0:
		baseType = "Interview"
	case releaseTypes&mediaprovider.ReleaseTypeMixtape > 0:
		baseType = "Mixtape"
	case releaseTypes&mediaprovider.ReleaseTypeSingle > 0:
		baseType = "Single"
	case releaseTypes&mediaprovider.ReleaseTypeSoundtrack > 0:
		baseType = "Soundtrack"
	}

	var modifiers []string
	if releaseTypes&mediaprovider.ReleaseTypeLive > 0 {
		modifiers = append(modifiers, "Live")
	}
	if releaseTypes&mediaprovider.ReleaseTypeDemo > 0 {
		modifiers = append(modifiers, "Demo")
	}
	if releaseTypes&mediaprovider.ReleaseTypeRemix > 0 {
		modifiers = append(modifiers, "Remix")
	}
	if releaseTypes&mediaprovider.ReleaseTypeSpokenWord > 0 {
		modifiers = append(modifiers, "Spoken Word")
	}
	if releaseTypes&mediaprovider.ReleaseTypeCompilation > 0 {
		modifiers = append(modifiers, "Compilation")
	}

	modifiers = append(modifiers, baseType)
	return strings.Join(modifiers, " ")
}

func NewRatingSubmenu(onSetRating func(int)) *fyne.MenuItem {
	newRatingMenuItem := func(rating int) *fyne.MenuItem {
		label := "(none)"
		if rating > 0 {
			label = strconv.Itoa(rating)
		}
		return fyne.NewMenuItem(label, func() {
			onSetRating(rating)
		})
	}
	ratingMenu := fyne.NewMenuItem("Set rating", nil)
	ratingMenu.Icon = theme.NewThemedResource(res.ResStarOutlineSvg)
	ratingMenu.ChildMenu = fyne.NewMenu("", []*fyne.MenuItem{
		newRatingMenuItem(0),
		newRatingMenuItem(1),
		newRatingMenuItem(2),
		newRatingMenuItem(3),
		newRatingMenuItem(4),
		newRatingMenuItem(5),
	}...)
	return ratingMenu
}

func NewReorderTracksSubmenu(onReorderTracks func(sharedutil.TrackReorderOp)) *fyne.MenuItem {
	reorderMenu := fyne.NewMenuItem("Reorder tracks", nil)
	reorderMenu.Icon = myTheme.SortIcon
	reorderMenu.ChildMenu = fyne.NewMenu("", []*fyne.MenuItem{
		fyne.NewMenuItem("Move to top", func() { onReorderTracks(sharedutil.MoveToTop) }),
		fyne.NewMenuItem("Move up", func() { onReorderTracks(sharedutil.MoveUp) }),
		fyne.NewMenuItem("Move down", func() { onReorderTracks(sharedutil.MoveDown) }),
		fyne.NewMenuItem("Move to bottom", func() { onReorderTracks(sharedutil.MoveToBottom) }),
	}...)
	return reorderMenu
}

func AddHeaderBackground(obj fyne.CanvasObject) *fyne.Container {
	bgrnd := myTheme.NewThemedRectangle(myTheme.ColorNamePageHeader)
	bgrnd.CornerRadiusName = theme.SizeNameInputRadius
	return container.NewStack(bgrnd,
		container.New(&layouts.MaxPadLayout{PadLeft: 10, PadRight: 10, PadTop: 10, PadBottom: 10},
			obj))
}

func NewTruncatingRichText() *widget.RichText {
	rt := widget.NewRichTextWithText("")
	rt.Truncation = fyne.TextTruncateEllipsis
	return rt
}

func NewTruncatingLabel() *widget.Label {
	rt := widget.NewLabel("")
	rt.Truncation = fyne.TextTruncateEllipsis
	return rt
}

func NewTrailingAlignLabel() *widget.Label {
	rt := widget.NewLabel("")
	rt.Alignment = fyne.TextAlignTrailing
	return rt
}

type HSpace struct {
	widget.BaseWidget

	Width float32
}

func NewHSpace(w float32) *HSpace {
	h := &HSpace{Width: w}
	h.ExtendBaseWidget(h)
	return h
}

func (h *HSpace) MinSize() fyne.Size {
	return fyne.NewSize(h.Width, 0)
}

func (h *HSpace) CreateRenderer() fyne.WidgetRenderer {
	return widget.NewSimpleRenderer(layout.NewSpacer())
}
