package widget

import (
	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/data/binding"
	"fyne.io/fyne/v2/theme"
)

var _ fyne.Widget = (*Label)(nil)

// Label widget is a label component with appropriate padding and layout.
type Label struct {
	BaseWidget
	Text      string
	Alignment fyne.TextAlign // The alignment of the text
	Wrapping  fyne.TextWrap  // The wrapping of the text
	TextStyle fyne.TextStyle // The style of the label text

	// The truncation mode of the text
	//
	// Since: 2.4
	Truncation fyne.TextTruncation
	// Importance informs how the label should be styled, i.e. warning or disabled
	//
	// Since: 2.4
	Importance Importance

	// The theme size name for the text size of the label
	//
	// Since: 2.6
	SizeName fyne.ThemeSizeName

	// If set to true, Selectable indicates that this label should support select interaction
	// to allow the text to be copied.
	//
	//Since: 2.6
	Selectable bool

	provider  *RichText
	binder    basicBinder
	selection *focusSelectable
}

// NewLabel creates a new label widget with the set text content
func NewLabel(text string) *Label {
	return NewLabelWithStyle(text, fyne.TextAlignLeading, fyne.TextStyle{})
}

// NewLabelWithData returns a Label widget connected to the specified data source.
//
// Since: 2.0
func NewLabelWithData(data binding.String) *Label {
	label := NewLabel("")
	label.Bind(data)

	return label
}

// NewLabelWithStyle creates a new label widget with the set text content
func NewLabelWithStyle(text string, alignment fyne.TextAlign, style fyne.TextStyle) *Label {
	l := &Label{
		Text:      text,
		Alignment: alignment,
		TextStyle: style,
	}

	l.ExtendBaseWidget(l)
	return l
}

// Bind connects the specified data source to this Label.
// The current value will be displayed and any changes in the data will cause the widget to update.
//
// Since: 2.0
func (l *Label) Bind(data binding.String) {
	l.binder.SetCallback(l.updateFromData) // This could only be done once, maybe in ExtendBaseWidget?
	l.binder.Bind(data)
}

// CreateRenderer is a private method to Fyne which links this widget to its renderer
func (l *Label) CreateRenderer() fyne.WidgetRenderer {
	l.provider = NewRichTextWithText(l.Text)
	l.ExtendBaseWidget(l)
	l.syncSegments()

	l.selection = &focusSelectable{}
	l.selection.ExtendBaseWidget(l.selection)
	l.selection.focus = l.selection
	l.selection.style = l.TextStyle
	l.selection.theme = l.Theme()
	l.selection.provider = l.provider

	return &labelRenderer{l}
}

// MinSize returns the size that this label should not shrink below.
func (l *Label) MinSize() fyne.Size {
	l.ExtendBaseWidget(l)
	return l.BaseWidget.MinSize()
}

// Refresh triggers a redraw of the label.
func (l *Label) Refresh() {
	if l.provider == nil { // not created until visible
		return
	}
	l.syncSegments()
	l.provider.Refresh()
	l.BaseWidget.Refresh()
}

// SelectedText returns the text currently selected in this Label.
// If the label is not Selectable it will return an empty string.
// If there is no selection it will return the empty string.
//
// Since: 2.6
func (l *Label) SelectedText() string {
	if !l.Selectable || l.selection == nil {
		return ""
	}

	return l.selection.SelectedText()
}

// SetText sets the text of the label
func (l *Label) SetText(text string) {
	l.Text = text
	l.Refresh()
}

// Unbind disconnects any configured data source from this Label.
// The current value will remain at the last value of the data source.
//
// Since: 2.0
func (l *Label) Unbind() {
	l.binder.Unbind()
}

func (l *Label) syncSegments() {
	var color fyne.ThemeColorName
	switch l.Importance {
	case LowImportance:
		color = theme.ColorNameDisabled
	case MediumImportance:
		color = theme.ColorNameForeground
	case HighImportance:
		color = theme.ColorNamePrimary
	case DangerImportance:
		color = theme.ColorNameError
	case WarningImportance:
		color = theme.ColorNameWarning
	case SuccessImportance:
		color = theme.ColorNameSuccess
	default:
		color = theme.ColorNameForeground
	}

	sizeName := l.SizeName
	if sizeName == "" {
		sizeName = theme.SizeNameText
	}
	l.provider.Wrapping = l.Wrapping
	l.provider.Truncation = l.Truncation
	l.provider.Segments[0].(*TextSegment).Style = RichTextStyle{
		Alignment: l.Alignment,
		ColorName: color,
		Inline:    true,
		TextStyle: l.TextStyle,
		SizeName:  sizeName,
	}
	l.provider.Segments[0].(*TextSegment).Text = l.Text
}

func (l *Label) updateFromData(data binding.DataItem) {
	if data == nil {
		return
	}
	textSource, ok := data.(binding.String)
	if !ok {
		return
	}
	val, err := textSource.Get()
	if err != nil {
		fyne.LogError("Error getting current data value", err)
		return
	}
	l.SetText(val)
}

type labelRenderer struct {
	l *Label
}

func (r *labelRenderer) Destroy() {
}

func (r *labelRenderer) Layout(s fyne.Size) {
	r.l.selection.Resize(s)
	r.l.provider.Resize(s)
}

func (r *labelRenderer) MinSize() fyne.Size {
	return r.l.provider.MinSize()
}

func (r *labelRenderer) Objects() []fyne.CanvasObject {
	if !r.l.Selectable {
		return []fyne.CanvasObject{r.l.provider}
	}

	return []fyne.CanvasObject{r.l.selection, r.l.provider}
}

func (r *labelRenderer) Refresh() {
	r.l.provider.Refresh()

	sel := r.l.selection
	if !r.l.Selectable || sel == nil {
		return
	}

	sel.sizeName = r.l.SizeName
	sel.style = r.l.TextStyle
	sel.theme = r.l.Theme()
	sel.Refresh()
}

type focusSelectable struct {
	selectable
}

func (f *focusSelectable) FocusGained() {
	f.focussed = true
	f.Refresh()
}

func (f *focusSelectable) FocusLost() {
	f.focussed = false
	f.Refresh()
}

func (f *focusSelectable) TypedKey(*fyne.KeyEvent) {
}

func (f *focusSelectable) TypedRune(rune) {
}
