package converter

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/daniel/cursor-adapter/internal/types"
)

// CursorLine 代表 Cursor CLI stream-json 的一行。
type CursorLine struct {
	Type    string         `json:"type"`
	Subtype string         `json:"subtype,omitempty"`
	Message *CursorMessage `json:"message,omitempty"`
	Result  string         `json:"result,omitempty"`
	IsError bool           `json:"is_error,omitempty"`
	Usage   *CursorUsage   `json:"usage,omitempty"`
}

// FlexibleContent 可以是 string 或 []CursorContent。
type FlexibleContent []CursorContent

func (fc *FlexibleContent) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err == nil {
		*fc = []CursorContent{{Type: "text", Text: s}}
		return nil
	}
	var items []CursorContent
	if err := json.Unmarshal(data, &items); err != nil {
		return err
	}
	*fc = items
	return nil
}

type CursorMessage struct {
	Role    string          `json:"role"`
	Content FlexibleContent `json:"content"`
}

type CursorContent struct {
	Type string `json:"type"`
	Text string `json:"text"`
}

type CursorUsage struct {
	InputTokens  int `json:"inputTokens"`
	OutputTokens int `json:"outputTokens"`
}

// ConvertResult 是轉換一行的結果。
type ConvertResult struct {
	Chunk *types.ChatCompletionChunk
	Done  bool
	Skip  bool
	Error error
	Usage *CursorUsage
}

// StreamParser tracks accumulated assistant output so the OpenAI stream only
// emits newly appended text, not the full Cursor message each time.
type StreamParser struct {
	chatID      string
	accumulated string
	lastRawText string
}

func NewStreamParser(chatID string) *StreamParser {
	return &StreamParser{chatID: chatID}
}

func (p *StreamParser) Parse(line string) ConvertResult {
	trimmed := strings.TrimSpace(line)
	if trimmed == "" {
		return ConvertResult{Skip: true}
	}

	var cl CursorLine
	if err := json.Unmarshal([]byte(trimmed), &cl); err != nil {
		return ConvertResult{Error: fmt.Errorf("unmarshal error: %w", err)}
	}

	switch cl.Type {
	case "system", "user":
		return ConvertResult{Skip: true}

	case "assistant":
		content := ExtractContent(cl.Message)
		return p.emitAssistantDelta(content)

	case "result":
		if cl.IsError {
			errMsg := cl.Result
			if errMsg == "" {
				errMsg = "unknown cursor error"
			}
			return ConvertResult{Error: fmt.Errorf("cursor error: %s", errMsg)}
		}
		return ConvertResult{
			Done:  true,
			Usage: cl.Usage,
		}

	default:
		return ConvertResult{Skip: true}
	}
}

func (p *StreamParser) ParseRawText(content string) ConvertResult {
	content = strings.TrimSpace(content)
	if content == "" {
		return ConvertResult{Skip: true}
	}
	if content == p.lastRawText {
		return ConvertResult{Skip: true}
	}
	p.lastRawText = content

	chunk := types.NewChatCompletionChunk(p.chatID, 0, "", types.Delta{
		Content: &content,
	})
	return ConvertResult{Chunk: &chunk}
}

// emitAssistantDelta handles both output modes the Cursor CLI can use:
//
//   - CUMULATIVE: each assistant message contains the full text so far
//     (text.startsWith(accumulated)). We emit only the new suffix and
//     replace accumulated with the full text.
//   - INCREMENTAL: each assistant message contains just the new fragment.
//     We emit the fragment verbatim and append it to accumulated.
//
// Either way, the duplicate final "assistant" message that Cursor CLI emits
// at the end of a session is caught by the content == accumulated check and
// skipped.
func (p *StreamParser) emitAssistantDelta(content string) ConvertResult {
	if content == "" {
		return ConvertResult{Skip: true}
	}
	if content == p.accumulated {
		return ConvertResult{Skip: true}
	}

	var delta string
	if p.accumulated != "" && strings.HasPrefix(content, p.accumulated) {
		delta = content[len(p.accumulated):]
		p.accumulated = content
	} else {
		delta = content
		p.accumulated += content
	}

	if delta == "" {
		return ConvertResult{Skip: true}
	}

	chunk := types.NewChatCompletionChunk(p.chatID, 0, "", types.Delta{
		Content: &delta,
	})
	return ConvertResult{Chunk: &chunk}
}

// ConvertLine 將一行 Cursor stream-json 轉換為 OpenAI SSE chunk。
func ConvertLine(line string, chatID string) ConvertResult {
	return NewStreamParser(chatID).Parse(line)
}

// ExtractContent 從 CursorMessage 提取所有文字內容。
func ExtractContent(msg *CursorMessage) string {
	if msg == nil {
		return ""
	}

	var parts []string
	for _, c := range msg.Content {
		if c.Text != "" {
			parts = append(parts, c.Text)
		}
	}
	return strings.Join(parts, "")
}
