package uv

import (
	"fmt"
	"image/color"
	"io"

	"github.com/charmbracelet/x/ansi"
	"github.com/lucasb-eyer/go-colorful"
)

var (
	// ErrNotTerminal is an error that indicates that the file is not a terminal.
	ErrNotTerminal = fmt.Errorf("not a terminal")
	// ErrPlatformNotSupported is an error that indicates that the platform is not supported.
	ErrPlatformNotSupported = fmt.Errorf("platform not supported")
)

// Drawable represents a drawable component on a [Screen].
type Drawable interface {
	// Draw renders the component on the screen for the given area.
	Draw(scr Screen, area Rectangle)
}

// DrawableFunc is a function that implements the [Drawable] interface.
type DrawableFunc func(scr Screen, rect Rectangle)

var _ Drawable = (DrawableFunc)(nil)

// Draw implements the [Drawable] interface.
func (f DrawableFunc) Draw(scr Screen, rect Rectangle) {
	f(scr, rect)
}

// WidthMethod determines how many columns a grapheme occupies on the screen.
type WidthMethod interface {
	StringWidth(s string) int
}

// Screen represents a screen that can be drawn to.
type Screen interface {
	// Bounds returns the bounds of the screen. This is the rectangle that
	// includes the start and end points of the screen.
	Bounds() Rectangle

	// CellAt returns the cell at the given position. If the position is out of
	// bounds, it returns nil. Otherwise, it always returns a cell, even if it
	// is empty (i.e., a cell with a space character and a width of 1).
	CellAt(x, y int) *Cell

	// SetCell sets the cell at the given position. A nil cell is treated as an
	// empty cell with a space character and a width of 1.
	SetCell(x, y int, c *Cell)

	// WidthMethod returns the width method used by the screen.
	WidthMethod() WidthMethod
}

// Cursor represents a cursor on the terminal screen.
type Cursor struct {
	// Position is a [Position] that determines the cursor's position on the
	// screen relative to the top left corner of the frame.
	Position

	// Color is a [color.Color] that determines the cursor's color.
	Color color.Color

	// Shape is a [CursorShape] that determines the cursor's shape.
	Shape CursorShape

	// Blink is a boolean that determines whether the cursor should blink.
	Blink bool

	// Hidden is a boolean that determines whether the cursor is hidden. You
	// can use this if you want to hide the cursor but still want to change its
	// position.
	Hidden bool
}

// NewCursor returns a new cursor with the default settings and the given
// position.
func NewCursor(x, y int) *Cursor {
	return &Cursor{
		Position: Position{X: x, Y: y},
		Color:    nil,
		Shape:    CursorBlock,
		Blink:    true,
	}
}

// ProgressBarState represents the state of the progress bar.
type ProgressBarState int

// Progress bar states.
const (
	ProgressBarNone ProgressBarState = iota
	ProgressBarDefault
	ProgressBarError
	ProgressBarIndeterminate
	ProgressBarWarning
)

// String return a human-readable value for the given [ProgressBarState].
func (s ProgressBarState) String() string {
	return [...]string{
		"None",
		"Default",
		"Error",
		"Indeterminate",
		"Warning",
	}[s]
}

// ProgressBar represents the terminal progress bar.
//
// Support depends on the terminal.
//
// See https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences
type ProgressBar struct {
	// State is the current state of the progress bar. It can be one of
	// [ProgressBarNone], [ProgressBarDefault], [ProgressBarError],
	// [ProgressBarIndeterminate], and [ProgressBarWarning].
	State ProgressBarState
	// Value is the current value of the progress bar. It should be between
	// 0 and 100.
	Value int
}

// NewProgressBar returns a new progress bar with the given state and value.
// The value is ignored if the state is [ProgressBarNone] or
// [ProgressBarIndeterminate].
func NewProgressBar(state ProgressBarState, value int) *ProgressBar {
	return &ProgressBar{
		State: state,
		Value: min(max(value, 0), 100),
	}
}

// KeyboardEnhancements defines different keyboard enhancement features that
// can be requested from the terminal.
type KeyboardEnhancements struct {
	// DisambiguateEscapeCodes requests the terminal to report ambiguous keys
	// such as Ctrl+i and Tab, and Ctrl+m and Enter, and others as distinct key
	// code sequences.
	// If supported, your program will receive distinct [KeyPressEvent]s for
	// these keys.
	DisambiguateEscapeCodes bool

	// ReportEventTypes requests the terminal to report key repeat and release
	// events.
	// If supported, your program will receive [KeyReleaseEvent]s and
	// [KeyPressEvent] with the [Key.IsRepeat] field set indicating that this
	// is a it's part of a key repeat sequence.
	ReportEventTypes bool

	// ReportAlternateKeys requests the terminal to report alternate key values
	// in addition to the main ones.
	// Note that only key events represented as escape codes will affected by
	// this enhancement.
	ReportAlternateKeys bool

	// ReportAllKeysAsEscapeCodes requests the terminal to report all key
	// events, including plain text keys, as escape codes.
	// When this is enabled, text won't be sent as plain text but instead as
	// escape codes that encode the key value and modifiers.
	ReportAllKeysAsEscapeCodes bool

	// ReportAssociatedText requests the terminal to report the text associated
	// with key events.
	// Note that this is an enhancement to
	// [KeyboardEnhancements.ReportAllKeysAsEscapeCodes] and only has an effect
	// if that is enabled.
	ReportAssociatedText bool
}

// NewKeyboardEnhancements returns a new [KeyboardEnhancements] with the given
// options as flags.
//
// A zero, or negative, flags value is treated as no enhancements.
//
// See [ansi.KittyKeyboard] for more details on the supported keyboard enhancements.
func NewKeyboardEnhancements(flags int) *KeyboardEnhancements {
	if flags <= 0 {
		return &KeyboardEnhancements{}
	}

	return &KeyboardEnhancements{
		DisambiguateEscapeCodes: flags&ansi.KittyDisambiguateEscapeCodes != 0,
		ReportEventTypes:        flags&ansi.KittyReportEventTypes != 0,
	}
}

// Flags returns the keyboard enhancements as bits that can be used to set the
// appropriate terminal modes.
func (ke KeyboardEnhancements) Flags() int {
	bits := 0
	if ke.DisambiguateEscapeCodes {
		bits |= ansi.KittyDisambiguateEscapeCodes
	}
	if ke.ReportEventTypes {
		bits |= ansi.KittyReportEventTypes
	}
	if ke.ReportAlternateKeys {
		bits |= ansi.KittyReportAlternateKeys
	}
	if ke.ReportAllKeysAsEscapeCodes {
		bits |= ansi.KittyReportAllKeysAsEscapeCodes
	}
	if ke.ReportAssociatedText {
		bits |= ansi.KittyReportAssociatedKeys
	}

	return bits
}

// EncodeBackgroundColor encodes the background color to the given writer. Use
// nil to reset the background color to the default.
func EncodeBackgroundColor(w io.Writer, c color.Color) error {
	var seq string
	if c == nil {
		seq = ansi.ResetBackgroundColor
	} else if col, ok := colorful.MakeColor(c); ok {
		seq = ansi.SetBackgroundColor(col.Hex())
	} else {
		return fmt.Errorf("invalid color: %v", c)
	}

	_, err := io.WriteString(w, seq)
	if err != nil {
		return fmt.Errorf("failed to set background color: %w", err)
	}

	return nil
}

// EncodeForegroundColor encodes the foreground color to the given writer. Use
// nil to reset the foreground color to the default.
func EncodeForegroundColor(w io.Writer, c color.Color) error {
	var seq string
	if c == nil {
		seq = ansi.ResetForegroundColor
	} else if col, ok := colorful.MakeColor(c); ok {
		seq = ansi.SetForegroundColor(col.Hex())
	} else {
		return fmt.Errorf("invalid color: %v", c)
	}

	_, err := io.WriteString(w, seq)
	if err != nil {
		return fmt.Errorf("failed to set foreground color: %w", err)
	}

	return nil
}

// EncodeCursorColor encodes the cursor color to the given writer. Use nil to
// reset the cursor color to the default.
func EncodeCursorColor(w io.Writer, c color.Color) error {
	var seq string
	if c == nil {
		seq = ansi.ResetCursorColor
	} else if col, ok := colorful.MakeColor(c); ok {
		seq = ansi.SetCursorColor(col.Hex())
	} else {
		return fmt.Errorf("invalid color: %v", c)
	}

	_, err := io.WriteString(w, seq)
	if err != nil {
		return fmt.Errorf("failed to set cursor color: %w", err)
	}

	return nil
}

// EncodeCursorStyle encodes the cursor style to the given writer.
func EncodeCursorStyle(w io.Writer, shape CursorShape, blink bool) error {
	seq := ansi.SetCursorStyle(shape.Encode(blink))
	_, err := io.WriteString(w, seq)
	if err != nil {
		return fmt.Errorf("failed to set cursor style: %w", err)
	}

	return nil
}

// EncodeBracketedPaste encodes the bracketed paste mode to the given writer.
func EncodeBracketedPaste(w io.Writer, enable bool) error {
	var seq string
	if enable {
		seq = ansi.SetModeBracketedPaste
	} else {
		seq = ansi.ResetModeBracketedPaste
	}

	_, err := io.WriteString(w, seq)
	if err != nil {
		return fmt.Errorf("failed to set bracketed paste mode: %w", err)
	}

	return nil
}

// EncodeMouseMode encodes the mouse tracking mode to the given writer.
func EncodeMouseMode(w io.Writer, mode MouseMode) error {
	var seq string
	switch mode {
	case MouseModeNone:
		seq = ansi.ResetModeMouseX10 +
			ansi.ResetModeMouseNormal +
			ansi.ResetModeMouseButtonEvent +
			ansi.ResetModeMouseAnyEvent
	case MouseModePress:
		seq = ansi.SetModeMouseX10
	case MouseModeClick:
		seq = ansi.SetModeMouseNormal
	case MouseModeDrag:
		seq = ansi.SetModeMouseButtonEvent
	case MouseModeMotion:
		seq = ansi.SetModeMouseAnyEvent
	}

	_, err := io.WriteString(w, seq) //nolint:errcheck
	if err != nil {
		return fmt.Errorf("failed to set mouse mode: %w", err)
	}

	return nil
}

// EncodeMouseEncoding encodes the mouse encoding mode to the given writer.
// When enc is [MouseEncodingLegacy], all extended encodings are reset.
func EncodeMouseEncoding(w io.Writer, enc MouseEncoding) error {
	var seq string
	switch enc {
	case MouseEncodingLegacy:
		seq = ansi.ResetModeMouseExtSgr +
			ansi.ResetModeMouseExtUrxvt +
			ansi.ResetModeMouseExtSgrPixel
	case MouseEncodingSGR:
		seq = ansi.SetModeMouseExtSgr
	case MouseEncodingSGRPixel:
		seq = ansi.SetModeMouseExtSgrPixel
	}

	_, err := io.WriteString(w, seq) //nolint:errcheck
	if err != nil {
		return fmt.Errorf("failed to set mouse encoding: %w", err)
	}

	return nil
}

// EncodeProgressBar encodes the progress bar to the given writer.
func EncodeProgressBar(w io.Writer, pb *ProgressBar) error {
	if pb == nil {
		pb = &ProgressBar{State: ProgressBarNone}
	}

	var seq string
	percent := clamp(pb.Value, 0, 100)
	switch pb.State {
	case ProgressBarNone:
		seq = ansi.ResetProgressBar
	case ProgressBarDefault:
		seq = ansi.SetProgressBar(percent)
	case ProgressBarError:
		seq = ansi.SetErrorProgressBar(percent)
	case ProgressBarIndeterminate:
		seq = ansi.SetIndeterminateProgressBar
	case ProgressBarWarning:
		seq = ansi.SetWarningProgressBar(percent)
	default:
		return fmt.Errorf("invalid progress bar state: %d", pb.State)
	}

	_, err := io.WriteString(w, seq)
	if err != nil {
		return fmt.Errorf("failed to set progress bar: %w", err)
	}

	return nil
}

// EncodeKeyboardEnhancements encodes the keyboard enhancements to the given
// writer.
func EncodeKeyboardEnhancements(w io.Writer, ke *KeyboardEnhancements) error {
	var flags int
	if ke != nil {
		flags = ke.Flags()
	}
	_, err := io.WriteString(w, ansi.KittyKeyboard(flags, 1))
	if err != nil {
		return fmt.Errorf("failed to set keyboard enhancements: %w", err)
	}
	return nil
}

// EncodeWindowTitle encodes the window title to the given writer.
func EncodeWindowTitle(w io.Writer, title string) error {
	_, err := io.WriteString(w, ansi.SetWindowTitle(title))
	if err != nil {
		return fmt.Errorf("failed to set window title: %w", err)
	}
	return nil
}
