import { Couple, CouplesList } from "../api/genealogy/CoupleApi";
import { Member, MembersList, dateTimestamp } from "../api/genealogy/MemberApi";

export interface CoupleInformation {
  couple: Couple;
  member: Member;
  down: FamilyTreeNode[];
}

export interface FamilyTreeNode {
  down?: FamilyTreeNode[];
  member: Member;
  couples?: CoupleInformation[];
}

export function buildAscendingTree(
  memberId: number,
  members: MembersList,
  couples: CouplesList
): FamilyTreeNode {
  const member = members.get(memberId)!;

  const parents = [];
  if (member.father)
    parents.push(buildAscendingTree(member.father, members, couples));

  if (member.mother)
    parents.push(buildAscendingTree(member.mother, members, couples));

  return {
    member: member,
    down: parents,
  };
}

export function buildDescendingTree(
  memberId: number,
  members: MembersList,
  couples: CouplesList
): FamilyTreeNode {
  const member = members.get(memberId)!;

  const children = members.children(member.id);

  // Process user couples
  const member_couples: CoupleInformation[] = [];
  for (const c of couples.getAllOf(member)) {
    // Currently, couples with missing pair information are not supported
    const pairId = c.otherPersonID(member.id);
    if (!pairId) continue;
    const pair = members.get(pairId);

    const c_children: Member[] = [];

    // Determine children associated with each couple
    for (let index = 0; index < children.length; ) {
      const c = children[index];
      if (c.mother !== pairId && c.father !== pairId) {
        index++;
        continue;
      }

      children.splice(index, 1);
      c_children.push(c);
    }

    member_couples.push({
      couple: c,
      member: pair!,
      down: sortChildren(
        c_children.map((c) => buildDescendingTree(c.id, members, couples))
      ),
    });
  }

  return {
    member: member,
    down: sortChildren(
      children.map((c) => buildDescendingTree(c.id, members, couples))
    ),
    couples: member_couples,
  };
}

/**
 * Sort family tree children per date of birth
 */
function sortChildren(n: FamilyTreeNode[]): FamilyTreeNode[] {
  n.sort(
    (a, b) =>
      dateTimestamp(a.member.dateOfBirth) - dateTimestamp(b.member.dateOfBirth)
  );
  return n;
}

/**
 * Compute family tree height
 */
export function treeHeight(node: FamilyTreeNode): number {
  let res =
    node.down?.reduce((prev, node) => Math.max(prev, treeHeight(node)), 0) ?? 0;

  node.couples?.forEach(
    (c) =>
      (res = Math.max(
        res,
        c.down.reduce((prev, node) => Math.max(prev, treeHeight(node)), 0)
      ))
  );

  return res + 1;
}

/**
 * Compute family tree width
 */
export function treeWidth(node: FamilyTreeNode): number {
  const values = new Array(treeHeight(node)).fill(0);
  treeWidthRecurse(node, values, 0);
  return Math.max(...values);
}

function treeWidthRecurse(node: FamilyTreeNode, vals: number[], level: number) {
  vals[level] +=
    1 + (node.couples?.length ?? 0) + ((node.down?.length ?? 0) > 0 ? 1 : 0);

  node.down?.forEach((n) => treeWidthRecurse(n, vals, level + 1));

  node.couples?.forEach((c) =>
    c.down.forEach((n) => treeWidthRecurse(n, vals, level + 1))
  );
}

/**
 * Get the list of members to be shown in a tree,
 * depending of a specified depth
 */
export function getAvailableMembers(
  t: FamilyTreeNode,
  depth: number
): Set<number> {
  const s = new Set<number>();
  getAvailableMembersRecurse(t, depth, s);
  return s;
}

function getAvailableMembersRecurse(
  t: FamilyTreeNode,
  depth: number,
  s: Set<number>
) {
  if (depth < 1) return;

  s.add(t.member.id);

  t.couples?.forEach((c) => {
    s.add(c.member.id);
    c.down.forEach((e) => getAvailableMembersRecurse(e, depth - 1, s));
  });

  t.down?.forEach((e) => getAvailableMembersRecurse(e, depth - 1, s));
}
