// Tree-style table. Top-level keys are listed; arrays / objects are
// collapsible groups with a count badge. Children are indented and connected
// with a soft vertical guide. Full path shown on hover (title attr).
//
// Why redesign:  flat dot-notation paths like
//   policyCondition[0].policyConditionStatement.policyOperator.name
// are exhausting to scan. Grouping + indentation + last-segment-only keys
// turns the same data into a navigable hierarchy.

function TableView({ rows /* legacy */, json, loading, query }) {
  const tree = React.useMemo(() => json ? buildTree(json) : flatRowsToTree(rows || []), [json, rows]);
  const [collapsed, setCollapsed] = React.useState(() => initialCollapsedSet(tree));
  const [filter, setFilter] = React.useState("");

  // When the user types a filter, auto-expand any group whose subtree contains a match.
  // selfMatchGroups: group nodes whose own key matches — their children are shown in full.
  const { matchingPaths, selfMatchGroups } = React.useMemo(() => {
    if (!filter.trim()) return { matchingPaths: null, selfMatchGroups: null };
    const q = filter.trim().toLowerCase();
    const matches = new Set();
    const selfMatchGroups = new Set();
    function walk(nodes) {
      let anyChild = false;
      for (const n of nodes) {
        const me = n.key.toLowerCase().includes(q) ||
          (n.kind === "leaf" && String(n.value).toLowerCase().includes(q));
        let childMatch = false;
        if (n.children) childMatch = walk(n.children);
        if (me || childMatch) { matches.add(n.path); anyChild = true; }
        if (me && n.kind !== "leaf") selfMatchGroups.add(n.path);
      }
      return anyChild;
    }
    walk(tree);
    return { matchingPaths: matches, selfMatchGroups };
  }, [filter, tree]);

  React.useEffect(() => {
    // Auto-expand all ancestors of matched leaves
    if (matchingPaths) {
      setCollapsed(prev => {
        const next = new Set(prev);
        matchingPaths.forEach(p => next.delete(p));
        return next;
      });
    }
  }, [matchingPaths]); // eslint-disable-line react-hooks/exhaustive-deps

  if (loading) {
    return (
      <div className="pv-loading" data-screen-label="Table loading">
        <div className="pv-spinner" />
        <span>Loading policy…</span>
      </div>
    );
  }
  if (!tree || tree.length === 0) {
    return (
      <div className="pv-empty" data-screen-label="Table empty">
        <div>No policy loaded.</div>
        <div className="hint">Search and open a file to inspect it.</div>
      </div>
    );
  }

  const toggle = (path) => setCollapsed(prev => {
    const next = new Set(prev);
    next.has(path) ? next.delete(path) : next.add(path);
    return next;
  });

  function expandAll() { setCollapsed(new Set()); }
  function collapseAll() { setCollapsed(new Set(allGroupPaths(tree))); }

  // Flatten + filter
  const visible = [];
  function walk(nodes, showAll = false) {
    for (const n of nodes) {
      if (!showAll && matchingPaths && !matchingPaths.has(n.path)) continue;
      visible.push(n);
      if (n.kind !== "leaf" && !collapsed.has(n.path) && n.children) {
        const expandFully = showAll || (selfMatchGroups && selfMatchGroups.has(n.path));
        walk(n.children, expandFully);
      }
    }
  }
  walk(tree);

  return (
    <div className="pv-tree-wrap" data-screen-label="Table view">
      <div className="pv-tree-toolbar">
        <div className="pv-tree-filter">
          <span className="pv-tree-filter-icon"><Icon name="search" size={14} /></span>
          <input
            type="text"
            value={filter}
            onChange={(e) => setFilter(e.target.value)}
            placeholder="Filter fields… (e.g. policyOperator, BSPC, Active)"
          />
          {filter && (
            <button className="pv-tree-filter-clear" onClick={() => setFilter("")} aria-label="Clear filter">
              <Icon name="x" size={12} />
            </button>
          )}
        </div>
        <span className="pv-tree-toolbar-count">
          {matchingPaths
            ? <><b>{visible.filter(n => n.kind === "leaf").length}</b> of <b>{countLeaves(tree)}</b> fields</>
            : <><b>{countLeaves(tree)}</b> fields · <b>{countGroups(tree)}</b> groups</>}
        </span>
        <span className="pv-tree-toolbar-actions">
          <button className="pv-mini-btn" onClick={expandAll}>Expand all</button>
          <button className="pv-mini-btn" onClick={collapseAll}>Collapse all</button>
        </span>
      </div>

      <div className="pv-tree">
        <div className="pv-tree-head">
          <span className="col-key">Key</span>
          <span className="col-type">Type</span>
          <span className="col-value">Value</span>
        </div>
        <ul className="pv-tree-rows">
          {visible.map((n) => (
            <TreeRow key={n.path} node={n} isCollapsed={collapsed.has(n.path)} onToggle={toggle} query={filter || query} />
          ))}
        </ul>
        {visible.length === 0 && (
          <div className="pv-empty" style={{ padding: "32px 20px" }}>
            <div>No fields match <code>{filter}</code>.</div>
          </div>
        )}
      </div>
    </div>
  );
}

function TreeRow({ node, isCollapsed, onToggle, query }) {
  const isGroup = node.kind !== "leaf";
  const indent = node.depth * 18;

  return (
    <li
      className={cx("pv-tree-row", isGroup && "is-group", `depth-${node.depth}`)}
      onClick={() => isGroup && onToggle(node.path)}
      title={node.path}
    >
      <span className="col-key" style={{ paddingLeft: indent + 12 }}>
        {/* depth guides */}
        {Array.from({ length: node.depth }).map((_, i) => (
          <span key={i} className="pv-tree-guide" style={{ left: i * 18 + 18 }} />
        ))}
        {isGroup ? (
          <span className={cx("pv-tree-chevron", isCollapsed && "is-collapsed")}>
            <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
          </span>
        ) : (
          <span className="pv-tree-bullet">·</span>
        )}
        <span className="pv-tree-key">{highlight(node.key, query)}</span>
        {isGroup && (
          <span className="pv-tree-count">{node.kind === "array" ? "Array" : "Object"}({(node.children || []).length})</span>
        )}
      </span>
      <span className="col-type">
        {!isGroup && <TypeBadge type={node.type} />}
      </span>
      <span className="col-value">
        {!isGroup && (
          <span className={cx(node.type !== "string" && "v-mono", node.value === "null" && "v-na")}>
            {node.value}
          </span>
        )}
      </span>
    </li>
  );
}

function highlight(text, q) {
  if (!q || q.length < 2) return text;
  const idx = String(text).toLowerCase().indexOf(q.toLowerCase());
  if (idx < 0) return text;
  return (
    <>
      {String(text).slice(0, idx)}
      <mark className="pv-hl">{String(text).slice(idx, idx + q.length)}</mark>
      {String(text).slice(idx + q.length)}
    </>
  );
}

// ---- Tree building -------------------------------------------------------

function buildTree(value, key = "", path = "", depth = 0) {
  // Return ARRAY at top level (no synthetic root).
  if (depth === 0 && value && typeof value === "object" && !Array.isArray(value)) {
    return Object.entries(value).map(([k, v]) => nodeFor(k, v, k, 0));
  }
  return [nodeFor(key, value, path, depth)];
}

function nodeFor(key, value, path, depth) {
  if (value === null) {
    return { key, path, depth, kind: "leaf", type: "null", value: "null" };
  }
  if (Array.isArray(value)) {
    const children = value.map((item, i) => {
      const childKey = `[${i}]`;
      const childPath = `${path}${childKey}`;
      if (item && typeof item === "object" && !Array.isArray(item)) {
        return {
          key: childKey, path: childPath, depth: depth + 1, kind: "object",
          children: Object.entries(item).map(([k, v]) => nodeFor(k, v, `${childPath}.${k}`, depth + 2))
        };
      }
      if (Array.isArray(item)) {
        return nodeFor(childKey, item, childPath, depth + 1);
      }
      return { key: childKey, path: childPath, depth: depth + 1, kind: "leaf", type: typeof item, value: String(item) };
    });
    return { key, path, depth, kind: "array", children };
  }
  if (typeof value === "object") {
    const children = Object.entries(value).map(([k, v]) => nodeFor(k, v, `${path}.${k}`, depth + 1));
    return { key, path, depth, kind: "object", children };
  }
  return { key, path, depth, kind: "leaf", type: typeof value, value: String(value) };
}

function flatRowsToTree(rows) {
  // legacy path — rebuild from dot-notation rows. Not used when json is supplied.
  return rows.filter(r => r.type !== "object").map((r, i) => ({
    key: r.key, path: r.key, depth: 0, kind: "leaf", type: r.type, value: r.value
  }));
}

function initialCollapsedSet(nodes) {
  // Collapse groups at depth >= 2 by default; keep depth 0 + 1 open.
  const set = new Set();
  function walk(ns) {
    for (const n of ns) {
      if (n.kind !== "leaf" && n.depth >= 2) set.add(n.path);
      if (n.children) walk(n.children);
    }
  }
  walk(nodes);
  return set;
}

function allGroupPaths(nodes) {
  const out = [];
  function walk(ns) { for (const n of ns) { if (n.kind !== "leaf") { out.push(n.path); walk(n.children || []); } } }
  walk(nodes);
  return out;
}

function countLeaves(nodes) {
  let n = 0;
  (function walk(ns) { for (const node of ns) { node.kind === "leaf" ? n++ : walk(node.children || []); } })(nodes);
  return n;
}

function countGroups(nodes) {
  let n = 0;
  (function walk(ns) { for (const node of ns) { if (node.kind !== "leaf") { n++; walk(node.children || []); } } })(nodes);
  return n;
}

Object.assign(window, { TableView });
