// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ 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 type { SunburstChart } from "./sunburst";
import { NULL_NODE } from "../common/node-store";
import {
    renderSunburstFrame,
    renderSunburstChromeOverlay,
    facetCenterForNode,
} from "./sunburst-render";
import {
    buildTreeTooltipLines,
    dismissTreePinnedTooltip,
    emitTreeNodeEvent,
    showTreePinnedTooltip,
    treeDrillTo,
} from "../common/tree-interact";

export type { BreadcrumbRegion as SunburstBreadcrumbRegion } from "../common/tree-chrome";

interface FacetHitContext {
    centerX: number;
    centerY: number;
    drillRoot: number;

    /**
     * Pre-upload visible range for this facet; undefined in non-facet mode.
     */
    range?: { start: number; end: number };
}

/**
 * Resolve the facet under cursor; returns single-plot defaults outside facet mode.
 */
function facetUnderCursor(
    chart: SunburstChart,
    mx: number,
    my: number,
): FacetHitContext | null {
    if (chart._facets.length === 0) {
        return {
            centerX: chart._centerX,
            centerY: chart._centerY,
            drillRoot: chart._currentRootId,
        };
    }

    for (const facet of chart._facets) {
        // Post-upload `instanceStart` / `instanceCount` are scan-index
        // ranges into `_visibleNodeIds` — we need the *pre-upload*
        // range to hit-test all arcs (including zero-width ones we
        // skipped for draw). Walk the IDs and match by drill root
        // ancestry instead.
        const dx = mx - facet.centerX;
        const dy = my - facet.centerY;
        const r = Math.sqrt(dx * dx + dy * dy);
        if (r > facet.maxRadius + 4) {
            continue;
        }

        return {
            centerX: facet.centerX,
            centerY: facet.centerY,
            drillRoot: facet.drillRoot,
        };
    }

    return null;
}

/**
 * Walk the ancestor chain from `id` up to (but not including) the
 * synthetic `_rootId`, returning true if any step equals `anc`.
 * Used to filter hit-test candidates to arcs that belong to a given
 * facet's drill subtree.
 */
function isDescendantOf(
    chart: SunburstChart,
    id: number,
    anc: number,
): boolean {
    const store = chart._nodeStore;
    let p = id;
    while (p !== NULL_NODE) {
        if (p === anc) {
            return true;
        }

        p = store.parent[p];
    }

    return false;
}

/**
 * Convert `(mx, my)` to polar and find the containing visible arc.
 */
function polarHitTest(chart: SunburstChart, mx: number, my: number): number {
    const ctx = facetUnderCursor(chart, mx, my);
    if (!ctx) {
        return NULL_NODE;
    }

    const store = chart._nodeStore;
    const ids = chart._visibleNodeIds;
    const n = chart._visibleNodeCount;
    if (!ids) {
        return NULL_NODE;
    }

    const dx = mx - ctx.centerX;
    const dy = my - ctx.centerY;
    const r = Math.sqrt(dx * dx + dy * dy);
    let theta = Math.atan2(dy, dx);
    if (theta < 0) {
        theta += 2 * Math.PI;
    }

    // Center-circle hit — drill-up target.
    if (r < store.r1[chart._rootId] + 0.001) {
        if (ctx.drillRoot !== chart._rootId) {
            return ctx.drillRoot;
        }
    }

    const faceted = chart._facets.length > 0;
    for (let i = 0; i < n; i++) {
        const id = ids[i];
        if (id === ctx.drillRoot) {
            continue;
        }

        if (faceted && !isDescendantOf(chart, id, ctx.drillRoot)) {
            continue;
        }

        const a0 = store.a0[id];
        const a1 = store.a1[id];
        const r0 = store.r0[id];
        const r1 = store.r1[id];
        if (r < r0 || r > r1) {
            continue;
        }

        if (theta < a0 || theta > a1) {
            continue;
        }

        return id;
    }

    return NULL_NODE;
}

export function handleSunburstHover(
    chart: SunburstChart,
    mx: number,
    my: number,
): void {
    if (chart._pinnedNodeId !== NULL_NODE) {
        return;
    }

    // Breadcrumb region check first (they sit atop the chart area).
    for (const region of chart._breadcrumbRegions) {
        if (
            mx >= region.x0 &&
            mx <= region.x1 &&
            my >= region.y0 &&
            my <= region.y1
        ) {
            chart._tooltip.setCursor("pointer");
            if (chart._hoveredNodeId !== NULL_NODE) {
                chart._hoveredNodeId = NULL_NODE;
                renderSunburstChromeOverlay(chart);
            }

            return;
        }
    }

    const hit = polarHitTest(chart, mx, my);
    const store = chart._nodeStore;
    chart._tooltip.setCursor(
        hit !== NULL_NODE && store.firstChild[hit] !== NULL_NODE
            ? "pointer"
            : "default",
    );

    if (hit !== chart._hoveredNodeId) {
        chart._hoveredNodeId = hit;
        if (hit !== NULL_NODE) {
            const serial = chart._lazyTooltip.beginHover(hit);
            buildTreeTooltipLines(chart, hit).then((lines) => {
                if (chart._lazyTooltip.commitHover(serial, lines)) {
                    renderSunburstChromeOverlay(chart);
                }
            });
        } else {
            chart._lazyTooltip.clearHover();
        }

        renderSunburstChromeOverlay(chart);
    }
}

export function handleSunburstClick(
    chart: SunburstChart,
    mx: number,
    my: number,
): void {
    if (chart._pinnedNodeId !== NULL_NODE) {
        dismissSunburstPinnedTooltip(chart);
        chart.emitUnselect();
        return;
    }

    // Breadcrumb click = drill to that crumb.
    for (const region of chart._breadcrumbRegions) {
        if (
            mx >= region.x0 &&
            mx <= region.x1 &&
            my >= region.y0 &&
            my <= region.y1
        ) {
            if (region.nodeId !== chart._currentRootId) {
                drillTo(chart, region.nodeId);
                chart.emitUnselect();
            }

            return;
        }
    }

    // Center-circle click = drill up one level (parent of current root).
    const store = chart._nodeStore;
    const ctx = facetUnderCursor(chart, mx, my);
    if (ctx) {
        const dx = mx - ctx.centerX;
        const dy = my - ctx.centerY;
        const r = Math.sqrt(dx * dx + dy * dy);
        if (r < store.r1[chart._rootId] + 0.001) {
            const parent = store.parent[ctx.drillRoot];
            if (parent !== NULL_NODE && parent !== chart._rootId) {
                drillTo(chart, parent);
                chart.emitUnselect();
            } else if (chart._facets.length > 0) {
                // Already at the facet root: reset this facet's drill.
                const facet = chart._facets.find(
                    (f) => f.drillRoot === ctx.drillRoot,
                );
                if (facet) {
                    chart._facetDrillRoots.delete(facet.label);
                    chart.emitUnselect();
                }

                if (chart._glManager) {
                    renderSunburstFrame(chart, chart._glManager);
                }
            }

            return;
        }
    }

    const hit = polarHitTest(chart, mx, my);
    if (hit === NULL_NODE) {
        return;
    }

    if (store.firstChild[hit] !== NULL_NODE) {
        drillTo(chart, hit);
        void emitTreeNodeEvent(chart, hit, "branch");
    } else {
        showSunburstPinnedTooltip(chart, hit);
        void emitTreeNodeEvent(chart, hit, "leaf");
    }
}

function drillTo(chart: SunburstChart, nodeId: number): void {
    treeDrillTo(chart, nodeId, () => {
        if (chart._glManager) {
            renderSunburstFrame(chart, chart._glManager);
        }
    });
}

export function showSunburstPinnedTooltip(
    chart: SunburstChart,
    nodeId: number,
): void {
    const store = chart._nodeStore;
    const midA = (store.a0[nodeId] + store.a1[nodeId]) / 2;
    const midR = (store.r0[nodeId] + store.r1[nodeId]) / 2;
    const { centerX, centerY } = facetCenterForNode(chart, nodeId);
    const cx = centerX + Math.cos(midA) * midR;
    const cy = centerY + Math.sin(midA) * midR;
    showTreePinnedTooltip(chart, nodeId, { cx, cy }, () =>
        renderSunburstChromeOverlay(chart),
    );
}

export function dismissSunburstPinnedTooltip(chart: SunburstChart): void {
    dismissTreePinnedTooltip(chart);
}

export { buildTreeTooltipLines as buildSunburstTooltipLines } from "../common/tree-interact";
