package browser

import (
	"strconv"
	"time"

	"github.com/chromedp/chromedp"
)

// FormField is one selector → value pair for FillForm.
type FormField struct {
	Selector string `json:"selector"`
	Value    string `json:"value"`
}

func (s *Session) Click(selector string) error {
	if err := s.bumpAction(); err != nil {
		return err
	}
	return s.runWithTimeout(chromedp.Click(selector, chromedp.ByQuery))
}

func (s *Session) Type(selector, text string) error {
	if err := s.bumpAction(); err != nil {
		return err
	}
	return s.runWithTimeout(
		chromedp.SendKeys(selector, text, chromedp.ByQuery),
	)
}

func (s *Session) Scroll(direction string, amount int) error {
	if err := s.bumpAction(); err != nil {
		return err
	}
	sign := 1
	if direction == "up" {
		sign = -1
	}
	js := "window.scrollBy(0, " + strconv.Itoa(sign*amount) + ");"
	return s.runWithTimeout(chromedp.Evaluate(js, nil))
}

func (s *Session) FillForm(fields []FormField) error {
	if err := s.bumpAction(); err != nil {
		return err
	}
	actions := []chromedp.Action{}
	for _, f := range fields {
		actions = append(actions,
			chromedp.SendKeys(f.Selector, f.Value, chromedp.ByQuery),
		)
	}
	return chromedp.Run(s.ctx, actions...)
}

// maxWaitMs caps the ms argument to WaitFor so a misbehaving agent can't
// pin a Chrome process and goroutine for hours. The selector path is bounded
// by the per-action timeout the caller wraps around chromedp.Run.
const maxWaitMs = 10_000

func (s *Session) WaitFor(selector string, ms int) error {
	if err := s.bumpAction(); err != nil {
		return err
	}
	if selector != "" {
		return s.runWithTimeout(chromedp.WaitVisible(selector, chromedp.ByQuery))
	}
	if ms > 0 {
		if ms > maxWaitMs {
			ms = maxWaitMs
		}
		return s.runWithTimeout(chromedp.Sleep(time.Duration(ms) * time.Millisecond))
	}
	return nil
}

func (s *Session) NavigateHistory(direction string) error {
	if err := s.bumpAction(); err != nil {
		return err
	}
	switch direction {
	case "back":
		return s.runWithTimeout(chromedp.NavigateBack())
	case "forward":
		return s.runWithTimeout(chromedp.NavigateForward())
	case "reload":
		return s.runWithTimeout(chromedp.Reload())
	default:
		return &ManagerError{Code: "invalid_argument", Message: "direction must be back|forward|reload"}
	}
}

// GetHTML returns the full outer HTML of the current page (counts against
// the action cap — agent-visible).
func (s *Session) GetHTML() (string, error) {
	if err := s.bumpAction(); err != nil {
		return "", err
	}
	return s.getHTMLInternal()
}

// getHTMLInternal extracts HTML without bumping the action counter. Used by
// BuildPageState so navigation actions only count once.
func (s *Session) getHTMLInternal() (string, error) {
	var html string
	err := s.runWithTimeout(chromedp.OuterHTML("html", &html, chromedp.ByQuery))
	return html, err
}

// CurrentURL returns the URL of the active page.
func (s *Session) CurrentURL() (string, error) {
	if s.ctx == nil {
		return "", nil
	}
	var u string
	err := s.runWithTimeout(chromedp.Location(&u))
	return u, err
}
