/**
 * FEAT-4: Aufbau und Erweiterung des Ahnenbaums + Konvertierung nach
 * react-d3-tree.
 *
 * Clientseitige Traversierung über die vorhandenen Endpunkte:
 * Tier → sein Wurf (litterId) → Vater/Mutter → rekursiv, tiefenbegrenzt
 * (Standard 4 Generationen, Board-Entscheidung). Tiefere Generationen werden
 * auf Wunsch nachgeladen (expandPedigree).
 *
 * Zyklenschutz: Steht ein Tier bereits auf dem Pfad zur Wurzel (defekte Daten:
 * „eigener Vorfahre“), wird die Rekursion dort beendet und der Knoten nicht
 * erweiterbar markiert.
 */
import type { RawNodeDatum } from 'react-d3-tree'
import type { Gerbil } from '../api/types'
import type { AnimalNode, PedigreeNode, PedigreePath, PedigreeSource, UnknownNode } from './types'

/** Standard-Generationstiefe der Erstanzeige (Board: UI 4 Generationen). */
export const DEFAULT_GENERATIONS = 4

function unknown(path: PedigreePath): UnknownNode {
  return { kind: 'unknown', path }
}

async function parentNode(
  source: PedigreeSource,
  id: string | null | undefined,
  path: PedigreePath,
  generationsLeft: number,
  ancestors: ReadonlySet<string>,
): Promise<PedigreeNode> {
  if (!id) return unknown(path)
  const gerbil = await source.getGerbil(id)
  if (!gerbil) return unknown(path)
  return buildAnimal(source, gerbil, path, generationsLeft, ancestors)
}

async function buildAnimal(
  source: PedigreeSource,
  gerbil: Gerbil,
  path: PedigreePath,
  generationsLeft: number,
  ancestors: ReadonlySet<string>,
): Promise<AnimalNode> {
  const isCycle = ancestors.has(gerbil.id)
  if (isCycle || generationsLeft <= 0) {
    // Ladegrenze (oder Zyklus): Eltern nicht laden. Erweiterbar nur, wenn es
    // einen Wurf gibt und kein Zyklus vorliegt.
    return { kind: 'animal', path, gerbil, expandable: !isCycle && gerbil.litterId != null }
  }

  if (!gerbil.litterId) {
    // Gründertier ohne erfassten Wurf: Eltern unbekannt. Platzhalter wahren
    // die Symmetrie der Ahnentafel.
    return {
      kind: 'animal',
      path,
      gerbil,
      father: unknown(path + 'v'),
      mother: unknown(path + 'm'),
      expandable: false,
    }
  }

  // Wurf laden; bei 404 (gelöschter Wurf) zählen die Eltern als unbekannt.
  const litter = await source.getLitter(gerbil.litterId)
  const nextAncestors = new Set(ancestors)
  nextAncestors.add(gerbil.id)
  const [father, mother] = await Promise.all([
    parentNode(source, litter?.fatherId, path + 'v', generationsLeft - 1, nextAncestors),
    parentNode(source, litter?.motherId, path + 'm', generationsLeft - 1, nextAncestors),
  ])
  return { kind: 'animal', path, gerbil, father, mother, expandable: false }
}

/**
 * Baut den Ahnenbaum für ein Wurzeltier. `null`, wenn das Tier selbst nicht
 * existiert (404).
 */
export async function buildPedigree(
  source: PedigreeSource,
  rootId: string,
  generations: number = DEFAULT_GENERATIONS,
): Promise<AnimalNode | null> {
  const root = await source.getGerbil(rootId)
  if (!root) return null
  return buildAnimal(source, root, '', generations, new Set())
}

/**
 * Lädt für den Knoten an `path` weitere `generations` Vorfahren-Generationen
 * nach und liefert eine NEUE Wurzel (unveränderte Äste werden strukturell
 * geteilt — geeignet für React-State).
 */
export async function expandPedigree(
  source: PedigreeSource,
  root: AnimalNode,
  path: PedigreePath,
  generations = 1,
): Promise<AnimalNode> {
  const next = await expandAt(source, root, path, new Set(), generations)
  return next as AnimalNode
}

async function expandAt(
  source: PedigreeSource,
  node: PedigreeNode,
  target: PedigreePath,
  ancestors: ReadonlySet<string>,
  generations: number,
): Promise<PedigreeNode> {
  if (node.kind === 'unknown') return node
  if (node.path === target) {
    return buildAnimal(source, node.gerbil, node.path, generations, ancestors)
  }
  if (!target.startsWith(node.path)) return node

  const branch = target.charAt(node.path.length) // 'v' | 'm'
  const nextAncestors = new Set(ancestors)
  nextAncestors.add(node.gerbil.id)
  if (branch === 'v' && node.father) {
    return { ...node, father: await expandAt(source, node.father, target, nextAncestors, generations) }
  }
  if (branch === 'm' && node.mother) {
    return { ...node, mother: await expandAt(source, node.mother, target, nextAncestors, generations) }
  }
  return node
}

/* ── Konvertierung für react-d3-tree ──────────────────────────── */

/**
 * Konvertiert den Ahnenbaum in die react-d3-tree-Struktur. Kinder eines
 * Knotens sind [Vater, Mutter] (Vater oben — klassische Ahnentafel-Ordnung).
 * Die Karten-Renderfunktion findet den vollen Knoten über `attributes.path`
 * (siehe collectNodes); RawNodeDatum.attributes erlaubt nur flache Primitive.
 */
export function toRawNodeDatum(node: PedigreeNode, unknownLabel: string): RawNodeDatum {
  if (node.kind === 'unknown') {
    return { name: unknownLabel, attributes: { path: node.path, kind: 'unknown' } }
  }
  const datum: RawNodeDatum = {
    name: node.gerbil.name,
    attributes: { path: node.path, kind: 'animal', expandable: node.expandable },
  }
  if (node.father && node.mother) {
    datum.children = [
      toRawNodeDatum(node.father, unknownLabel),
      toRawNodeDatum(node.mother, unknownLabel),
    ]
  }
  return datum
}

/** Index aller Knoten nach Position (für die Karten-Renderfunktion). */
export function collectNodes(root: PedigreeNode): Map<PedigreePath, PedigreeNode> {
  const map = new Map<PedigreePath, PedigreeNode>()
  const walk = (node: PedigreeNode): void => {
    map.set(node.path, node)
    if (node.kind === 'animal') {
      if (node.father) walk(node.father)
      if (node.mother) walk(node.mother)
    }
  }
  walk(root)
  return map
}

/**
 * Alle Plätze einer Generation in Ahnentafel-Ordnung (Vater-Linie zuerst).
 * Länge = 2^generation; `null` = Platz nicht belegt/geladen (Druckansicht
 * rendert dafür leere Zellen).
 */
export function ancestorsAt(root: PedigreeNode, generation: number): (PedigreeNode | null)[] {
  let slots: (PedigreeNode | null)[] = [root]
  for (let g = 0; g < generation; g++) {
    slots = slots.flatMap((node) =>
      node && node.kind === 'animal' ? [node.father ?? null, node.mother ?? null] : [null, null],
    )
  }
  return slots
}
