import { useMemo, useState } from "preact/hooks";
import { marked } from "marked";
import "../../styles/jambox-search.css";

function norm(s) {
  return String(s || "")
    .toLowerCase()
    .replace(/\u00a0/g, " ")
    .replace(/\s+/g, " ")
    .trim();
}

function escapeRegExp(s) {
  return String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

/** Podświetlenie w czystym tekście (np. title) */
function highlightText(text, q) {
  const qq = (q || "").trim();
  if (!qq) return text;

  const re = new RegExp(escapeRegExp(qq), "ig");
  const parts = String(text || "").split(re);

  if (parts.length === 1) return text;

  // split() gubi match — więc budujemy przez exec na oryginale
  const matches = String(text || "").match(re) || [];

  const out = [];
  for (let i = 0; i < parts.length; i++) {
    out.push(parts[i]);
    if (i < matches.length) out.push(<mark class="f-hl">{matches[i]}</mark>);
  }
  return out;
}

/** Podświetlenie wewnątrz HTML (po markdown), omijamy PRE/CODE */
function highlightHtml(html, q) {
  const qq = (q || "").trim();
  if (!qq) return html;

  const re = new RegExp(escapeRegExp(qq), "ig");

  const doc = new DOMParser().parseFromString(html, "text/html");
  const root = doc.body;

  const walker = doc.createTreeWalker(root, NodeFilter.SHOW_TEXT, null);

  const toSkip = (node) => {
    const p = node.parentElement;
    if (!p) return true;
    const tag = p.tagName;
    return tag === "SCRIPT" || tag === "STYLE" || tag === "CODE" || tag === "PRE";
  };

  const nodes = [];
  let n;
  while ((n = walker.nextNode())) nodes.push(n);

  for (const textNode of nodes) {
    if (toSkip(textNode)) continue;

    const txt = textNode.nodeValue || "";
    if (!re.test(txt)) continue;

    // reset RegExp state (bo test() z /g/ potrafi przesuwać lastIndex)
    re.lastIndex = 0;

    const frag = doc.createDocumentFragment();
    let last = 0;
    let m;

    while ((m = re.exec(txt))) {
      const start = m.index;
      const end = start + m[0].length;

      if (start > last) frag.appendChild(doc.createTextNode(txt.slice(last, start)));

      const mark = doc.createElement("mark");
      mark.className = "f-hl";
      mark.textContent = txt.slice(start, end);
      frag.appendChild(mark);

      last = end;
    }

    if (last < txt.length) frag.appendChild(doc.createTextNode(txt.slice(last)));

    textNode.parentNode?.replaceChild(frag, textNode);
  }

  return root.innerHTML;
}

function HighlightedMarkdown({ text, q }) {
  const html = useMemo(() => {
    // markdown -> html
    const raw = marked.parse(String(text || ""), {
      gfm: true,
      breaks: true,
      headerIds: false,
      mangle: false,
    });

    // highlight w HTML
    return highlightHtml(raw, q);
  }, [text, q]);

  return (
    <div
      class="fuz-markdown"
      dangerouslySetInnerHTML={{ __html: html }}
    />
  );
}

export default function JamboxMozliwosciSearch({ items = [] }) {
  const [q, setQ] = useState("");

  const filtered = useMemo(() => {
    const qq = norm(q);
    if (qq.length === 0) return items;
    return items.filter((it) => norm(`${it.title}\n${it.content}`).includes(qq));
  }, [items, q]);

  const meta = useMemo(() => {
    const qq = q.trim();
    if (qq.length === 0) return "";
    return `Znaleziono: ${filtered.length} sekcje`;
  }, [q, filtered]);

  return (
    <div class="f-chsearch">
      <div class="f-chsearch__top">
        <div class="f-chsearch__inputwrap">
          <input
            class="f-chsearch__input"
            type="search"
            value={q}
            onInput={(e) => setQ(e.currentTarget.value)}
            placeholder="Szukaj funkcji po nazwie lub opisie…"
            aria-label="Szukaj funkcji po nazwie lub opisie"
          />

          {q && (
            <button
              type="button"
              class="f-chsearch__clear"
              aria-label="Wyczyść wyszukiwanie"
              onClick={() => setQ("")}
            >
              ✕
            </button>
          )}
        </div>

        <div class="f-chsearch-meta">{meta}</div>
      </div>

      {filtered.map((it, index) => {
        const reverse = index % 2 === 1;
        const imageUrl = it.image || "";
        const hasImage = !!imageUrl;

        return (
          <section class="f-section" id={it.id} key={it.id}>
            <div class={`f-section-grid ${hasImage ? "md:grid-cols-2" : "md:grid-cols-1"}`}>
              {hasImage && (
                <img
                  src={imageUrl}
                  alt={it.title}
                  class={`f-section-image ${reverse ? "md:order-1" : "md:order-2"}`}
                  loading="lazy"
                  decoding="async"
                />
              )}

              <div class={`${hasImage ? (reverse ? "md:order-2" : "md:order-1") : ""}`}>
                <h2 class="f-section-title">{highlightText(it.title, q)}</h2>

                <HighlightedMarkdown text={it.content} q={q} />

                <div class="f-section-nav">
                  <a href="#top" class="btn btn-outline">Do góry ↑</a>
                </div>
              </div>
            </div>
          </section>
        );
      })}

      {q.length > 0 && filtered.length === 0 && (
        <div class="f-chsearch-empty">
          Brak wyników dla: <strong>{q}</strong>
        </div>
      )}
    </div>
  );
}
