package caller

import (
	agentContext "github.com/yaoapp/yao/agent/context"
	"github.com/yaoapp/yao/agent/output/message"
)

// JSAPI implements context.AgentAPI and context.AgentAPIWithCallback interfaces
// Provides ctx.agent.Call(), ctx.agent.All(), ctx.agent.Any(), ctx.agent.Race()
// and their *WithHandler variants for streaming callback support
type JSAPI struct {
	ctx          *agentContext.Context
	orchestrator *Orchestrator
}

// Ensure JSAPI implements AgentAPIWithCallback
var _ agentContext.AgentAPIWithCallback = (*JSAPI)(nil)

// NewJSAPI creates a new agent JSAPI instance
func NewJSAPI(ctx *agentContext.Context) *JSAPI {
	return &JSAPI{
		ctx:          ctx,
		orchestrator: NewOrchestrator(ctx),
	}
}

// Call executes a single agent call
// Usage: ctx.agent.Call("assistant-id", messages, options?)
// Returns: { agent_id, response, content, error }
// Note: For sub-agent calls, skip.history = true is automatically set
// to prevent A2A messages from being saved to chat history.
// Sub-agents output normally with ThreadID for SSE stream isolation.
func (api *JSAPI) Call(agentID string, messages []interface{}, opts map[string]interface{}) interface{} {
	req := api.buildRequest(agentID, messages, opts)
	// Force skip options for sub-agent calls
	api.forceSkipForSubAgent(req)
	result := api.orchestrator.callAgent(req)
	return result
}

// All executes all agent calls and waits for all to complete (like Promise.all)
// Each request should have:
//   - agent: string - target agent ID
//   - messages: array - messages to send
//   - options?: object - call options
func (api *JSAPI) All(requests []interface{}) []interface{} {
	reqs := api.parseRequests(requests)
	results := api.orchestrator.All(reqs)
	return api.convertResults(results)
}

// Any returns as soon as any agent call succeeds (like Promise.any)
// Each request should have:
//   - agent: string - target agent ID
//   - messages: array - messages to send
//   - options?: object - call options
func (api *JSAPI) Any(requests []interface{}) []interface{} {
	reqs := api.parseRequests(requests)
	results := api.orchestrator.Any(reqs)
	return api.convertResults(results)
}

// Race returns as soon as any agent call completes (like Promise.race)
// Each request should have:
//   - agent: string - target agent ID
//   - messages: array - messages to send
//   - options?: object - call options
func (api *JSAPI) Race(requests []interface{}) []interface{} {
	reqs := api.parseRequests(requests)
	results := api.orchestrator.Race(reqs)
	return api.convertResults(results)
}

// ============================================================================
// AgentAPIWithCallback Implementation
// ============================================================================

// CallWithHandler executes a single agent call with an OnMessage handler
// Note: For sub-agent calls, skip.history = true is automatically set.
// Sub-agents output normally with ThreadID. Use the handler callback
// to receive streaming messages.
func (api *JSAPI) CallWithHandler(agentID string, messages []interface{}, opts map[string]interface{}, handler agentContext.OnMessageFunc) interface{} {
	req := api.buildRequest(agentID, messages, opts)
	req.Handler = handler
	// Force skip options for sub-agent calls
	api.forceSkipForSubAgent(req)
	result := api.orchestrator.callAgent(req)
	return result
}

// AllWithHandler executes all agent calls with handlers
func (api *JSAPI) AllWithHandler(requests []interface{}, globalHandler agentContext.BatchOnMessageFunc) []interface{} {
	reqs := api.parseRequestsWithHandlers(requests, globalHandler)
	results := api.orchestrator.All(reqs)
	return api.convertResults(results)
}

// AnyWithHandler executes agent calls and returns on first success, with handlers
func (api *JSAPI) AnyWithHandler(requests []interface{}, globalHandler agentContext.BatchOnMessageFunc) []interface{} {
	reqs := api.parseRequestsWithHandlers(requests, globalHandler)
	results := api.orchestrator.Any(reqs)
	return api.convertResults(results)
}

// RaceWithHandler executes agent calls and returns on first completion, with handlers
func (api *JSAPI) RaceWithHandler(requests []interface{}, globalHandler agentContext.BatchOnMessageFunc) []interface{} {
	reqs := api.parseRequestsWithHandlers(requests, globalHandler)
	results := api.orchestrator.Race(reqs)
	return api.convertResults(results)
}

// forceSkipForSubAgent ensures proper A2A call behavior:
//   - skip.history = true: always set — A2A messages are not saved to chat history
//   - skip.output: defaults to false (sub-agents output with ThreadID for SSE stream isolation),
//     but if the caller explicitly sets skip.output = true, it is respected.
//     This allows internal worker agents (e.g. classifiers) to run silently.
func (api *JSAPI) forceSkipForSubAgent(req *Request) {
	if req.Options == nil {
		req.Options = &CallOptions{}
	}

	// Preserve caller's explicit skip.output = true before overwriting Skip struct
	callerSkipOutput := req.Options.Skip != nil && req.Options.Skip.Output

	if req.Options.Skip == nil {
		req.Options.Skip = &agentContext.Skip{}
	}
	req.Options.Skip.History = true

	if callerSkipOutput {
		req.Options.Skip.Output = true
	}
	// else: skip.output remains false (default zero value) — sub-agent outputs normally
}

// parseRequestsWithHandlers parses requests and attaches handlers
// It checks for per-request _handler fields and wraps globalHandler with agentID/index
// For all calls, this automatically sets:
// - skip.history = true: prevents A2A messages from being saved to chat history
// - skip.output = false: ensures sub-agents output with ThreadID (overrides user settings)
func (api *JSAPI) parseRequestsWithHandlers(requests []interface{}, globalHandler agentContext.BatchOnMessageFunc) []*Request {
	reqs := make([]*Request, 0, len(requests))

	for i, r := range requests {
		reqMap, ok := r.(map[string]interface{})
		if !ok {
			continue
		}

		// Get agent ID
		agentID, ok := reqMap["agent"].(string)
		if !ok {
			continue
		}

		// Get messages
		messages, ok := reqMap["messages"].([]interface{})
		if !ok {
			continue
		}

		// Get options (optional)
		var opts map[string]interface{}
		if o, ok := reqMap["options"].(map[string]interface{}); ok {
			opts = o
		}

		req := api.buildRequest(agentID, messages, opts)

		// Force skip.output = true for all sub-agent calls
		api.forceSkipForSubAgent(req)

		// Check for per-request handler first (takes precedence)
		if handler, ok := reqMap["_handler"].(agentContext.OnMessageFunc); ok && handler != nil {
			req.Handler = handler
		} else if globalHandler != nil {
			// Wrap global handler with agentID and index
			idx := i // Capture index for closure
			aid := agentID
			req.Handler = func(msg *message.Message) int {
				return globalHandler(aid, idx, msg)
			}
		}

		reqs = append(reqs, req)
	}

	return reqs
}

// buildRequest builds a Request from agentID, messages, and options
func (api *JSAPI) buildRequest(agentID string, messages []interface{}, opts map[string]interface{}) *Request {
	req := &Request{
		AgentID:  agentID,
		Messages: api.parseMessages(messages),
	}

	if opts != nil {
		req.Options = api.parseCallOptions(opts)
	}

	return req
}

// parseMessages converts []interface{} to []agentContext.Message
func (api *JSAPI) parseMessages(messages []interface{}) []agentContext.Message {
	result := make([]agentContext.Message, 0, len(messages))
	for _, m := range messages {
		msg, ok := m.(map[string]interface{})
		if !ok {
			continue
		}

		ctxMsg := agentContext.Message{}

		// Parse role
		if role, ok := msg["role"].(string); ok {
			ctxMsg.Role = agentContext.MessageRole(role)
		}

		// Parse content (can be string or array)
		ctxMsg.Content = msg["content"]

		// Parse name
		if name, ok := msg["name"].(string); ok {
			ctxMsg.Name = &name
		}

		// Parse tool_call_id
		if toolCallID, ok := msg["tool_call_id"].(string); ok {
			ctxMsg.ToolCallID = &toolCallID
		}

		// Parse tool_calls
		if toolCalls, ok := msg["tool_calls"].([]interface{}); ok {
			ctxMsg.ToolCalls = api.parseToolCalls(toolCalls)
		}

		// Parse refusal
		if refusal, ok := msg["refusal"].(string); ok {
			ctxMsg.Refusal = &refusal
		}

		result = append(result, ctxMsg)
	}
	return result
}

// parseToolCalls converts []interface{} to []agentContext.ToolCall
func (api *JSAPI) parseToolCalls(toolCalls []interface{}) []agentContext.ToolCall {
	result := make([]agentContext.ToolCall, 0, len(toolCalls))
	for _, tc := range toolCalls {
		tcMap, ok := tc.(map[string]interface{})
		if !ok {
			continue
		}

		toolCall := agentContext.ToolCall{}

		if id, ok := tcMap["id"].(string); ok {
			toolCall.ID = id
		}
		if tcType, ok := tcMap["type"].(string); ok {
			toolCall.Type = agentContext.ToolCallType(tcType)
		}
		if fn, ok := tcMap["function"].(map[string]interface{}); ok {
			if name, ok := fn["name"].(string); ok {
				toolCall.Function.Name = name
			}
			if args, ok := fn["arguments"].(string); ok {
				toolCall.Function.Arguments = args
			}
		}

		result = append(result, toolCall)
	}
	return result
}

// parseCallOptions converts map to CallOptions
func (api *JSAPI) parseCallOptions(opts map[string]interface{}) *CallOptions {
	callOpts := &CallOptions{}

	if connector, ok := opts["connector"].(string); ok {
		callOpts.Connector = connector
	}
	if mode, ok := opts["mode"].(string); ok {
		callOpts.Mode = mode
	}
	if metadata, ok := opts["metadata"].(map[string]interface{}); ok {
		callOpts.Metadata = metadata
	}

	// Parse skip configuration
	if skip, ok := opts["skip"].(map[string]interface{}); ok {
		callOpts.Skip = &agentContext.Skip{}
		if history, ok := skip["history"].(bool); ok {
			callOpts.Skip.History = history
		}
		if trace, ok := skip["trace"].(bool); ok {
			callOpts.Skip.Trace = trace
		}
		if output, ok := skip["output"].(bool); ok {
			callOpts.Skip.Output = output
		}
		if keyword, ok := skip["keyword"].(bool); ok {
			callOpts.Skip.Keyword = keyword
		}
		if search, ok := skip["search"].(bool); ok {
			callOpts.Skip.Search = search
		}
		if contentParsing, ok := skip["content_parsing"].(bool); ok {
			callOpts.Skip.ContentParsing = contentParsing
		}
	}

	return callOpts
}

// parseRequests parses an array of request objects into typed Requests
func (api *JSAPI) parseRequests(requests []interface{}) []*Request {
	return api.parseRequestsWithHandlers(requests, nil)
}

// convertResults converts typed Results to interface slice for JS
func (api *JSAPI) convertResults(results []*Result) []interface{} {
	out := make([]interface{}, len(results))
	for i, r := range results {
		out[i] = r
	}
	return out
}

// SetJSAPIFactory sets the factory function for creating AgentAPI instances
// Called by assistant package during initialization
func SetJSAPIFactory() {
	agentContext.AgentAPIFactory = func(ctx *agentContext.Context) agentContext.AgentAPI {
		return NewJSAPI(ctx)
	}
}
