import React from "react";
import f3, { f3Data } from "family-chart";
import "./family-chart.css";
import { FamilyTreeNode } from "../../utils/family_tree";
import { Member, fmtDate } from "../../api/MemberApi";
import { Couple } from "../../api/CoupleApi";
import { useDarkTheme } from "../../hooks/context_providers/DarkThemeProvider";
import { IconButton } from "@mui/material";
import PictureAsPdfIcon from "@mui/icons-material/PictureAsPdf";
import { jsPDF } from "jspdf";
import "svg2pdf.js";
import { getAllIndexes } from "../../utils/string_utils";

export function ComplexFamilyTree(p: {
  tree: FamilyTreeNode;
  isUp: boolean;
}): React.ReactElement {
  const darkTheme = useDarkTheme();

  const applyTree = (container: HTMLDivElement) => {
    if (!container) return;

    const store = f3.createStore({
        data: treeToF3Data(p.tree, p.isUp),
        node_separation: 250,
        level_separation: 150,
      }),
      view = f3.d3AnimationView({
        store,
        cont: container,
      }),
      Card = f3.elements.Card({
        store,
        svg: view.svg,
        card_dim: {
          w: 230,
          h: 70,
          text_x: 75,
          text_y: 15,
          img_w: 60,
          img_h: 60,
          img_x: 5,
          img_y: 5,
        },
        card_display: [
          (d) =>
            `${d.data.first_name || ""} ${d.data.last_name || ""} ${
              d.data.dead ? "✝" : ""
            }`,
          (d) => {
            let birthDeath = [];
            if (d.data.birthday) birthDeath.push(d.data.birthday);
            if (d.data.deathday) birthDeath.push(d.data.deathday);

            let s = birthDeath.join(" -> ");

            if (d.data.wedding_state || d.data.dateOfWedding) {
              let weddingInfo = [];
              if (d.data.wedding_state) weddingInfo.push(d.data.wedding_state);
              if (d.data.dateOfWedding)
                weddingInfo.push("Mariage : " + d.data.dateOfWedding);
              s += `</tspan> <tspan x="0" dy="14" font-size="10">${weddingInfo.join(
                " - "
              )}`;
            }

            return s;
          },
        ],
        mini_tree: true,
        link_break: false,
      });

    view.setCard(Card);
    store.setOnUpdate((props) => view.update(props || {}));
    store.update.tree({ initial: false, transition_time: 0 });
  };

  const exportPDF = async () => {
    const docWidth = treeWidth(p.tree) * 60;
    const docHeight = treeHeight(p.tree) * 41;

    const doc = new jsPDF({
      orientation: "l",
      format: [docHeight, docWidth],
    });

    // Clone the SVG to manipulate it
    const container = document.createElement("div");
    container.classList.add("f3", "f3-export");
    container.style.width = docWidth + "px";
    container.style.height = docHeight + "px";
    document.body.appendChild(container);
    applyTree(container);

    const target = container.children[0];

    await new Promise((res) => setTimeout(() => res(null), 100));

    // SVG manipulations (adaptations to export)
    let dstSVG = target.innerHTML.replaceAll(
      `<path class="link" fill="none" stroke="#fff"`,
      `<path class="link" fill="none" stroke="#000"`
    );

    dstSVG = dstSVG.replaceAll(
      `class="card-body-rect"`,
      `class="card-body-rect" fill="white"`
    );

    dstSVG = dstSVG.replaceAll(
      `class="text-overflow-mask"`,
      `class="text-overflow-mask" fill="transparent"`
    );

    dstSVG = dstSVG.replaceAll(`>UNKNOWN<`, `fill="#000">INCONNU<`);

    dstSVG = dstSVG.replaceAll(
      `class="card-outline`,
      `fill="transparent" class="card-outline`
    );

    dstSVG = dstSVG.replaceAll("✝", " ");

    let womanTiles = getAllIndexes(dstSVG, "card-female");
    for (const i of womanTiles) {
      dstSVG =
        dstSVG.substring(0, i) +
        dstSVG.substring(i + 1).replace(`fill="white"`, `fill="#ffb6c1"`);
    }

    let manTiles = getAllIndexes(dstSVG, "card-male");
    for (const i of manTiles) {
      dstSVG =
        dstSVG.substring(0, i) +
        dstSVG.substring(i + 1).replace(`fill="white"`, `fill="#add8e6"`);
    }

    //navigator.clipboard.writeText(dstSVG);
    target.innerHTML = dstSVG;

    await doc.svg(target, {
      height: docHeight,
      width: docWidth,
    });

    container.remove();

    // Save the created pdf
    doc.save("myPDF.pdf");
  };

  return (
    <div>
      <div style={{ textAlign: "right" }}>
        <IconButton onClick={exportPDF}>
          <PictureAsPdfIcon />
        </IconButton>
      </div>
      <div
        className={`f3 ${darkTheme.enabled ? "f3-dark" : "f3-light"}`}
        id="FamilyChart"
        ref={applyTree}
      ></div>
    </div>
  );
}

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;
}

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 ? 1 : 0);

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

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

function treeToF3Data(node: FamilyTreeNode, isUp: boolean): f3Data[] {
  const availableMembers = new Set<number>();
  getAvailableMembers(node, availableMembers);

  const list: f3Data[] = [];
  if (isUp) treeToF3DataUpRecurse(node, list, availableMembers);
  else treeToF3DataDownRecurse(node, list, availableMembers);
  return list;
}

function getAvailableMembers(t: FamilyTreeNode, s: Set<number>) {
  s.add(t.member.id);

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

  t.down?.forEach((e) => getAvailableMembers(e, s));
}

function memberData(m: Member, c?: Couple): f3.f3DataData {
  return {
    first_name: m.first_name ?? "_",
    last_name: m.last_name ?? "_",
    gender: m.sex ?? "M",
    avatar: m.thumbnailURL ?? undefined,
    dead: m.dead,
    birthday: m.dateOfBirth ? fmtDate(m.dateOfBirth) : undefined,
    deathday: m.dateOfDeath ? fmtDate(m.dateOfDeath) : undefined,
    wedding_state: c?.stateFr,
    dateOfWedding: c?.dateOfWedding ? fmtDate(c?.dateOfWedding) : undefined,
  };
}

function treeToF3DataUpRecurse(
  node: FamilyTreeNode,
  array: f3Data[],
  availableMembers: Set<number>,
  child?: number,
  spouses?: number[]
) {
  array.push({
    data: memberData(node.member),
    id: node.member.id.toString(),
    rels: {
      father:
        node.member.father && availableMembers.has(node.member.father)
          ? node.member.father.toString()
          : undefined,
      mother:
        node.member.mother && availableMembers.has(node.member.mother)
          ? node.member.mother.toString()
          : undefined,

      spouses: spouses
        ?.filter((c) => c !== node.member.id)
        .map((c) => c.toString()),
      children: child ? [child.toString()] : undefined,
    },
  });

  const parentSpouses = node.down?.map((c) => c.member.id);

  node.down?.forEach((d) =>
    treeToF3DataUpRecurse(
      d,
      array,
      availableMembers,
      node.member.id,
      parentSpouses
    )
  );
}

function treeToF3DataDownRecurse(
  node: FamilyTreeNode,
  array: f3Data[],
  availableMembers: Set<number>
) {
  // Get all members ids
  const children = node?.down?.map((c) => c.member.id.toString()) ?? [];
  node.couples?.map((c) =>
    c.down.forEach((m) => children.push(m.member.id.toString()))
  );

  array.push({
    data: memberData(node.member),
    id: node.member.id.toString(),
    rels: {
      father:
        node.member.father && availableMembers.has(node.member.father)
          ? node.member.father.toString()
          : undefined,
      mother:
        node.member.mother && availableMembers.has(node.member.mother)
          ? node.member.mother.toString()
          : undefined,

      spouses: node.couples?.map((c) => c.member.id.toString()),
      children: children,
    },
  });

  node?.down?.forEach((e) =>
    treeToF3DataDownRecurse(e, array, availableMembers)
  );

  if (node.couples) {
    for (const c of node.couples) {
      array.push({
        data: memberData(c.member, c.couple),
        id: c.member.id.toString(),
        rels: {
          father:
            c.member.father && availableMembers.has(c.member.father)
              ? c.member.father.toString()
              : undefined,
          mother:
            c.member.mother && availableMembers.has(c.member.mother)
              ? c.member.mother.toString()
              : undefined,
          spouses: [node.member.id.toString()],
          children: c.down.map((c) => c.member.id.toString()),
        },
      });

      c.down.forEach((e) =>
        treeToF3DataDownRecurse(e, array, availableMembers)
      );
    }
  }
}
