// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s

package model

import (
	"strings"

	"github.com/derailed/k9s/internal"
	"github.com/sahilm/fuzzy"
)

// Filterable represents an entity that can be filtered.
type Filterable interface {
	Filter(string)
	ClearFilter()
}

// Textable represents a text resource.
type Textable interface {
	Peek() []string
	SetText(string)
	AddListener(TextListener)
	RemoveListener(TextListener)
}

// TextListener represents a text model listener.
type TextListener interface {
	// TextChanged notifies the model changed.
	TextChanged([]string)

	// TextFiltered notifies when the filter changed.
	TextFiltered([]string, fuzzy.Matches)
}

// Text represents a text model.
type Text struct {
	lines     []string
	listeners []TextListener
	query     string
}

// NewText returns a new model.
func NewText() *Text {
	return &Text{}
}

// Peek returns the current model state.
func (t *Text) Peek() []string {
	return t.lines
}

// ClearFilter clear out filter.
func (t *Text) ClearFilter() {
	t.query = ""
	t.filterChanged(t.lines)
}

// Filter filters out the text.
func (t *Text) Filter(q string) {
	t.query = q
	t.filterChanged(t.lines)
}

// SetText sets the current model content.
func (t *Text) SetText(buff string) {
	t.lines = strings.Split(buff, "\n")
	t.fireTextChanged(t.lines)
}

// AddListener adds a new model listener.
func (t *Text) AddListener(listener TextListener) {
	t.listeners = append(t.listeners, listener)
}

// RemoveListener delete a listener from the list.
func (t *Text) RemoveListener(listener TextListener) {
	victim := -1
	for i, lis := range t.listeners {
		if lis == listener {
			victim = i
			break
		}
	}
	if victim >= 0 {
		t.listeners = append(t.listeners[:victim], t.listeners[victim+1:]...)
	}
}

func (t *Text) filterChanged(lines []string) {
	t.fireTextFiltered(lines, t.filter(t.query, lines))
}

func (t *Text) fireTextChanged(lines []string) {
	for _, lis := range t.listeners {
		lis.TextChanged(lines)
	}
}

func (t *Text) fireTextFiltered(lines []string, matches fuzzy.Matches) {
	for _, lis := range t.listeners {
		lis.TextFiltered(lines, matches)
	}
}

// ----------------------------------------------------------------------------
// Helpers...

func (t *Text) filter(q string, lines []string) fuzzy.Matches {
	if q == "" {
		return nil
	}
	if f, ok := internal.IsFuzzySelector(q); ok {
		return t.fuzzyFilter(strings.TrimSpace(f), lines)
	}
	return rxFilter(q, lines)
}

func (*Text) fuzzyFilter(q string, lines []string) fuzzy.Matches {
	return fuzzy.Find(q, lines)
}
