package browser

import (
	"fmt"
	"math/rand"
	"strings"
	"time"
)

// assertEditableFocus verifica que el activeElement de la pagina acepta texto
// (input/textarea/select/contentEditable). Sin foco, los caracteres se pierden
// silenciosamente (van a document.body); devolvemos un error claro en vez de
// "escribir a la nada". Compartido por CdpTypeText (camino human) y CdpInsertText
// (camino rapido).
func assertEditableFocus(c *CDPConn) error {
	focus, ferr := CdpEvaluate(c, `(function(){var a=document.activeElement;if(!a)return 'none';var t=a.tagName.toLowerCase();return (t==='input'||t==='textarea'||t==='select'||a.isContentEditable)?'ok':t;})()`)
	if ferr != nil {
		return fmt.Errorf("verificar foco: %w", ferr)
	}
	if strings.TrimSpace(focus) != "ok" {
		return fmt.Errorf("no hay campo de texto enfocado (activeElement: %s); enfoca el input primero", strings.TrimSpace(focus))
	}
	return nil
}

// CdpTypeText escribe texto en el elemento activo de la pagina caracter por
// caracter, con una pausa ALEATORIA entre teclas. Es el camino "human": emite
// keyDown/keyUp reales por tecla (sitios que validan pulsacion a pulsacion
// reaccionan) y el ritmo irregular reduce la deteccion de automatizacion. Para el
// camino rapido (modo auto) usa CdpInsertText: un solo round-trip, sin teclear.
func CdpTypeText(c *CDPConn, text string) error {
	if c == nil {
		return fmt.Errorf("cdp type text: conexion nula")
	}
	if err := assertEditableFocus(c); err != nil {
		return fmt.Errorf("cdp type text: %w", err)
	}

	// keyDown (con `text`) ya inserta el caracter en el elemento focado en
	// Chrome — enviar ademas un evento "char" lo duplicaba en sitios que
	// reaccionan a `input` events (DuckDuckGo, Google, etc.). Patron
	// alineado con Puppeteer page.type(): keyDown + (insert via text) + keyUp.
	for _, ch := range text {
		charStr := string(ch)

		keyDown := map[string]any{
			"type": "keyDown",
			"key":  charStr,
			"text": charStr,
		}
		if _, err := c.sendCDP("Input.dispatchKeyEvent", keyDown); err != nil {
			return fmt.Errorf("cdp type text: keyDown %q: %w", charStr, err)
		}

		keyUp := map[string]any{
			"type": "keyUp",
			"key":  charStr,
		}
		if _, err := c.sendCDP("Input.dispatchKeyEvent", keyUp); err != nil {
			return fmt.Errorf("cdp type text: keyUp %q: %w", charStr, err)
		}

		// Pausa ALEATORIA entre caracteres (15-65 ms) para imitar el ritmo
		// irregular de un humano escribiendo, en vez de un intervalo de maquina fijo.
		time.Sleep(time.Duration(15+rand.Intn(51)) * time.Millisecond)
	}

	return nil
}

// CdpInsertText inserta todo el texto en el elemento enfocado en UN solo
// round-trip via Input.insertText. Es el camino rapido del modo automatico: no
// emite keyDown/keyUp por tecla, por lo que sitios que validan pulsacion a
// pulsacion (autocompletes muy estrictos) pueden no reaccionar — para esos casos
// usa CdpTypeText (modo human). Requiere un campo editable enfocado.
func CdpInsertText(c *CDPConn, text string) error {
	if c == nil {
		return fmt.Errorf("cdp insert text: conexion nula")
	}
	if err := assertEditableFocus(c); err != nil {
		return fmt.Errorf("cdp insert text: %w", err)
	}
	if _, err := c.sendCDP("Input.insertText", map[string]any{"text": text}); err != nil {
		return fmt.Errorf("cdp insert text: %w", err)
	}
	return nil
}
