package app

import "strings"

// jsonFieldExtractor 从流式 JSON 碎片中提取指定字段的字符串值。
//
// LLM 流式生成 tool call 时，参数是逐片段到达的（OpenAI/Anthropic）
// 或一次性到达的（Gemini）。本提取器用状态机逐字符扫描，
// 检测到目标 key 后提取其字符串值，处理 JSON 转义。
type jsonFieldExtractor struct {
	key      string // 匹配目标，如 `"content"` 或 `"task"`
	state    extractState
	matchPos int
	escape   bool
	buf      strings.Builder
}

type extractState int

const (
	stateScan    extractState = iota // 扫描，寻找目标 key
	stateColon                       // 已匹配 key，等冒号和开头引号
	stateExtract                     // 提取字符串值中
)

func newFieldExtractor(fieldName string) *jsonFieldExtractor {
	return &jsonFieldExtractor{key: `"` + fieldName + `"`}
}

// Feed 处理一段 delta，返回提取到的文本（可能为空）。
func (e *jsonFieldExtractor) Feed(delta string) string {
	e.buf.Reset()
	for _, r := range delta {
		switch e.state {
		case stateScan:
			e.feedScan(r)
		case stateColon:
			e.feedColon(r)
		case stateExtract:
			e.feedExtract(r)
		}
	}
	return e.buf.String()
}

func (e *jsonFieldExtractor) feedScan(r rune) {
	if e.matchPos < len(e.key) && byte(r) == e.key[e.matchPos] {
		e.matchPos++
		if e.matchPos == len(e.key) {
			e.state = stateColon
			e.matchPos = 0
		}
		return
	}
	e.matchPos = 0
	if byte(r) == e.key[0] {
		e.matchPos = 1
	}
}

func (e *jsonFieldExtractor) feedColon(r rune) {
	switch r {
	case ':', ' ', '\t':
		// 跳过
	case '"':
		e.state = stateExtract
		e.escape = false
	default:
		e.state = stateScan
		e.matchPos = 0
		if byte(r) == e.key[0] {
			e.matchPos = 1
		}
	}
}

func (e *jsonFieldExtractor) feedExtract(r rune) {
	if e.escape {
		e.escape = false
		switch r {
		case 'n':
			e.buf.WriteByte('\n')
		case 't':
			e.buf.WriteByte('\t')
		case 'r':
			e.buf.WriteByte('\r')
		case '"', '\\', '/':
			e.buf.WriteRune(r)
		default:
			e.buf.WriteByte('\\')
			e.buf.WriteRune(r)
		}
		return
	}
	switch r {
	case '\\':
		e.escape = true
	case '"':
		e.state = stateScan
		e.matchPos = 0
	default:
		e.buf.WriteRune(r)
	}
}

// Reset 重置状态（新 LLM 消息轮次时调用）。
func (e *jsonFieldExtractor) Reset() {
	e.state = stateScan
	e.matchPos = 0
	e.escape = false
}

// ThinkingSep 是思考文本与正文之间的分隔标记。
// streamFilter 在思考文本段前插入此标记，TUI 据此切换渲染样式。
const ThinkingSep = "\x02"

// streamFilter 区分 SubAgent 的文本回复和 JSON 工具调用。
// 文本回复标记为思考内容（前缀 ThinkingSep）；JSON 工具调用只提取指定字段。
//
// 判断依据：遇到 { 进入 JSON 模式（追踪大括号深度），
// 深度归零后回到文本模式。
type streamFilter struct {
	fieldExt   *jsonFieldExtractor
	mode       filterMode
	braceDepth int
	inString   bool // 在 JSON 字符串内（大括号不计数）
	escJSON    bool // JSON 字符串内的转义
	thinking   bool // 当前处于思考文本段
	buf        strings.Builder
}

type filterMode int

const (
	filterText filterMode = iota // 文本回复，直接透传
	filterJSON                   // JSON 工具调用，提取目标字段
)

func newStreamFilter(fieldName string) *streamFilter {
	return &streamFilter{fieldExt: newFieldExtractor(fieldName)}
}

// Feed 处理一段 delta，返回可展示文本。
// 文本回复直接输出；JSON 中的目标字段值被提取输出；其余 JSON 结构丢弃。
func (f *streamFilter) Feed(delta string) string {
	f.buf.Reset()
	for _, r := range delta {
		switch f.mode {
		case filterText:
			if r == '{' {
				f.thinking = false
				f.mode = filterJSON
				f.braceDepth = 1
				f.inString = false
				f.escJSON = false
				f.fieldExt.Reset()
				f.feedExtractor(r)
			} else {
				if !f.thinking {
					f.thinking = true
					f.buf.WriteString(ThinkingSep)
				}
				f.buf.WriteRune(r)
			}
		case filterJSON:
			f.feedExtractor(r)
			f.trackBraces(r)
		}
	}
	return f.buf.String()
}

// feedExtractor 将单个字符喂给 fieldExt，提取结果写入 buf。
func (f *streamFilter) feedExtractor(r rune) {
	if text := f.fieldExt.Feed(string(r)); text != "" {
		f.buf.WriteString(text)
	}
}

// trackBraces 追踪 JSON 大括号深度，深度归零时切回文本模式。
func (f *streamFilter) trackBraces(r rune) {
	if f.escJSON {
		f.escJSON = false
		return
	}
	if f.inString {
		switch r {
		case '\\':
			f.escJSON = true
		case '"':
			f.inString = false
		}
		return
	}
	switch r {
	case '"':
		f.inString = true
	case '{':
		f.braceDepth++
	case '}':
		f.braceDepth--
		if f.braceDepth <= 0 {
			f.mode = filterText
		}
	}
}

// Reset 重置状态。
func (f *streamFilter) Reset() {
	f.mode = filterText
	f.braceDepth = 0
	f.inString = false
	f.escJSON = false
	f.thinking = false
	f.fieldExt.Reset()
}
