import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { ChevronRight, ChevronDown } from "lucide-react";
import type { TaxonomyNode } from "../../services/categoryTaxonomyService";
import type { CollapseAccessors } from "../../utils/collapsibleRows";

/**
 * Collapse-state accessors for the taxonomy tree, shared by every consumer that
 * drives it through `useCollapsibleGroups` (the guide page and the migration
 * wizard's Discover step) — issue #290. Only keyOf/isParent are consulted: the
 * tree renders recursively and gates each node on `isCollapsed(node)`, so it never
 * calls `visibleRows`; parentKeyOf/depthOf are unused stubs (TaxonomyNode carries
 * no parent_id).
 */
export const TAXONOMY_COLLAPSE_ACCESSORS: CollapseAccessors<TaxonomyNode> = {
  keyOf: (node) => `p:${node.id}`,
  parentKeyOf: () => null,
  isParent: (node) => node.children.length > 0,
  depthOf: () => 0,
};

interface CategoryTaxonomyTreeProps {
  nodes: TaxonomyNode[];
  isCollapsed: (node: TaxonomyNode) => boolean;
  onToggle: (node: TaxonomyNode) => void;
  searchQuery: string;
}

interface NodeRowProps {
  node: TaxonomyNode;
  depth: number;
  isCollapsed: (node: TaxonomyNode) => boolean;
  onToggle: (node: TaxonomyNode) => void;
  visibleIds: Set<number> | null;
}

function NodeRow({
  node,
  depth,
  isCollapsed,
  onToggle,
  visibleIds,
}: NodeRowProps) {
  const { t } = useTranslation();
  const label = t(node.i18n_key, { defaultValue: node.name });
  const hasChildren = node.children.length > 0;
  const isExpanded = !isCollapsed(node);

  // Filter children by visibility set (search mode) if provided.
  const visibleChildren = useMemo(() => {
    if (visibleIds === null) return node.children;
    return node.children.filter((child) => visibleIds.has(child.id));
  }, [node.children, visibleIds]);

  const typeLabel =
    node.type === "income"
      ? t("categoriesSeed.guidePage.type.income")
      : node.type === "transfer"
        ? t("categoriesSeed.guidePage.type.transfer")
        : t("categoriesSeed.guidePage.type.expense");

  const tooltipText = [
    `${t("categoriesSeed.guidePage.tooltip.key")}: ${node.i18n_key}`,
    `${t("categoriesSeed.guidePage.tooltip.type")}: ${typeLabel}`,
    `${t("categoriesSeed.guidePage.tooltip.id")}: ${node.id}`,
  ].join("\n");

  // On screen: show children only if expanded.
  // In print: @media print in styles.css overrides display:none to show everything.
  const childrenHidden = !isExpanded;

  return (
    <li className="taxonomy-node">
      <div
        className="flex items-center gap-2 py-1.5 px-2 rounded-md hover:bg-[var(--muted)] transition-colors"
        style={{ paddingLeft: `${depth * 1.25 + 0.5}rem` }}
        title={tooltipText}
      >
        {hasChildren ? (
          <button
            type="button"
            onClick={() => onToggle(node)}
            aria-label={
              isExpanded
                ? t("categoriesSeed.guidePage.collapseAll")
                : t("categoriesSeed.guidePage.expandAll")
            }
            aria-expanded={isExpanded}
            className="print:hidden shrink-0 text-[var(--muted-foreground)] hover:text-[var(--foreground)] transition-colors"
          >
            {isExpanded ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
          </button>
        ) : (
          <span className="shrink-0 w-[14px]" aria-hidden="true" />
        )}

        <span
          className="shrink-0 inline-block h-3 w-3 rounded-sm border border-[var(--border)]"
          style={{ backgroundColor: node.color }}
          aria-hidden="true"
        />

        <span
          className={
            depth === 0
              ? "font-semibold text-[var(--foreground)]"
              : depth === 1
                ? "font-medium text-[var(--foreground)]"
                : "text-[var(--foreground)]"
          }
        >
          {label}
        </span>

        {depth === 0 && (
          <span className="ml-2 text-xs text-[var(--muted-foreground)]">
            ({node.children.length})
          </span>
        )}
      </div>

      {visibleChildren.length > 0 && (
        <ul
          className={`list-none m-0 p-0 taxonomy-children ${
            childrenHidden ? "taxonomy-children-collapsed" : ""
          }`}
        >
          {visibleChildren.map((child) => (
            <NodeRow
              key={child.id}
              node={child}
              depth={depth + 1}
              isCollapsed={isCollapsed}
              onToggle={onToggle}
              visibleIds={visibleIds}
            />
          ))}
        </ul>
      )}
    </li>
  );
}

/**
 * Build the set of node IDs whose visible subtree matches the query.
 * A node is kept if its translated name contains the query OR any of its descendants match.
 */
export function collectVisibleIds(
  roots: TaxonomyNode[],
  normalizedQuery: string,
  translate: (key: string, fallback: string) => string,
): Set<number> {
  const visible = new Set<number>();
  if (normalizedQuery.length === 0) {
    const walk = (n: TaxonomyNode) => {
      visible.add(n.id);
      n.children.forEach(walk);
    };
    roots.forEach(walk);
    return visible;
  }

  const walk = (node: TaxonomyNode): boolean => {
    const label = translate(node.i18n_key, node.name);
    const selfMatches = normalize(label).includes(normalizedQuery);
    let anyChildMatches = false;
    for (const child of node.children) {
      if (walk(child)) anyChildMatches = true;
    }
    if (selfMatches || anyChildMatches) {
      visible.add(node.id);
      return true;
    }
    return false;
  };

  roots.forEach(walk);
  return visible;
}

// Case-and-accent insensitive normalization for search.
export function normalize(s: string): string {
  return s
    .toLowerCase()
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "");
}

export default function CategoryTaxonomyTree({
  nodes,
  isCollapsed,
  onToggle,
  searchQuery,
}: CategoryTaxonomyTreeProps) {
  const { t } = useTranslation();
  const normalizedQuery = normalize(searchQuery.trim());

  const visibleIds = useMemo(() => {
    if (normalizedQuery.length === 0) return null;
    return collectVisibleIds(nodes, normalizedQuery, (key, fallback) =>
      t(key, { defaultValue: fallback }),
    );
  }, [nodes, normalizedQuery, t]);

  const visibleRoots =
    visibleIds === null ? nodes : nodes.filter((r) => visibleIds.has(r.id));

  if (visibleRoots.length === 0) {
    return (
      <p className="text-sm text-[var(--muted-foreground)] p-4">
        {t("categoriesSeed.guidePage.noResults")}
      </p>
    );
  }

  return (
    <ul className="list-none m-0 p-0">
      {visibleRoots.map((root) => (
        <NodeRow
          key={root.id}
          node={root}
          depth={0}
          isCollapsed={isCollapsed}
          onToggle={onToggle}
          visibleIds={visibleIds}
        />
      ))}
    </ul>
  );
}
