import type { Category } from "../../shared/types";
import type { TaxonomyNode } from "../../services/categoryTaxonomyService";

/**
 * Flatten the v1 taxonomy into `Category`-shaped rows for the migration target
 * picker, keeping ONLY the leaves — the inputable end-categories a transaction
 * can actually be filed under. Intermediate parents (grouping buckets) are
 * dropped so the type-ahead offers real targets only, never a bucket.
 *
 * Leaves are emitted in depth-first reading order and carry a running
 * `sort_order` with `parent_id` undefined, so `CategoryCombobox` renders them as
 * one flat, un-indented list in taxonomy order (leaves stay grouped under their
 * original parent) instead of re-scrambling them by the per-parent sort_order.
 *
 * Every leaf id is `is_inputable` in the DB after the migration, so any pick is
 * FK-safe.
 *
 * Edge case: a low-confidence default can point a v2 category at a NON-leaf
 * parent (e.g. v2 "Jeux, Films & Livres" -> v1 "Divertissement" #1710). Such a
 * value is absent from this leaves-only list; `MappingRow` re-injects it for that
 * single row (via `findTaxonomyCategory`) so the input still shows the current
 * suggestion, while new picks stay leaves-only.
 *
 * Pure helper — no i18n, no DB, no React.
 */
export function taxonomyToComboboxCategories(roots: TaxonomyNode[]): Category[] {
  const out: Category[] = [];
  let order = 0;
  const walk = (node: TaxonomyNode): void => {
    if (node.children.length === 0) {
      out.push({
        id: node.id,
        name: node.name,
        parent_id: undefined,
        color: node.color,
        type: node.type,
        is_active: true,
        is_inputable: true,
        sort_order: order++,
        i18n_key: node.i18n_key,
        created_at: "",
      });
      return;
    }
    for (const child of node.children) walk(child);
  };
  for (const root of roots) walk(root);
  return out;
}

/**
 * Resolve any taxonomy node (leaf OR parent) by id into the `Category` shape,
 * for DISPLAY of a current non-leaf target that the leaves-only picker omits.
 * `sort_order` is pushed to the end so, when injected, the node sorts last.
 * Returns null when the id is absent from the taxonomy. Pure helper.
 */
export function findTaxonomyCategory(
  roots: TaxonomyNode[],
  id: number,
): Category | null {
  const stack: TaxonomyNode[] = [...roots];
  while (stack.length > 0) {
    const node = stack.pop()!;
    if (node.id === id) {
      return {
        id: node.id,
        name: node.name,
        parent_id: undefined,
        color: node.color,
        type: node.type,
        is_active: true,
        is_inputable: node.children.length === 0,
        sort_order: Number.MAX_SAFE_INTEGER,
        i18n_key: node.i18n_key,
        created_at: "",
      };
    }
    for (const child of node.children) stack.push(child);
  }
  return null;
}

/**
 * Build the target-picker option list for one migration row.
 *
 * Returns the leaves-only `leafOptions` unchanged when the row's current target
 * is null or already a leaf (the common case — same array reference, so the
 * combobox never re-renders needlessly).
 *
 * When the target is a NON-leaf parent absent from the leaves list (a
 * low-confidence default like "Divertissement" #1710), the resolved parent is
 * appended so the combobox can DISPLAY it as the current value. It stays visible
 * (and, since `CategoryCombobox` renders every option, clickable) but selecting
 * it is a no-op: the row's resolve handler only accepts ids present in the
 * leaves list, so re-picking the parent changes nothing and the user is steered
 * to a leaf. New picks that take effect are therefore always leaves.
 *
 * Pure helper — no React, no i18n, no DB.
 */
export function comboboxCategoriesForTarget(
  leafOptions: Category[],
  targetId: number | null,
  resolveTarget: (id: number) => Category | null,
): Category[] {
  if (targetId == null || leafOptions.some((c) => c.id === targetId)) {
    return leafOptions;
  }
  const current = resolveTarget(targetId);
  return current ? [...leafOptions, current] : leafOptions;
}
