import { mdiXml } from "@mdi/js";
import Icon from "@mdi/react";
import PictureAsPdfIcon from "@mui/icons-material/PictureAsPdf";
import { IconButton, Tooltip } from "@mui/material";
import f3, { f3Data } from "family-chart";
import { jsPDF } from "jspdf";
import React from "react";
import "svg2pdf.js";
import { Couple } from "../../api/CoupleApi";
import { Member, fmtDate } from "../../api/MemberApi";
import { useDarkTheme } from "../../hooks/context_providers/DarkThemeProvider";
import {
  FamilyTreeNode,
  getAvailableMembers,
  treeHeight,
  treeWidth,
} from "../../utils/family_tree";
import { downloadBlob } from "../../utils/files_utils";
import "./family-chart.css";

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

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

    const store = f3.createStore({
      data: treeToF3Data(p.tree, p.isUp, p.depth),
      node_separation: 250,
      level_separation: 150,
    });
    const view = f3.d3AnimationView({
      store,
      cont: container,
    });
    const Card = f3.elements.Card({
      store,
      svg: view.svg,
      card_dim: {
        w: 210,
        h: 120,
        text_x: 5,
        text_y: 75,
        img_w: 60,
        img_h: 70,
        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,
    });

    // Patch generated card
    const PatchedCard: f3.F3CardBuilder = (p) => {
      const res = Card(p);

      // Patch card colors for PDF export
      res
        .querySelector(".card-male")
        ?.querySelector(".card-body-rect")
        ?.setAttribute("fill", "#add8e6");
      res
        .querySelector(".card-female")
        ?.querySelector(".card-body-rect")
        ?.setAttribute("fill", "#ffb6c1");

      return res;
    };

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

  const doExport = async (onlySVG: boolean) => {
    const docWidth = treeWidth(p.tree) * 65;
    const docHeight = treeHeight(p.tree) * 60;
    console.info(`Tree w=${treeWidth(p.tree)} h=${treeHeight(p.tree)}`);

    // 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="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("✝", " ");

    // Download in SVG format
    if (onlySVG) {
      // Fix background color (first rect background)
      dstSVG = dstSVG.replace(
        `fill="transparent"></rect>`,
        `fill="white"></rect>`
      );

      const blob = new Blob([`<svg>${dstSVG}</svg>`], {
        type: "image/svg+xml",
      });

      downloadBlob(blob, "ArbreGenealogique.svg");

      return;
    }

    // Download in PDF format
    //navigator.clipboard.writeText(dstSVG);
    target.innerHTML = dstSVG;

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

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

    container.remove();

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

  const exportPDF = () => doExport(false);

  const exportSVG = () => doExport(true);

  return (
    <div>
      <div style={{ textAlign: "right" }}>
        <Tooltip title="Exporter le graphique au format PDF">
          <IconButton onClick={exportPDF}>
            <PictureAsPdfIcon />
          </IconButton>
        </Tooltip>
        <Tooltip title="Exporter le graphique au format SVG">
          <IconButton onClick={exportSVG}>
            <Icon path={mdiXml} size={1} />
          </IconButton>
        </Tooltip>
      </div>
      <div
        style={{ width: "100%" }}
        className={`f3 ${darkTheme.enabled ? "f3-dark" : "f3-light"}`}
        id="FamilyChart"
        ref={applyTree}
      ></div>
    </div>
  );
}

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

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

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[]
) {
  if (!availableMembers.has(node.member.id)) return;

  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>
) {
  if (!availableMembers.has(node.member.id)) return;

  // Get all members ids
  let children = node?.down?.map((c) => c.member.id) ?? [];
  node.couples?.map((c) => c.down.forEach((m) => children.push(m.member.id)));

  children = children.filter((c) => availableMembers.has(c));

  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
        ?.filter((s) => availableMembers.has(s.member.id))
        .map((c) => c.member.id.toString()),
      children: children.map((c) => c.toString()),
    },
  });

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

  if (node.couples) {
    for (const c of node.couples) {
      if (!availableMembers.has(c.member.id)) continue;
      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
            .filter((c) => availableMembers.has(c.member.id))
            .map((c) => c.member.id.toString()),
        },
      });

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