package status

import (
	"fmt"
	"regexp"
	"strconv"
	"strings"
	"unicode"
)

const wideContinuation rune = -1

// RenderVisibleScreen runs the raw PTY stream through a small terminal screen
// model and returns the visible text.
func RenderVisibleScreen(raw string, rows, cols int) string {
	return renderVisibleScreen(raw, rows, cols)
}

// renderVisibleScreen runs the raw PTY stream through a small terminal screen
// model (cursor + grid) and returns the visible text. Unlike cleanANSI which
// strips control sequences in place, this honors cursor positioning and erase
// commands so that text overwritten by later repaints disappears, matching
// what a user would see in a terminal. The grid is sized large enough for
// Claude's /usage panel (60 rows × 200 cols); content past the bottom row
// scrolls up.
//
// Only the sequences Claude's /usage TUI is actually observed to emit are
// implemented — CUP (H/f), CUU/CUD/CUF/CUB (A/B/C/D), CHA (G), ED (J), EL (K),
// plus \r, \n, \b, \t. SGR (m), OSC, and DEC private modes are recognized and
// discarded; unknown CSIs are skipped.
func renderVisibleScreen(raw string, rows, cols int) string {
	scr := newScreen(rows, cols)
	rs := []rune(raw)
	for i := 0; i < len(rs); {
		c := rs[i]
		switch {
		case c == 0x1b:
			i++
			if i >= len(rs) {
				break
			}
			switch rs[i] {
			case '[':
				i++
				priv := byte(0)
				if i < len(rs) && (rs[i] == '?' || rs[i] == '>' || rs[i] == '!') {
					priv = byte(rs[i])
					i++
				}
				start := i
				for i < len(rs) && ((rs[i] >= '0' && rs[i] <= '9') || rs[i] == ';') {
					i++
				}
				params := string(rs[start:i])
				if i >= len(rs) {
					break
				}
				cmd := rs[i]
				i++
				scr.handleCSI(priv, params, cmd)
			case ']':
				i++
				for i < len(rs) {
					if rs[i] == 0x07 {
						i++
						break
					}
					if rs[i] == 0x1b && i+1 < len(rs) && rs[i+1] == '\\' {
						i += 2
						break
					}
					i++
				}
			default:
				i++
			}
		case c == '\r':
			scr.cx = 0
			i++
		case c == '\n':
			scr.cy++
			if scr.cy >= scr.rows {
				scr.scrollUp()
				scr.cy = scr.rows - 1
			}
			i++
		case c == '\b':
			if scr.cx > 0 {
				scr.cx--
			}
			i++
		case c == '\t':
			scr.cx = ((scr.cx / 8) + 1) * 8
			if scr.cx >= scr.cols {
				scr.cx = scr.cols - 1
			}
			i++
		case c < 0x20:
			i++
		default:
			scr.putRune(c)
			i++
		}
	}
	return scr.render()
}

type screen struct {
	rows, cols int
	grid       [][]rune
	cy, cx     int
}

func newScreen(rows, cols int) *screen {
	g := make([][]rune, rows)
	for i := range g {
		g[i] = make([]rune, cols)
		for j := range g[i] {
			g[i][j] = ' '
		}
	}
	return &screen{rows: rows, cols: cols, grid: g}
}

func (s *screen) clampCursor() {
	if s.cy < 0 {
		s.cy = 0
	}
	if s.cy >= s.rows {
		s.cy = s.rows - 1
	}
	if s.cx < 0 {
		s.cx = 0
	}
	if s.cx >= s.cols {
		s.cx = s.cols - 1
	}
}

func (s *screen) putRune(r rune) {
	w := terminalRuneWidth(r)
	if w <= 0 {
		return
	}
	if s.cx >= s.cols {
		s.wrapLine()
	}
	if w > 1 && s.cx == s.cols-1 {
		s.wrapLine()
	}
	for i := 0; i < w && s.cx+i < s.cols; i++ {
		s.clearCell(s.cy, s.cx+i)
	}
	s.grid[s.cy][s.cx] = r
	if w > 1 && s.cx+1 < s.cols {
		s.grid[s.cy][s.cx+1] = wideContinuation
	}
	s.cx += w
}

func (s *screen) wrapLine() {
	s.cx = 0
	s.cy++
	if s.cy >= s.rows {
		s.scrollUp()
		s.cy = s.rows - 1
	}
}

func (s *screen) clearCell(row, col int) {
	if row < 0 || row >= s.rows || col < 0 || col >= s.cols {
		return
	}
	if s.grid[row][col] == wideContinuation && col > 0 {
		s.grid[row][col-1] = ' '
	}
	if col+1 < s.cols && s.grid[row][col+1] == wideContinuation {
		s.grid[row][col+1] = ' '
	}
	s.grid[row][col] = ' '
}

func terminalRuneWidth(r rune) int {
	if r == 0 || r < 0x20 {
		return 0
	}
	if unicode.Is(unicode.Mn, r) || unicode.Is(unicode.Me, r) {
		return 0
	}
	if unicode.In(r, unicode.Hangul, unicode.Han, unicode.Hiragana, unicode.Katakana) {
		return 2
	}
	switch {
	case r >= 0x1100 && r <= 0x115f:
		return 2
	case r >= 0x2329 && r <= 0x232a:
		return 2
	case r >= 0x2e80 && r <= 0xa4cf:
		return 2
	case r >= 0xac00 && r <= 0xd7a3:
		return 2
	case r >= 0xf900 && r <= 0xfaff:
		return 2
	case r >= 0xfe10 && r <= 0xfe19:
		return 2
	case r >= 0xfe30 && r <= 0xfe6f:
		return 2
	case r >= 0xff00 && r <= 0xff60:
		return 2
	case r >= 0xffe0 && r <= 0xffe6:
		return 2
	default:
		return 1
	}
}

func (s *screen) scrollUp() {
	for i := 0; i < s.rows-1; i++ {
		s.grid[i] = s.grid[i+1]
	}
	blank := make([]rune, s.cols)
	for j := range blank {
		blank[j] = ' '
	}
	s.grid[s.rows-1] = blank
}

func (s *screen) handleCSI(priv byte, params string, cmd rune) {
	if priv != 0 {
		return
	}
	parts := strings.Split(params, ";")
	p1 := csiInt(parts, 0, 0)
	switch cmd {
	case 'H', 'f':
		row := csiInt(parts, 0, 1)
		col := csiInt(parts, 1, 1)
		s.cy = row - 1
		s.cx = col - 1
		s.clampCursor()
	case 'A':
		s.cy -= csiInt(parts, 0, 1)
		s.clampCursor()
	case 'B':
		s.cy += csiInt(parts, 0, 1)
		s.clampCursor()
	case 'C':
		s.cx += csiInt(parts, 0, 1)
		s.clampCursor()
	case 'D':
		s.cx -= csiInt(parts, 0, 1)
		s.clampCursor()
	case 'G':
		s.cx = csiInt(parts, 0, 1) - 1
		s.clampCursor()
	case 'J':
		s.eraseDisplay(p1)
	case 'K':
		s.eraseLine(p1)
	}
}

func csiInt(parts []string, idx, def int) int {
	if idx >= len(parts) || parts[idx] == "" {
		return def
	}
	n, err := strconv.Atoi(parts[idx])
	if err != nil {
		return def
	}
	return n
}

func (s *screen) eraseDisplay(mode int) {
	blankRow := func(i int) {
		for j := range s.grid[i] {
			s.grid[i][j] = ' '
		}
	}
	switch mode {
	case 0:
		for j := s.cx; j < s.cols; j++ {
			s.clearCell(s.cy, j)
		}
		for i := s.cy + 1; i < s.rows; i++ {
			blankRow(i)
		}
	case 1:
		for i := 0; i < s.cy; i++ {
			blankRow(i)
		}
		for j := 0; j <= s.cx && j < s.cols; j++ {
			s.clearCell(s.cy, j)
		}
	case 2, 3:
		for i := 0; i < s.rows; i++ {
			blankRow(i)
		}
	}
}

func (s *screen) eraseLine(mode int) {
	switch mode {
	case 0:
		for j := s.cx; j < s.cols; j++ {
			s.clearCell(s.cy, j)
		}
	case 1:
		for j := 0; j <= s.cx && j < s.cols; j++ {
			s.clearCell(s.cy, j)
		}
	case 2:
		for j := range s.grid[s.cy] {
			s.grid[s.cy][j] = ' '
		}
	}
}

func (s *screen) render() string {
	var b strings.Builder
	for i := range s.grid {
		end := len(s.grid[i])
		for end > 0 && (s.grid[i][end-1] == ' ' || s.grid[i][end-1] == wideContinuation) {
			end--
		}
		for _, r := range s.grid[i][:end] {
			if r == wideContinuation {
				continue
			}
			b.WriteRune(r)
		}
		b.WriteByte('\n')
	}
	return b.String()
}

// cleanANSI removes ANSI escape codes from the string.
func cleanANSI(str string) string {
	// Remove OSC sequences first (before CSI so we don't split them)
	str = regexp.MustCompile(`\x1b\][^\x07]*\x07`).ReplaceAllString(str, "")
	str = regexp.MustCompile(`\x1b\][^\x1b]*\x1b\\`).ReplaceAllString(str, "")

	// Replace cursor-forward escapes with the exact count of spaces
	str = regexp.MustCompile(`\x1b\[(\d+)C`).ReplaceAllStringFunc(str, func(m string) string {
		var count int
		fmt.Sscanf(m, "\x1b[%dC", &count)
		if count > 100 {
			count = 100
		}
		return strings.Repeat(" ", count)
	})

	// Remove standard SGR (Select Graphic Rendition) codes e.g., ESC[31m, ESC[0m
	str = regexp.MustCompile(`\x1b\[[0-9;]*m`).ReplaceAllString(str, "")

	// Remove other CSI sequences
	str = regexp.MustCompile(`\x1b\[[?0-9;]*[a-zA-Z]`).ReplaceAllString(str, "")

	// Normalize carriage returns
	str = strings.ReplaceAll(str, "\r\n", "\n")
	str = strings.ReplaceAll(str, "\r", "\n")

	// Replace remaining C0 control chars (except \n and \t) with spaces
	controlRe := regexp.MustCompile(`[\x00-\x08\x0b\x0c\x0e-\x1f]`)
	str = controlRe.ReplaceAllString(str, " ")

	return str
}
