import { useEffect, useLayoutEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import Icon from "./Icon";

/**
 * Aufklappbarer Kategorie-Baum als Auswahlfeld – Ersatz für die früheren, mit
 * „— —“ eingerückten <select>. Zeigt echte Einrückung und lässt Äste per Pfeil
 * ein- und ausklappen (wie die Kategorienliste selbst). Gibt `null` für die
 * oberste Ebene zurück, sonst die Kategorie-ID.
 *
 * Das Menü wird per Portal an den <body> gehängt, damit es nicht vom
 * horizontal scrollenden Tabellenrahmen (overflow) abgeschnitten wird.
 */
export default function CategorySelect({
  value,
  onChange,
  nodes,
  rootLabel = "– oberste Ebene –",
  // Zusätzliche Einträge über dem Baum, z.B. { value: 0, label: "Ohne Kategorie" }
  // für Filter. Ihr Wert wird unverändert an onChange gereicht.
  extraOptions = [],
  disabled = false,
}) {
  const [open, setOpen] = useState(false);
  const [collapsed, setCollapsed] = useState(() => new Set());
  const [pos, setPos] = useState(null);
  const btnRef = useRef(null);
  const popRef = useRef(null);

  const nameById = Object.fromEntries(nodes.map((n) => [n.id, n.name]));
  const parentById = Object.fromEntries(nodes.map((n) => [n.id, n.parent_id]));
  const idset = new Set(nodes.map((n) => n.id));
  const hasKids = (id) => nodes.some((n) => n.parent_id === id);

  const isRoot = value == null || value === "";
  const extra = extraOptions.find((o) => String(o.value) === String(value));
  // Wert unverändert übernehmen: IDs können Zahlen (Kategorien) ODER
  // Zeichenketten (Lagerort-Codes) sein – Number() würde Codes zu NaN machen.
  const selectedId = !isRoot && !extra ? value : null;
  const label = isRoot ? rootLabel : extra ? extra.label : nameById[selectedId] ?? rootLabel;

  function place() {
    const r = btnRef.current?.getBoundingClientRect();
    if (r) setPos({ left: r.left, top: r.bottom + 4, width: r.width });
  }
  useLayoutEffect(() => { if (open) place(); }, [open]);

  useEffect(() => {
    if (!open) return;
    function onDoc(e) {
      if (btnRef.current?.contains(e.target)) return;
      if (popRef.current?.contains(e.target)) return;
      setOpen(false);
    }
    function onScroll(e) {
      // Scrollen IM Menü (die Liste ist scrollbar) darf es nicht schließen –
      // nur ein Scrollen der Seite dahinter, weil das fixierte Menü sonst
      // verrutschen würde.
      if (popRef.current && e.target instanceof Node && popRef.current.contains(e.target)) return;
      setOpen(false);
    }
    function onResize() { setOpen(false); }
    function onKey(e) { if (e.key === "Escape") setOpen(false); }
    document.addEventListener("mousedown", onDoc);
    window.addEventListener("scroll", onScroll, true);
    window.addEventListener("resize", onResize);
    document.addEventListener("keydown", onKey);
    return () => {
      document.removeEventListener("mousedown", onDoc);
      window.removeEventListener("scroll", onScroll, true);
      window.removeEventListener("resize", onResize);
      document.removeEventListener("keydown", onKey);
    };
  }, [open]);

  function hidden(id) {
    let p = parentById[id];
    while (p != null && idset.has(p)) {
      if (collapsed.has(p)) return true;
      p = parentById[p];
    }
    return false;
  }

  function toggle(id) {
    setCollapsed((alt) => {
      const neu = new Set(alt);
      if (neu.has(id)) neu.delete(id);
      else neu.add(id);
      return neu;
    });
  }

  function pick(id) {
    onChange(id);
    setOpen(false);
  }

  const visible = nodes.filter((n) => !hidden(n.id));

  return (
    <>
      <button
        type="button"
        ref={btnRef}
        className="tree-select-btn"
        disabled={disabled}
        aria-expanded={open}
        onClick={() => setOpen((o) => !o)}
      >
        <span className={isRoot ? "muted" : ""}>{label}</span>
        <Icon name="chevronRight" size={13} className={`caret ${open ? "open" : ""}`} />
      </button>

      {open && pos && createPortal(
        <div
          ref={popRef}
          className="tree-pop"
          style={{ left: pos.left, top: pos.top, minWidth: Math.max(pos.width, 220) }}
        >
          <button
            type="button"
            className={`tree-pop-opt ${isRoot ? "sel" : ""}`}
            style={{ paddingLeft: 12 }}
            onClick={() => pick(null)}
          >
            {rootLabel}
          </button>
          {extraOptions.map((o) => (
            <button
              key={String(o.value)}
              type="button"
              className={`tree-pop-opt ${extra && String(extra.value) === String(o.value) ? "sel" : ""}`}
              style={{ paddingLeft: 12 }}
              onClick={() => pick(o.value)}
            >
              {o.label}
            </button>
          ))}
          {visible.map((n) => {
            const kids = hasKids(n.id);
            const zu = collapsed.has(n.id);
            return (
              <div key={n.id} className="tree-pop-line" style={{ paddingLeft: 6 + n.depth * 16 }}>
                {kids ? (
                  <span
                    className="tree-toggle"
                    role="button"
                    tabIndex={0}
                    title={zu ? "Unterkategorien einblenden" : "Unterkategorien ausblenden"}
                    onClick={(e) => { e.stopPropagation(); toggle(n.id); }}
                  >
                    <Icon name="chevronRight" size={12} className={zu ? "" : "rotated"} />
                  </span>
                ) : (
                  <span className="tree-spacer-sm" />
                )}
                <button
                  type="button"
                  className={`tree-pop-opt ${String(selectedId) === String(n.id) ? "sel" : ""}`}
                  onClick={() => pick(n.id)}
                >
                  {n.name}
                </button>
              </div>
            );
          })}
          {nodes.length === 0 && <div className="tree-pop-empty">Keine Auswahl möglich.</div>}
        </div>,
        document.body,
      )}
    </>
  );
}
