package client

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
)

// Client is the client for communicating with the daemon
type Client struct {
	serverURL  string
	httpClient *http.Client
}

// Command represents a command sent from the client to the daemon
type Command struct {
	Action string            `json:"action"`
	Params map[string]string `json:"params"`
}

// Response represents a response from the daemon to the client
type Response struct {
	Success bool        `json:"success"`
	Data    interface{} `json:"data,omitempty"`
	Error   string      `json:"error,omitempty"`
}

// NewClient creates a new client
func NewClient(host string, port int) *Client {
	return &Client{
		serverURL: fmt.Sprintf("http://%s:%d", host, port),
		httpClient: &http.Client{
			Timeout: 60 * time.Second, // 60 second timeout for long operations
		},
	}
}

// CheckStatus checks if the daemon is running
func (c *Client) CheckStatus() (bool, error) {
	resp, err := c.httpClient.Get(c.serverURL + "/status")
	if err != nil {
		return false, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
	}

	var response Response
	err = json.NewDecoder(resp.Body).Decode(&response)
	if err != nil {
		return false, err
	}

	return response.Success, nil
}

// GetVersion gets the daemon version
func (c *Client) GetVersion() (string, error) {
	response, err := c.SendCommand("version", map[string]string{})
	if err != nil {
		return "", err
	}

	if !response.Success {
		return "", fmt.Errorf("failed to get version: %s", response.Error)
	}

	// The version should be in the Data field as a string
	if version, ok := response.Data.(string); ok {
		return version, nil
	}

	return "", fmt.Errorf("unexpected version response format")
}

// TabInfo contains information about a tab
type TabInfo struct {
	ID           string `json:"id"`
	URL          string `json:"url"`
	IsCurrent    bool   `json:"is_current"`
	HistoryIndex int    `json:"history_index"` // Position in tab history (higher = more recent)
}

// ListTabs returns a list of all open tabs
func (c *Client) ListTabs() ([]TabInfo, error) {
	resp, err := http.Get(c.serverURL + "/status")
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
	}

	var response Response
	err = json.NewDecoder(resp.Body).Decode(&response)
	if err != nil {
		return nil, fmt.Errorf("failed to parse response: %w", err)
	}

	if !response.Success {
		return nil, fmt.Errorf("daemon returned error: %s", response.Error)
	}

	// Extract the data
	data, ok := response.Data.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("unexpected response format")
	}

	// Get the tabs
	tabsData, ok := data["tabs"].(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("unexpected tabs format")
	}

	// Get the current tab
	currentTab, _ := data["current_tab"].(string)

	// Get the tab history
	tabHistoryData, ok := data["tab_history"].([]interface{})
	if !ok {
		tabHistoryData = []interface{}{}
	}

	// Create a map of tab history indices
	tabHistoryIndices := make(map[string]int)
	for i, idInterface := range tabHistoryData {
		id, ok := idInterface.(string)
		if ok {
			tabHistoryIndices[id] = i
		}
	}

	// Convert to TabInfo
	tabs := make([]TabInfo, 0, len(tabsData))
	for id, urlInterface := range tabsData {
		url, ok := urlInterface.(string)
		if !ok {
			url = "<unknown>"
		}

		// Get the history index (default to -1 if not in history)
		historyIndex, inHistory := tabHistoryIndices[id]
		if !inHistory {
			historyIndex = -1
		}

		tabs = append(tabs, TabInfo{
			ID:           id,
			URL:          url,
			IsCurrent:    id == currentTab,
			HistoryIndex: historyIndex,
		})
	}

	return tabs, nil
}

// SendCommand sends a command to the daemon
func (c *Client) SendCommand(action string, params map[string]string) (*Response, error) {
	cmd := Command{
		Action: action,
		Params: params,
	}

	jsonData, err := json.Marshal(cmd)
	if err != nil {
		return nil, err
	}

	resp, err := c.httpClient.Post(c.serverURL+"/command", "application/json", bytes.NewBuffer(jsonData))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, body)
	}

	var response Response
	err = json.NewDecoder(resp.Body).Decode(&response)
	if err != nil {
		return nil, err
	}

	return &response, nil
}

// OpenTab opens a new tab
// timeout is in seconds, 0 means no timeout
func (c *Client) OpenTab(timeout int) (string, error) {
	params := map[string]string{}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("open-tab", params)
	if err != nil {
		return "", err
	}

	if !resp.Success {
		return "", fmt.Errorf("failed to open tab: %s", resp.Error)
	}

	tabID, ok := resp.Data.(string)
	if !ok {
		return "", fmt.Errorf("unexpected response data type")
	}

	return tabID, nil
}

// LoadURL loads a URL in a tab
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) LoadURL(tabID, url string, timeout int) error {
	params := map[string]string{
		"url": url,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("load-url", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to load URL: %s", resp.Error)
	}

	return nil
}

// FillFormField fills a form field with a value
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) FillFormField(tabID, selector, value string, timeout int) error {
	params := map[string]string{
		"selector": selector,
		"value":    value,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("fill-form", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to fill form field: %s", resp.Error)
	}

	return nil
}

// UploadFile uploads a file to a file input
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) UploadFile(tabID, selector, filePath string, timeout int) error {
	params := map[string]string{
		"selector": selector,
		"file":     filePath,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("upload-file", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to upload file: %s", resp.Error)
	}

	return nil
}

// SelectElement selects an option in a select dropdown
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) SelectElement(tabID, selector, value string, timeout int) error {
	params := map[string]string{
		"selector": selector,
		"value":    value,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("select-element", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to select element: %s", resp.Error)
	}

	return nil
}

// SubmitForm submits a form
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) SubmitForm(tabID, selector string, timeout int) error {
	params := map[string]string{
		"selector": selector,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("submit-form", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to submit form: %s", resp.Error)
	}

	return nil
}

// GetPageSource gets the source code of a page
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) GetPageSource(tabID string, timeout int) (string, error) {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("get-source", params)
	if err != nil {
		return "", err
	}

	if !resp.Success {
		return "", fmt.Errorf("failed to get page source: %s", resp.Error)
	}

	source, ok := resp.Data.(string)
	if !ok {
		return "", fmt.Errorf("unexpected response data type")
	}

	return source, nil
}

// GetElementHTML gets the HTML of an element
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) GetElementHTML(tabID, selector string, timeout int) (string, error) {
	params := map[string]string{
		"selector": selector,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("get-element", params)
	if err != nil {
		return "", err
	}

	if !resp.Success {
		return "", fmt.Errorf("failed to get element HTML: %s", resp.Error)
	}

	html, ok := resp.Data.(string)
	if !ok {
		return "", fmt.Errorf("unexpected response data type")
	}

	return html, nil
}

// CloseTab closes a tab
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) CloseTab(tabID string, timeout int) error {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("close-tab", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to close tab: %s", resp.Error)
	}

	return nil
}

// WaitNavigation waits for a navigation event
// If tabID is empty, the current tab will be used
func (c *Client) WaitNavigation(tabID string, timeout int) error {
	params := map[string]string{
		"timeout": fmt.Sprintf("%d", timeout),
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	resp, err := c.SendCommand("wait-navigation", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to wait for navigation: %s", resp.Error)
	}

	return nil
}

// EvalJS executes JavaScript code in a tab and returns the result
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) EvalJS(tabID, jsCode string, timeout int) (string, error) {
	params := map[string]string{
		"code": jsCode,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("eval-js", params)
	if err != nil {
		return "", err
	}

	if !resp.Success {
		return "", fmt.Errorf("failed to execute JavaScript: %s", resp.Error)
	}

	// Convert result to string if it exists
	if resp.Data != nil {
		if result, ok := resp.Data.(string); ok {
			return result, nil
		}
		// If it's not a string, convert it to string representation
		return fmt.Sprintf("%v", resp.Data), nil
	}

	return "", nil
}

// TakeScreenshot takes a screenshot of a tab and saves it to a file
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) TakeScreenshot(tabID, outputPath string, fullPage bool, timeout int) error {
	params := map[string]string{
		"output":    outputPath,
		"full-page": strconv.FormatBool(fullPage),
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("screenshot", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to take screenshot: %s", resp.Error)
	}

	return nil
}

// SwitchToIframe switches the context to an iframe for subsequent commands
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) SwitchToIframe(tabID, selector string, timeout int) error {
	params := map[string]string{
		"selector": selector,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("switch-iframe", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to switch to iframe: %s", resp.Error)
	}

	return nil
}

// SwitchToMain switches the context back to the main page
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) SwitchToMain(tabID string, timeout int) error {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("switch-main", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to switch to main context: %s", resp.Error)
	}

	return nil
}

// ClickElement clicks on an element
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) ClickElement(tabID, selector string, timeout int) error {
	params := map[string]string{
		"selector": selector,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("click-element", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to click element: %s", resp.Error)
	}

	return nil
}

// UploadFileToContainer uploads a file from the client to the container
// localPath: path to the file on the client machine
// containerPath: optional path where to store the file in the container (defaults to /tmp/filename)
func (c *Client) UploadFileToContainer(localPath, containerPath string) (string, error) {
	// Open the local file
	file, err := os.Open(localPath)
	if err != nil {
		return "", fmt.Errorf("failed to open local file: %w", err)
	}
	defer file.Close()

	// Get file info
	fileInfo, err := file.Stat()
	if err != nil {
		return "", fmt.Errorf("failed to get file info: %w", err)
	}

	// Create multipart form
	var body bytes.Buffer
	writer := multipart.NewWriter(&body)

	// Add the file field
	fileWriter, err := writer.CreateFormFile("file", fileInfo.Name())
	if err != nil {
		return "", fmt.Errorf("failed to create form file: %w", err)
	}

	_, err = io.Copy(fileWriter, file)
	if err != nil {
		return "", fmt.Errorf("failed to copy file data: %w", err)
	}

	// Add the path field if specified
	if containerPath != "" {
		err = writer.WriteField("path", containerPath)
		if err != nil {
			return "", fmt.Errorf("failed to write path field: %w", err)
		}
	}

	err = writer.Close()
	if err != nil {
		return "", fmt.Errorf("failed to close multipart writer: %w", err)
	}

	// Send the request
	req, err := http.NewRequest("POST", c.serverURL+"/upload", &body)
	if err != nil {
		return "", fmt.Errorf("failed to create request: %w", err)
	}
	req.Header.Set("Content-Type", writer.FormDataContentType())

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return "", fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return "", fmt.Errorf("upload failed with status %d: %s", resp.StatusCode, body)
	}

	// Parse the response
	var response Response
	err = json.NewDecoder(resp.Body).Decode(&response)
	if err != nil {
		return "", fmt.Errorf("failed to decode response: %w", err)
	}

	if !response.Success {
		return "", fmt.Errorf("upload failed: %s", response.Error)
	}

	// Extract the target path from response
	data, ok := response.Data.(map[string]interface{})
	if !ok {
		return "", fmt.Errorf("invalid response data format")
	}

	targetPath, ok := data["target_path"].(string)
	if !ok {
		return "", fmt.Errorf("target_path not found in response")
	}

	return targetPath, nil
}

// DownloadFileFromContainer downloads a file from the container to the client
// containerPath: path to the file in the container
// localPath: path where to save the file on the client machine
func (c *Client) DownloadFileFromContainer(containerPath, localPath string) error {
	// Create the request
	url := fmt.Sprintf("%s/download?path=%s", c.serverURL, containerPath)
	resp, err := http.Get(url)
	if err != nil {
		return fmt.Errorf("failed to send download request: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("download failed with status %d: %s", resp.StatusCode, body)
	}

	// Create the local file
	localFile, err := os.Create(localPath)
	if err != nil {
		return fmt.Errorf("failed to create local file: %w", err)
	}
	defer localFile.Close()

	// Copy the response body to the local file
	_, err = io.Copy(localFile, resp.Body)
	if err != nil {
		return fmt.Errorf("failed to save file: %w", err)
	}

	return nil
}

// GetConsoleLogs retrieves console logs from a tab
// If tabID is empty, the current tab will be used
// If clear is true, the logs will be cleared after retrieval
func (c *Client) GetConsoleLogs(tabID string, clear bool) ([]map[string]interface{}, error) {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add clear flag if specified
	if clear {
		params["clear"] = "true"
	}

	resp, err := c.SendCommand("console-logs", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to get console logs: %s", resp.Error)
	}

	// Convert response data to slice of console logs
	logs, ok := resp.Data.([]interface{})
	if !ok {
		return nil, fmt.Errorf("unexpected response data type")
	}

	result := make([]map[string]interface{}, len(logs))
	for i, log := range logs {
		logMap, ok := log.(map[string]interface{})
		if !ok {
			return nil, fmt.Errorf("unexpected log entry format")
		}
		result[i] = logMap
	}

	return result, nil
}

// ExecuteConsoleCommand executes a command in the browser console
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) ExecuteConsoleCommand(tabID, command string, timeout int) (string, error) {
	params := map[string]string{
		"command": command,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("console-command", params)
	if err != nil {
		return "", err
	}

	if !resp.Success {
		return "", fmt.Errorf("failed to execute console command: %s", resp.Error)
	}

	result, ok := resp.Data.(string)
	if !ok {
		return "", fmt.Errorf("unexpected response data type")
	}

	return result, nil
}

// ElementCheckResult represents the result of an element check
type ElementCheckResult struct {
	Exists   bool `json:"exists"`
	Visible  bool `json:"visible,omitempty"`
	Enabled  bool `json:"enabled,omitempty"`
	Focused  bool `json:"focused,omitempty"`
	Selected bool `json:"selected,omitempty"`
	Count    int  `json:"count,omitempty"`
}

// MultipleExtractionResult represents the result of extracting from multiple selectors
type MultipleExtractionResult struct {
	Results map[string]interface{} `json:"results"`
	Errors  map[string]string      `json:"errors,omitempty"`
}

// LinkInfo represents information about a link
type LinkInfo struct {
	Href   string `json:"href"`
	Text   string `json:"text"`
	Title  string `json:"title,omitempty"`
	Target string `json:"target,omitempty"`
}

// LinksExtractionResult represents the result of extracting links
type LinksExtractionResult struct {
	Links []LinkInfo `json:"links"`
	Count int        `json:"count"`
}

// TableExtractionResult represents the result of extracting table data
type TableExtractionResult struct {
	Headers []string            `json:"headers,omitempty"`
	Rows    [][]string          `json:"rows"`
	Data    []map[string]string `json:"data,omitempty"` // Only if headers are included
	Count   int                 `json:"count"`
}

// TextExtractionResult represents the result of extracting text
type TextExtractionResult struct {
	Text    string   `json:"text"`
	Matches []string `json:"matches,omitempty"` // If pattern was used
	Count   int      `json:"count"`             // Number of elements matched
}

// FormField represents a form field with its properties
type FormField struct {
	Name        string            `json:"name"`
	Type        string            `json:"type"`
	Value       string            `json:"value"`
	Placeholder string            `json:"placeholder,omitempty"`
	Required    bool              `json:"required"`
	Disabled    bool              `json:"disabled"`
	ReadOnly    bool              `json:"readonly"`
	Selector    string            `json:"selector"`
	Label       string            `json:"label,omitempty"`
	Options     []FormFieldOption `json:"options,omitempty"` // For select/radio/checkbox
}

// FormFieldOption represents an option in a select, radio, or checkbox group
type FormFieldOption struct {
	Value    string `json:"value"`
	Text     string `json:"text"`
	Selected bool   `json:"selected"`
}

// FormAnalysisResult represents the result of analyzing a form
type FormAnalysisResult struct {
	Action     string      `json:"action,omitempty"`
	Method     string      `json:"method,omitempty"`
	Fields     []FormField `json:"fields"`
	FieldCount int         `json:"field_count"`
	CanSubmit  bool        `json:"can_submit"`
	SubmitText string      `json:"submit_text,omitempty"`
}

// InteractionItem represents a single interaction to perform
type InteractionItem struct {
	Selector string `json:"selector"`
	Action   string `json:"action"` // click, fill, select, check, uncheck
	Value    string `json:"value,omitempty"`
}

// InteractionResult represents the result of a single interaction
type InteractionResult struct {
	Selector string `json:"selector"`
	Action   string `json:"action"`
	Success  bool   `json:"success"`
	Error    string `json:"error,omitempty"`
}

// MultipleInteractionResult represents the result of multiple interactions
type MultipleInteractionResult struct {
	Results      []InteractionResult `json:"results"`
	SuccessCount int                 `json:"success_count"`
	ErrorCount   int                 `json:"error_count"`
	TotalCount   int                 `json:"total_count"`
}

// FormBulkFillResult represents the result of bulk form filling
type FormBulkFillResult struct {
	FilledFields []InteractionResult `json:"filled_fields"`
	SuccessCount int                 `json:"success_count"`
	ErrorCount   int                 `json:"error_count"`
	TotalCount   int                 `json:"total_count"`
}

// PageInfo represents page metadata and state information
type PageInfo struct {
	Title         string `json:"title"`
	URL           string `json:"url"`
	LoadingState  string `json:"loading_state"`
	ReadyState    string `json:"ready_state"`
	Referrer      string `json:"referrer"`
	Domain        string `json:"domain"`
	Protocol      string `json:"protocol"`
	Charset       string `json:"charset"`
	ContentType   string `json:"content_type"`
	LastModified  string `json:"last_modified"`
	CookieEnabled bool   `json:"cookie_enabled"`
	OnlineStatus  bool   `json:"online_status"`
}

// ViewportInfo represents viewport and scroll information
type ViewportInfo struct {
	Width            int     `json:"width"`
	Height           int     `json:"height"`
	ScrollX          int     `json:"scroll_x"`
	ScrollY          int     `json:"scroll_y"`
	ScrollWidth      int     `json:"scroll_width"`
	ScrollHeight     int     `json:"scroll_height"`
	ClientWidth      int     `json:"client_width"`
	ClientHeight     int     `json:"client_height"`
	DevicePixelRatio float64 `json:"device_pixel_ratio"`
	Orientation      string  `json:"orientation"`
}

// PerformanceMetrics represents page performance data
type PerformanceMetrics struct {
	NavigationStart      int64 `json:"navigation_start"`
	LoadEventEnd         int64 `json:"load_event_end"`
	DOMContentLoaded     int64 `json:"dom_content_loaded"`
	FirstPaint           int64 `json:"first_paint"`
	FirstContentfulPaint int64 `json:"first_contentful_paint"`
	LoadTime             int64 `json:"load_time"`
	DOMLoadTime          int64 `json:"dom_load_time"`
	ResourceCount        int   `json:"resource_count"`
	JSHeapSizeLimit      int64 `json:"js_heap_size_limit"`
	JSHeapSizeTotal      int64 `json:"js_heap_size_total"`
	JSHeapSizeUsed       int64 `json:"js_heap_size_used"`
}

// ContentCheck represents content verification results
type ContentCheck struct {
	Type           string   `json:"type"`
	ImagesLoaded   int      `json:"images_loaded,omitempty"`
	ImagesTotal    int      `json:"images_total,omitempty"`
	ScriptsLoaded  int      `json:"scripts_loaded,omitempty"`
	ScriptsTotal   int      `json:"scripts_total,omitempty"`
	StylesLoaded   int      `json:"styles_loaded,omitempty"`
	StylesTotal    int      `json:"styles_total,omitempty"`
	FormsPresent   int      `json:"forms_present,omitempty"`
	LinksPresent   int      `json:"links_present,omitempty"`
	IframesPresent int      `json:"iframes_present,omitempty"`
	HasErrors      bool     `json:"has_errors,omitempty"`
	ErrorCount     int      `json:"error_count,omitempty"`
	ErrorMessages  []string `json:"error_messages,omitempty"`
}

// ScreenshotMetadata represents metadata for enhanced screenshots
type ScreenshotMetadata struct {
	Timestamp    string `json:"timestamp"`
	URL          string `json:"url"`
	Title        string `json:"title"`
	ViewportSize struct {
		Width  int `json:"width"`
		Height int `json:"height"`
	} `json:"viewport_size"`
	FullPage   bool   `json:"full_page"`
	FilePath   string `json:"file_path"`
	FileSize   int64  `json:"file_size"`
	Resolution struct {
		Width  int `json:"width"`
		Height int `json:"height"`
	} `json:"resolution"`
}

// FileOperation represents a single file operation
type FileOperation struct {
	LocalPath     string `json:"local_path"`
	ContainerPath string `json:"container_path"`
	Operation     string `json:"operation"` // "upload" or "download"
}

// BulkFileResult represents the result of bulk file operations
type BulkFileResult struct {
	Successful []FileOperationResult `json:"successful"`
	Failed     []FileOperationError  `json:"failed"`
	Summary    struct {
		Total      int `json:"total"`
		Successful int `json:"successful"`
		Failed     int `json:"failed"`
	} `json:"summary"`
}

// FileOperationResult represents a successful file operation
type FileOperationResult struct {
	LocalPath     string `json:"local_path"`
	ContainerPath string `json:"container_path"`
	Operation     string `json:"operation"`
	Size          int64  `json:"size"`
}

// FileOperationError represents a failed file operation
type FileOperationError struct {
	LocalPath     string `json:"local_path"`
	ContainerPath string `json:"container_path"`
	Operation     string `json:"operation"`
	Error         string `json:"error"`
}

// FileManagementResult represents the result of file management operations
type FileManagementResult struct {
	Operation string                 `json:"operation"`
	Files     []FileInfo             `json:"files,omitempty"`
	Cleaned   []string               `json:"cleaned,omitempty"`
	Summary   map[string]interface{} `json:"summary"`
}

// FileInfo represents information about a file
type FileInfo struct {
	Path        string    `json:"path"`
	Size        int64     `json:"size"`
	ModTime     time.Time `json:"mod_time"`
	IsDir       bool      `json:"is_dir"`
	Permissions string    `json:"permissions"`
}

// CheckElement checks various states of an element
// checkType can be: "exists", "visible", "enabled", "focused", "selected", "all"
// timeout is in seconds, 0 means no timeout
func (c *Client) CheckElement(tabID, selector, checkType string, timeout int) (*ElementCheckResult, error) {
	params := map[string]string{
		"selector": selector,
		"type":     checkType,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("check-element", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to check element: %s", resp.Error)
	}

	// Parse the response data
	data, ok := resp.Data.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("unexpected response data type")
	}

	result := &ElementCheckResult{}

	if exists, ok := data["exists"].(bool); ok {
		result.Exists = exists
	}
	if visible, ok := data["visible"].(bool); ok {
		result.Visible = visible
	}
	if enabled, ok := data["enabled"].(bool); ok {
		result.Enabled = enabled
	}
	if focused, ok := data["focused"].(bool); ok {
		result.Focused = focused
	}
	if selected, ok := data["selected"].(bool); ok {
		result.Selected = selected
	}
	if count, ok := data["count"].(float64); ok {
		result.Count = int(count)
	}

	return result, nil
}

// GetElementAttributes gets attributes, properties, and computed styles of an element
// attributes can be a comma-separated list of attribute names or "all" for common attributes
// Use prefixes: "style_" for computed styles, "prop_" for JavaScript properties
// timeout is in seconds, 0 means no timeout
func (c *Client) GetElementAttributes(tabID, selector, attributes string, timeout int) (map[string]interface{}, error) {
	params := map[string]string{
		"selector":   selector,
		"attributes": attributes,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("get-element-attributes", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to get element attributes: %s", resp.Error)
	}

	// Parse the response data
	result, ok := resp.Data.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("unexpected response data type")
	}

	return result, nil
}

// CountElements counts the number of elements matching a selector
// timeout is in seconds, 0 means no timeout
func (c *Client) CountElements(tabID, selector string, timeout int) (int, error) {
	params := map[string]string{
		"selector": selector,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("count-elements", params)
	if err != nil {
		return 0, err
	}

	if !resp.Success {
		return 0, fmt.Errorf("failed to count elements: %s", resp.Error)
	}

	// Parse the response data
	count, ok := resp.Data.(float64)
	if !ok {
		return 0, fmt.Errorf("unexpected response data type")
	}

	return int(count), nil
}

// ExtractMultiple extracts data from multiple selectors in a single call
// selectors should be a map[string]string where keys are labels and values are CSS selectors
// timeout is in seconds, 0 means no timeout
func (c *Client) ExtractMultiple(tabID string, selectors map[string]string, timeout int) (*MultipleExtractionResult, error) {
	// Convert selectors map to JSON
	selectorsJSON, err := json.Marshal(selectors)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal selectors: %w", err)
	}

	params := map[string]string{
		"selectors": string(selectorsJSON),
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("extract-multiple", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to extract multiple: %s", resp.Error)
	}

	// Parse the response data
	data, ok := resp.Data.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("unexpected response data type")
	}

	result := &MultipleExtractionResult{
		Results: make(map[string]interface{}),
		Errors:  make(map[string]string),
	}

	if results, ok := data["results"].(map[string]interface{}); ok {
		result.Results = results
	}

	if errors, ok := data["errors"].(map[string]interface{}); ok {
		for key, value := range errors {
			if errorStr, ok := value.(string); ok {
				result.Errors[key] = errorStr
			}
		}
	}

	return result, nil
}

// ExtractLinks extracts all links from the page with optional filtering
// containerSelector: optional CSS selector to limit search to a container (empty for entire page)
// hrefPattern: optional regex pattern to filter links by href (empty for no filtering)
// textPattern: optional regex pattern to filter links by text content (empty for no filtering)
// timeout is in seconds, 0 means no timeout
func (c *Client) ExtractLinks(tabID, containerSelector, hrefPattern, textPattern string, timeout int) (*LinksExtractionResult, error) {
	params := map[string]string{}

	// Add optional parameters
	if containerSelector != "" {
		params["selector"] = containerSelector
	}
	if hrefPattern != "" {
		params["href-pattern"] = hrefPattern
	}
	if textPattern != "" {
		params["text-pattern"] = textPattern
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("extract-links", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to extract links: %s", resp.Error)
	}

	// Parse the response data
	data, ok := resp.Data.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("unexpected response data type")
	}

	result := &LinksExtractionResult{
		Links: make([]LinkInfo, 0),
		Count: 0,
	}

	if count, ok := data["count"].(float64); ok {
		result.Count = int(count)
	}

	if linksData, ok := data["links"].([]interface{}); ok {
		for _, linkInterface := range linksData {
			if linkMap, ok := linkInterface.(map[string]interface{}); ok {
				linkInfo := LinkInfo{}

				if href, ok := linkMap["href"].(string); ok {
					linkInfo.Href = href
				}
				if text, ok := linkMap["text"].(string); ok {
					linkInfo.Text = text
				}
				if title, ok := linkMap["title"].(string); ok {
					linkInfo.Title = title
				}
				if target, ok := linkMap["target"].(string); ok {
					linkInfo.Target = target
				}

				result.Links = append(result.Links, linkInfo)
			}
		}
	}

	return result, nil
}

// ExtractTable extracts table data as structured JSON
// selector: CSS selector for the table element
// includeHeaders: whether to extract and use headers for structured data
// timeout is in seconds, 0 means no timeout
func (c *Client) ExtractTable(tabID, selector string, includeHeaders bool, timeout int) (*TableExtractionResult, error) {
	params := map[string]string{
		"selector":        selector,
		"include-headers": strconv.FormatBool(includeHeaders),
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("extract-table", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to extract table: %s", resp.Error)
	}

	// Parse the response data
	data, ok := resp.Data.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("unexpected response data type")
	}

	result := &TableExtractionResult{
		Rows: make([][]string, 0),
	}

	if count, ok := data["count"].(float64); ok {
		result.Count = int(count)
	}

	// Parse headers if present
	if headersData, ok := data["headers"].([]interface{}); ok {
		headers := make([]string, 0)
		for _, headerInterface := range headersData {
			if header, ok := headerInterface.(string); ok {
				headers = append(headers, header)
			}
		}
		result.Headers = headers
	}

	// Parse rows
	if rowsData, ok := data["rows"].([]interface{}); ok {
		for _, rowInterface := range rowsData {
			if rowArray, ok := rowInterface.([]interface{}); ok {
				row := make([]string, 0)
				for _, cellInterface := range rowArray {
					if cell, ok := cellInterface.(string); ok {
						row = append(row, cell)
					}
				}
				result.Rows = append(result.Rows, row)
			}
		}
	}

	// Parse structured data if present
	if dataArray, ok := data["data"].([]interface{}); ok {
		structuredData := make([]map[string]string, 0)
		for _, dataInterface := range dataArray {
			if dataMap, ok := dataInterface.(map[string]interface{}); ok {
				rowMap := make(map[string]string)
				for key, value := range dataMap {
					if valueStr, ok := value.(string); ok {
						rowMap[key] = valueStr
					}
				}
				structuredData = append(structuredData, rowMap)
			}
		}
		result.Data = structuredData
	}

	return result, nil
}

// ExtractText extracts text content with optional pattern matching
// selector: CSS selector for elements to extract text from
// pattern: optional regex pattern to match within the extracted text (empty for no pattern matching)
// extractType: type of text extraction - "text", "innerText", "textContent" (default: "textContent")
// timeout is in seconds, 0 means no timeout
func (c *Client) ExtractText(tabID, selector, pattern, extractType string, timeout int) (*TextExtractionResult, error) {
	params := map[string]string{
		"selector": selector,
	}

	// Add optional parameters
	if pattern != "" {
		params["pattern"] = pattern
	}
	if extractType != "" {
		params["type"] = extractType
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("extract-text", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to extract text: %s", resp.Error)
	}

	// Parse the response data
	data, ok := resp.Data.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("unexpected response data type")
	}

	result := &TextExtractionResult{}

	if text, ok := data["text"].(string); ok {
		result.Text = text
	}

	if count, ok := data["count"].(float64); ok {
		result.Count = int(count)
	}

	// Parse matches if present
	if matchesData, ok := data["matches"].([]interface{}); ok {
		matches := make([]string, 0)
		for _, matchInterface := range matchesData {
			if match, ok := matchInterface.(string); ok {
				matches = append(matches, match)
			}
		}
		result.Matches = matches
	}

	return result, nil
}

// AnalyzeForm analyzes a form and returns detailed information about its fields
// selector: CSS selector for the form element
// timeout is in seconds, 0 means no timeout
func (c *Client) AnalyzeForm(tabID, selector string, timeout int) (*FormAnalysisResult, error) {
	params := map[string]string{
		"selector": selector,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("analyze-form", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to analyze form: %s", resp.Error)
	}

	// Parse the response data
	data, ok := resp.Data.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("unexpected response data type")
	}

	result := &FormAnalysisResult{
		Fields: make([]FormField, 0),
	}

	if action, ok := data["action"].(string); ok {
		result.Action = action
	}
	if method, ok := data["method"].(string); ok {
		result.Method = method
	}
	if fieldCount, ok := data["field_count"].(float64); ok {
		result.FieldCount = int(fieldCount)
	}
	if canSubmit, ok := data["can_submit"].(bool); ok {
		result.CanSubmit = canSubmit
	}
	if submitText, ok := data["submit_text"].(string); ok {
		result.SubmitText = submitText
	}

	// Parse fields
	if fieldsData, ok := data["fields"].([]interface{}); ok {
		for _, fieldInterface := range fieldsData {
			if fieldMap, ok := fieldInterface.(map[string]interface{}); ok {
				field := FormField{}

				if name, ok := fieldMap["name"].(string); ok {
					field.Name = name
				}
				if fieldType, ok := fieldMap["type"].(string); ok {
					field.Type = fieldType
				}
				if value, ok := fieldMap["value"].(string); ok {
					field.Value = value
				}
				if placeholder, ok := fieldMap["placeholder"].(string); ok {
					field.Placeholder = placeholder
				}
				if required, ok := fieldMap["required"].(bool); ok {
					field.Required = required
				}
				if disabled, ok := fieldMap["disabled"].(bool); ok {
					field.Disabled = disabled
				}
				if readonly, ok := fieldMap["readonly"].(bool); ok {
					field.ReadOnly = readonly
				}
				if selector, ok := fieldMap["selector"].(string); ok {
					field.Selector = selector
				}
				if label, ok := fieldMap["label"].(string); ok {
					field.Label = label
				}

				// Parse options if present
				if optionsData, ok := fieldMap["options"].([]interface{}); ok {
					options := make([]FormFieldOption, 0)
					for _, optionInterface := range optionsData {
						if optionMap, ok := optionInterface.(map[string]interface{}); ok {
							option := FormFieldOption{}
							if value, ok := optionMap["value"].(string); ok {
								option.Value = value
							}
							if text, ok := optionMap["text"].(string); ok {
								option.Text = text
							}
							if selected, ok := optionMap["selected"].(bool); ok {
								option.Selected = selected
							}
							options = append(options, option)
						}
					}
					field.Options = options
				}

				result.Fields = append(result.Fields, field)
			}
		}
	}

	return result, nil
}

// InteractMultiple performs multiple interactions in sequence
// interactions: slice of InteractionItem specifying what actions to perform
// timeout is in seconds, 0 means no timeout
func (c *Client) InteractMultiple(tabID string, interactions []InteractionItem, timeout int) (*MultipleInteractionResult, error) {
	// Convert interactions to JSON
	interactionsJSON, err := json.Marshal(interactions)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal interactions: %w", err)
	}

	params := map[string]string{
		"interactions": string(interactionsJSON),
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("interact-multiple", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to perform multiple interactions: %s", resp.Error)
	}

	// Parse the response data
	data, ok := resp.Data.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("unexpected response data type")
	}

	result := &MultipleInteractionResult{
		Results: make([]InteractionResult, 0),
	}

	if successCount, ok := data["success_count"].(float64); ok {
		result.SuccessCount = int(successCount)
	}
	if errorCount, ok := data["error_count"].(float64); ok {
		result.ErrorCount = int(errorCount)
	}
	if totalCount, ok := data["total_count"].(float64); ok {
		result.TotalCount = int(totalCount)
	}

	// Parse results
	if resultsData, ok := data["results"].([]interface{}); ok {
		for _, resultInterface := range resultsData {
			if resultMap, ok := resultInterface.(map[string]interface{}); ok {
				interactionResult := InteractionResult{}

				if selector, ok := resultMap["selector"].(string); ok {
					interactionResult.Selector = selector
				}
				if action, ok := resultMap["action"].(string); ok {
					interactionResult.Action = action
				}
				if success, ok := resultMap["success"].(bool); ok {
					interactionResult.Success = success
				}
				if errorMsg, ok := resultMap["error"].(string); ok {
					interactionResult.Error = errorMsg
				}

				result.Results = append(result.Results, interactionResult)
			}
		}
	}

	return result, nil
}

// FillFormBulk fills multiple form fields in a single operation
// formSelector: CSS selector for the form element (optional, can be empty to search entire page)
// fields: map of field names/selectors to values
// timeout is in seconds, 0 means no timeout
func (c *Client) FillFormBulk(tabID, formSelector string, fields map[string]string, timeout int) (*FormBulkFillResult, error) {
	// Convert fields to JSON
	fieldsJSON, err := json.Marshal(fields)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal fields: %w", err)
	}

	params := map[string]string{
		"fields": string(fieldsJSON),
	}

	// Add form selector if provided
	if formSelector != "" {
		params["form-selector"] = formSelector
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("fill-form-bulk", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to fill form bulk: %s", resp.Error)
	}

	// Parse the response data
	data, ok := resp.Data.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("unexpected response data type")
	}

	result := &FormBulkFillResult{
		FilledFields: make([]InteractionResult, 0),
	}

	if successCount, ok := data["success_count"].(float64); ok {
		result.SuccessCount = int(successCount)
	}
	if errorCount, ok := data["error_count"].(float64); ok {
		result.ErrorCount = int(errorCount)
	}
	if totalCount, ok := data["total_count"].(float64); ok {
		result.TotalCount = int(totalCount)
	}

	// Parse filled fields
	if fieldsData, ok := data["filled_fields"].([]interface{}); ok {
		for _, fieldInterface := range fieldsData {
			if fieldMap, ok := fieldInterface.(map[string]interface{}); ok {
				fieldResult := InteractionResult{}

				if selector, ok := fieldMap["selector"].(string); ok {
					fieldResult.Selector = selector
				}
				if action, ok := fieldMap["action"].(string); ok {
					fieldResult.Action = action
				}
				if success, ok := fieldMap["success"].(bool); ok {
					fieldResult.Success = success
				}
				if errorMsg, ok := fieldMap["error"].(string); ok {
					fieldResult.Error = errorMsg
				}

				result.FilledFields = append(result.FilledFields, fieldResult)
			}
		}
	}

	return result, nil
}

// GetPageInfo retrieves comprehensive page metadata and state information
func (c *Client) GetPageInfo(tabID string, timeout int) (*PageInfo, error) {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("get-page-info", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to get page info: %s", resp.Error)
	}

	// Parse the response data
	data, ok := resp.Data.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("unexpected response data type")
	}

	result := &PageInfo{}

	if title, ok := data["title"].(string); ok {
		result.Title = title
	}
	if url, ok := data["url"].(string); ok {
		result.URL = url
	}
	if loadingState, ok := data["loading_state"].(string); ok {
		result.LoadingState = loadingState
	}
	if readyState, ok := data["ready_state"].(string); ok {
		result.ReadyState = readyState
	}
	if referrer, ok := data["referrer"].(string); ok {
		result.Referrer = referrer
	}
	if domain, ok := data["domain"].(string); ok {
		result.Domain = domain
	}
	if protocol, ok := data["protocol"].(string); ok {
		result.Protocol = protocol
	}
	if charset, ok := data["charset"].(string); ok {
		result.Charset = charset
	}
	if contentType, ok := data["content_type"].(string); ok {
		result.ContentType = contentType
	}
	if lastModified, ok := data["last_modified"].(string); ok {
		result.LastModified = lastModified
	}
	if cookieEnabled, ok := data["cookie_enabled"].(bool); ok {
		result.CookieEnabled = cookieEnabled
	}
	if onlineStatus, ok := data["online_status"].(bool); ok {
		result.OnlineStatus = onlineStatus
	}

	return result, nil
}

// GetViewportInfo retrieves viewport and scroll information
func (c *Client) GetViewportInfo(tabID string, timeout int) (*ViewportInfo, error) {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("get-viewport-info", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to get viewport info: %s", resp.Error)
	}

	// Parse the response data
	data, ok := resp.Data.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("unexpected response data type")
	}

	result := &ViewportInfo{}

	if width, ok := data["width"].(float64); ok {
		result.Width = int(width)
	}
	if height, ok := data["height"].(float64); ok {
		result.Height = int(height)
	}
	if scrollX, ok := data["scroll_x"].(float64); ok {
		result.ScrollX = int(scrollX)
	}
	if scrollY, ok := data["scroll_y"].(float64); ok {
		result.ScrollY = int(scrollY)
	}
	if scrollWidth, ok := data["scroll_width"].(float64); ok {
		result.ScrollWidth = int(scrollWidth)
	}
	if scrollHeight, ok := data["scroll_height"].(float64); ok {
		result.ScrollHeight = int(scrollHeight)
	}
	if clientWidth, ok := data["client_width"].(float64); ok {
		result.ClientWidth = int(clientWidth)
	}
	if clientHeight, ok := data["client_height"].(float64); ok {
		result.ClientHeight = int(clientHeight)
	}
	if devicePixelRatio, ok := data["device_pixel_ratio"].(float64); ok {
		result.DevicePixelRatio = devicePixelRatio
	}
	if orientation, ok := data["orientation"].(string); ok {
		result.Orientation = orientation
	}

	return result, nil
}

// GetPerformance retrieves page performance metrics
func (c *Client) GetPerformance(tabID string, timeout int) (*PerformanceMetrics, error) {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("get-performance", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to get performance metrics: %s", resp.Error)
	}

	// Parse the response data
	data, ok := resp.Data.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("unexpected response data type")
	}

	result := &PerformanceMetrics{}

	if navigationStart, ok := data["navigation_start"].(float64); ok {
		result.NavigationStart = int64(navigationStart)
	}
	if loadEventEnd, ok := data["load_event_end"].(float64); ok {
		result.LoadEventEnd = int64(loadEventEnd)
	}
	if domContentLoaded, ok := data["dom_content_loaded"].(float64); ok {
		result.DOMContentLoaded = int64(domContentLoaded)
	}
	if firstPaint, ok := data["first_paint"].(float64); ok {
		result.FirstPaint = int64(firstPaint)
	}
	if firstContentfulPaint, ok := data["first_contentful_paint"].(float64); ok {
		result.FirstContentfulPaint = int64(firstContentfulPaint)
	}
	if loadTime, ok := data["load_time"].(float64); ok {
		result.LoadTime = int64(loadTime)
	}
	if domLoadTime, ok := data["dom_load_time"].(float64); ok {
		result.DOMLoadTime = int64(domLoadTime)
	}
	if resourceCount, ok := data["resource_count"].(float64); ok {
		result.ResourceCount = int(resourceCount)
	}
	if jsHeapSizeLimit, ok := data["js_heap_size_limit"].(float64); ok {
		result.JSHeapSizeLimit = int64(jsHeapSizeLimit)
	}
	if jsHeapSizeTotal, ok := data["js_heap_size_total"].(float64); ok {
		result.JSHeapSizeTotal = int64(jsHeapSizeTotal)
	}
	if jsHeapSizeUsed, ok := data["js_heap_size_used"].(float64); ok {
		result.JSHeapSizeUsed = int64(jsHeapSizeUsed)
	}

	return result, nil
}

// CheckContent verifies specific content types and loading states
// contentType can be: "images", "scripts", "styles", "forms", "links", "iframes", "errors"
func (c *Client) CheckContent(tabID string, contentType string, timeout int) (*ContentCheck, error) {
	params := map[string]string{
		"type": contentType,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("check-content", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to check content: %s", resp.Error)
	}

	// Parse the response data
	data, ok := resp.Data.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("unexpected response data type")
	}

	result := &ContentCheck{}

	if contentTypeResult, ok := data["type"].(string); ok {
		result.Type = contentTypeResult
	}
	if imagesLoaded, ok := data["images_loaded"].(float64); ok {
		result.ImagesLoaded = int(imagesLoaded)
	}
	if imagesTotal, ok := data["images_total"].(float64); ok {
		result.ImagesTotal = int(imagesTotal)
	}
	if scriptsLoaded, ok := data["scripts_loaded"].(float64); ok {
		result.ScriptsLoaded = int(scriptsLoaded)
	}
	if scriptsTotal, ok := data["scripts_total"].(float64); ok {
		result.ScriptsTotal = int(scriptsTotal)
	}
	if stylesLoaded, ok := data["styles_loaded"].(float64); ok {
		result.StylesLoaded = int(stylesLoaded)
	}
	if stylesTotal, ok := data["styles_total"].(float64); ok {
		result.StylesTotal = int(stylesTotal)
	}
	if formsPresent, ok := data["forms_present"].(float64); ok {
		result.FormsPresent = int(formsPresent)
	}
	if linksPresent, ok := data["links_present"].(float64); ok {
		result.LinksPresent = int(linksPresent)
	}
	if iframesPresent, ok := data["iframes_present"].(float64); ok {
		result.IframesPresent = int(iframesPresent)
	}
	if hasErrors, ok := data["has_errors"].(bool); ok {
		result.HasErrors = hasErrors
	}
	if errorCount, ok := data["error_count"].(float64); ok {
		result.ErrorCount = int(errorCount)
	}
	if errorMessages, ok := data["error_messages"].([]interface{}); ok {
		for _, msg := range errorMessages {
			if msgStr, ok := msg.(string); ok {
				result.ErrorMessages = append(result.ErrorMessages, msgStr)
			}
		}
	}

	return result, nil
}

// ScreenshotElement takes a screenshot of a specific element
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) ScreenshotElement(tabID, selector, outputPath string, timeout int) error {
	params := map[string]string{
		"selector": selector,
		"output":   outputPath,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("screenshot-element", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to take element screenshot: %s", resp.Error)
	}

	return nil
}

// Accessibility tree data structures (matching daemon types)

// AXNode represents a node in the accessibility tree
type AXNode struct {
	NodeID           string       `json:"nodeId"`
	Ignored          bool         `json:"ignored"`
	IgnoredReasons   []AXProperty `json:"ignoredReasons,omitempty"`
	Role             *AXValue     `json:"role,omitempty"`
	ChromeRole       *AXValue     `json:"chromeRole,omitempty"`
	Name             *AXValue     `json:"name,omitempty"`
	Description      *AXValue     `json:"description,omitempty"`
	Value            *AXValue     `json:"value,omitempty"`
	Properties       []AXProperty `json:"properties,omitempty"`
	ParentID         string       `json:"parentId,omitempty"`
	ChildIDs         []string     `json:"childIds,omitempty"`
	BackendDOMNodeID int          `json:"backendDOMNodeId,omitempty"`
	FrameID          string       `json:"frameId,omitempty"`
}

// AXProperty represents a property of an accessibility node
type AXProperty struct {
	Name  string   `json:"name"`
	Value *AXValue `json:"value"`
}

// AXValue represents a computed accessibility value
type AXValue struct {
	Type         string          `json:"type"`
	Value        interface{}     `json:"value,omitempty"`
	RelatedNodes []AXRelatedNode `json:"relatedNodes,omitempty"`
	Sources      []AXValueSource `json:"sources,omitempty"`
}

// AXRelatedNode represents a related node in the accessibility tree
type AXRelatedNode struct {
	BackendDOMNodeID int    `json:"backendDOMNodeId"`
	IDRef            string `json:"idref,omitempty"`
	Text             string `json:"text,omitempty"`
}

// AXValueSource represents a source for a computed accessibility value
type AXValueSource struct {
	Type              string   `json:"type"`
	Value             *AXValue `json:"value,omitempty"`
	Attribute         string   `json:"attribute,omitempty"`
	AttributeValue    *AXValue `json:"attributeValue,omitempty"`
	Superseded        bool     `json:"superseded,omitempty"`
	NativeSource      string   `json:"nativeSource,omitempty"`
	NativeSourceValue *AXValue `json:"nativeSourceValue,omitempty"`
	Invalid           bool     `json:"invalid,omitempty"`
	InvalidReason     string   `json:"invalidReason,omitempty"`
}

// AccessibilityTreeResult represents the result of accessibility tree operations
type AccessibilityTreeResult struct {
	Nodes []AXNode `json:"nodes"`
}

// AccessibilityQueryResult represents the result of accessibility queries
type AccessibilityQueryResult struct {
	Nodes []AXNode `json:"nodes"`
}

// GetAccessibilityTree retrieves the full accessibility tree for a tab
// If tabID is empty, the current tab will be used
// depth limits the tree depth (optional, nil for full tree)
// timeout is in seconds, 0 means no timeout
func (c *Client) GetAccessibilityTree(tabID string, depth *int, timeout int) (*AccessibilityTreeResult, error) {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add depth if specified
	if depth != nil {
		params["depth"] = strconv.Itoa(*depth)
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("get-accessibility-tree", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to get accessibility tree: %s", resp.Error)
	}

	// Parse the response data
	var result AccessibilityTreeResult
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal accessibility tree result: %w", err)
	}

	return &result, nil
}

// GetPartialAccessibilityTree retrieves a partial accessibility tree for a specific element
// If tabID is empty, the current tab will be used
// selector is the CSS selector for the element to get the tree for
// fetchRelatives determines whether to include ancestors, siblings, and children
// timeout is in seconds, 0 means no timeout
func (c *Client) GetPartialAccessibilityTree(tabID, selector string, fetchRelatives bool, timeout int) (*AccessibilityTreeResult, error) {
	params := map[string]string{
		"selector":        selector,
		"fetch-relatives": strconv.FormatBool(fetchRelatives),
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("get-partial-accessibility-tree", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to get partial accessibility tree: %s", resp.Error)
	}

	// Parse the response data
	var result AccessibilityTreeResult
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal accessibility tree result: %w", err)
	}

	return &result, nil
}

// QueryAccessibilityTree queries the accessibility tree for nodes matching specific criteria
// If tabID is empty, the current tab will be used
// selector is optional CSS selector to limit the search scope
// accessibleName is optional accessible name to match
// role is optional role to match
// timeout is in seconds, 0 means no timeout
func (c *Client) QueryAccessibilityTree(tabID, selector, accessibleName, role string, timeout int) (*AccessibilityQueryResult, error) {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add optional parameters
	if selector != "" {
		params["selector"] = selector
	}
	if accessibleName != "" {
		params["accessible-name"] = accessibleName
	}
	if role != "" {
		params["role"] = role
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("query-accessibility-tree", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to query accessibility tree: %s", resp.Error)
	}

	// Parse the response data
	var result AccessibilityQueryResult
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal accessibility query result: %w", err)
	}

	return &result, nil
}

// ScreenshotEnhanced takes a screenshot with metadata
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) ScreenshotEnhanced(tabID, outputPath string, fullPage bool, timeout int) (*ScreenshotMetadata, error) {
	params := map[string]string{
		"output":    outputPath,
		"full-page": strconv.FormatBool(fullPage),
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("screenshot-enhanced", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to take enhanced screenshot: %s", resp.Error)
	}

	// Parse the response data
	var metadata ScreenshotMetadata
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &metadata)
	if err != nil {
		return nil, fmt.Errorf("failed to parse screenshot metadata: %w", err)
	}

	return &metadata, nil
}

// BulkFiles performs bulk file operations (upload/download)
// operationType: "upload" or "download"
// operations: slice of FileOperation structs
// timeout is in seconds, 0 means no timeout (default 30s for bulk operations)
func (c *Client) BulkFiles(operationType string, operations []FileOperation, timeout int) (*BulkFileResult, error) {
	// Convert operations to JSON
	operationsJSON, err := json.Marshal(operations)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal operations: %w", err)
	}

	params := map[string]string{
		"operation": operationType,
		"files":     string(operationsJSON),
	}

	// Add timeout if specified (default to 30 seconds for bulk operations)
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("bulk-files", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to perform bulk file operations: %s", resp.Error)
	}

	// Parse the response data
	var result BulkFileResult
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to parse bulk file result: %w", err)
	}

	return &result, nil
}

// ManageFiles performs file management operations
// operation: "cleanup", "list", or "info"
// pattern: file pattern for cleanup/list operations, or file path for info
// maxAge: max age in hours for cleanup operations (optional)
func (c *Client) ManageFiles(operation, pattern, maxAge string) (*FileManagementResult, error) {
	params := map[string]string{
		"operation": operation,
	}

	// Add optional parameters
	if pattern != "" {
		params["pattern"] = pattern
	}
	if maxAge != "" {
		params["max-age"] = maxAge
	}

	resp, err := c.SendCommand("manage-files", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to manage files: %s", resp.Error)
	}

	// Parse the response data
	var result FileManagementResult
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to parse file management result: %w", err)
	}

	return &result, nil
}

// DisableCache disables browser cache for a tab
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) DisableCache(tabID string, timeout int) error {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("disable-cache", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to disable cache: %s", resp.Error)
	}

	return nil
}

// EnableCache enables browser cache for a tab
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) EnableCache(tabID string, timeout int) error {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("enable-cache", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to enable cache: %s", resp.Error)
	}

	return nil
}

// ClearCache clears browser cache for a tab
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) ClearCache(tabID string, timeout int) error {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("clear-cache", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to clear cache: %s", resp.Error)
	}

	return nil
}

// ClearAllSiteData clears all site data including cookies, storage, cache, etc. for a tab
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) ClearAllSiteData(tabID string, timeout int) error {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("clear-all-site-data", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to clear all site data: %s", resp.Error)
	}

	return nil
}

// ClearCookies clears cookies for a tab
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) ClearCookies(tabID string, timeout int) error {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("clear-cookies", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to clear cookies: %s", resp.Error)
	}

	return nil
}

// ClearStorage clears web storage (localStorage, sessionStorage, IndexedDB, etc.) for a tab
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) ClearStorage(tabID string, timeout int) error {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("clear-storage", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to clear storage: %s", resp.Error)
	}

	return nil
}

// DragAndDrop performs a drag and drop operation from source to target element
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) DragAndDrop(tabID, sourceSelector, targetSelector string, timeout int) error {
	params := map[string]string{
		"source": sourceSelector,
		"target": targetSelector,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("drag-and-drop", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to perform drag and drop: %s", resp.Error)
	}

	return nil
}

// DragAndDropToCoordinates performs a drag and drop operation from source element to specific coordinates
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) DragAndDropToCoordinates(tabID, sourceSelector string, targetX, targetY int, timeout int) error {
	params := map[string]string{
		"source":   sourceSelector,
		"target-x": strconv.Itoa(targetX),
		"target-y": strconv.Itoa(targetY),
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("drag-and-drop-coordinates", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to perform drag and drop to coordinates: %s", resp.Error)
	}

	return nil
}

// DragAndDropByOffset performs a drag and drop operation from source element by a relative offset
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) DragAndDropByOffset(tabID, sourceSelector string, offsetX, offsetY int, timeout int) error {
	params := map[string]string{
		"source":   sourceSelector,
		"offset-x": strconv.Itoa(offsetX),
		"offset-y": strconv.Itoa(offsetY),
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("drag-and-drop-offset", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to perform drag and drop by offset: %s", resp.Error)
	}

	return nil
}

// RightClick performs a right-click on an element
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) RightClick(tabID, selector string, timeout int) error {
	params := map[string]string{
		"selector": selector,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("right-click", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to right-click element: %s", resp.Error)
	}

	return nil
}

// DoubleClick performs a double-click on an element
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) DoubleClick(tabID, selector string, timeout int) error {
	params := map[string]string{
		"selector": selector,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("double-click", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to double-click element: %s", resp.Error)
	}

	return nil
}

// MiddleClick performs a middle-click on an element
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) MiddleClick(tabID, selector string, timeout int) error {
	params := map[string]string{
		"selector": selector,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("middle-click", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to middle-click element: %s", resp.Error)
	}

	return nil
}

// Hover moves the mouse over an element without clicking
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) Hover(tabID, selector string, timeout int) error {
	params := map[string]string{
		"selector": selector,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("hover", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to hover over element: %s", resp.Error)
	}

	return nil
}

// MouseMove moves the mouse to specific coordinates without clicking
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) MouseMove(tabID string, x, y int, timeout int) error {
	params := map[string]string{
		"x": strconv.Itoa(x),
		"y": strconv.Itoa(y),
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("mouse-move", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to move mouse: %s", resp.Error)
	}

	return nil
}

// ScrollWheel performs mouse wheel scrolling at specific coordinates
// If tabID is empty, the current tab will be used
// deltaX and deltaY specify scroll amounts (negative = up/left, positive = down/right)
// timeout is in seconds, 0 means no timeout
func (c *Client) ScrollWheel(tabID string, x, y, deltaX, deltaY int, timeout int) error {
	params := map[string]string{
		"x":       strconv.Itoa(x),
		"y":       strconv.Itoa(y),
		"delta-x": strconv.Itoa(deltaX),
		"delta-y": strconv.Itoa(deltaY),
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("scroll-wheel", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to scroll with mouse wheel: %s", resp.Error)
	}

	return nil
}

// KeyCombination sends a key combination (e.g., "Ctrl+C", "Alt+Tab", "Shift+Enter")
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) KeyCombination(tabID, keys string, timeout int) error {
	params := map[string]string{
		"keys": keys,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("key-combination", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to send key combination: %s", resp.Error)
	}

	return nil
}

// SpecialKey sends a special key (e.g., "Enter", "Escape", "Tab", "F1", "ArrowUp")
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) SpecialKey(tabID, key string, timeout int) error {
	params := map[string]string{
		"key": key,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("special-key", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to send special key: %s", resp.Error)
	}

	return nil
}

// ModifierClick performs a click with modifier keys (e.g., Ctrl+click, Shift+click)
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) ModifierClick(tabID, selector, modifiers string, timeout int) error {
	params := map[string]string{
		"selector":  selector,
		"modifiers": modifiers,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("modifier-click", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to perform modifier click: %s", resp.Error)
	}

	return nil
}

// TouchTap performs a single finger tap at specific coordinates
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) TouchTap(tabID string, x, y int, timeout int) error {
	params := map[string]string{
		"x": strconv.Itoa(x),
		"y": strconv.Itoa(y),
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("touch-tap", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to perform touch tap: %s", resp.Error)
	}

	return nil
}

// TouchLongPress performs a long press at specific coordinates
// If tabID is empty, the current tab will be used
// duration is in milliseconds (default: 1000ms)
// timeout is in seconds, 0 means no timeout
func (c *Client) TouchLongPress(tabID string, x, y, duration int, timeout int) error {
	params := map[string]string{
		"x": strconv.Itoa(x),
		"y": strconv.Itoa(y),
	}

	if duration > 0 {
		params["duration"] = strconv.Itoa(duration)
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("touch-long-press", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to perform touch long press: %s", resp.Error)
	}

	return nil
}

// TouchSwipe performs a swipe gesture from start to end coordinates
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) TouchSwipe(tabID string, startX, startY, endX, endY int, timeout int) error {
	params := map[string]string{
		"start-x": strconv.Itoa(startX),
		"start-y": strconv.Itoa(startY),
		"end-x":   strconv.Itoa(endX),
		"end-y":   strconv.Itoa(endY),
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("touch-swipe", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to perform touch swipe: %s", resp.Error)
	}

	return nil
}

// PinchZoom performs a pinch-to-zoom gesture
// If tabID is empty, the current tab will be used
// scale > 1.0 zooms in, scale < 1.0 zooms out
// timeout is in seconds, 0 means no timeout
func (c *Client) PinchZoom(tabID string, centerX, centerY int, scale float64, timeout int) error {
	params := map[string]string{
		"center-x": strconv.Itoa(centerX),
		"center-y": strconv.Itoa(centerY),
		"scale":    fmt.Sprintf("%.2f", scale),
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("pinch-zoom", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to perform pinch zoom: %s", resp.Error)
	}

	return nil
}

// ScrollElement scrolls a specific element by a given amount
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) ScrollElement(tabID, selector string, deltaX, deltaY int, timeout int) error {
	params := map[string]string{
		"selector": selector,
		"delta-x":  strconv.Itoa(deltaX),
		"delta-y":  strconv.Itoa(deltaY),
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("scroll-element", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to scroll element: %s", resp.Error)
	}

	return nil
}

// ScrollToCoordinates scrolls the page to specific coordinates
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) ScrollToCoordinates(tabID string, x, y int, timeout int) error {
	params := map[string]string{
		"x": strconv.Itoa(x),
		"y": strconv.Itoa(y),
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("scroll-to-coordinates", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to scroll to coordinates: %s", resp.Error)
	}

	return nil
}

// SelectText selects text within an element by character range
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) SelectText(tabID, selector string, startIndex, endIndex int, timeout int) error {
	params := map[string]string{
		"selector": selector,
		"start":    strconv.Itoa(startIndex),
		"end":      strconv.Itoa(endIndex),
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("select-text", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to select text: %s", resp.Error)
	}

	return nil
}

// SelectAllText selects all text within an element or the entire page
// If tabID is empty, the current tab will be used
// If selector is empty, selects all text on the page
// timeout is in seconds, 0 means no timeout
func (c *Client) SelectAllText(tabID, selector string, timeout int) error {
	params := map[string]string{}

	if selector != "" {
		params["selector"] = selector
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("select-all-text", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to select all text: %s", resp.Error)
	}

	return nil
}

// AxeResults represents the results from running axe-core accessibility tests
type AxeResults struct {
	Violations   []AxeViolation    `json:"violations"`
	Passes       []AxePass         `json:"passes"`
	Incomplete   []AxeIncomplete   `json:"incomplete"`
	Inapplicable []AxeInapplicable `json:"inapplicable"`
	TestEngine   AxeTestEngine     `json:"testEngine"`
	TestRunner   AxeTestRunner     `json:"testRunner"`
	Timestamp    string            `json:"timestamp"`
	URL          string            `json:"url"`
}

// AxeViolation represents an accessibility violation found by axe-core
type AxeViolation struct {
	ID          string    `json:"id"`
	Impact      string    `json:"impact"`
	Tags        []string  `json:"tags"`
	Description string    `json:"description"`
	Help        string    `json:"help"`
	HelpURL     string    `json:"helpUrl"`
	Nodes       []AxeNode `json:"nodes"`
}

// AxePass represents an accessibility check that passed
type AxePass struct {
	ID          string    `json:"id"`
	Impact      string    `json:"impact"`
	Tags        []string  `json:"tags"`
	Description string    `json:"description"`
	Help        string    `json:"help"`
	HelpURL     string    `json:"helpUrl"`
	Nodes       []AxeNode `json:"nodes"`
}

// AxeIncomplete represents an accessibility check that needs manual review
type AxeIncomplete struct {
	ID          string    `json:"id"`
	Impact      string    `json:"impact"`
	Tags        []string  `json:"tags"`
	Description string    `json:"description"`
	Help        string    `json:"help"`
	HelpURL     string    `json:"helpUrl"`
	Nodes       []AxeNode `json:"nodes"`
}

// AxeInapplicable represents an accessibility check that doesn't apply to this page
type AxeInapplicable struct {
	ID          string   `json:"id"`
	Impact      string   `json:"impact"`
	Tags        []string `json:"tags"`
	Description string   `json:"description"`
	Help        string   `json:"help"`
	HelpURL     string   `json:"helpUrl"`
}

// AxeNode represents a specific DOM node with accessibility issues
type AxeNode struct {
	HTML   string           `json:"html"`
	Impact string           `json:"impact"`
	Target []string         `json:"target"`
	Any    []AxeCheckResult `json:"any"`
	All    []AxeCheckResult `json:"all"`
	None   []AxeCheckResult `json:"none"`
}

// AxeCheckResult represents the result of a specific accessibility check
type AxeCheckResult struct {
	ID      string          `json:"id"`
	Impact  string          `json:"impact"`
	Message string          `json:"message"`
	Data    json.RawMessage `json:"data"` // Can be string or object, use RawMessage
}

// AxeTestEngine represents the axe-core test engine information
type AxeTestEngine struct {
	Name    string `json:"name"`
	Version string `json:"version"`
}

// AxeTestRunner represents the test runner information
type AxeTestRunner struct {
	Name string `json:"name"`
}

// InjectAxeCore injects the axe-core library into the page
// If tabID is empty, the current tab will be used
// axeVersion specifies the axe-core version (e.g., "4.8.0"), empty string uses default
// timeout is in seconds, 0 means no timeout
func (c *Client) InjectAxeCore(tabID, axeVersion string, timeout int) error {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Only include version if it's provided
	if axeVersion != "" {
		params["version"] = axeVersion
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("inject-axe", params)
	if err != nil {
		return err
	}

	if !resp.Success {
		return fmt.Errorf("failed to inject axe-core: %s", resp.Error)
	}

	return nil
}

// RunAxeCore runs axe-core accessibility tests on the page
// If tabID is empty, the current tab will be used
// options is a map of axe.run() options (can be nil for defaults)
// timeout is in seconds, 0 means no timeout
func (c *Client) RunAxeCore(tabID string, options map[string]interface{}, timeout int) (*AxeResults, error) {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Include options if provided
	if options != nil && len(options) > 0 {
		optionsBytes, err := json.Marshal(options)
		if err != nil {
			return nil, fmt.Errorf("failed to marshal options: %w", err)
		}
		params["options"] = string(optionsBytes)
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("run-axe", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to run axe-core: %s", resp.Error)
	}

	// Parse the response data
	var result AxeResults
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal axe results: %w", err)
	}

	return &result, nil
}

// ContrastCheckResult represents the result of contrast checking for text elements
type ContrastCheckResult struct {
	TotalElements int                    `json:"total_elements"`
	PassedAA      int                    `json:"passed_aa"`
	PassedAAA     int                    `json:"passed_aaa"`
	FailedAA      int                    `json:"failed_aa"`
	FailedAAA     int                    `json:"failed_aaa"`
	UnableToCheck int                    `json:"unable_to_check"`
	Elements      []ContrastCheckElement `json:"elements"`
}

// ContrastCheckElement represents a single element's contrast check
type ContrastCheckElement struct {
	Selector        string  `json:"selector"`
	Text            string  `json:"text"`
	ForegroundColor string  `json:"foreground_color"`
	BackgroundColor string  `json:"background_color"`
	ContrastRatio   float64 `json:"contrast_ratio"`
	FontSize        string  `json:"font_size"`
	FontWeight      string  `json:"font_weight"`
	IsLargeText     bool    `json:"is_large_text"`
	PassesAA        bool    `json:"passes_aa"`
	PassesAAA       bool    `json:"passes_aaa"`
	RequiredAA      float64 `json:"required_aa"`
	RequiredAAA     float64 `json:"required_aaa"`
}

// PageAccessibilityReport represents a comprehensive accessibility assessment of a single page
type PageAccessibilityReport struct {
	URL              string                 `json:"url"`
	Timestamp        string                 `json:"timestamp"`
	ComplianceStatus string                 `json:"compliance_status"` // COMPLIANT, NON_COMPLIANT, PARTIAL
	OverallScore     int                    `json:"overall_score"`     // 0-100
	LegalRisk        string                 `json:"legal_risk"`        // LOW, MEDIUM, HIGH, CRITICAL
	CriticalIssues   []AccessibilityIssue   `json:"critical_issues"`
	SeriousIssues    []AccessibilityIssue   `json:"serious_issues"`
	HighIssues       []AccessibilityIssue   `json:"high_issues"`
	MediumIssues     []AccessibilityIssue   `json:"medium_issues"`
	SummaryByWCAG    map[string]WCAGSummary `json:"summary_by_wcag"`
	ContrastSummary  ContrastSummary        `json:"contrast_summary"`
	KeyboardSummary  KeyboardSummary        `json:"keyboard_summary"`
	ARIASummary      ARIASummary            `json:"aria_summary"`
	FormSummary      *FormSummary           `json:"form_summary,omitempty"`
	Screenshots      map[string]string      `json:"screenshots,omitempty"`
	EstimatedHours   int                    `json:"estimated_remediation_hours"`
}

// AccessibilityIssue represents a single accessibility issue
type AccessibilityIssue struct {
	WCAG        string   `json:"wcag"`
	Title       string   `json:"title"`
	Description string   `json:"description"`
	Impact      string   `json:"impact"`
	Count       int      `json:"count"`
	Examples    []string `json:"examples,omitempty"`
	Remediation string   `json:"remediation"`
}

// WCAGSummary represents violations grouped by WCAG principle
type WCAGSummary struct {
	Violations int    `json:"violations"`
	Severity   string `json:"severity"`
}

// ContrastSummary represents a summary of contrast check results
type ContrastSummary struct {
	TotalChecked     int                       `json:"total_checked"`
	Passed           int                       `json:"passed"`
	Failed           int                       `json:"failed"`
	PassRate         string                    `json:"pass_rate"`
	CriticalFailures []ContrastFailure         `json:"critical_failures"`
	FailurePatterns  map[string]FailurePattern `json:"failure_patterns"`
}

// ContrastFailure represents a critical contrast failure
type ContrastFailure struct {
	Selector string  `json:"selector"`
	Text     string  `json:"text"`
	Ratio    float64 `json:"ratio"`
	Required float64 `json:"required"`
	FgColor  string  `json:"fg_color"`
	BgColor  string  `json:"bg_color"`
	Fix      string  `json:"fix"`
}

// FailurePattern represents a pattern of similar failures
type FailurePattern struct {
	Count int     `json:"count"`
	Ratio float64 `json:"ratio"`
	Fix   string  `json:"fix"`
}

// KeyboardSummary represents a summary of keyboard navigation results
type KeyboardSummary struct {
	TotalInteractive      int             `json:"total_interactive"`
	Focusable             int             `json:"focusable"`
	MissingFocusIndicator int             `json:"missing_focus_indicator"`
	KeyboardTraps         int             `json:"keyboard_traps"`
	TabOrderIssues        int             `json:"tab_order_issues"`
	Issues                []KeyboardIssue `json:"issues"`
}

// KeyboardIssue represents a keyboard accessibility issue
type KeyboardIssue struct {
	Type        string   `json:"type"`
	Severity    string   `json:"severity"`
	Count       int      `json:"count"`
	Description string   `json:"description"`
	Fix         string   `json:"fix"`
	Examples    []string `json:"examples,omitempty"`
}

// ARIASummary represents a summary of ARIA validation results
type ARIASummary struct {
	TotalViolations   int         `json:"total_violations"`
	MissingNames      int         `json:"missing_names"`
	InvalidAttributes int         `json:"invalid_attributes"`
	HiddenInteractive int         `json:"hidden_interactive"`
	Issues            []ARIAIssue `json:"issues"`
}

// ARIAIssue represents an ARIA accessibility issue
type ARIAIssue struct {
	Type        string   `json:"type"`
	Severity    string   `json:"severity"`
	Count       int      `json:"count"`
	Description string   `json:"description"`
	Fix         string   `json:"fix"`
	Examples    []string `json:"examples,omitempty"`
}

// FormSummary represents a summary of form accessibility
type FormSummary struct {
	FormsFound int         `json:"forms_found"`
	Forms      []FormAudit `json:"forms"`
}

// FormAudit represents accessibility audit of a single form
type FormAudit struct {
	ID                 string      `json:"id"`
	Fields             int         `json:"fields"`
	Issues             []FormIssue `json:"issues"`
	ARIACompliance     string      `json:"aria_compliance"`
	KeyboardAccessible bool        `json:"keyboard_accessible"`
	RequiredMarked     bool        `json:"required_fields_marked"`
}

// FormIssue represents a form accessibility issue
type FormIssue struct {
	Type        string  `json:"type"`
	Severity    string  `json:"severity"`
	Count       int     `json:"count,omitempty"`
	Description string  `json:"description"`
	Fix         string  `json:"fix"`
	Ratio       float64 `json:"ratio,omitempty"`
}

// CheckContrast checks color contrast for text elements on the page
// If tabID is empty, the current tab will be used
// selector is optional CSS selector for specific elements (defaults to all text elements)
// timeout is in seconds, 0 means no timeout
func (c *Client) CheckContrast(tabID, selector string, timeout int) (*ContrastCheckResult, error) {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Only include selector if it's provided
	if selector != "" {
		params["selector"] = selector
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("check-contrast", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to check contrast: %s", resp.Error)
	}

	// Parse the response data
	var result ContrastCheckResult
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal contrast results: %w", err)
	}

	return &result, nil
}

// GradientContrastResult represents the result of gradient contrast checking
type GradientContrastResult struct {
	Selector        string  `json:"selector"`
	TextColor       string  `json:"text_color"`
	DarkestBgColor  string  `json:"darkest_bg_color"`
	LightestBgColor string  `json:"lightest_bg_color"`
	WorstContrast   float64 `json:"worst_contrast"`
	BestContrast    float64 `json:"best_contrast"`
	PassesAA        bool    `json:"passes_aa"`
	PassesAAA       bool    `json:"passes_aaa"`
	RequiredAA      float64 `json:"required_aa"`
	RequiredAAA     float64 `json:"required_aaa"`
	IsLargeText     bool    `json:"is_large_text"`
	SamplePoints    int     `json:"sample_points"`
	Error           string  `json:"error,omitempty"`
}

// CheckGradientContrast checks color contrast for text on gradient backgrounds using ImageMagick
// If tabID is empty, the current tab will be used
// selector is required CSS selector for element with gradient background
// timeout is in seconds, 0 means no timeout
func (c *Client) CheckGradientContrast(tabID, selector string, timeout int) (*GradientContrastResult, error) {
	if selector == "" {
		return nil, fmt.Errorf("selector parameter is required for gradient contrast check")
	}

	params := map[string]string{
		"selector": selector,
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("check-gradient-contrast", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to check gradient contrast: %s", resp.Error)
	}

	// Parse the response data
	var result GradientContrastResult
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal gradient contrast results: %w", err)
	}

	return &result, nil
}

// MediaValidationResult represents the result of time-based media validation
type MediaValidationResult struct {
	Videos             []MediaElement `json:"videos"`
	Audios             []MediaElement `json:"audios"`
	EmbeddedPlayers    []MediaElement `json:"embedded_players"`
	TranscriptLinks    []string       `json:"transcript_links"`
	TotalViolations    int            `json:"total_violations"`
	CriticalViolations int            `json:"critical_violations"`
	Warnings           int            `json:"warnings"`
}

// MediaElement represents a video or audio element
type MediaElement struct {
	Type              string   `json:"type"` // "video", "audio", "youtube", "vimeo"
	Src               string   `json:"src"`
	HasCaptions       bool     `json:"has_captions"`
	HasDescriptions   bool     `json:"has_descriptions"`
	HasControls       bool     `json:"has_controls"`
	Autoplay          bool     `json:"autoplay"`
	CaptionTracks     []Track  `json:"caption_tracks"`
	DescriptionTracks []Track  `json:"description_tracks"`
	Violations        []string `json:"violations"`
	Warnings          []string `json:"warnings"`
}

// Track represents a text track (captions, descriptions, etc.)
type Track struct {
	Kind       string `json:"kind"`
	Src        string `json:"src"`
	Srclang    string `json:"srclang"`
	Label      string `json:"label"`
	Accessible bool   `json:"accessible"`
}

// ValidateMedia checks for video/audio captions, descriptions, and transcripts
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) ValidateMedia(tabID string, timeout int) (*MediaValidationResult, error) {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("validate-media", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to validate media: %s", resp.Error)
	}

	// Parse the response data
	var result MediaValidationResult
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal media validation results: %w", err)
	}

	return &result, nil
}

// HoverFocusTestResult represents the result of hover/focus content testing
type HoverFocusTestResult struct {
	TotalElements      int                 `json:"total_elements"`
	ElementsWithIssues int                 `json:"elements_with_issues"`
	PassedElements     int                 `json:"passed_elements"`
	Issues             []HoverFocusIssue   `json:"issues"`
	TestedElements     []HoverFocusElement `json:"tested_elements"`
}

// HoverFocusElement represents an element that shows content on hover/focus
type HoverFocusElement struct {
	Selector    string   `json:"selector"`
	Type        string   `json:"type"` // "tooltip", "dropdown", "popover", "custom"
	Dismissible bool     `json:"dismissible"`
	Hoverable   bool     `json:"hoverable"`
	Persistent  bool     `json:"persistent"`
	PassesWCAG  bool     `json:"passes_wcag"`
	Violations  []string `json:"violations"`
}

// HoverFocusIssue represents a specific issue with hover/focus content
type HoverFocusIssue struct {
	Selector    string `json:"selector"`
	Type        string `json:"type"`     // "not_dismissible", "not_hoverable", "not_persistent"
	Severity    string `json:"severity"` // "critical", "serious", "moderate"
	Description string `json:"description"`
	WCAG        string `json:"wcag"` // "1.4.13"
}

// TestHoverFocusContent tests WCAG 1.4.13 compliance for content on hover or focus
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) TestHoverFocusContent(tabID string, timeout int) (*HoverFocusTestResult, error) {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("test-hover-focus", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to test hover/focus content: %s", resp.Error)
	}

	// Parse the response data
	var result HoverFocusTestResult
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal hover/focus test results: %w", err)
	}

	return &result, nil
}

// TextInImagesResult represents the result of text-in-images detection
type TextInImagesResult struct {
	TotalImages       int                 `json:"total_images"`
	ImagesWithText    int                 `json:"images_with_text"`
	ImagesWithoutText int                 `json:"images_without_text"`
	Violations        int                 `json:"violations"`
	Warnings          int                 `json:"warnings"`
	Images            []ImageTextAnalysis `json:"images"`
}

// ImageTextAnalysis represents OCR analysis of a single image
type ImageTextAnalysis struct {
	Src            string  `json:"src"`
	Alt            string  `json:"alt"`
	HasAlt         bool    `json:"has_alt"`
	DetectedText   string  `json:"detected_text"`
	TextLength     int     `json:"text_length"`
	Confidence     float64 `json:"confidence"`
	IsViolation    bool    `json:"is_violation"`
	ViolationType  string  `json:"violation_type"` // "missing_alt", "insufficient_alt", "decorative_with_text"
	Recommendation string  `json:"recommendation"`
}

// DetectTextInImages uses Tesseract OCR to detect text in images
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) DetectTextInImages(tabID string, timeout int) (*TextInImagesResult, error) {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("detect-text-in-images", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to detect text in images: %s", resp.Error)
	}

	// Parse the response data
	var result TextInImagesResult
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal text-in-images results: %w", err)
	}

	return &result, nil
}

// CrossPageConsistencyResult represents the result of cross-page consistency checking
type CrossPageConsistencyResult struct {
	PagesAnalyzed     int                       `json:"pages_analyzed"`
	ConsistencyIssues int                       `json:"consistency_issues"`
	NavigationIssues  int                       `json:"navigation_issues"`
	StructureIssues   int                       `json:"structure_issues"`
	Pages             []PageConsistencyAnalysis `json:"pages"`
	CommonNavigation  []string                  `json:"common_navigation"`
	InconsistentPages []string                  `json:"inconsistent_pages"`
}

// PageConsistencyAnalysis represents consistency analysis of a single page
type PageConsistencyAnalysis struct {
	URL                 string   `json:"url"`
	Title               string   `json:"title"`
	HasHeader           bool     `json:"has_header"`
	HasFooter           bool     `json:"has_footer"`
	HasNavigation       bool     `json:"has_navigation"`
	NavigationLinks     []string `json:"navigation_links"`
	MainLandmarks       int      `json:"main_landmarks"`
	HeaderLandmarks     int      `json:"header_landmarks"`
	FooterLandmarks     int      `json:"footer_landmarks"`
	NavigationLandmarks int      `json:"navigation_landmarks"`
	Issues              []string `json:"issues"`
}

// CheckCrossPageConsistency analyzes multiple pages for consistency
// If tabID is empty, the current tab will be used
// timeout is in seconds per page, 0 means no timeout
func (c *Client) CheckCrossPageConsistency(tabID string, urls []string, timeout int) (*CrossPageConsistencyResult, error) {
	if len(urls) == 0 {
		return nil, fmt.Errorf("no URLs provided for consistency check")
	}

	params := map[string]string{
		"urls": strings.Join(urls, ","),
	}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("check-cross-page-consistency", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to check cross-page consistency: %s", resp.Error)
	}

	// Parse the response data
	var result CrossPageConsistencyResult
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal cross-page consistency results: %w", err)
	}

	return &result, nil
}

// AnimationFlashResult represents the result of animation/flash detection
type AnimationFlashResult struct {
	TotalAnimations    int                     `json:"total_animations"`
	FlashingContent    int                     `json:"flashing_content"`
	RapidAnimations    int                     `json:"rapid_animations"`
	AutoplayAnimations int                     `json:"autoplay_animations"`
	Violations         int                     `json:"violations"`
	Warnings           int                     `json:"warnings"`
	Elements           []AnimationFlashElement `json:"elements"`
}

// AnimationFlashElement represents an animated or flashing element
type AnimationFlashElement struct {
	TagName        string  `json:"tag_name"`
	Selector       string  `json:"selector"`
	AnimationType  string  `json:"animation_type"` // "css", "gif", "video", "canvas", "svg"
	FlashRate      float64 `json:"flash_rate"`     // Flashes per second
	Duration       float64 `json:"duration"`       // Animation duration in seconds
	IsAutoplay     bool    `json:"is_autoplay"`
	HasControls    bool    `json:"has_controls"`
	CanPause       bool    `json:"can_pause"`
	IsViolation    bool    `json:"is_violation"`
	ViolationType  string  `json:"violation_type"`
	Recommendation string  `json:"recommendation"`
}

// DetectAnimationFlash detects animations and flashing content
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) DetectAnimationFlash(tabID string, timeout int) (*AnimationFlashResult, error) {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("detect-animation-flash", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to detect animation/flash: %s", resp.Error)
	}

	// Parse the response data
	var result AnimationFlashResult
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal animation/flash results: %w", err)
	}

	return &result, nil
}

// EnhancedAccessibilityResult represents enhanced accessibility tree analysis
type EnhancedAccessibilityResult struct {
	TotalElements      int                            `json:"total_elements"`
	ElementsWithIssues int                            `json:"elements_with_issues"`
	ARIAViolations     int                            `json:"aria_violations"`
	RoleViolations     int                            `json:"role_violations"`
	RelationshipIssues int                            `json:"relationship_issues"`
	LandmarkIssues     int                            `json:"landmark_issues"`
	Elements           []EnhancedAccessibilityElement `json:"elements"`
}

// EnhancedAccessibilityElement represents an element with accessibility analysis
type EnhancedAccessibilityElement struct {
	TagName           string   `json:"tag_name"`
	Selector          string   `json:"selector"`
	Role              string   `json:"role"`
	AriaLabel         string   `json:"aria_label"`
	AriaDescribedBy   string   `json:"aria_described_by"`
	AriaLabelledBy    string   `json:"aria_labelled_by"`
	AriaRequired      bool     `json:"aria_required"`
	AriaInvalid       bool     `json:"aria_invalid"`
	AriaHidden        bool     `json:"aria_hidden"`
	TabIndex          int      `json:"tab_index"`
	IsInteractive     bool     `json:"is_interactive"`
	HasAccessibleName bool     `json:"has_accessible_name"`
	Issues            []string `json:"issues"`
	Recommendations   []string `json:"recommendations"`
}

// AnalyzeEnhancedAccessibility performs enhanced accessibility tree analysis
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) AnalyzeEnhancedAccessibility(tabID string, timeout int) (*EnhancedAccessibilityResult, error) {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("analyze-enhanced-accessibility", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to analyze enhanced accessibility: %s", resp.Error)
	}

	// Parse the response data
	var result EnhancedAccessibilityResult
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal enhanced accessibility results: %w", err)
	}

	return &result, nil
}

// KeyboardTestResult represents the result of keyboard navigation testing
type KeyboardTestResult struct {
	TotalInteractive int                   `json:"total_interactive"`
	Focusable        int                   `json:"focusable"`
	NotFocusable     int                   `json:"not_focusable"`
	NoFocusIndicator int                   `json:"no_focus_indicator"`
	KeyboardTraps    int                   `json:"keyboard_traps"`
	TabOrder         []KeyboardTestElement `json:"tab_order"`
	Issues           []KeyboardTestIssue   `json:"issues"`
}

// KeyboardTestElement represents an interactive element in tab order
type KeyboardTestElement struct {
	Index         int    `json:"index"`
	Selector      string `json:"selector"`
	TagName       string `json:"tag_name"`
	Role          string `json:"role"`
	Text          string `json:"text"`
	TabIndex      int    `json:"tab_index"`
	HasFocusStyle bool   `json:"has_focus_style"`
	IsVisible     bool   `json:"is_visible"`
}

// KeyboardTestIssue represents a keyboard accessibility issue
type KeyboardTestIssue struct {
	Type        string `json:"type"`
	Severity    string `json:"severity"`
	Element     string `json:"element"`
	Description string `json:"description"`
}

// TestKeyboardNavigation tests keyboard navigation and accessibility
// If tabID is empty, the current tab will be used
// useRealKeys determines whether to use real Tab key simulation (true) or programmatic focus (false)
// timeout is in seconds, 0 means no timeout
func (c *Client) TestKeyboardNavigation(tabID string, useRealKeys bool, timeout int) (*KeyboardTestResult, error) {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Note: useRealKeys parameter is ignored - always uses real keyboard simulation
	// for accurate :focus-visible detection

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("test-keyboard", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to test keyboard navigation: %s", resp.Error)
	}

	// Parse the response data
	var result KeyboardTestResult
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal keyboard test results: %w", err)
	}

	return &result, nil
}

// ZoomTestResult represents the result of zoom level testing
type ZoomTestResult struct {
	ZoomLevels []ZoomLevelTest `json:"zoom_levels"`
	Issues     []ZoomTestIssue `json:"issues"`
}

// ZoomLevelTest represents testing at a specific zoom level
type ZoomLevelTest struct {
	ZoomLevel           float64 `json:"zoom_level"`
	ViewportWidth       int     `json:"viewport_width"`
	ViewportHeight      int     `json:"viewport_height"`
	HasHorizontalScroll bool    `json:"has_horizontal_scroll"`
	ContentWidth        int     `json:"content_width"`
	ContentHeight       int     `json:"content_height"`
	VisibleElements     int     `json:"visible_elements"`
	OverflowingElements int     `json:"overflowing_elements"`
	TextReadable        bool    `json:"text_readable"`
}

// ZoomTestIssue represents an issue found during zoom testing
type ZoomTestIssue struct {
	ZoomLevel   float64 `json:"zoom_level"`
	Type        string  `json:"type"`
	Severity    string  `json:"severity"`
	Description string  `json:"description"`
	Element     string  `json:"element,omitempty"`
}

// TestZoom tests page at different zoom levels
// If tabID is empty, the current tab will be used
// zoomLevels is an array of zoom levels to test (e.g., []float64{1.0, 2.0, 4.0})
// If empty, defaults to [1.0, 2.0, 4.0]
// timeout is in seconds per zoom level, 0 means no timeout
func (c *Client) TestZoom(tabID string, zoomLevels []float64, timeout int) (*ZoomTestResult, error) {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Include zoom levels if provided
	if len(zoomLevels) > 0 {
		levels := make([]string, len(zoomLevels))
		for i, level := range zoomLevels {
			levels[i] = strconv.FormatFloat(level, 'f', 1, 64)
		}
		params["zoom_levels"] = strings.Join(levels, ",")
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("test-zoom", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to test zoom: %s", resp.Error)
	}

	// Parse the response data
	var result ZoomTestResult
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal zoom test results: %w", err)
	}

	return &result, nil
}

// ReflowTestResult represents the result of reflow/responsive testing
type ReflowTestResult struct {
	Breakpoints []ReflowBreakpoint `json:"breakpoints"`
	Issues      []ReflowTestIssue  `json:"issues"`
}

// ReflowBreakpoint represents testing at a specific viewport width
type ReflowBreakpoint struct {
	Width               int  `json:"width"`
	Height              int  `json:"height"`
	HasHorizontalScroll bool `json:"has_horizontal_scroll"`
	ContentWidth        int  `json:"content_width"`
	ContentHeight       int  `json:"content_height"`
	VisibleElements     int  `json:"visible_elements"`
	OverflowingElements int  `json:"overflowing_elements"`
	ResponsiveLayout    bool `json:"responsive_layout"`
}

// ReflowTestIssue represents an issue found during reflow testing
type ReflowTestIssue struct {
	Width       int    `json:"width"`
	Type        string `json:"type"`
	Severity    string `json:"severity"`
	Description string `json:"description"`
	Element     string `json:"element,omitempty"`
}

// TestReflow tests page at different viewport widths for responsive design
// If tabID is empty, the current tab will be used
// widths is an array of viewport widths to test (e.g., []int{320, 1280})
// If empty, defaults to [320, 1280] (WCAG 1.4.10 breakpoints)
// timeout is in seconds per width, 0 means no timeout
func (c *Client) TestReflow(tabID string, widths []int, timeout int) (*ReflowTestResult, error) {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Include widths if provided
	if len(widths) > 0 {
		widthStrs := make([]string, len(widths))
		for i, width := range widths {
			widthStrs[i] = strconv.Itoa(width)
		}
		params["widths"] = strings.Join(widthStrs, ",")
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("test-reflow", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to test reflow: %s", resp.Error)
	}

	// Parse the response data
	var result ReflowTestResult
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal reflow test results: %w", err)
	}

	return &result, nil
}

// GetPageAccessibilityReport performs a comprehensive accessibility assessment of a page
// and returns a summarized report with actionable findings
// If tabID is empty, the current tab will be used
// tests is an array of test types to run (e.g., ["wcag", "contrast", "keyboard", "forms"])
// If empty, runs all tests
// standard is the WCAG standard to test against (e.g., "WCAG21AA")
// includeScreenshots determines whether to capture screenshots of violations
// timeout is in seconds, 0 means no timeout
func (c *Client) GetPageAccessibilityReport(tabID string, tests []string, standard string, includeScreenshots bool, timeout int) (*PageAccessibilityReport, error) {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Include tests if provided
	if len(tests) > 0 {
		params["tests"] = strings.Join(tests, ",")
	} else {
		params["tests"] = "all"
	}

	// Include standard if provided
	if standard != "" {
		params["standard"] = standard
	} else {
		params["standard"] = "WCAG21AA"
	}

	// Include screenshot flag
	if includeScreenshots {
		params["include_screenshots"] = "true"
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("page-accessibility-report", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to get page accessibility report: %s", resp.Error)
	}

	// Parse the response data
	var result PageAccessibilityReport
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal page accessibility report: %w", err)
	}

	return &result, nil
}

// ContrastAuditResult represents a smart contrast audit with prioritized failures
type ContrastAuditResult struct {
	TotalChecked     int                       `json:"total_checked"`
	Passed           int                       `json:"passed"`
	Failed           int                       `json:"failed"`
	PassRate         string                    `json:"pass_rate"`
	CriticalFailures []ContrastFailure         `json:"critical_failures"`
	FailurePatterns  map[string]FailurePattern `json:"failure_patterns"`
}

// GetContrastAudit performs a smart contrast check with prioritized failures
// If tabID is empty, the current tab will be used
// prioritySelectors is an array of CSS selectors to prioritize (e.g., ["button", "a", "nav"])
// threshold is the WCAG level to test against ("AA" or "AAA")
// timeout is in seconds, 0 means no timeout
func (c *Client) GetContrastAudit(tabID string, prioritySelectors []string, threshold string, timeout int) (*ContrastAuditResult, error) {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Include priority selectors if provided
	if len(prioritySelectors) > 0 {
		params["priority_selectors"] = strings.Join(prioritySelectors, ",")
	}

	// Include threshold if provided
	if threshold != "" {
		params["threshold"] = threshold
	} else {
		params["threshold"] = "AA"
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("contrast-audit", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to get contrast audit: %s", resp.Error)
	}

	// Parse the response data
	var result ContrastAuditResult
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal contrast audit: %w", err)
	}

	return &result, nil
}

// KeyboardAuditResult represents a keyboard navigation audit
type KeyboardAuditResult struct {
	Status           string          `json:"status"` // PASS, FAIL, PARTIAL
	TotalInteractive int             `json:"total_interactive"`
	Focusable        int             `json:"focusable"`
	Issues           []KeyboardIssue `json:"issues"`
	TabOrderIssues   []string        `json:"tab_order_issues"`
	Recommendation   string          `json:"recommendation"`
}

// GetKeyboardAudit performs a keyboard navigation assessment
// If tabID is empty, the current tab will be used
// checkFocusIndicators determines whether to check for visible focus indicators
// checkTabOrder determines whether to check tab order
// checkKeyboardTraps determines whether to check for keyboard traps
// useRealKeys determines whether to use real Tab key simulation (true, default) or programmatic focus (false)
// timeout is in seconds, 0 means no timeout
func (c *Client) GetKeyboardAudit(tabID string, checkFocusIndicators, checkTabOrder, checkKeyboardTraps, useRealKeys bool, timeout int) (*KeyboardAuditResult, error) {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Include check flags
	if checkFocusIndicators {
		params["check_focus_indicators"] = "true"
	}
	if checkTabOrder {
		params["check_tab_order"] = "true"
	}
	if checkKeyboardTraps {
		params["check_keyboard_traps"] = "true"
	}

	// Note: useRealKeys parameter is ignored - always uses real keyboard simulation
	// for accurate :focus-visible detection

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("keyboard-audit", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to get keyboard audit: %s", resp.Error)
	}

	// Parse the response data
	var result KeyboardAuditResult
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal keyboard audit: %w", err)
	}

	return &result, nil
}

// GetFormAccessibilityAudit performs a comprehensive form accessibility check
// If tabID is empty, the current tab will be used
// formSelector is an optional CSS selector for a specific form (defaults to all forms)
// timeout is in seconds, 0 means no timeout
func (c *Client) GetFormAccessibilityAudit(tabID, formSelector string, timeout int) (*FormSummary, error) {
	params := map[string]string{}

	// Only include tab ID if it's provided
	if tabID != "" {
		params["tab"] = tabID
	}

	// Only include form selector if it's provided
	if formSelector != "" {
		params["form_selector"] = formSelector
	}

	// Add timeout if specified
	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("form-accessibility-audit", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to get form accessibility audit: %s", resp.Error)
	}

	// Parse the response data
	var result FormSummary
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal form accessibility audit: %w", err)
	}

	return &result, nil
}

// DiviStructure represents the extracted Divi page structure
type DiviStructure struct {
	URL      string        `json:"url"`
	Sections []DiviSection `json:"sections"`
	Metadata struct {
		ExtractionDate string `json:"extraction_date"`
		Accuracy       string `json:"accuracy"`
		Limitations    string `json:"limitations"`
	} `json:"metadata"`
}

// DiviSection represents a Divi section
type DiviSection struct {
	Type            string    `json:"type"` // regular, specialty, fullwidth
	HasParallax     bool      `json:"has_parallax"`
	BackgroundColor string    `json:"background_color"`
	BackgroundImage string    `json:"background_image"`
	BackgroundStyle string    `json:"background_style"`
	Rows            []DiviRow `json:"rows"`
	CSSClasses      []string  `json:"css_classes"`
}

// DiviRow represents a Divi row
type DiviRow struct {
	ColumnStructure string       `json:"column_structure"` // e.g., "1_2,1_2" for two half columns
	Columns         []DiviColumn `json:"columns"`
	CSSClasses      []string     `json:"css_classes"`
}

// DiviColumn represents a Divi column
type DiviColumn struct {
	Type       string       `json:"type"` // e.g., "1_2", "1_3", "4_4"
	Modules    []DiviModule `json:"modules"`
	CSSClasses []string     `json:"css_classes"`
}

// DiviModule represents a Divi module
type DiviModule struct {
	Type       string            `json:"type"` // text, image, button, blurb, etc.
	Content    string            `json:"content"`
	Attributes map[string]string `json:"attributes"`
	CSSClasses []string          `json:"css_classes"`
}

// DiviImage represents an extracted image
type DiviImage struct {
	URL          string `json:"url"`
	Alt          string `json:"alt"`
	Title        string `json:"title"`
	Width        int    `json:"width"`
	Height       int    `json:"height"`
	Context      string `json:"context"` // e.g., "section 0, row 0, column 0, module 2"
	IsBackground bool   `json:"is_background"`
}

// DiviContent represents extracted content
type DiviContent struct {
	URL      string       `json:"url"`
	Modules  []DiviModule `json:"modules"`
	Images   []DiviImage  `json:"images"`
	Metadata struct {
		ExtractionDate string `json:"extraction_date"`
		TotalModules   int    `json:"total_modules"`
		TotalImages    int    `json:"total_images"`
	} `json:"metadata"`
}

// ExtractDiviStructure extracts Divi page structure from rendered HTML
func (c *Client) ExtractDiviStructure(tabID string, timeout int) (*DiviStructure, error) {
	params := map[string]string{}

	if tabID != "" {
		params["tab"] = tabID
	}

	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("extract-divi-structure", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to extract Divi structure: %s", resp.Error)
	}

	var result DiviStructure
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal Divi structure: %w", err)
	}

	return &result, nil
}

// ExtractDiviImages extracts all images from a Divi page
func (c *Client) ExtractDiviImages(tabID string, timeout int) ([]DiviImage, error) {
	params := map[string]string{}

	if tabID != "" {
		params["tab"] = tabID
	}

	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("extract-divi-images", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to extract Divi images: %s", resp.Error)
	}

	var result []DiviImage
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal Divi images: %w", err)
	}

	return result, nil
}

// ExtractDiviContent extracts text content and module data from Divi pages
func (c *Client) ExtractDiviContent(tabID string, timeout int) (*DiviContent, error) {
	params := map[string]string{}

	if tabID != "" {
		params["tab"] = tabID
	}

	if timeout > 0 {
		params["timeout"] = strconv.Itoa(timeout)
	}

	resp, err := c.SendCommand("extract-divi-content", params)
	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, fmt.Errorf("failed to extract Divi content: %s", resp.Error)
	}

	var result DiviContent
	dataBytes, err := json.Marshal(resp.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal response data: %w", err)
	}

	err = json.Unmarshal(dataBytes, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal Divi content: %w", err)
	}

	return &result, nil
}
