// Package screen provides functions and helpers to manipulate a [uv.Screen].
package screen

import (
	uv "github.com/charmbracelet/ultraviolet"
)

// Clear clears the screen with empty cells. This is equivalent to filling the
// screen with empty cells.
//
// If the screen implements a [Clear] method, it will be called instead of
// filling the screen with empty cells.
func Clear(scr uv.Screen) {
	if c, ok := scr.(interface {
		Clear()
	}); ok {
		c.Clear()
		return
	}
	Fill(scr, nil)
}

// ClearArea clears the given area of the screen with empty cells. This is
// equivalent to filling the area with empty cells.
//
// If the screen implements a [ClearArea] method, it will be called instead of
// filling the area with empty cells.
func ClearArea(scr uv.Screen, area uv.Rectangle) {
	if c, ok := scr.(interface {
		ClearArea(area uv.Rectangle)
	}); ok {
		c.ClearArea(area)
		return
	}
	FillArea(scr, nil, area)
}

// Fill fills the screen with the given cell. If the cell is nil, it fills the
// screen with empty cells.
//
// If the screen implements a [Fill] method, it will be called instead of
// filling the screen with empty cells.
func Fill(scr uv.Screen, cell *uv.Cell) {
	if f, ok := scr.(interface {
		Fill(cell *uv.Cell)
	}); ok {
		f.Fill(cell)
		return
	}
	FillArea(scr, cell, scr.Bounds())
}

// FillArea fills the given area of the screen with the given cell. If the cell
// is nil, it fills the area with empty cells.
//
// If the screen implements a [FillArea] method, it will be called instead of
// filling the area with empty cells.
func FillArea(scr uv.Screen, cell *uv.Cell, area uv.Rectangle) {
	if f, ok := scr.(interface {
		FillArea(cell *uv.Cell, area uv.Rectangle)
	}); ok {
		f.FillArea(cell, area)
		return
	}
	cellWidth := 1
	if cell != nil && cell.Width > 1 {
		cellWidth = cell.Width
	}
	for y := area.Min.Y; y < area.Max.Y; y++ {
		for x := area.Min.X; x < area.Max.X; x += cellWidth {
			scr.SetCell(x, y, cell)
		}
	}
}

// CloneArea clones the given area of the screen and returns a new buffer
// with the same size as the area. The new buffer will contain the same cells
// as the area in the screen.
// Use [uv.Buffer.Draw] to draw the cloned buffer to a screen again.
//
// If the screen implements a [CloneArea] method, it will be called instead of
// cloning the area manually.
func CloneArea(scr uv.Screen, area uv.Rectangle) *uv.Buffer {
	if c, ok := scr.(interface {
		CloneArea(area uv.Rectangle) *uv.Buffer
	}); ok {
		return c.CloneArea(area)
	}
	buf := uv.NewBuffer(area.Dx(), area.Dy())
	for y := area.Min.Y; y < area.Max.Y; y++ {
		for x := area.Min.X; x < area.Max.X; {
			cell := scr.CellAt(x, y)
			if cell == nil || cell.IsZero() {
				x++
				continue
			}
			buf.SetCell(x-area.Min.X, y-area.Min.Y, cell)
			x += max(cell.Width, 1)
		}
	}
	return buf
}

// Clone creates a new [uv.Buffer] clone of the given screen. The new buffer will
// have the same size as the screen and will contain the same cells.
// Use [uv.Buffer.Draw] to draw the cloned buffer to a screen again.
//
// If the screen implements a [Clone] method, it will be called instead of
// cloning the entire screen manually.
func Clone(scr uv.Screen) *uv.Buffer {
	if c, ok := scr.(interface {
		Clone() *uv.Buffer
	}); ok {
		return c.Clone()
	}
	return CloneArea(scr, scr.Bounds())
}
