package client

import (
	"context"
	"io"
	"log/slog"

	"github.com/keepmind9/acp-sdk-go/rpc"
	"github.com/keepmind9/acp-sdk-go/schema"
)

func ptrSessionId(s string) *schema.SessionId  { v := schema.SessionId(s); return &v }
func ptrVers(v int) *schema.ProtocolVersion    { pv := schema.ProtocolVersion(v); return &pv }
func ptrModeId(s string) *schema.SessionModeId { mid := schema.SessionModeId(s); return &mid }

// ClientSideConnection wraps an rpc.Connection and provides typed methods
// for the client to call on the agent (initialize, new_session, prompt, etc.).
// It also dispatches incoming JSON-RPC messages to the Client implementation.
type ClientSideConnection struct {
	conn *rpc.Connection
}

// NewClientSideConnection creates a new client-side connection.
// The client parameter implements the Client interface to handle incoming calls.
// The connection reads from reader and writes to writer.
func NewClientSideConnection(client Client, reader io.Reader, writer io.Writer) *ClientSideConnection {
	handler := buildClientRouter(client)
	conn := rpc.NewConnection(context.Background(), handler, reader, writer)
	csc := &ClientSideConnection{conn: conn}

	if h, ok := client.(OnConnectHandler); ok {
		h.OnConnect(csc)
	}

	return csc
}

// ReceiveLoop starts the main event loop.
func (c *ClientSideConnection) ReceiveLoop() {
	c.conn.ReceiveLoop()
}

// Close shuts down the connection.
func (c *ClientSideConnection) Close() error {
	return c.conn.Close()
}

// SetLogger configures the logger for this connection.
func (c *ClientSideConnection) SetLogger(logger *slog.Logger) {
	c.conn.SetLogger(logger)
}

// --- Agent methods (client calls these to communicate with the agent) ---

// Initialize sends an initialize request to the agent.
func (c *ClientSideConnection) Initialize(req *schema.InitializeRequest) (*schema.InitializeResponse, error) {
	return rpc.SendRequest[*schema.InitializeResponse](context.Background(), c.conn, schema.MethodInitialize, req)
}

// Authenticate sends an authenticate request to the agent.
func (c *ClientSideConnection) Authenticate(req *schema.AuthenticateRequest) (*schema.AuthenticateResponse, error) {
	return rpc.SendRequest[*schema.AuthenticateResponse](context.Background(), c.conn, schema.MethodAuthenticate, req)
}

// NewSession creates a new session on the agent.
func (c *ClientSideConnection) NewSession(req *schema.NewSessionRequest) (*schema.NewSessionResponse, error) {
	return rpc.SendRequest[*schema.NewSessionResponse](context.Background(), c.conn, schema.MethodSessionNew, req)
}

// LoadSession loads an existing session on the agent.
func (c *ClientSideConnection) LoadSession(req *schema.LoadSessionRequest) (*schema.LoadSessionResponse, error) {
	return rpc.SendRequest[*schema.LoadSessionResponse](context.Background(), c.conn, schema.MethodSessionLoad, req)
}

// ListSessions lists sessions on the agent.
func (c *ClientSideConnection) ListSessions(req *schema.ListSessionsRequest) (*schema.ListSessionsResponse, error) {
	return rpc.SendRequest[*schema.ListSessionsResponse](context.Background(), c.conn, schema.MethodSessionList, req)
}

// Prompt sends a prompt to the agent.
func (c *ClientSideConnection) Prompt(req *schema.PromptRequest) (*schema.PromptResponse, error) {
	return rpc.SendRequest[*schema.PromptResponse](context.Background(), c.conn, schema.MethodSessionPrompt, req)
}

// Cancel sends a cancel notification to the agent.
func (c *ClientSideConnection) Cancel(sessionID string) error {
	notif := &schema.CancelNotification{SessionId: ptrSessionId(sessionID)}
	return c.conn.SendNotification(schema.MethodSessionCancel, notif)
}

// SetSessionMode changes the session mode on the agent.
func (c *ClientSideConnection) SetSessionMode(req *schema.SetSessionModeRequest) (*schema.SetSessionModeResponse, error) {
	return rpc.SendRequest[*schema.SetSessionModeResponse](context.Background(), c.conn, schema.MethodSessionSet_mode, req)
}

// SetSessionConfigOption updates a session config option on the agent.
func (c *ClientSideConnection) SetSessionConfigOption(req *schema.SetSessionConfigOptionRequest) (*schema.SetSessionConfigOptionResponse, error) {
	return rpc.SendRequest[*schema.SetSessionConfigOptionResponse](context.Background(), c.conn, schema.MethodSessionSet_config_option, req)
}

// ExtMethod sends an extension request to the agent.
func (c *ClientSideConnection) ExtMethod(method string, params map[string]any) (any, error) {
	return c.conn.SendRequest(context.Background(), "_"+method, params)
}

// ExtNotification sends an extension notification to the agent.
func (c *ClientSideConnection) ExtNotification(method string, params map[string]any) error {
	return c.conn.SendNotification("_"+method, params)
}
