package browser

import (
	"encoding/json"
	"fmt"
	"strings"
)

// FindByTextOpts configura la busqueda por texto visible.
type FindByTextOpts struct {
	// Tag filtra por nombre de tag (ej "button", "a"). Vacio = cualquiera.
	Tag string
	// Exact: true = innerText.trim() === text. false (default) = contiene.
	Exact bool
	// CaseSensitive: false (default) = comparacion lowercased.
	CaseSensitive bool
}

// CdpFindByText busca el primer elemento cuyo `innerText` matchea `text` y
// devuelve un selector CSS unico utilizable con CdpClick / CdpEvaluate.
// Prefiere elementos hoja (no contenedores que envuelven hijos con el mismo
// texto) — asi el click va al elemento mas interno, donde el handler vive.
//
// El selector retornado es:
//   - "#<id>" si el elemento tiene id.
//   - path "tag:nth-of-type(n) > tag:nth-of-type(n) > ..." si no.
//
// Retorna error si no encuentra ningun elemento con ese texto. Antes devolvia
// ("", nil) en silencio, lo que hacia que el caller creyera que habia encontrado
// algo y operara sobre un selector vacio. Tambien error si la evaluacion JS rompe
// (conexion CDP caida).
func CdpFindByText(c *CDPConn, text string, opts FindByTextOpts) (string, error) {
	if c == nil {
		return "", fmt.Errorf("cdp find by text: conexion nula")
	}
	if text == "" {
		return "", fmt.Errorf("cdp find by text: texto vacio")
	}

	// Serializamos opts como JSON literal en el script para evitar quoting hell.
	payload, _ := json.Marshal(map[string]any{
		"text":  text,
		"tag":   opts.Tag,
		"exact": opts.Exact,
		"cs":    opts.CaseSensitive,
	})

	js := fmt.Sprintf(`
(function() {
  var P = %s;
  var target = P.cs ? P.text : P.text.toLowerCase();
  var nodes = document.querySelectorAll(P.tag || '*');
  function norm(v) {
    v = (v || '').replace(/\s+/g, ' ').trim();
    return P.cs ? v : v.toLowerCase();
  }
  function matches(el) {
    var v = norm(el.innerText || el.textContent || '');
    return P.exact ? v === target : v.indexOf(target) >= 0;
  }
  function leafmost(el) {
    for (var i = 0; i < el.children.length; i++) {
      if (matches(el.children[i])) return false;
    }
    return true;
  }
  function selectorOf(el) {
    if (el.id) return '#' + CSS.escape(el.id);
    var path = [];
    while (el && el.nodeType === 1 && el.tagName !== 'HTML') {
      var sel = el.tagName.toLowerCase();
      var parent = el.parentNode;
      if (parent && parent.children) {
        var sib = Array.prototype.filter.call(parent.children, function(c) {
          return c.tagName === el.tagName;
        });
        if (sib.length > 1) sel += ':nth-of-type(' + (sib.indexOf(el) + 1) + ')';
      }
      path.unshift(sel);
      if (el === document.body) break;
      el = el.parentElement;
    }
    return path.join(' > ');
  }
  for (var i = 0; i < nodes.length; i++) {
    var el = nodes[i];
    if (matches(el) && leafmost(el)) {
      return selectorOf(el);
    }
  }
  return '';
})()`, string(payload))

	res, err := CdpEvaluate(c, js)
	if err != nil {
		return "", fmt.Errorf("cdp find by text: %w", err)
	}
	// CdpEvaluate retorna el valor stringificado. Para "" devuelve cadena vacia.
	res = strings.TrimSpace(res)
	if res == "" || res == "<nil>" {
		return "", fmt.Errorf("cdp find by text: no se encontro elemento con texto %q", text)
	}
	return res, nil
}
