package terminalstate

import (
	"fmt"
	"strconv"
	"strings"
	"sync"

	"github.com/rcarmo/go-te/pkg/te"
)

var ansi16Names = [...]string{
	"black",
	"red",
	"green",
	"yellow",
	"blue",
	"magenta",
	"cyan",
	"white",
	"brightblack",
	"brightred",
	"brightgreen",
	"brightyellow",
	"brightblue",
	"brightmagenta",
	"brightcyan",
	"brightwhite",
}

type Cell struct {
	Data       string `json:"data"`
	FG         string `json:"fg"`
	BG         string `json:"bg"`
	Bold       bool   `json:"bold"`
	Italics    bool   `json:"italics"`
	Underscore bool   `json:"underscore"`
	Reverse    bool   `json:"reverse"`
}

type Snapshot struct {
	Width      int      `json:"width"`
	Height     int      `json:"height"`
	Buffer     [][]Cell `json:"buffer"`
	HasChanges bool     `json:"has_changes"`
}

type Tracker struct {
	mu                  sync.Mutex
	screen              *te.DiffScreen
	stream              *te.ByteStream
	changeCounter       uint64
	lastActivityCounter uint64
	lastSnapshotCounter uint64
}

func NewTracker(width, height int) *Tracker {
	screen := te.NewDiffScreen(width, height)
	return &Tracker{
		screen: screen,
		stream: te.NewByteStream(screen, false),
	}
}

func (t *Tracker) Feed(data []byte) error {
	if len(data) == 0 {
		return nil
	}
	t.mu.Lock()
	defer t.mu.Unlock()
	if err := t.stream.Feed(data); err != nil {
		return err
	}
	if len(t.screen.Dirty) > 0 {
		t.changeCounter++
		// Clear dirty set so subsequent feeds detect new changes
		for k := range t.screen.Dirty {
			delete(t.screen.Dirty, k)
		}
	}
	return nil
}

func (t *Tracker) Resize(width, height int) {
	t.mu.Lock()
	defer t.mu.Unlock()
	if width == t.screen.Columns && height == t.screen.Lines {
		return
	}
	t.screen.Resize(height, width)
	t.changeCounter++
}

func (t *Tracker) Snapshot() Snapshot {
	t.mu.Lock()
	defer t.mu.Unlock()

	snapshot := Snapshot{
		Width:      t.screen.Columns,
		Height:     t.screen.Lines,
		HasChanges: t.changeCounter > t.lastSnapshotCounter,
		Buffer:     make([][]Cell, t.screen.Lines),
	}
	t.lastSnapshotCounter = t.changeCounter

	for row := 0; row < t.screen.Lines; row++ {
		line := make([]Cell, t.screen.Columns)
		for col := 0; col < t.screen.Columns; col++ {
			raw := t.screen.Buffer[row][col]
			data := raw.Data
			if data == "" {
				data = " "
			}
			line[col] = Cell{
				Data:       data,
				FG:         colorToString(raw.Attr.Fg),
				BG:         colorToString(raw.Attr.Bg),
				Bold:       raw.Attr.Bold,
				Italics:    raw.Attr.Italics,
				Underscore: raw.Attr.Underline,
				Reverse:    raw.Attr.Reverse,
			}
		}
		snapshot.Buffer[row] = line
	}
	return snapshot
}

func (t *Tracker) ConsumeActivityChanged() bool {
	t.mu.Lock()
	defer t.mu.Unlock()
	if t.changeCounter > t.lastActivityCounter {
		t.lastActivityCounter = t.changeCounter
		return true
	}
	return false
}

func colorToString(color te.Color) string {
	if color.Name != "" {
		name := strings.ToLower(strings.TrimPrefix(color.Name, "#"))
		if len(name) == 6 {
			if _, err := strconv.ParseUint(name, 16, 32); err == nil {
				return name
			}
		}
		return name
	}
	switch color.Mode {
	case te.ColorDefault:
		return "default"
	case te.ColorANSI16:
		if int(color.Index) < len(ansi16Names) {
			return ansi16Names[color.Index]
		}
		return "default"
	case te.ColorANSI256:
		return fmt.Sprintf("%d", color.Index)
	case te.ColorTrueColor:
		return "default"
	default:
		return "default"
	}
}
