import { useMemo, useState } from 'react';
import { Icon } from '@iconify/react';
import type { ObjectTreeNode } from '@tts/extract';
import { iconsForObject } from './objectIcons';

interface Props {
  nodes: ObjectTreeNode[];
  selectedPath: string | null;
  onSelect: (path: string) => void;
}

export default function ObjectTree({ nodes, selectedPath, onSelect }: Props) {
  const [highlighted, setHighlighted] = useState<Set<string>>(() => new Set());

  const types = useMemo(() => collectTypes(nodes), [nodes]);

  // When nothing is highlighted, every type is shown. Otherwise only the
  // highlighted types are visible.
  const visibleNodes = useMemo(
    () => filterTree(nodes, highlighted),
    [nodes, highlighted],
  );

  const toggleType = (name: string) =>
    setHighlighted((prev) => {
      const next = new Set(prev);
      if (next.has(name)) next.delete(name);
      else next.add(name);
      return next;
    });

  const clearAll = () => setHighlighted(new Set());

  return (
    <div className="space-y-2">
      {types.length > 0 && (
        <div className="flex flex-wrap gap-1 border-b border-zinc-800 pb-2">
          {types.map(({ name, count }) => {
            const active = highlighted.has(name);
            return (
              <button
                key={name}
                onClick={() => toggleType(name)}
                aria-pressed={active}
                title={`${active ? 'Unhighlight' : 'Highlight'} ${name}`}
                className={`inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs transition-colors ${
                  active
                    ? 'border-zinc-300 bg-zinc-700 text-zinc-100'
                    : 'border-zinc-800 text-zinc-400 hover:border-zinc-600 hover:text-zinc-200'
                }`}
              >
                <span className="inline-flex items-center gap-0.5">
                  {iconsForObject(name).map((icon) => (
                    <Icon key={icon} icon={icon} className="h-3.5 w-3.5" />
                  ))}
                </span>
                <span>{name}</span>
                <span className="text-zinc-500">{count}</span>
              </button>
            );
          })}
          {highlighted.size > 0 && (
            <button
              onClick={clearAll}
              className="ml-auto inline-flex items-center rounded px-1.5 py-0.5 text-xs text-zinc-500 hover:text-zinc-200"
            >
              Clear
            </button>
          )}
        </div>
      )}
      <ul className="space-y-0.5">
        {visibleNodes.map(({ node, path }) => (
          <TreeNode
            key={path}
            node={node}
            depth={0}
            path={path}
            selectedPath={selectedPath}
            onSelect={onSelect}
          />
        ))}
      </ul>
    </div>
  );
}

function TreeNode({
  node,
  depth,
  path,
  selectedPath,
  onSelect,
}: {
  node: ObjectTreeNode;
  depth: number;
  /** Index path from the root, used as a stable unique key and selection id. */
  path: string;
  selectedPath: string | null;
  onSelect: (path: string) => void;
}) {
  // Selection is keyed by the node's unique index path, not its GUID: cards in
  // a deck frequently share a GUID (the deck's), so GUID-based selection would
  // highlight and render the wrong card.
  const selected = path === selectedPath;
  const hasChildren = node.children.length > 0;
  const [expanded, setExpanded] = useState(false);
  return (
    <li>
      <div
        className={`flex items-center rounded pr-2 ${
          selected
            ? 'bg-zinc-700 text-zinc-100'
            : 'text-zinc-300 hover:bg-zinc-800 hover:text-zinc-100'
        }`}
        style={{ paddingLeft: `${depth * 16 + 4}px` }}
      >
        {hasChildren ? (
          <button
            onClick={() => setExpanded((e) => !e)}
            aria-label={expanded ? 'Collapse' : 'Expand'}
            className="flex h-6 w-6 shrink-0 items-center justify-center rounded text-zinc-500 hover:text-zinc-200"
          >
            <span
              className={`inline-block text-xs transition-transform ${expanded ? 'rotate-90' : ''}`}
            >
              ▸
            </span>
          </button>
        ) : (
          <span className="h-6 w-6 shrink-0" />
        )}
        <button
          onClick={() => onSelect(path)}
          className="block min-w-0 flex-1 truncate py-1 text-left text-sm"
        >
          <span
            title={node.object.Name}
            className="mr-1.5 inline-flex items-center gap-0.5 align-middle text-zinc-400"
          >
            {iconsForObject(node.object.Name).map((icon) => (
              <Icon key={icon} icon={icon} className="h-4 w-4" />
            ))}
          </span>
          <span className="truncate">{node.label}</span>
        </button>
      </div>
      {hasChildren && expanded && (
        <ul>
          {node.children.map((child, index) => (
            <TreeNode
              key={`${path}-${index}`}
              node={child}
              depth={depth + 1}
              path={`${path}-${index}`}
              selectedPath={selectedPath}
              onSelect={onSelect}
            />
          ))}
        </ul>
      )}
    </li>
  );
}

/** Distinct object types present in the tree, with a count of each. */
function collectTypes(nodes: ObjectTreeNode[]): { name: string; count: number }[] {
  const counts = new Map<string, number>();
  const visit = (node: ObjectTreeNode) => {
    counts.set(node.object.Name, (counts.get(node.object.Name) ?? 0) + 1);
    node.children.forEach(visit);
  };
  nodes.forEach(visit);
  return [...counts.entries()]
    .map(([name, count]) => ({ name, count }))
    .sort((a, b) => a.name.localeCompare(b.name));
}

/**
 * Keep a node when its type is highlighted or any descendant survives, so the
 * containment hierarchy is preserved and highlighted parents still lead to
 * highlighted children. With nothing highlighted, every node is kept.
 *
 * Each result carries the node's original index path into the full (unfiltered)
 * tree, so selection stays stable regardless of filtering.
 */
function filterTree(
  nodes: ObjectTreeNode[],
  highlighted: Set<string>,
  prefix = '',
): { node: ObjectTreeNode; path: string }[] {
  const result: { node: ObjectTreeNode; path: string }[] = [];
  nodes.forEach((node, index) => {
    const path = prefix ? `${prefix}-${index}` : `${index}`;
    const children = filterTree(node.children, highlighted, path);
    const visible =
      highlighted.size === 0 ||
      highlighted.has(node.object.Name) ||
      children.length > 0;
    if (visible) {
      result.push({
        node:
          children.length > 0
            ? { ...node, children: children.map((c) => c.node) }
            : node,
        path,
      });
    }
  });
  return result;
}