package browser

import (
	"context"
	"fmt"
	"time"

	"github.com/go-rod/rod"
	"github.com/go-rod/rod/lib/input"
	"github.com/go-rod/rod/lib/proto"
)

// Click clicks an element by ref.
func (m *Manager) Click(ctx context.Context, targetID, ref string, opts ClickOpts) error {
	_, el, err := m.getPageAndResolve(targetID, ref)
	if err != nil {
		return err
	}

	button := proto.InputMouseButtonLeft
	if opts.Button == "right" {
		button = proto.InputMouseButtonRight
	} else if opts.Button == "middle" {
		button = proto.InputMouseButtonMiddle
	}

	clickCount := 1
	if opts.DoubleClick {
		clickCount = 2
	}

	return el.Click(button, clickCount)
}

// Type types text into an element by ref.
func (m *Manager) Type(ctx context.Context, targetID, ref, text string, opts TypeOpts) error {
	page, el, err := m.getPageAndResolve(targetID, ref)
	if err != nil {
		return err
	}

	// Focus the element first
	_ = el.Click(proto.InputMouseButtonLeft, 1)
	time.Sleep(50 * time.Millisecond)

	if opts.Slowly {
		// Type character by character with delay
		for _, ch := range text {
			el.MustInput(string(ch))
			time.Sleep(50 * time.Millisecond)
		}
	} else {
		el.MustInput(text)
	}

	if opts.Submit {
		time.Sleep(50 * time.Millisecond)
		_ = page.Keyboard.Press(input.Enter)
	}

	return nil
}

// Press presses a keyboard key.
func (m *Manager) Press(ctx context.Context, targetID, key string) error {
	m.mu.Lock()
	page, err := m.getPage(targetID)
	m.mu.Unlock()
	if err != nil {
		return err
	}

	k := mapKey(key)
	return page.Keyboard.Press(k)
}

// Hover hovers over an element by ref.
func (m *Manager) Hover(ctx context.Context, targetID, ref string) error {
	_, el, err := m.getPageAndResolve(targetID, ref)
	if err != nil {
		return err
	}

	return el.Hover()
}

// Wait waits for a condition on a page.
func (m *Manager) Wait(ctx context.Context, targetID string, opts WaitOpts) error {
	m.mu.Lock()
	page, err := m.getPage(targetID)
	m.mu.Unlock()
	if err != nil {
		return err
	}

	// Simple time wait
	if opts.TimeMs > 0 {
		select {
		case <-time.After(time.Duration(opts.TimeMs) * time.Millisecond):
			return nil
		case <-ctx.Done():
			return ctx.Err()
		}
	}

	// Wait for text to appear
	if opts.Text != "" {
		return rod.Try(func() {
			page.Timeout(30 * time.Second).MustElementR("*", opts.Text)
		})
	}

	// Wait for text to disappear
	if opts.TextGone != "" {
		timeout := time.After(30 * time.Second)
		ticker := time.NewTicker(500 * time.Millisecond)
		defer ticker.Stop()
		for {
			select {
			case <-timeout:
				return fmt.Errorf("timeout waiting for text %q to disappear", opts.TextGone)
			case <-ticker.C:
				has, _, _ := page.Has("*")
				if !has {
					return nil
				}
				el, err := page.ElementR("*", opts.TextGone)
				if err != nil || el == nil {
					return nil
				}
			case <-ctx.Done():
				return ctx.Err()
			}
		}
	}

	// Wait for URL
	if opts.URL != "" {
		wait := page.WaitNavigation(proto.PageLifecycleEventNameLoad)
		wait()
		return nil
	}

	// Default: wait for page to stabilize
	waitStable(page)
	return nil
}

// Evaluate runs JavaScript on a page.
func (m *Manager) Evaluate(ctx context.Context, targetID, js string) (string, error) {
	m.mu.Lock()
	page, err := m.getPage(targetID)
	m.mu.Unlock()
	if err != nil {
		return "", err
	}

	result, err := page.Eval(js)
	if err != nil {
		return "", fmt.Errorf("evaluate: %w", err)
	}

	return result.Value.String(), nil
}

// mapKey converts a key name string to a Rod keyboard key.
func mapKey(key string) input.Key {
	switch key {
	case "Enter":
		return input.Enter
	case "Tab":
		return input.Tab
	case "Escape":
		return input.Escape
	case "Backspace":
		return input.Backspace
	case "Delete":
		return input.Delete
	case "ArrowUp":
		return input.ArrowUp
	case "ArrowDown":
		return input.ArrowDown
	case "ArrowLeft":
		return input.ArrowLeft
	case "ArrowRight":
		return input.ArrowRight
	case "Home":
		return input.Home
	case "End":
		return input.End
	case "PageUp":
		return input.PageUp
	case "PageDown":
		return input.PageDown
	case "Space":
		return input.Space
	default:
		// Try single character
		if len(key) == 1 {
			return input.Key(key[0])
		}
		return input.Enter
	}
}
