// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors.                              ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

import * as d3 from "d3";
import { calcWidth, calcHeight } from "./treemapSeries";
import {
    labelMapExists,
    toggleLabels,
    preventTextCollisions,
    lockTextOpacity,
    unlockTextOpacity,
    textOpacity,
    selectVisibleNodes,
    adjustLabelsThatOverflow,
    restoreLabels,
} from "./treemapLabel";
import { calculateSubTreeMap, saveLabelMap } from "./treemapLevelCalculation";
import { raiseEvent } from "../../tooltip/selectionEvent";

export function returnToLevel(
    rects,
    nodesMerge,
    labels,
    settings,
    treemapDiv,
    treemapSvg,
    rootNode,
    parentCtrls,
    root_settings
) {
    if (settings.treemapLevel > 0) {
        const crossValues = rootNode.crossValue;
        executeTransition(
            rootNode,
            rects,
            nodesMerge,
            labels,
            settings,
            treemapDiv,
            treemapSvg,
            rootNode,
            0,
            crossValues,
            parentCtrls,
            root_settings,
            1,
            false
        );

        settings.treemapRoute
            .slice(1, settings.treemapRoute.length)
            .forEach((cv) => {
                const d = nodesMerge.filter((d) => d.crossValue === cv).datum();
                const crossValues = d.crossValue;
                calculateSubTreeMap(
                    d,
                    crossValues,
                    nodesMerge,
                    d.depth,
                    rootNode,
                    treemapDiv
                );
                executeTransition(
                    d,
                    rects,
                    nodesMerge,
                    labels,
                    settings,
                    treemapDiv,
                    treemapSvg,
                    rootNode,
                    d.depth,
                    crossValues,
                    parentCtrls,
                    root_settings,
                    1,
                    false
                );
            });
    }
}

export function changeLevel(
    d,
    rects,
    nodesMerge,
    labels,
    settings,
    treemapDiv,
    treemapSvg,
    rootNode,
    parentCtrls,
    root_settings
) {
    if (!d.children) return;

    if (settings.treemapLevel < d.depth) {
        settings.treemapRoute.push(d.crossValue);
    } else {
        settings.treemapRoute.pop();
    }

    settings.treemapLevel = d.depth;

    const crossValues = d.crossValue;

    if (
        !d.mapLevel[settings.treemapLevel] ||
        !d.mapLevel[settings.treemapLevel].levelRoot
    ) {
        calculateSubTreeMap(
            d,
            crossValues,
            nodesMerge,
            settings.treemapLevel,
            rootNode,
            treemapDiv
        );
    }

    executeTransition(
        d,
        rects,
        nodesMerge,
        labels,
        settings,
        treemapDiv,
        treemapSvg,
        rootNode,
        settings.treemapLevel,
        crossValues,
        parentCtrls,
        root_settings
    );
}

function executeTransition(
    d,
    rects,
    nodesMerge,
    labels,
    settings,
    treemapDiv,
    treemapSvg,
    rootNode,
    treemapLevel,
    crossValues,
    parentCtrls,
    root_settings,
    duration = 500,
    recordLabelMap = true
) {
    const parent = d.parent;

    const t = treemapSvg
        .transition("main transition")
        .duration(duration)
        .ease(d3.easeCubicOut);

    nodesMerge.each((d) => (d.target = d.mapLevel[treemapLevel]));

    if (!labelMapExists(d)) preventUserInteraction(nodesMerge, parentCtrls);

    // hide hidden svgs
    nodesMerge
        .transition(t)
        .tween("data", (d) => {
            const i = d3.interpolate(d.current, d.target);
            return (t) => (d.current = i(t));
        })
        .styleTween("opacity", (d) => () => d.current.opacity)
        .attrTween(
            "pointer-events",
            (d) => () => d.target.visible ? "all" : "none"
        );

    rects
        .transition(t)
        .filter((d) => d.target.visible)
        .styleTween("x", (d) => () => `${d.current.x0}px`)
        .styleTween("y", (d) => () => `${d.current.y0}px`)
        .styleTween("width", (d) => () => `${d.current.x1 - d.current.x0}px`)
        .styleTween("height", (d) => () => `${d.current.y1 - d.current.y0}px`);

    labels
        .transition(t)
        .filter((d) => d.target.visible)
        .attrTween("x", (d) => () => d.current.x0 + calcWidth(d.current) / 2)
        .attrTween("y", (d) => () => d.current.y0 + calcHeight(d.current) / 2)
        .end()
        .catch(() => enableUserInteraction(nodesMerge))
        .then(() => {
            if (!labelMapExists(d)) {
                preventTextCollisions(visibleLabelNodes);
                adjustLabelsThatOverflow(visibleLabelNodes);
                fadeTextTransition(labels, treemapSvg, duration);
                if (recordLabelMap) saveLabelMap(nodesMerge, treemapLevel);
                enableUserInteraction(nodesMerge, parentCtrls);
            }
        })
        .catch((ex) => {
            console.error(
                "Exception completing promises after main transition",
                ex
            );
            enableUserInteraction(nodesMerge, parentCtrls);
        });

    if (!labelMapExists(d)) {
        labels.each((_, i, labels) => lockTextOpacity(labels[i]));
        toggleLabels(nodesMerge, treemapLevel, crossValues);
    } else {
        restoreLabels(nodesMerge);
    }

    const visibleLabelNodes = selectVisibleNodes(nodesMerge);

    if (parent) {
        parentCtrls
            .hide(false)
            .text(d.label)
            .onClick(() => {
                changeLevel(
                    parent,
                    rects,
                    nodesMerge,
                    labels,
                    settings,
                    treemapDiv,
                    treemapSvg,
                    rootNode,
                    parentCtrls,
                    root_settings
                    // duration
                );
                const viewer = treemapDiv.node().getRootNode()
                    .host.parentElement;
                raiseEvent(viewer, parent, root_settings);
            })();
    } else {
        parentCtrls.hide(true)();
    }
}

async function fadeTextTransition(labels, treemapSvg, duration = 400) {
    const t = treemapSvg
        .transition("text fade transition")
        .duration(duration)
        .ease(d3.easeCubicOut);

    await labels
        .transition(t)
        .filter((d) => d.target.visible)
        .tween("data", (d, i, labels) => {
            const label = labels[i];
            const interpolation = d3.interpolate(
                lockedOpacity(d),
                targetOpacity(label)
            );
            return (t) => (d.current.opacity = interpolation(t));
        })
        .styleTween("opacity", (d) => () => d.current.opacity)
        .end()
        .catch((ex) => console.error("Exception in text fade transition", ex))
        .then(() =>
            labels.each((_, i, labels) => unlockTextOpacity(labels[i]))
        );
}

const lockedOpacity = (d) => d.target.textLockedAt.opacity;
const targetOpacity = (d) => textOpacity[d3.select(d).attr("class")];

const preventUserInteraction = (nodes, parentCtrls) => {
    parentCtrls.deactivate(true);

    nodes.each((_, i, nodes) => {
        const rect = d3.select(nodes[i]).selectAll("rect");
        rect.style("pointer-events", "none");
    });
};

const enableUserInteraction = (nodes, parentCtrls = undefined) => {
    if (parentCtrls) parentCtrls.deactivate(false);

    nodes.each((_, i, nodes) => {
        const rect = d3.select(nodes[i]).selectAll("rect");
        rect.style("pointer-events", null);
    });
};
