import { describe, it, expect } from "vitest";
import { sortHierarchical } from "./CategoryCombobox";
import type { Category } from "../../shared/types";

function cat(
  id: number,
  name: string,
  parentId: number | null,
  sortOrder: number,
): Category {
  return {
    id,
    name,
    parent_id: parentId ?? undefined,
    type: "expense",
    is_active: true,
    is_inputable: true,
    sort_order: sortOrder,
    created_at: "",
  };
}

const displayName = (c: Category) => c.name;

describe("sortHierarchical", () => {
  it("returns [] for empty input", () => {
    expect(sortHierarchical([], displayName)).toEqual([]);
  });

  it("orders a single root before its children (parent-first DFS)", () => {
    const input = [
      cat(10, "Paie", 1, 1),
      cat(1, "Revenus", null, 1),
      cat(11, "Autres revenus", 1, 2),
    ];
    const ordered = sortHierarchical(input, displayName).map((c) => c.id);
    expect(ordered).toEqual([1, 10, 11]);
  });

  it("keeps each root fully grouped with its sub-tree, roots ordered by sort_order", () => {
    // Reproduces the reported bug: a flat list coming back globally ordered by
    // (sort_order, name) would interleave roots and children that share the
    // same sort_order. DFS must un-scramble that.
    const input: Category[] = [
      // Roots
      cat(1, "Revenus", null, 1),
      cat(2, "Dépenses récurrentes", null, 2),
      // Children of Revenus (sort_order 1 & 2 within that parent)
      cat(10, "Paie", 1, 1),
      cat(11, "Autres revenus", 1, 2),
      // Children of Dépenses récurrentes (sort_order 1 & 2 within that parent)
      cat(20, "Loyer", 2, 1),
      cat(21, "Électricité", 2, 2),
    ];

    // Simulate the SQL artifact: global sort by (sort_order, name), which is
    // exactly what triggered the bug.
    const scrambled = [...input].sort((a, b) => {
      if (a.sort_order !== b.sort_order) return a.sort_order - b.sort_order;
      return a.name.localeCompare(b.name);
    });

    const ordered = sortHierarchical(scrambled, displayName).map((c) => c.id);
    expect(ordered).toEqual([1, 10, 11, 2, 20, 21]);
  });

  it("orders siblings by sort_order, then by display name as tiebreaker", () => {
    const input: Category[] = [
      cat(1, "Root", null, 1),
      cat(12, "Beta", 1, 5),
      cat(10, "Zulu", 1, 5), // same sort_order as 12 -> name tiebreak
      cat(11, "Alpha", 1, 1),
    ];
    const ordered = sortHierarchical(input, displayName).map((c) => c.id);
    // Under Root, order should be: Alpha(sort=1), Beta(sort=5,B<Z), Zulu(sort=5)
    expect(ordered).toEqual([1, 11, 12, 10]);
  });

  it("handles 3-level hierarchies (parent -> intermediate -> leaf)", () => {
    const input: Category[] = [
      cat(2, "Dépenses", null, 2),
      cat(31, "Assurances", 2, 12),
      cat(310, "Assurance-auto", 31, 1),
      cat(311, "Assurance-habitation", 31, 2),
      cat(32, "Pharmacie", 2, 13),
    ];
    const ordered = sortHierarchical(input, displayName).map((c) => c.id);
    expect(ordered).toEqual([2, 31, 310, 311, 32]);
  });

  it("appends orphans (parent filtered out / missing) at the end", () => {
    const input: Category[] = [
      cat(1, "Revenus", null, 1),
      cat(10, "Paie", 1, 1),
      // Orphan: parent_id 999 not in the set
      cat(500, "Orphan", 999, 1),
    ];
    const ordered = sortHierarchical(input, displayName).map((c) => c.id);
    expect(ordered).toEqual([1, 10, 500]);
  });

  it("is stable (does not duplicate) when called with already-ordered input", () => {
    const input: Category[] = [
      cat(1, "Revenus", null, 1),
      cat(10, "Paie", 1, 1),
      cat(2, "Dépenses", null, 2),
      cat(20, "Loyer", 2, 1),
    ];
    const once = sortHierarchical(input, displayName);
    const twice = sortHierarchical(once, displayName);
    expect(twice.map((c) => c.id)).toEqual(once.map((c) => c.id));
    expect(twice).toHaveLength(input.length);
  });
});
