package agent

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

	"github.com/google/uuid"

	"github.com/tidbcloud/AgentTeam/desktop/internal/agent/drivercontract"
)

// InterruptActiveRun asks Claude Code stream-json to abort the currently
// running query while keeping the CLI session alive. Claude Code exposes this
// through the SDK control_request protocol, the same path used by print-mode
// clients for an interrupt/Escape action.
func (d *ClaudeDriver) InterruptActiveRun(ctx context.Context, sessionID string) error {
	if ctx == nil {
		ctx = context.Background()
	}
	d.mu.Lock()
	stdinPipe := d.stdinPipe
	if d.controlWaiters == nil {
		d.controlWaiters = make(map[string]chan error)
	}
	requestID := "agentteam-interrupt-" + strings.ToLower(uuid.NewString())
	waiter := make(chan error, 1)
	d.controlWaiters[requestID] = waiter
	d.mu.Unlock()

	if stdinPipe == nil {
		d.forgetClaudeControlWaiter(requestID)
		return fmt.Errorf("claude stdin is unavailable")
	}
	payload, err := json.Marshal(map[string]any{
		"type":       "control_request",
		"request_id": requestID,
		"request": map[string]any{
			"subtype": "interrupt",
		},
	})
	if err != nil {
		d.forgetClaudeControlWaiter(requestID)
		return err
	}
	select {
	case <-ctx.Done():
		d.forgetClaudeControlWaiter(requestID)
		return ctx.Err()
	default:
	}
	if _, err := stdinPipe.Write(append(payload, '\n')); err != nil {
		d.forgetClaudeControlWaiter(requestID)
		return fmt.Errorf("claude interrupt: %w", err)
	}
	select {
	case err := <-waiter:
		return err
	case <-ctx.Done():
		d.forgetClaudeControlWaiter(requestID)
		return ctx.Err()
	}
}

// SupportsSessionlessActiveRunInterrupt reports that Claude's stream-json
// control protocol can interrupt the current run without knowing the eventual
// Claude session id. Claude may not emit a session id until after the first
// message has completed, but the interrupt control_request is written to the
// active process stdin and still targets the in-flight query.
func (d *ClaudeDriver) SupportsSessionlessActiveRunInterrupt() bool {
	return true
}

func (d *ClaudeDriver) forgetClaudeControlWaiter(requestID string) {
	d.mu.Lock()
	delete(d.controlWaiters, strings.TrimSpace(requestID))
	d.mu.Unlock()
}

func (d *ClaudeDriver) completeClaudeControlWaiter(requestID string, err error) {
	requestID = strings.TrimSpace(requestID)
	if requestID == "" {
		return
	}
	d.mu.Lock()
	waiter := d.controlWaiters[requestID]
	delete(d.controlWaiters, requestID)
	d.mu.Unlock()
	if waiter == nil {
		return
	}
	select {
	case waiter <- err:
	default:
	}
}

// SupportsAckedBusyRuntimeInputDelivery reports that Claude stream-json can
// queue active-turn input and later echo its UUID as a correlated replay
// acknowledgement on stdout.
func (d *ClaudeDriver) SupportsAckedBusyRuntimeInputDelivery() bool { return true }

// DeliverAckedBusyRuntimeInput writes a correlated queued user message to
// Claude's stream-json stdin. Claude emits the same uuid as an isReplay user
// stdout acknowledgement. The standard stdio protocol does not expose an
// authoritative per-uuid read API or prove that a model request consumed it.
func (d *ClaudeDriver) DeliverAckedBusyRuntimeInput(ctx context.Context, input drivercontract.RuntimeInput, sessionID string) error {
	if input.Empty() {
		return nil
	}
	if strings.TrimSpace(input.ID) == "" {
		return fmt.Errorf("claude acked delivery requires runtime input id")
	}
	d.mu.Lock()
	stdinPipe := d.stdinPipe
	d.mu.Unlock()
	if stdinPipe == nil {
		return fmt.Errorf("claude stdin is unavailable")
	}
	data := buildClaudeStdinMsgWithOptions(input.PromptText(), sessionID, claudeStdinOptions{
		UUID:     input.ID,
		Priority: "next",
	})
	select {
	case <-ctx.Done():
		return ctx.Err()
	default:
	}
	if _, err := stdinPipe.Write(append(data, '\n')); err != nil {
		return fmt.Errorf("claude stdin write: %w", err)
	}
	return nil
}

// EncodeStdinMessage encodes a user notification in Claude's stdin format.
func (d *ClaudeDriver) EncodeStdinMessage(text, sessionID string) []byte {
	data := buildClaudeStdinMsgWithOptions(text, sessionID, claudeStdinOptions{})
	return append(data, '\n')
}

type claudeStdinOptions struct {
	UUID     string
	Priority string
}

func buildClaudeStdinMsg(prompt, sessionID string) []byte {
	return buildClaudeStdinMsgWithOptions(prompt, sessionID, claudeStdinOptions{})
}

func buildClaudeStdinMsgWithOptions(prompt, sessionID string, opts claudeStdinOptions) []byte {
	msg := map[string]interface{}{
		"type":               "user",
		"parent_tool_use_id": nil,
		"message": map[string]interface{}{
			"role": "user",
			"content": []map[string]interface{}{
				{"type": "text", "text": prompt},
			},
		},
	}
	if sessionID != "" {
		msg["session_id"] = sessionID
	}
	if uuid := strings.TrimSpace(opts.UUID); uuid != "" {
		msg["uuid"] = uuid
	}
	if priority := strings.TrimSpace(opts.Priority); priority != "" {
		msg["priority"] = priority
	}
	data, _ := json.Marshal(msg)
	return data
}
