// ExampleClient implements the ACP Client interface.
// Run: go run ./examples/client/main.go -- ./examples/agent/main.go
// Or point it at any ACP agent process.
package main

import (
	"bufio"
	"context"
	"flag"
	"fmt"
	"log"
	"os"
	"os/signal"
	"sync"

	"github.com/keepmind9/acp-sdk-go/client"
	"github.com/keepmind9/acp-sdk-go/core"
	"github.com/keepmind9/acp-sdk-go/helpers"
	"github.com/keepmind9/acp-sdk-go/schema"
	"github.com/keepmind9/acp-sdk-go/transport"
)

func ptrOutcome(o schema.RequestPermissionOutcome) *schema.RequestPermissionOutcome { return &o }
func ptrOptId(s string) *schema.PermissionOptionId                                  { v := schema.PermissionOptionId(s); return &v }

type ExampleClient struct {
	*client.Base
	mu sync.Mutex
}

func (c *ExampleClient) RequestPermission(_ context.Context, req *schema.RequestPermissionRequest) (*schema.RequestPermissionResponse, error) {
	title := "<permission>"
	if req.ToolCall != nil && req.ToolCall.Title != nil {
		title = *req.ToolCall.Title
	}
	fmt.Printf("\n🔐 Permission requested: %s\n", title)
	if req.Options != nil {
		for i, opt := range req.Options {
			kind := ""
			if opt.Kind != nil {
				kind = string(*opt.Kind)
			}
			fmt.Printf("  %d. %s (%s)\n", i+1, opt.Name, kind)
		}
	}
	fmt.Print("Select option: ")

	scanner := bufio.NewScanner(os.Stdin)
	if !scanner.Scan() {
		return &schema.RequestPermissionResponse{
			Outcome: ptrOutcome(schema.RequestPermissionOutcomeCancelled),
		}, nil
	}
	input := scanner.Text()

	if req.Options != nil {
		var idx int
		if _, err := fmt.Sscanf(input, "%d", &idx); err == nil && idx >= 1 && idx <= len(req.Options) {
			// Option selected but not needed in response per current schema
			_ = req.Options[idx-1]
			return &schema.RequestPermissionResponse{
				Outcome: ptrOutcome(schema.RequestPermissionOutcomeSelected),
			}, nil
		}
	}
	return &schema.RequestPermissionResponse{
		Outcome: ptrOutcome(schema.RequestPermissionOutcomeCancelled),
	}, nil
}

func (c *ExampleClient) SessionUpdate(_ context.Context, notif *schema.SessionNotification) error {
	c.mu.Lock()
	defer c.mu.Unlock()

	switch notif.Update.SessionUpdate {
	case schema.SessionUpdateKindAgentMessageChunk:
		if notif.Update.AgentMessageChunk != nil && notif.Update.AgentMessageChunk.Content != nil {
			c.printContent(*notif.Update.AgentMessageChunk.Content, "Agent")
		}
	case schema.SessionUpdateKindAgentThoughtChunk:
		if notif.Update.AgentThoughtChunk != nil && notif.Update.AgentThoughtChunk.Content != nil {
			fmt.Print("\n[agent thought] ")
			c.printContent(*notif.Update.AgentThoughtChunk.Content, "")
			fmt.Println()
		}
	case schema.SessionUpdateKindUserMessageChunk:
		if notif.Update.UserMessageChunk != nil && notif.Update.UserMessageChunk.Content != nil {
			fmt.Print("\n[user message] ")
			c.printContent(*notif.Update.UserMessageChunk.Content, "")
			fmt.Println()
		}
	case schema.SessionUpdateKindToolCall:
		if notif.Update.ToolCall != nil {
			title := notif.Update.ToolCall.Title
			if title == "" {
				title = "<tool>"
			}
			fmt.Printf("\n🔧 %s\n", title)
		}
	case schema.SessionUpdateKindToolCallUpdate:
		if notif.Update.ToolCallUpdate != nil {
			id := notif.Update.ToolCallUpdate.ToolCallId
			status := ""
			if notif.Update.ToolCallUpdate.Status != nil {
				status = string(*notif.Update.ToolCallUpdate.Status)
			}
			fmt.Printf("\n🔧 Tool `%s` → %s\n", *id, status)
		}
	case schema.SessionUpdateKindPlan:
		if notif.Update.Plan != nil && notif.Update.Plan.Entries != nil {
			fmt.Println("\n[plan]")
			for _, entry := range notif.Update.Plan.Entries {
				status := ""
				if entry.Status != nil {
					status = string(*entry.Status)
				}
				fmt.Printf("  - %-10s %s\n", status, entry.Content)
			}
		}
	default:
	}
	return nil
}

func (c *ExampleClient) printContent(block schema.ContentBlock, label string) {
	switch {
	case block.Text != nil:
		if label != "" {
			fmt.Printf("\n%s: ", label)
		}
		fmt.Print(block.Text.Text)
	case block.Image != nil:
		fmt.Print("<image>")
	case block.Audio != nil:
		fmt.Print("<audio>")
	case block.Resource_link != nil:
		fmt.Printf("<resource: %s>", block.Resource_link.Uri)
	case block.Resource != nil:
		fmt.Print("<embedded resource>")
	default:
		fmt.Print("<content>")
	}
}

func main() {
	flag.Usage = func() {
		fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [flags] -- AGENT_PROGRAM [ARGS...]\n", os.Args[0])
		fmt.Fprintln(flag.CommandLine.Output())
		fmt.Fprintln(flag.CommandLine.Output(), "Spawns an ACP agent process and connects an interactive client to it.")
		fmt.Fprintln(flag.CommandLine.Output(), "Type a message and press Enter to send a prompt to the agent.")
		fmt.Fprintln(flag.CommandLine.Output(), "Commands:")
		fmt.Fprintln(flag.CommandLine.Output(), "  :exit, :quit  Exit the client")
		fmt.Fprintln(flag.CommandLine.Output(), "  :cancel       Cancel the current prompt")
		flag.PrintDefaults()
	}
	flag.Parse()

	args := flag.Args()
	if len(args) == 0 {
		flag.Usage()
		os.Exit(2)
	}

	agentCmd := args[0]
	agentArgs := args[1:]

	subprocess, err := transport.Spawn(agentCmd, transport.WithArgs(agentArgs...))
	if err != nil {
		log.Fatalf("failed to spawn agent: %v", err)
	}

	impl := &ExampleClient{Base: &client.Base{}}
	conn := core.ConnectToAgent(impl, subprocess)

	go func() {
		conn.ReceiveLoop()
	}()

	protoVers := 1
	initResp, err := conn.Initialize(&schema.InitializeRequest{
		ProtocolVersion:    &protoVers,
		ClientCapabilities: &schema.ClientCapabilities{},
		ClientInfo:         &schema.Implementation{Name: "example-client", Title: ptrString("Example ACP Client"), Version: "0.1.0"},
	})
	if err != nil {
		log.Fatalf("initialize failed: %v", err)
	}
	fmt.Printf("✅ Connected to agent (protocol v%d)\n\n", *initResp.ProtocolVersion)

	session, err := conn.NewSession(&schema.NewSessionRequest{
		Cwd:        cwd(),
		McpServers: nil,
	})
	if err != nil {
		log.Fatalf("new_session failed: %v", err)
	}
	sessionID := *session.SessionId
	fmt.Printf("📝 Session: %s\n\n", sessionID)

	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, os.Interrupt)

	scanner := bufio.NewScanner(os.Stdin)
	fmt.Print("> ")
	for scanner.Scan() {
		line := scanner.Text()

		select {
		case <-sigChan:
			fmt.Println("\nInterrupted.")
			shutdown(subprocess, conn)
			return
		default:
		}

		if line == "" {
			fmt.Print("> ")
			continue
		}
		if line == ":exit" || line == ":quit" {
			break
		}
		if line == ":cancel" {
			if err := conn.Cancel(sessionID); err != nil {
				log.Printf("cancel failed: %v", err)
			}
			fmt.Print("> ")
			continue
		}

		block := helpers.TextBlock(line)
		_, err := conn.Prompt(&schema.PromptRequest{
			SessionId: session.SessionId,
			Prompt:    []*schema.ContentBlock{&block},
		})
		if err != nil {
			log.Printf("prompt failed: %v", err)
		}
		fmt.Print("\n> ")
	}

	shutdown(subprocess, conn)
}

func ptrString(s string) *string { return &s }

func cwd() string {
	dir, _ := os.Getwd()
	if dir == "" {
		return "/tmp"
	}
	return dir
}

func shutdown(subprocess *transport.Subprocess, conn *client.ClientSideConnection) {
	conn.Close()
	subprocess.Close()
}
