// Package mcp provides an MCP (Model Context Protocol) server for Core.
// This allows Claude Code and other MCP clients to interact with Core's
// IDE, file system, and display services.
package mcp

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/modelcontextprotocol/go-sdk/mcp"

	"forge.lthn.ai/core/gui/pkg/core"
	"forge.lthn.ai/core/gui/pkg/display"
	"forge.lthn.ai/core/gui/pkg/ide"
	"forge.lthn.ai/core/gui/pkg/process"
	"forge.lthn.ai/core/gui/pkg/webview"
	"forge.lthn.ai/core/gui/pkg/ws"
)

// Service provides an MCP server that exposes Core functionality.
type Service struct {
	core      *core.Core
	server    *mcp.Server
	ide       *ide.Service
	display   *display.Service
	process   *process.Service
	webview   *webview.Service
	wsHub     *ws.Hub
	wsPort    int
	wsRunning bool
}

// New creates a new MCP service.
func New(c *core.Core) *Service {
	impl := &mcp.Implementation{
		Name:    "core",
		Version: "0.1.0",
	}

	server := mcp.NewServer(impl, nil)
	s := &Service{
		core:    c,
		server:  server,
		process: process.New(),
	}

	// Try to get the IDE service if available
	if c != nil {
		ideSvc, _ := core.ServiceFor[*ide.Service](c, "forge.lthn.ai/core/gui/ide")
		s.ide = ideSvc
	}

	s.registerTools()
	return s
}

// NewStandalone creates an MCP service without a Core instance.
// This allows running the MCP server independently with basic file operations.
func NewStandalone() *Service {
	return NewStandaloneWithPort(9876)
}

// NewStandaloneWithPort creates an MCP service with a specific WebSocket port.
func NewStandaloneWithPort(wsPort int) *Service {
	impl := &mcp.Implementation{
		Name:    "core",
		Version: "0.1.0",
	}

	server := mcp.NewServer(impl, nil)
	hub := ws.NewHub()
	proc := process.New()

	s := &Service{
		server:  server,
		process: proc,
		wsHub:   hub,
		wsPort:  wsPort,
	}

	// Wire process output to WebSocket
	proc.OnOutput(func(processID string, output string) {
		hub.SendProcessOutput(processID, output)
	})

	proc.OnStatusChange(func(processID string, status process.Status, exitCode int) {
		hub.SendProcessStatus(processID, string(status), exitCode)
	})

	s.registerTools()
	return s
}

// registerTools adds all Core tools to the MCP server.
// Naming convention: prefix_action for discoverability
// file_* dir_* lang_* process_*
func (s *Service) registerTools() {
	// File operations
	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "file_read",
		Description: "Read the contents of a file",
	}, s.readFile)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "file_write",
		Description: "Write content to a file",
	}, s.writeFile)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "file_delete",
		Description: "Delete a file or empty directory",
	}, s.deleteFile)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "file_rename",
		Description: "Rename or move a file",
	}, s.renameFile)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "file_exists",
		Description: "Check if a file or directory exists",
	}, s.fileExists)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "file_edit",
		Description: "Edit a file by replacing old_string with new_string. Use replace_all=true to replace all occurrences.",
	}, s.editDiff)

	// Directory operations
	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "dir_list",
		Description: "List contents of a directory",
	}, s.listDirectory)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "dir_create",
		Description: "Create a new directory",
	}, s.createDirectory)

	// Language detection
	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "lang_detect",
		Description: "Detect the programming language of a file",
	}, s.detectLanguage)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "lang_list",
		Description: "Get list of supported programming languages",
	}, s.getSupportedLanguages)

	// Process management
	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "process_start",
		Description: "Start a new process with the given command and arguments",
	}, s.processStart)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "process_stop",
		Description: "Stop a running process gracefully",
	}, s.processStop)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "process_kill",
		Description: "Forcefully kill a process",
	}, s.processKill)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "process_list",
		Description: "List all managed processes",
	}, s.processList)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "process_output",
		Description: "Get the output of a process",
	}, s.processOutput)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "process_input",
		Description: "Send input to a running process stdin",
	}, s.processSendInput)

	// WebSocket streaming
	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "ws_start",
		Description: "Start WebSocket server for real-time streaming",
	}, s.wsStart)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "ws_info",
		Description: "Get WebSocket server info (port, connected clients)",
	}, s.wsInfo)

	// WebView interaction (only available when embedded in GUI app)
	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "webview_list",
		Description: "List all open windows in the application",
	}, s.webviewList)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "webview_eval",
		Description: "Execute JavaScript in a window and return the result",
	}, s.webviewEval)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "webview_console",
		Description: "Get captured console messages (log, warn, error) from the WebView",
	}, s.webviewConsole)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "webview_click",
		Description: "Click an element by CSS selector",
	}, s.webviewClick)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "webview_type",
		Description: "Type text into an element by CSS selector",
	}, s.webviewType)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "webview_query",
		Description: "Query elements by CSS selector and return info about matches",
	}, s.webviewQuery)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "webview_navigate",
		Description: "Navigate to a URL or Angular route",
	}, s.webviewNavigate)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "webview_source",
		Description: "Get the current page HTML source",
	}, s.webviewSource)

	// Window/Display management (the unique value-add for native app control)
	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "window_list",
		Description: "List all windows with their positions and sizes",
	}, s.windowList)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "window_get",
		Description: "Get detailed info about a specific window",
	}, s.windowGet)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "window_position",
		Description: "Move a window to a specific position (x, y coordinates)",
	}, s.windowPosition)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "window_size",
		Description: "Resize a window to specific dimensions",
	}, s.windowSize)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "window_bounds",
		Description: "Set both position and size of a window in one call",
	}, s.windowBounds)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "window_maximize",
		Description: "Maximize a window",
	}, s.windowMaximize)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "window_minimize",
		Description: "Minimize a window",
	}, s.windowMinimize)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "window_restore",
		Description: "Restore a window from maximized/minimized state",
	}, s.windowRestore)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "window_focus",
		Description: "Bring a window to the front and focus it",
	}, s.windowFocus)

	mcp.AddTool(s.server, &mcp.Tool{
		Name:        "screen_list",
		Description: "List all available screens/monitors with their dimensions",
	}, s.screenList)
}

// SetWebView sets the WebView service for GUI interaction.
// This must be called when running embedded in the GUI app.
func (s *Service) SetWebView(wv *webview.Service) {
	s.webview = wv
}

// SetDisplay sets the Display service for window management.
// This must be called when running embedded in the GUI app.
func (s *Service) SetDisplay(d *display.Service) {
	s.display = d
}

// Tool input/output types

// ReadFileInput contains parameters for reading a file.
type ReadFileInput struct {
	// Absolute path to the file to read.
	Path string `json:"path"`
}

// ReadFileOutput contains the result of reading a file.
type ReadFileOutput struct {
	Content  string `json:"content"`
	Language string `json:"language"`
	Path     string `json:"path"`
}

// WriteFileInput contains parameters for writing a file.
type WriteFileInput struct {
	// Absolute path to the file to write.
	Path string `json:"path"`
	// Content to write to the file.
	Content string `json:"content"`
}

// WriteFileOutput contains the result of writing a file.
type WriteFileOutput struct {
	Success bool   `json:"success"`
	Path    string `json:"path"`
}

// ListDirectoryInput contains parameters for listing a directory.
type ListDirectoryInput struct {
	// Absolute path to the directory to list.
	Path string `json:"path"`
}

// ListDirectoryOutput contains the result of listing a directory.
type ListDirectoryOutput struct {
	Entries []DirectoryEntry `json:"entries"`
	Path    string           `json:"path"`
}

// DirectoryEntry represents a file or directory entry.
type DirectoryEntry struct {
	Name  string `json:"name"`
	Path  string `json:"path"`
	IsDir bool   `json:"isDir"`
	Size  int64  `json:"size"`
}

// CreateDirectoryInput contains parameters for creating a directory.
type CreateDirectoryInput struct {
	// Absolute path to the directory to create.
	Path string `json:"path"`
}

// CreateDirectoryOutput contains the result of creating a directory.
type CreateDirectoryOutput struct {
	Success bool   `json:"success"`
	Path    string `json:"path"`
}

// DeleteFileInput contains parameters for deleting a file.
type DeleteFileInput struct {
	// Absolute path to the file to delete.
	Path string `json:"path"`
}

// DeleteFileOutput contains the result of deleting a file.
type DeleteFileOutput struct {
	Success bool   `json:"success"`
	Path    string `json:"path"`
}

// RenameFileInput contains parameters for renaming a file.
type RenameFileInput struct {
	// Current path of the file.
	OldPath string `json:"oldPath"`
	// New path for the file.
	NewPath string `json:"newPath"`
}

// RenameFileOutput contains the result of renaming a file.
type RenameFileOutput struct {
	Success bool   `json:"success"`
	OldPath string `json:"oldPath"`
	NewPath string `json:"newPath"`
}

// FileExistsInput contains parameters for checking if a file exists.
type FileExistsInput struct {
	// Absolute path to check.
	Path string `json:"path"`
}

// FileExistsOutput contains the result of checking file existence.
type FileExistsOutput struct {
	Exists bool   `json:"exists"`
	IsDir  bool   `json:"isDir"`
	Path   string `json:"path"`
}

// DetectLanguageInput contains parameters for detecting file language.
type DetectLanguageInput struct {
	// File path to detect language for.
	Path string `json:"path"`
}

type DetectLanguageOutput struct {
	Language string `json:"language"`
	Path     string `json:"path"`
}

type GetSupportedLanguagesInput struct{}

type GetSupportedLanguagesOutput struct {
	Languages []LanguageInfo `json:"languages"`
}

type LanguageInfo struct {
	ID         string   `json:"id"`
	Name       string   `json:"name"`
	Extensions []string `json:"extensions"`
}

// EditDiffInput contains parameters for diff-based editing.
type EditDiffInput struct {
	// Absolute path to the file to edit.
	Path string `json:"path"`
	// The text to find and replace.
	OldString string `json:"old_string"`
	// The replacement text.
	NewString string `json:"new_string"`
	// Replace all occurrences if true, otherwise only the first.
	ReplaceAll bool `json:"replace_all,omitempty"`
}

// EditDiffOutput contains the result of the edit.
type EditDiffOutput struct {
	Path         string `json:"path"`
	Success      bool   `json:"success"`
	Replacements int    `json:"replacements"`
}

// Tool handlers

func (s *Service) readFile(ctx context.Context, req *mcp.CallToolRequest, input ReadFileInput) (*mcp.CallToolResult, ReadFileOutput, error) {
	if s.ide != nil {
		info, err := s.ide.OpenFile(input.Path)
		if err != nil {
			return nil, ReadFileOutput{}, fmt.Errorf("failed to read file: %w", err)
		}
		return nil, ReadFileOutput{
			Content:  info.Content,
			Language: info.Language,
			Path:     info.Path,
		}, nil
	}

	// Fallback to direct file read
	content, err := os.ReadFile(input.Path)
	if err != nil {
		return nil, ReadFileOutput{}, fmt.Errorf("failed to read file: %w", err)
	}
	return nil, ReadFileOutput{
		Content:  string(content),
		Language: detectLanguage(input.Path),
		Path:     input.Path,
	}, nil
}

func (s *Service) writeFile(ctx context.Context, req *mcp.CallToolRequest, input WriteFileInput) (*mcp.CallToolResult, WriteFileOutput, error) {
	if s.ide != nil {
		err := s.ide.SaveFile(input.Path, input.Content)
		if err != nil {
			return nil, WriteFileOutput{}, fmt.Errorf("failed to write file: %w", err)
		}
		return nil, WriteFileOutput{Success: true, Path: input.Path}, nil
	}

	// Fallback to direct file write
	dir := filepath.Dir(input.Path)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return nil, WriteFileOutput{}, fmt.Errorf("failed to create directory: %w", err)
	}
	err := os.WriteFile(input.Path, []byte(input.Content), 0644)
	if err != nil {
		return nil, WriteFileOutput{}, fmt.Errorf("failed to write file: %w", err)
	}
	return nil, WriteFileOutput{Success: true, Path: input.Path}, nil
}

func (s *Service) listDirectory(ctx context.Context, req *mcp.CallToolRequest, input ListDirectoryInput) (*mcp.CallToolResult, ListDirectoryOutput, error) {
	if s.ide != nil {
		entries, err := s.ide.ListDirectory(input.Path)
		if err != nil {
			return nil, ListDirectoryOutput{}, fmt.Errorf("failed to list directory: %w", err)
		}
		result := make([]DirectoryEntry, 0, len(entries))
		for _, e := range entries {
			result = append(result, DirectoryEntry{
				Name:  e.Name,
				Path:  e.Path,
				IsDir: e.IsDir,
				Size:  e.Size,
			})
		}
		return nil, ListDirectoryOutput{Entries: result, Path: input.Path}, nil
	}

	// Fallback to direct directory listing
	entries, err := os.ReadDir(input.Path)
	if err != nil {
		return nil, ListDirectoryOutput{}, fmt.Errorf("failed to list directory: %w", err)
	}
	result := make([]DirectoryEntry, 0, len(entries))
	for _, e := range entries {
		info, _ := e.Info()
		var size int64
		if info != nil {
			size = info.Size()
		}
		result = append(result, DirectoryEntry{
			Name:  e.Name(),
			Path:  filepath.Join(input.Path, e.Name()),
			IsDir: e.IsDir(),
			Size:  size,
		})
	}
	return nil, ListDirectoryOutput{Entries: result, Path: input.Path}, nil
}

func (s *Service) createDirectory(ctx context.Context, req *mcp.CallToolRequest, input CreateDirectoryInput) (*mcp.CallToolResult, CreateDirectoryOutput, error) {
	if s.ide != nil {
		err := s.ide.CreateDirectory(input.Path)
		if err != nil {
			return nil, CreateDirectoryOutput{}, fmt.Errorf("failed to create directory: %w", err)
		}
		return nil, CreateDirectoryOutput{Success: true, Path: input.Path}, nil
	}

	err := os.MkdirAll(input.Path, 0755)
	if err != nil {
		return nil, CreateDirectoryOutput{}, fmt.Errorf("failed to create directory: %w", err)
	}
	return nil, CreateDirectoryOutput{Success: true, Path: input.Path}, nil
}

func (s *Service) deleteFile(ctx context.Context, req *mcp.CallToolRequest, input DeleteFileInput) (*mcp.CallToolResult, DeleteFileOutput, error) {
	if s.ide != nil {
		err := s.ide.DeleteFile(input.Path)
		if err != nil {
			return nil, DeleteFileOutput{}, fmt.Errorf("failed to delete file: %w", err)
		}
		return nil, DeleteFileOutput{Success: true, Path: input.Path}, nil
	}

	err := os.Remove(input.Path)
	if err != nil {
		return nil, DeleteFileOutput{}, fmt.Errorf("failed to delete file: %w", err)
	}
	return nil, DeleteFileOutput{Success: true, Path: input.Path}, nil
}

func (s *Service) renameFile(ctx context.Context, req *mcp.CallToolRequest, input RenameFileInput) (*mcp.CallToolResult, RenameFileOutput, error) {
	if s.ide != nil {
		err := s.ide.RenameFile(input.OldPath, input.NewPath)
		if err != nil {
			return nil, RenameFileOutput{}, fmt.Errorf("failed to rename file: %w", err)
		}
		return nil, RenameFileOutput{Success: true, OldPath: input.OldPath, NewPath: input.NewPath}, nil
	}

	err := os.Rename(input.OldPath, input.NewPath)
	if err != nil {
		return nil, RenameFileOutput{}, fmt.Errorf("failed to rename file: %w", err)
	}
	return nil, RenameFileOutput{Success: true, OldPath: input.OldPath, NewPath: input.NewPath}, nil
}

func (s *Service) fileExists(ctx context.Context, req *mcp.CallToolRequest, input FileExistsInput) (*mcp.CallToolResult, FileExistsOutput, error) {
	info, err := os.Stat(input.Path)
	if os.IsNotExist(err) {
		return nil, FileExistsOutput{Exists: false, IsDir: false, Path: input.Path}, nil
	}
	if err != nil {
		return nil, FileExistsOutput{}, fmt.Errorf("failed to check file: %w", err)
	}
	return nil, FileExistsOutput{Exists: true, IsDir: info.IsDir(), Path: input.Path}, nil
}

func (s *Service) detectLanguage(ctx context.Context, req *mcp.CallToolRequest, input DetectLanguageInput) (*mcp.CallToolResult, DetectLanguageOutput, error) {
	lang := detectLanguage(input.Path)
	return nil, DetectLanguageOutput{Language: lang, Path: input.Path}, nil
}

func (s *Service) getSupportedLanguages(ctx context.Context, req *mcp.CallToolRequest, input GetSupportedLanguagesInput) (*mcp.CallToolResult, GetSupportedLanguagesOutput, error) {
	languages := []LanguageInfo{
		{ID: "typescript", Name: "TypeScript", Extensions: []string{".ts", ".tsx"}},
		{ID: "javascript", Name: "JavaScript", Extensions: []string{".js", ".jsx"}},
		{ID: "go", Name: "Go", Extensions: []string{".go"}},
		{ID: "python", Name: "Python", Extensions: []string{".py"}},
		{ID: "rust", Name: "Rust", Extensions: []string{".rs"}},
		{ID: "java", Name: "Java", Extensions: []string{".java"}},
		{ID: "csharp", Name: "C#", Extensions: []string{".cs"}},
		{ID: "cpp", Name: "C++", Extensions: []string{".cpp", ".hpp", ".cc", ".cxx"}},
		{ID: "c", Name: "C", Extensions: []string{".c", ".h"}},
		{ID: "html", Name: "HTML", Extensions: []string{".html", ".htm"}},
		{ID: "css", Name: "CSS", Extensions: []string{".css"}},
		{ID: "scss", Name: "SCSS", Extensions: []string{".scss"}},
		{ID: "json", Name: "JSON", Extensions: []string{".json"}},
		{ID: "yaml", Name: "YAML", Extensions: []string{".yaml", ".yml"}},
		{ID: "markdown", Name: "Markdown", Extensions: []string{".md", ".markdown"}},
		{ID: "sql", Name: "SQL", Extensions: []string{".sql"}},
		{ID: "shell", Name: "Shell", Extensions: []string{".sh", ".bash"}},
		{ID: "xml", Name: "XML", Extensions: []string{".xml"}},
		{ID: "swift", Name: "Swift", Extensions: []string{".swift"}},
		{ID: "kotlin", Name: "Kotlin", Extensions: []string{".kt", ".kts"}},
		{ID: "php", Name: "PHP", Extensions: []string{".php"}},
		{ID: "ruby", Name: "Ruby", Extensions: []string{".rb"}},
	}
	return nil, GetSupportedLanguagesOutput{Languages: languages}, nil
}

func (s *Service) editDiff(ctx context.Context, req *mcp.CallToolRequest, input EditDiffInput) (*mcp.CallToolResult, EditDiffOutput, error) {
	// Read the file
	content, err := os.ReadFile(input.Path)
	if err != nil {
		return nil, EditDiffOutput{}, fmt.Errorf("failed to read file: %w", err)
	}

	fileContent := string(content)
	count := 0

	if input.ReplaceAll {
		// Count occurrences
		count = strings.Count(fileContent, input.OldString)
		if count == 0 {
			return nil, EditDiffOutput{}, fmt.Errorf("old_string not found in file")
		}
		fileContent = strings.ReplaceAll(fileContent, input.OldString, input.NewString)
	} else {
		// Replace only first occurrence
		if !strings.Contains(fileContent, input.OldString) {
			return nil, EditDiffOutput{}, fmt.Errorf("old_string not found in file")
		}
		fileContent = strings.Replace(fileContent, input.OldString, input.NewString, 1)
		count = 1
	}

	// Write the file back
	err = os.WriteFile(input.Path, []byte(fileContent), 0644)
	if err != nil {
		return nil, EditDiffOutput{}, fmt.Errorf("failed to write file: %w", err)
	}

	return nil, EditDiffOutput{
		Path:         input.Path,
		Success:      true,
		Replacements: count,
	}, nil
}

// detectLanguage maps file extensions to Monaco editor languages.
func detectLanguage(path string) string {
	ext := filepath.Ext(path)
	switch ext {
	case ".ts", ".tsx":
		return "typescript"
	case ".js", ".jsx":
		return "javascript"
	case ".go":
		return "go"
	case ".py":
		return "python"
	case ".rs":
		return "rust"
	case ".rb":
		return "ruby"
	case ".java":
		return "java"
	case ".c", ".h":
		return "c"
	case ".cpp", ".hpp", ".cc", ".cxx":
		return "cpp"
	case ".cs":
		return "csharp"
	case ".html", ".htm":
		return "html"
	case ".css":
		return "css"
	case ".scss":
		return "scss"
	case ".less":
		return "less"
	case ".json":
		return "json"
	case ".yaml", ".yml":
		return "yaml"
	case ".xml":
		return "xml"
	case ".md", ".markdown":
		return "markdown"
	case ".sql":
		return "sql"
	case ".sh", ".bash":
		return "shell"
	case ".ps1":
		return "powershell"
	case ".toml":
		return "toml"
	case ".ini", ".cfg":
		return "ini"
	case ".swift":
		return "swift"
	case ".kt", ".kts":
		return "kotlin"
	case ".php":
		return "php"
	case ".r":
		return "r"
	case ".lua":
		return "lua"
	case ".pl", ".pm":
		return "perl"
	default:
		if filepath.Base(path) == "Dockerfile" {
			return "dockerfile"
		}
		return "plaintext"
	}
}

// Run starts the MCP server on stdio.
func (s *Service) Run(ctx context.Context) error {
	return s.server.Run(ctx, &mcp.StdioTransport{})
}

// Server returns the underlying MCP server for advanced configuration.
func (s *Service) Server() *mcp.Server {
	return s.server
}

// Process management types

// ProcessStartInput contains parameters for starting a process.
type ProcessStartInput struct {
	// Command to execute.
	Command string `json:"command"`
	// Arguments for the command.
	Args []string `json:"args,omitempty"`
	// Working directory for the process.
	Dir string `json:"dir,omitempty"`
}

// ProcessStartOutput contains the result of starting a process.
type ProcessStartOutput struct {
	ID        string    `json:"id"`
	Command   string    `json:"command"`
	Args      []string  `json:"args"`
	Dir       string    `json:"dir"`
	PID       int       `json:"pid"`
	StartedAt time.Time `json:"startedAt"`
}

// ProcessIDInput contains a process ID parameter.
type ProcessIDInput struct {
	// Process ID to operate on.
	ID string `json:"id"`
}

// ProcessStopOutput contains the result of stopping a process.
type ProcessStopOutput struct {
	ID      string `json:"id"`
	Success bool   `json:"success"`
}

// ProcessListInput is empty but required for the handler signature.
type ProcessListInput struct{}

// ProcessInfo represents process information.
type ProcessInfo struct {
	ID        string    `json:"id"`
	Command   string    `json:"command"`
	Args      []string  `json:"args"`
	Dir       string    `json:"dir"`
	Status    string    `json:"status"`
	ExitCode  int       `json:"exitCode"`
	PID       int       `json:"pid"`
	StartedAt time.Time `json:"startedAt"`
}

// ProcessListOutput contains the list of processes.
type ProcessListOutput struct {
	Processes []ProcessInfo `json:"processes"`
}

// ProcessOutputOutput contains the captured output of a process.
type ProcessOutputOutput struct {
	ID     string `json:"id"`
	Output string `json:"output"`
	Length int    `json:"length"`
}

// ProcessSendInputInput contains input to send to a process.
type ProcessSendInputInput struct {
	// Process ID to send input to.
	ID string `json:"id"`
	// Input text to send.
	Input string `json:"input"`
}

// ProcessSendInputOutput contains the result of sending input.
type ProcessSendInputOutput struct {
	ID      string `json:"id"`
	Success bool   `json:"success"`
}

// Process management handlers

func (s *Service) processStart(ctx context.Context, req *mcp.CallToolRequest, input ProcessStartInput) (*mcp.CallToolResult, ProcessStartOutput, error) {
	dir := input.Dir
	if dir == "" {
		var err error
		dir, err = os.Getwd()
		if err != nil {
			dir = "."
		}
	}

	proc, err := s.process.Start(input.Command, input.Args, dir)
	if err != nil {
		return nil, ProcessStartOutput{}, fmt.Errorf("failed to start process: %w", err)
	}

	info := proc.Info()
	return nil, ProcessStartOutput{
		ID:        info.ID,
		Command:   info.Command,
		Args:      info.Args,
		Dir:       info.Dir,
		PID:       info.PID,
		StartedAt: info.StartedAt,
	}, nil
}

func (s *Service) processStop(ctx context.Context, req *mcp.CallToolRequest, input ProcessIDInput) (*mcp.CallToolResult, ProcessStopOutput, error) {
	err := s.process.Stop(input.ID)
	if err != nil {
		return nil, ProcessStopOutput{}, fmt.Errorf("failed to stop process: %w", err)
	}
	return nil, ProcessStopOutput{ID: input.ID, Success: true}, nil
}

func (s *Service) processKill(ctx context.Context, req *mcp.CallToolRequest, input ProcessIDInput) (*mcp.CallToolResult, ProcessStopOutput, error) {
	err := s.process.Kill(input.ID)
	if err != nil {
		return nil, ProcessStopOutput{}, fmt.Errorf("failed to kill process: %w", err)
	}
	return nil, ProcessStopOutput{ID: input.ID, Success: true}, nil
}

func (s *Service) processList(ctx context.Context, req *mcp.CallToolRequest, input ProcessListInput) (*mcp.CallToolResult, ProcessListOutput, error) {
	procs := s.process.List()
	result := make([]ProcessInfo, 0, len(procs))
	for _, p := range procs {
		info := p.Info()
		result = append(result, ProcessInfo{
			ID:        info.ID,
			Command:   info.Command,
			Args:      info.Args,
			Dir:       info.Dir,
			Status:    string(info.Status),
			ExitCode:  info.ExitCode,
			PID:       info.PID,
			StartedAt: info.StartedAt,
		})
	}
	return nil, ProcessListOutput{Processes: result}, nil
}

func (s *Service) processOutput(ctx context.Context, req *mcp.CallToolRequest, input ProcessIDInput) (*mcp.CallToolResult, ProcessOutputOutput, error) {
	output, err := s.process.Output(input.ID)
	if err != nil {
		return nil, ProcessOutputOutput{}, fmt.Errorf("failed to get process output: %w", err)
	}
	return nil, ProcessOutputOutput{
		ID:     input.ID,
		Output: output,
		Length: len(output),
	}, nil
}

func (s *Service) processSendInput(ctx context.Context, req *mcp.CallToolRequest, input ProcessSendInputInput) (*mcp.CallToolResult, ProcessSendInputOutput, error) {
	err := s.process.SendInput(input.ID, input.Input)
	if err != nil {
		return nil, ProcessSendInputOutput{}, fmt.Errorf("failed to send input: %w", err)
	}
	return nil, ProcessSendInputOutput{ID: input.ID, Success: true}, nil
}

// WebSocket types

// WsStartInput contains parameters for starting the WebSocket server.
type WsStartInput struct {
	// Port to run WebSocket server on. Defaults to 9876.
	Port int `json:"port,omitempty"`
}

// WsStartOutput contains the result of starting the WebSocket server.
type WsStartOutput struct {
	Port    int    `json:"port"`
	URL     string `json:"url"`
	Started bool   `json:"started"`
}

// WsInfoInput is empty but required for handler signature.
type WsInfoInput struct{}

// WsInfoOutput contains WebSocket server status.
type WsInfoOutput struct {
	Running  bool   `json:"running"`
	Port     int    `json:"port"`
	URL      string `json:"url"`
	Clients  int    `json:"clients"`
	Channels int    `json:"channels"`
}

// WebSocket handlers

func (s *Service) wsStart(ctx context.Context, req *mcp.CallToolRequest, input WsStartInput) (*mcp.CallToolResult, WsStartOutput, error) {
	if s.wsHub == nil {
		return nil, WsStartOutput{}, fmt.Errorf("WebSocket not available in this configuration")
	}

	// Already running?
	if s.wsRunning {
		url := fmt.Sprintf("ws://localhost:%d/ws", s.wsPort)
		return nil, WsStartOutput{
			Port:    s.wsPort,
			URL:     url,
			Started: true,
		}, nil
	}

	port := input.Port
	if port == 0 {
		port = s.wsPort
	}
	if port == 0 {
		port = 9876
	}

	// Start the hub event loop
	hubCtx := context.Background()
	go s.wsHub.Run(hubCtx)

	// Start HTTP server for WebSocket
	go func() {
		mux := http.NewServeMux()
		mux.HandleFunc("/ws", s.wsHub.HandleWebSocket)
		addr := fmt.Sprintf(":%d", port)
		http.ListenAndServe(addr, mux)
	}()

	s.wsPort = port
	s.wsRunning = true
	url := fmt.Sprintf("ws://localhost:%d/ws", port)

	return nil, WsStartOutput{
		Port:    port,
		URL:     url,
		Started: true,
	}, nil
}

func (s *Service) wsInfo(ctx context.Context, req *mcp.CallToolRequest, input WsInfoInput) (*mcp.CallToolResult, WsInfoOutput, error) {
	if s.wsHub == nil {
		return nil, WsInfoOutput{Running: false}, nil
	}

	stats := s.wsHub.Stats()
	url := ""
	if s.wsRunning {
		url = fmt.Sprintf("ws://localhost:%d/ws", s.wsPort)
	}

	return nil, WsInfoOutput{
		Running:  s.wsRunning,
		Port:     s.wsPort,
		URL:      url,
		Clients:  stats.Clients,
		Channels: stats.Channels,
	}, nil
}

// WebView types

// WebviewListInput is empty.
type WebviewListInput struct{}

// WebviewListOutput contains the list of windows.
type WebviewListOutput struct {
	Windows []WebviewWindowInfo `json:"windows"`
}

// WebviewWindowInfo contains window information.
type WebviewWindowInfo struct {
	Name string `json:"name"`
}

// WebviewEvalInput contains parameters for JS evaluation.
type WebviewEvalInput struct {
	// Window name (empty for first window).
	Window string `json:"window,omitempty"`
	// JavaScript code to execute.
	Code string `json:"code"`
}

// WebviewEvalOutput contains the evaluation result.
type WebviewEvalOutput struct {
	Result string `json:"result"`
}

// WebviewConsoleInput contains parameters for console retrieval.
type WebviewConsoleInput struct {
	// Filter by level: log, warn, error, info, debug (empty for all).
	Level string `json:"level,omitempty"`
	// Maximum messages to return.
	Limit int `json:"limit,omitempty"`
	// Clear buffer after reading.
	Clear bool `json:"clear,omitempty"`
}

// WebviewConsoleOutput contains console messages.
type WebviewConsoleOutput struct {
	Messages []webview.ConsoleMessage `json:"messages"`
	Count    int                      `json:"count"`
}

// WebviewClickInput contains parameters for clicking.
type WebviewClickInput struct {
	// Window name (empty for first window).
	Window string `json:"window,omitempty"`
	// CSS selector for the element to click.
	Selector string `json:"selector"`
}

// WebviewClickOutput contains the click result.
type WebviewClickOutput struct {
	Success bool `json:"success"`
}

// WebviewTypeInput contains parameters for typing.
type WebviewTypeInput struct {
	// Window name (empty for first window).
	Window string `json:"window,omitempty"`
	// CSS selector for the input element.
	Selector string `json:"selector"`
	// Text to type.
	Text string `json:"text"`
}

// WebviewTypeOutput contains the type result.
type WebviewTypeOutput struct {
	Success bool `json:"success"`
}

// WebviewQueryInput contains parameters for querying.
type WebviewQueryInput struct {
	// Window name (empty for first window).
	Window string `json:"window,omitempty"`
	// CSS selector to query.
	Selector string `json:"selector"`
}

// WebviewQueryOutput contains query results.
type WebviewQueryOutput struct {
	Elements []map[string]any `json:"elements"`
	Count    int              `json:"count"`
}

// WebviewNavigateInput contains parameters for navigation.
type WebviewNavigateInput struct {
	// Window name (empty for first window).
	Window string `json:"window,omitempty"`
	// URL or route to navigate to.
	URL string `json:"url"`
}

// WebviewNavigateOutput contains navigation result.
type WebviewNavigateOutput struct {
	Success bool `json:"success"`
}

// WebviewSourceInput contains parameters for getting source.
type WebviewSourceInput struct {
	// Window name (empty for first window).
	Window string `json:"window,omitempty"`
}

// WebviewSourceOutput contains the page source.
type WebviewSourceOutput struct {
	HTML   string `json:"html"`
	Length int    `json:"length"`
}

// WebView handlers

func (s *Service) webviewList(ctx context.Context, req *mcp.CallToolRequest, input WebviewListInput) (*mcp.CallToolResult, WebviewListOutput, error) {
	if s.webview == nil {
		return nil, WebviewListOutput{}, fmt.Errorf("WebView not available (MCP server running standalone)")
	}

	windows := s.webview.ListWindows()
	result := make([]WebviewWindowInfo, len(windows))
	for i, w := range windows {
		result[i] = WebviewWindowInfo{Name: w.Name}
	}

	return nil, WebviewListOutput{Windows: result}, nil
}

func (s *Service) webviewEval(ctx context.Context, req *mcp.CallToolRequest, input WebviewEvalInput) (*mcp.CallToolResult, WebviewEvalOutput, error) {
	if s.webview == nil {
		return nil, WebviewEvalOutput{}, fmt.Errorf("WebView not available (MCP server running standalone)")
	}

	result, err := s.webview.ExecJS(input.Window, input.Code)
	if err != nil {
		return nil, WebviewEvalOutput{}, fmt.Errorf("failed to execute JS: %w", err)
	}

	return nil, WebviewEvalOutput{Result: result}, nil
}

func (s *Service) webviewConsole(ctx context.Context, req *mcp.CallToolRequest, input WebviewConsoleInput) (*mcp.CallToolResult, WebviewConsoleOutput, error) {
	if s.webview == nil {
		return nil, WebviewConsoleOutput{}, fmt.Errorf("WebView not available (MCP server running standalone)")
	}

	messages := s.webview.GetConsoleMessages(input.Level, input.Limit)

	if input.Clear {
		s.webview.ClearConsole()
	}

	return nil, WebviewConsoleOutput{
		Messages: messages,
		Count:    len(messages),
	}, nil
}

func (s *Service) webviewClick(ctx context.Context, req *mcp.CallToolRequest, input WebviewClickInput) (*mcp.CallToolResult, WebviewClickOutput, error) {
	if s.webview == nil {
		return nil, WebviewClickOutput{}, fmt.Errorf("WebView not available (MCP server running standalone)")
	}

	err := s.webview.Click(input.Window, input.Selector)
	if err != nil {
		return nil, WebviewClickOutput{}, fmt.Errorf("failed to click: %w", err)
	}

	return nil, WebviewClickOutput{Success: true}, nil
}

func (s *Service) webviewType(ctx context.Context, req *mcp.CallToolRequest, input WebviewTypeInput) (*mcp.CallToolResult, WebviewTypeOutput, error) {
	if s.webview == nil {
		return nil, WebviewTypeOutput{}, fmt.Errorf("WebView not available (MCP server running standalone)")
	}

	err := s.webview.Type(input.Window, input.Selector, input.Text)
	if err != nil {
		return nil, WebviewTypeOutput{}, fmt.Errorf("failed to type: %w", err)
	}

	return nil, WebviewTypeOutput{Success: true}, nil
}

func (s *Service) webviewQuery(ctx context.Context, req *mcp.CallToolRequest, input WebviewQueryInput) (*mcp.CallToolResult, WebviewQueryOutput, error) {
	if s.webview == nil {
		return nil, WebviewQueryOutput{}, fmt.Errorf("WebView not available (MCP server running standalone)")
	}

	result, err := s.webview.QuerySelector(input.Window, input.Selector)
	if err != nil {
		return nil, WebviewQueryOutput{}, fmt.Errorf("failed to query: %w", err)
	}

	// Parse result as JSON array
	var elements []map[string]any
	if err := json.Unmarshal([]byte(result), &elements); err != nil {
		// Return raw result if not valid JSON
		return nil, WebviewQueryOutput{
			Elements: []map[string]any{{"raw": result}},
			Count:    1,
		}, nil
	}

	return nil, WebviewQueryOutput{
		Elements: elements,
		Count:    len(elements),
	}, nil
}

func (s *Service) webviewNavigate(ctx context.Context, req *mcp.CallToolRequest, input WebviewNavigateInput) (*mcp.CallToolResult, WebviewNavigateOutput, error) {
	if s.webview == nil {
		return nil, WebviewNavigateOutput{}, fmt.Errorf("WebView not available (MCP server running standalone)")
	}

	err := s.webview.Navigate(input.Window, input.URL)
	if err != nil {
		return nil, WebviewNavigateOutput{}, fmt.Errorf("failed to navigate: %w", err)
	}

	return nil, WebviewNavigateOutput{Success: true}, nil
}

func (s *Service) webviewSource(ctx context.Context, req *mcp.CallToolRequest, input WebviewSourceInput) (*mcp.CallToolResult, WebviewSourceOutput, error) {
	if s.webview == nil {
		return nil, WebviewSourceOutput{}, fmt.Errorf("WebView not available (MCP server running standalone)")
	}

	html, err := s.webview.GetPageSource(input.Window)
	if err != nil {
		return nil, WebviewSourceOutput{}, fmt.Errorf("failed to get source: %w", err)
	}

	return nil, WebviewSourceOutput{
		HTML:   html,
		Length: len(html),
	}, nil
}

// Window/Display management types

// WindowListInput is empty.
type WindowListInput struct{}

// WindowListOutput contains the list of windows with positions.
type WindowListOutput struct {
	Windows []WindowInfo `json:"windows"`
}

// WindowInfo contains detailed window information.
type WindowInfo struct {
	Name      string `json:"name"`
	X         int    `json:"x"`
	Y         int    `json:"y"`
	Width     int    `json:"width"`
	Height    int    `json:"height"`
	Maximized bool   `json:"maximized"`
}

// WindowGetInput contains the window name to get.
type WindowGetInput struct {
	// Window name to get info for.
	Name string `json:"name"`
}

// WindowGetOutput contains the window information.
type WindowGetOutput struct {
	Window *WindowInfo `json:"window"`
}

// WindowPositionInput contains parameters for moving a window.
type WindowPositionInput struct {
	// Window name to move.
	Name string `json:"name"`
	// X coordinate (pixels from left edge of screen).
	X int `json:"x"`
	// Y coordinate (pixels from top edge of screen).
	Y int `json:"y"`
}

// WindowPositionOutput contains the result of moving a window.
type WindowPositionOutput struct {
	Success bool   `json:"success"`
	Name    string `json:"name"`
	X       int    `json:"x"`
	Y       int    `json:"y"`
}

// WindowSizeInput contains parameters for resizing a window.
type WindowSizeInput struct {
	// Window name to resize.
	Name string `json:"name"`
	// Width in pixels.
	Width int `json:"width"`
	// Height in pixels.
	Height int `json:"height"`
}

// WindowSizeOutput contains the result of resizing a window.
type WindowSizeOutput struct {
	Success bool   `json:"success"`
	Name    string `json:"name"`
	Width   int    `json:"width"`
	Height  int    `json:"height"`
}

// WindowBoundsInput contains parameters for setting window bounds.
type WindowBoundsInput struct {
	// Window name to modify.
	Name string `json:"name"`
	// X coordinate.
	X int `json:"x"`
	// Y coordinate.
	Y int `json:"y"`
	// Width in pixels.
	Width int `json:"width"`
	// Height in pixels.
	Height int `json:"height"`
}

// WindowBoundsOutput contains the result of setting window bounds.
type WindowBoundsOutput struct {
	Success bool   `json:"success"`
	Name    string `json:"name"`
	X       int    `json:"x"`
	Y       int    `json:"y"`
	Width   int    `json:"width"`
	Height  int    `json:"height"`
}

// WindowNameInput contains just a window name.
type WindowNameInput struct {
	// Window name to operate on.
	Name string `json:"name"`
}

// WindowActionOutput contains the result of a window action.
type WindowActionOutput struct {
	Success bool   `json:"success"`
	Name    string `json:"name"`
	Action  string `json:"action"`
}

// ScreenListInput is empty.
type ScreenListInput struct{}

// ScreenListOutput contains the list of screens.
type ScreenListOutput struct {
	Screens []ScreenInfo `json:"screens"`
}

// ScreenInfo contains screen/monitor information.
type ScreenInfo struct {
	ID      string `json:"id"`
	Name    string `json:"name"`
	X       int    `json:"x"`
	Y       int    `json:"y"`
	Width   int    `json:"width"`
	Height  int    `json:"height"`
	Primary bool   `json:"primary"`
}

// Window/Display handlers

func (s *Service) windowList(ctx context.Context, req *mcp.CallToolRequest, input WindowListInput) (*mcp.CallToolResult, WindowListOutput, error) {
	if s.display == nil {
		return nil, WindowListOutput{}, fmt.Errorf("display service not available (MCP server running standalone)")
	}

	windows := s.display.ListWindowInfos()
	result := make([]WindowInfo, len(windows))
	for i, w := range windows {
		result[i] = WindowInfo{
			Name:      w.Name,
			X:         w.X,
			Y:         w.Y,
			Width:     w.Width,
			Height:    w.Height,
			Maximized: w.Maximized,
		}
	}

	return nil, WindowListOutput{Windows: result}, nil
}

func (s *Service) windowGet(ctx context.Context, req *mcp.CallToolRequest, input WindowGetInput) (*mcp.CallToolResult, WindowGetOutput, error) {
	if s.display == nil {
		return nil, WindowGetOutput{}, fmt.Errorf("display service not available (MCP server running standalone)")
	}

	info, err := s.display.GetWindowInfo(input.Name)
	if err != nil {
		return nil, WindowGetOutput{}, fmt.Errorf("failed to get window info: %w", err)
	}

	return nil, WindowGetOutput{
		Window: &WindowInfo{
			Name:      info.Name,
			X:         info.X,
			Y:         info.Y,
			Width:     info.Width,
			Height:    info.Height,
			Maximized: info.Maximized,
		},
	}, nil
}

func (s *Service) windowPosition(ctx context.Context, req *mcp.CallToolRequest, input WindowPositionInput) (*mcp.CallToolResult, WindowPositionOutput, error) {
	if s.display == nil {
		return nil, WindowPositionOutput{}, fmt.Errorf("display service not available (MCP server running standalone)")
	}

	err := s.display.SetWindowPosition(input.Name, input.X, input.Y)
	if err != nil {
		return nil, WindowPositionOutput{}, fmt.Errorf("failed to move window: %w", err)
	}

	return nil, WindowPositionOutput{
		Success: true,
		Name:    input.Name,
		X:       input.X,
		Y:       input.Y,
	}, nil
}

func (s *Service) windowSize(ctx context.Context, req *mcp.CallToolRequest, input WindowSizeInput) (*mcp.CallToolResult, WindowSizeOutput, error) {
	if s.display == nil {
		return nil, WindowSizeOutput{}, fmt.Errorf("display service not available (MCP server running standalone)")
	}

	err := s.display.SetWindowSize(input.Name, input.Width, input.Height)
	if err != nil {
		return nil, WindowSizeOutput{}, fmt.Errorf("failed to resize window: %w", err)
	}

	return nil, WindowSizeOutput{
		Success: true,
		Name:    input.Name,
		Width:   input.Width,
		Height:  input.Height,
	}, nil
}

func (s *Service) windowBounds(ctx context.Context, req *mcp.CallToolRequest, input WindowBoundsInput) (*mcp.CallToolResult, WindowBoundsOutput, error) {
	if s.display == nil {
		return nil, WindowBoundsOutput{}, fmt.Errorf("display service not available (MCP server running standalone)")
	}

	err := s.display.SetWindowBounds(input.Name, input.X, input.Y, input.Width, input.Height)
	if err != nil {
		return nil, WindowBoundsOutput{}, fmt.Errorf("failed to set window bounds: %w", err)
	}

	return nil, WindowBoundsOutput{
		Success: true,
		Name:    input.Name,
		X:       input.X,
		Y:       input.Y,
		Width:   input.Width,
		Height:  input.Height,
	}, nil
}

func (s *Service) windowMaximize(ctx context.Context, req *mcp.CallToolRequest, input WindowNameInput) (*mcp.CallToolResult, WindowActionOutput, error) {
	if s.display == nil {
		return nil, WindowActionOutput{}, fmt.Errorf("display service not available (MCP server running standalone)")
	}

	err := s.display.MaximizeWindow(input.Name)
	if err != nil {
		return nil, WindowActionOutput{}, fmt.Errorf("failed to maximize window: %w", err)
	}

	return nil, WindowActionOutput{
		Success: true,
		Name:    input.Name,
		Action:  "maximize",
	}, nil
}

func (s *Service) windowMinimize(ctx context.Context, req *mcp.CallToolRequest, input WindowNameInput) (*mcp.CallToolResult, WindowActionOutput, error) {
	if s.display == nil {
		return nil, WindowActionOutput{}, fmt.Errorf("display service not available (MCP server running standalone)")
	}

	err := s.display.MinimizeWindow(input.Name)
	if err != nil {
		return nil, WindowActionOutput{}, fmt.Errorf("failed to minimize window: %w", err)
	}

	return nil, WindowActionOutput{
		Success: true,
		Name:    input.Name,
		Action:  "minimize",
	}, nil
}

func (s *Service) windowRestore(ctx context.Context, req *mcp.CallToolRequest, input WindowNameInput) (*mcp.CallToolResult, WindowActionOutput, error) {
	if s.display == nil {
		return nil, WindowActionOutput{}, fmt.Errorf("display service not available (MCP server running standalone)")
	}

	err := s.display.RestoreWindow(input.Name)
	if err != nil {
		return nil, WindowActionOutput{}, fmt.Errorf("failed to restore window: %w", err)
	}

	return nil, WindowActionOutput{
		Success: true,
		Name:    input.Name,
		Action:  "restore",
	}, nil
}

func (s *Service) windowFocus(ctx context.Context, req *mcp.CallToolRequest, input WindowNameInput) (*mcp.CallToolResult, WindowActionOutput, error) {
	if s.display == nil {
		return nil, WindowActionOutput{}, fmt.Errorf("display service not available (MCP server running standalone)")
	}

	err := s.display.FocusWindow(input.Name)
	if err != nil {
		return nil, WindowActionOutput{}, fmt.Errorf("failed to focus window: %w", err)
	}

	return nil, WindowActionOutput{
		Success: true,
		Name:    input.Name,
		Action:  "focus",
	}, nil
}

func (s *Service) screenList(ctx context.Context, req *mcp.CallToolRequest, input ScreenListInput) (*mcp.CallToolResult, ScreenListOutput, error) {
	if s.display == nil {
		return nil, ScreenListOutput{}, fmt.Errorf("display service not available (MCP server running standalone)")
	}

	screens := s.display.GetScreens()
	result := make([]ScreenInfo, len(screens))
	for i, sc := range screens {
		result[i] = ScreenInfo{
			ID:      sc.ID,
			Name:    sc.Name,
			X:       sc.X,
			Y:       sc.Y,
			Width:   sc.Width,
			Height:  sc.Height,
			Primary: sc.Primary,
		}
	}

	return nil, ScreenListOutput{Screens: result}, nil
}
