// Package claudecode provides a CodeAgent implementation for Anthropic's Claude Code CLI.
package claudecode

import (
	"encoding/json"
	"time"

	"github.com/orchard9/rdev/internal/domain"
)

// StreamMessageType identifies the type of stream-json message from Claude Code CLI.
type StreamMessageType string

const (
	// StreamMessageInit is emitted when a session starts.
	StreamMessageInit StreamMessageType = "init"
	// StreamMessageMessage contains assistant or user text.
	StreamMessageMessage StreamMessageType = "message"
	// StreamMessageToolUse indicates a tool is being invoked.
	StreamMessageToolUse StreamMessageType = "tool_use"
	// StreamMessageToolResult contains the output of a tool invocation.
	StreamMessageToolResult StreamMessageType = "tool_result"
	// StreamMessageResult is the final message indicating completion.
	StreamMessageResult StreamMessageType = "result"
)

// StreamMessage represents a single NDJSON message from Claude Code's stream-json output.
type StreamMessage struct {
	Type       StreamMessageType `json:"type"`
	Subtype    string            `json:"subtype,omitempty"`  // "success" or "error" (for result type)
	IsError    bool              `json:"is_error,omitempty"` // true if result is an error
	Timestamp  string            `json:"timestamp,omitempty"`
	SessionID  string            `json:"session_id,omitempty"`
	Role       string            `json:"role,omitempty"` // "assistant" or "user"
	Content    []ContentBlock    `json:"content,omitempty"`
	Name       string            `json:"name,omitempty"`  // Tool name for tool_use
	Input      json.RawMessage   `json:"input,omitempty"` // Tool input for tool_use
	Output     string            `json:"output,omitempty"`
	DurationMs int64             `json:"duration_ms,omitempty"`
	Error      string            `json:"error,omitempty"`
}

// ContentBlock represents a content item within a message.
type ContentBlock struct {
	Type  string          `json:"type"` // "text" or "tool_use"
	Text  string          `json:"text,omitempty"`
	Name  string          `json:"name,omitempty"`
	Input json.RawMessage `json:"input,omitempty"`
}

// ParseStreamMessage parses a single NDJSON line into a StreamMessage.
func ParseStreamMessage(line []byte) (*StreamMessage, error) {
	var msg StreamMessage
	if err := json.Unmarshal(line, &msg); err != nil {
		return nil, err
	}
	return &msg, nil
}

// ToAgentEvent converts a StreamMessage to a domain.AgentEvent.
func (m *StreamMessage) ToAgentEvent() domain.AgentEvent {
	event := domain.AgentEvent{
		Timestamp: parseTimestamp(m.Timestamp),
		Metadata:  make(map[string]any),
	}

	switch m.Type {
	case StreamMessageInit:
		event.Type = domain.AgentEventOutput
		event.Content = "Session started"
		if m.SessionID != "" {
			event.Metadata["session_id"] = m.SessionID
		}

	case StreamMessageMessage:
		event.Type = domain.AgentEventOutput
		event.Content = extractTextContent(m.Content)
		if m.Role != "" {
			event.Metadata["role"] = m.Role
		}

	case StreamMessageToolUse:
		event.Type = domain.AgentEventToolUse
		event.ToolName = m.Name
		if len(m.Name) == 0 {
			// Check content blocks for tool_use
			for _, block := range m.Content {
				if block.Type == "tool_use" {
					event.ToolName = block.Name
					if len(block.Input) > 0 {
						var input map[string]any
						if err := json.Unmarshal(block.Input, &input); err == nil {
							event.ToolInput = input
						}
					}
					break
				}
			}
		} else if len(m.Input) > 0 {
			var input map[string]any
			if err := json.Unmarshal(m.Input, &input); err == nil {
				event.ToolInput = input
			}
		}
		event.Content = event.ToolName

	case StreamMessageToolResult:
		event.Type = domain.AgentEventToolResult
		event.Content = m.Output

	case StreamMessageResult:
		event.Type = domain.AgentEventComplete
		if m.Subtype == "error" || m.IsError {
			event.Type = domain.AgentEventError
			event.Content = m.Error
		}
		if m.DurationMs > 0 {
			event.Metadata["duration_ms"] = m.DurationMs
		}
		if m.Subtype != "" {
			event.Metadata["status"] = m.Subtype
		}

	default:
		// Unknown message type, treat as output
		event.Type = domain.AgentEventOutput
		event.Content = extractTextContent(m.Content)
	}

	return event
}

// extractTextContent extracts text from content blocks.
func extractTextContent(blocks []ContentBlock) string {
	for _, block := range blocks {
		if block.Type == "text" && block.Text != "" {
			return block.Text
		}
	}
	return ""
}

// parseTimestamp parses an ISO8601 timestamp string.
func parseTimestamp(s string) time.Time {
	if s == "" {
		return time.Now()
	}
	t, err := time.Parse(time.RFC3339, s)
	if err != nil {
		return time.Now()
	}
	return t
}

// IsTerminal returns true if this message indicates execution is complete.
func (m *StreamMessage) IsTerminal() bool {
	return m.Type == StreamMessageResult
}

// IsSuccess returns true if this is a successful result message.
func (m *StreamMessage) IsSuccess() bool {
	return m.Type == StreamMessageResult && m.Subtype == "success" && !m.IsError
}
