package cli

import (
	"strings"

	"iop/apps/node/internal/adapters/cli/status"
)

type claudeTUIOutputFilter struct {
	raw               strings.Builder
	prompt            string
	baselineAssistant string
	emitted           string
	seenAssistant     bool
}

type claudeMessageSnapshot struct {
	text   string
	closed bool
}

func newClaudeTUIOutputFilter(prompt string, baselineAssistant ...string) *claudeTUIOutputFilter {
	filter := &claudeTUIOutputFilter{prompt: strings.TrimSpace(prompt)}
	if len(baselineAssistant) > 0 {
		filter.baselineAssistant = strings.TrimSpace(baselineAssistant[0])
	}
	return filter
}

func (f *claudeTUIOutputFilter) Filter(chunk string) string {
	appendBounded(&f.raw, chunk, 256*1024)
	message := f.currentMessage()
	return f.emit(message.text, message.closed, false)
}

func (f *claudeTUIOutputFilter) Flush() string {
	message := f.currentMessage()
	return f.emit(message.text, message.closed, true)
}

func (f *claudeTUIOutputFilter) HasOutput() bool {
	return f.emitted != "" || f.currentMessage().text != ""
}

func (f *claudeTUIOutputFilter) CompletionMessage() string {
	if f.emitted == "" {
		return ""
	}
	if claudeScreenHasInputPrompt(status.RenderVisibleScreen(f.raw.String(), terminalRows, terminalCols)) {
		return "prompt-ready"
	}
	return ""
}

func (f *claudeTUIOutputFilter) currentMessage() claudeMessageSnapshot {
	screen := status.RenderVisibleScreen(f.raw.String(), terminalRows, terminalCols)
	clean := cleanClaudeTerminalOutput(f.raw.String())
	screenMessage, screenClosed := latestClaudeAssistantMessage(screen)
	if promptMessage, promptClosed, promptSeen := latestClaudeAssistantMessageAfterPromptFromCleanOutput(clean, f.prompt); promptSeen {
		if promptMessage == "" || isClaudePromptEchoMessage(promptMessage, f.prompt) {
			return claudeMessageSnapshot{}
		}
		f.seenAssistant = true
		text, closed := preferLongerClaudeMessage(promptMessage, promptClosed, screenMessage, screenClosed)
		return claudeMessageSnapshot{text: text, closed: closed}
	}
	message, closed := screenMessage, screenClosed
	cleanMessage, cleanClosed := latestClaudeAssistantMessageFromCleanOutput(clean)
	if cleanMessage != "" {
		if isClaudePromptEchoMessage(cleanMessage, f.prompt) {
			cleanMessage = ""
		}
	}
	if f.emitted == "" && f.baselineAssistant != "" && cleanMessage == f.baselineAssistant {
		cleanMessage = ""
	}
	if cleanMessage != "" {
		f.seenAssistant = true
		if cleanMessage == message {
			closed = closed || cleanClosed
			return claudeMessageSnapshot{text: message, closed: closed}
		}
		return claudeMessageSnapshot{text: cleanMessage, closed: cleanClosed}
	}
	if isClaudePromptEchoMessage(message, f.prompt) {
		message = ""
		closed = false
	}
	if f.emitted == "" && f.baselineAssistant != "" && message == f.baselineAssistant {
		message = ""
		closed = false
	}
	if message != "" {
		f.seenAssistant = true
	}
	if !f.seenAssistant {
		return claudeMessageSnapshot{}
	}
	if message == "" {
		message = fallbackClaudeVisibleMessage(screen, f.prompt)
		if message != "" {
			closed = claudeScreenHasInputPrompt(screen) || strings.HasSuffix(clean, "\n")
		}
	}
	if message == "" {
		message = fallbackClaudeVisibleMessage(clean, f.prompt)
		if message != "" {
			closed = strings.HasSuffix(clean, "\n")
		}
	}
	return claudeMessageSnapshot{text: message, closed: closed}
}

func preferLongerClaudeMessage(promptMessage string, promptClosed bool, screenMessage string, screenClosed bool) (string, bool) {
	promptMessage = strings.TrimRight(promptMessage, "\n")
	screenMessage = strings.TrimRight(screenMessage, "\n")
	if screenMessage == "" || isClaudePromptEchoMessage(screenMessage, promptMessage) {
		return promptMessage, promptClosed
	}
	if promptMessage == "" {
		return screenMessage, screenClosed
	}
	if screenMessage == promptMessage {
		return promptMessage, promptClosed || screenClosed
	}
	if strings.HasPrefix(screenMessage, promptMessage) || strings.Contains(screenMessage, promptMessage) {
		if len(screenMessage) > len(promptMessage) {
			return screenMessage, promptClosed || screenClosed
		}
		return promptMessage, promptClosed || screenClosed
	}
	if strings.Contains(promptMessage, screenMessage) {
		return promptMessage, promptClosed || screenClosed
	}
	if len(screenMessage) > len(promptMessage) {
		return screenMessage, promptClosed || screenClosed
	}
	return promptMessage, promptClosed
}

func (f *claudeTUIOutputFilter) emit(message string, closed bool, flush bool) string {
	message = strings.TrimRight(message, "\n")
	if !flush {
		message = readyLinePrefix(message, closed)
	}
	if message == "" || message == f.emitted {
		return ""
	}
	if f.emitted == "" {
		f.emitted = message
		return message
	}
	if strings.HasPrefix(message, f.emitted) {
		delta := strings.TrimPrefix(message, f.emitted)
		f.emitted = message
		return delta
	}
	f.emitted = message
	return "\n" + message
}

func readyLinePrefix(message string, closed bool) string {
	if closed {
		return message
	}
	end := strings.LastIndex(message, "\n")
	if end < 0 {
		return ""
	}
	return message[:end+1]
}

func claudeScreenHasInputPrompt(screen string) bool {
	for _, line := range strings.Split(screen, "\n") {
		line = strings.TrimSpace(line)
		if strings.HasPrefix(line, "\u276f") {
			return true
		}
	}
	return false
}

func latestClaudeAssistantMessage(screen string) (string, bool) {
	lines := strings.Split(screen, "\n")
	start := -1
	for i, line := range lines {
		if _, ok := claudeAssistantLine(line); ok {
			start = i
		}
	}
	if start < 0 {
		return "", false
	}

	out := make([]string, 0, len(lines)-start)
	closed := false
	if text, ok := claudeAssistantLine(lines[start]); ok {
		appendClaudeMessageLine(&out, text)
	}
	for _, line := range lines[start+1:] {
		line = strings.TrimSpace(line)
		if line == "" {
			if len(out) > 0 {
				out = append(out, "")
			}
			continue
		}
		if isClaudeTUISoftBoundaryLine(line) {
			continue
		}
		if isClaudeTUIHardBoundaryLine(line) {
			closed = true
			break
		}
		appendClaudeMessageLine(&out, line)
	}
	return strings.Join(trimTrailingEmptyLines(out), "\n"), closed
}

func latestClaudeAssistantMessageFromCleanOutput(text string) (string, bool) {
	lines := strings.SplitAfter(text, "\n")
	start := -1
	for i, line := range lines {
		if _, ok := claudeAssistantLine(strings.TrimRight(line, "\r\n")); ok {
			start = i
		}
	}
	if start < 0 {
		return "", false
	}
	return claudeAssistantMessageFromCleanLines(lines, start)
}

func latestClaudeAssistantMessageAfterPromptFromCleanOutput(text, prompt string) (string, bool, bool) {
	normalizedPrompt := normalizeClaudePromptEchoText(prompt)
	if normalizedPrompt == "" {
		return "", false, false
	}

	lines := strings.SplitAfter(text, "\n")
	var assistantIndexes []int
	var promptIndexes []int
	for i, raw := range lines {
		line := strings.TrimSpace(strings.TrimRight(raw, "\r\n"))
		if _, ok := claudeAssistantLine(line); ok {
			assistantIndexes = append(assistantIndexes, i)
			continue
		}
		if isClaudePromptEchoLine(line, normalizedPrompt) {
			promptIndexes = append(promptIndexes, i)
		}
	}

	start := -1
	for _, assistantIndex := range assistantIndexes {
		for _, promptIndex := range promptIndexes {
			if promptIndex < assistantIndex {
				start = assistantIndex
				break
			}
		}
	}
	if start >= 0 {
		message, closed := claudeAssistantMessageFromCleanLines(lines, start)
		return message, closed, true
	}

	lastAssistantIndex := -1
	if len(assistantIndexes) > 0 {
		lastAssistantIndex = assistantIndexes[len(assistantIndexes)-1]
	}
	for _, promptIndex := range promptIndexes {
		if promptIndex > lastAssistantIndex {
			return "", false, true
		}
	}
	return "", false, false
}

func claudeAssistantMessageFromCleanLines(lines []string, start int) (string, bool) {
	out := make([]string, 0, len(lines)-start)
	closed := false
	if text, ok := claudeAssistantLine(strings.TrimRight(lines[start], "\r\n")); ok {
		appendClaudeMessageLine(&out, text)
		closed = strings.HasSuffix(lines[start], "\n")
	}
	for _, raw := range lines[start+1:] {
		line := strings.TrimSpace(strings.TrimRight(raw, "\r\n"))
		if line == "" {
			if len(out) > 0 {
				out = append(out, "")
				if strings.HasSuffix(raw, "\n") {
					closed = true
				}
			}
			continue
		}
		if isClaudeTUISoftBoundaryLine(line) {
			closed = strings.HasSuffix(raw, "\n")
			continue
		}
		if isClaudeTUIHardBoundaryLine(line) {
			closed = true
			break
		}
		appendClaudeMessageLine(&out, line)
		closed = strings.HasSuffix(raw, "\n")
	}
	return strings.Join(trimTrailingEmptyLines(out), "\n"), closed
}

func isClaudePromptEchoLine(line, normalizedPrompt string) bool {
	line = strings.TrimSpace(line)
	if line == "" || isClaudeTUIChromeLine(line) {
		return false
	}
	normalizedLine := normalizeClaudePromptEchoText(line)
	minLen := 6
	if len(normalizedPrompt) < minLen {
		minLen = len(normalizedPrompt)
	}
	if len(normalizedLine) < minLen {
		return false
	}
	return strings.Contains(normalizedPrompt, normalizedLine)
}

func appendClaudeMessageLine(out *[]string, line string) {
	line = strings.TrimSpace(line)
	if line == "" || isClaudeTUIBoundaryLine(line) {
		return
	}
	*out = append(*out, line)
}

func claudeAssistantLine(line string) (string, bool) {
	for _, marker := range []string{"\u25cf", "\u23fa"} {
		idx := strings.Index(line, marker)
		if idx < 0 {
			continue
		}
		text := strings.TrimSpace(line[idx+len(marker):])
		if text == "" || isClaudeTUIChromeLine(text) {
			return "", false
		}
		return text, true
	}
	return "", false
}

func isClaudeTUIBoundaryLine(line string) bool {
	return isClaudeTUIHardBoundaryLine(line) || isClaudeTUISoftBoundaryLine(line)
}

func isClaudeTUIHardBoundaryLine(line string) bool {
	line = strings.TrimSpace(line)
	if line == "" {
		return true
	}
	lower := strings.ToLower(line)
	switch {
	case strings.HasPrefix(line, ">"):
		return true
	case strings.HasPrefix(line, "\u276f"):
		return true
	case strings.HasPrefix(line, "\u2500"):
		return true
	case strings.HasPrefix(line, "\u2570") || strings.HasPrefix(line, "\u256d"):
		return true
	case strings.HasPrefix(line, "\u23bf"):
		return true
	case strings.HasPrefix(line, "\u23fa"):
		return true
	case strings.HasPrefix(line, "\u25cf"):
		return true
	case strings.HasPrefix(lower, "tip:"):
		return true
	}
	return false
}

func isClaudeTUISoftBoundaryLine(line string) bool {
	line = strings.TrimSpace(line)
	if line == "" {
		return false
	}
	lower := strings.ToLower(line)
	switch {
	case strings.HasPrefix(line, "\u2722") || strings.HasPrefix(line, "\u2736") || strings.HasPrefix(line, "\u273b") || strings.HasPrefix(line, "\u273d") || strings.HasPrefix(line, "\u00b7"):
		return true
	case strings.HasPrefix(line, "\u23f5"):
		return true
	case strings.Contains(lower, "tokens)"):
		return true
	case isClaudeTransientStatusLine(line):
		return true
	case strings.Contains(lower, "press shift+tab"):
		return true
	case strings.Contains(lower, "shift+tab to cycle"):
		return true
	case strings.Contains(lower, "/effort"):
		return true
	case strings.Contains(lower, "bypass permissions on"):
		return true
	case strings.Contains(lower, "for agents"):
		return true
	case strings.Contains(lower, "esc to interrupt"):
		return true
	case strings.Contains(lower, "bypasspermissions"):
		return true
	}
	return false
}

func isClaudeTUIChromeLine(line string) bool {
	line = strings.TrimSpace(line)
	if line == "" {
		return true
	}
	lower := strings.ToLower(line)
	switch {
	case strings.Contains(lower, "/effort"):
		return true
	case strings.Contains(lower, "esc to interrupt"):
		return true
	case isClaudeTransientStatusLine(line):
		return true
	}
	return false
}
