// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ 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 { ColumnDataMap } from "../../data/view-reader";
import type { WebGLContextManager } from "../../webgl/context-manager";
import { TreeChartBase, firstNonMetadataColumn } from "../common/tree-chart";
import { NULL_NODE } from "../common/node-store";
import {
    processTreeChunk,
    finalizeTree,
    resetTreeState,
} from "../common/tree-data";
import {
    renderSunburstFrame,
    renderSunburstChromeOverlay,
} from "./sunburst-render";
import {
    handleSunburstHover,
    handleSunburstClick,
    dismissSunburstPinnedTooltip,
    type SunburstBreadcrumbRegion,
} from "./sunburst-interact";

export interface SunburstLocations {
    u_center: WebGLUniformLocation | null;
    u_resolution: WebGLUniformLocation | null;
    u_border_px: WebGLUniformLocation | null;
    a_strip_t: number;
    a_side: number;
    a_angles: number;
    a_radii: number;
    a_color: number;
}

/**
 * Sunburst chart. Shares tree storage + streaming pipeline + color
 * mode with `TreeChartBase`; adds polar layout + instanced-arc WebGL
 * rendering + drill / tooltip interactions.
 *
 * Internal option: `_labelRotation` — `"upright"` keeps labels on the
 * left half flipped 180° so they read upright (d3fc behavior);
 * `"radial"` leaves them purely tangent to the arc. Defaults to
 * `"upright"`; toggle here if a call site wants flat radial labels.
 */
export class SunburstChart extends TreeChartBase {
    _program: WebGLProgram | null = null;
    _locations: SunburstLocations | null = null;
    _stripBuffer: WebGLBuffer | null = null;
    _instanceBuffer: WebGLBuffer | null = null;
    _instanceCount = 0;

    /**
     * Label orientation mode — see class docstring.
     */
    _labelRotation: "upright" | "radial" = "upright";

    // Center / radius state resolved per frame.
    _centerX = 0;
    _centerY = 0;
    _maxRadius = 0;

    //  Interaction
    _hoveredNodeId: number = NULL_NODE;
    _pinnedNodeId: number = NULL_NODE;
    _breadcrumbRegions: SunburstBreadcrumbRegion[] = [];

    _chromeCache: ImageBitmap | null = null;
    _chromeCacheDirty = true;

    /**
     * See `TreemapChart._chromeCacheGen` — same race, same fix.
     */
    _chromeCacheGen = 0;

    //  Faceted state
    _facetGrid: import("../../layout/facet-grid").FacetGrid | null = null;

    /**
     * Per-facet drill roots — mirrors `TreemapChart._facetDrillRoots`.
     */
    _facetDrillRoots: Map<string, number> = new Map();

    /**
     * Per-facet rendering state. `index` matches the facet grid cell;
     * `centerX`, `centerY`, `maxRadius` are used for layout + hit test;
     * `drillRoot` is the sub-root the facet is currently showing;
     * `instanceStart`, `instanceCount` index into the shared GPU
     * instance buffer for draw dispatch (these are post-skip values,
     * rewritten by `uploadArcInstances` after zero-width arcs and the
     * drill root are filtered out). `nodeStart`, `nodeCount` are the
     * pre-skip range over `_visibleNodeIds` and are *not* rewritten —
     * canvas chrome (arc-label translate origin) walks this range so
     * each label can be placed around its own facet's center instead
     * of the chart-wide `_centerX/_centerY` (which always point at the
     * first facet).
     */
    _facets: {
        label: string;
        centerX: number;
        centerY: number;
        maxRadius: number;
        drillRoot: number;
        instanceStart: number;
        instanceCount: number;
        nodeStart: number;
        nodeCount: number;
    }[] = [];

    protected override tooltipCallbacks() {
        return {
            onHover: (mx: number, my: number) =>
                handleSunburstHover(this, mx, my),
            onLeave: () => {
                if (
                    this._hoveredNodeId !== NULL_NODE &&
                    this._pinnedNodeId === NULL_NODE
                ) {
                    this._hoveredNodeId = NULL_NODE;
                    renderSunburstChromeOverlay(this);
                }
            },
            onClickPre: (mx: number, my: number) => {
                handleSunburstClick(this, mx, my);
                return true;
            },
        };
    }

    async uploadAndRender(
        glManager: WebGLContextManager,
        columns: ColumnDataMap,
        startRow: number,
        _endRow: number,
    ): Promise<void> {
        this._glManager = glManager;

        if (startRow === 0) {
            const slots = this._columnSlots;
            this._sizeName = slots[0] || firstNonMetadataColumn(columns) || "";
            this._colorName = slots[1] || "";
            if (!this._colorName) {
                this._colorMode = "empty";
            } else {
                const t = this._columnTypes[this._colorName];
                const isNumeric =
                    t === "float" ||
                    t === "integer" ||
                    t === "date" ||
                    t === "datetime";
                this._colorMode = isNumeric ? "numeric" : "series";
            }

            // Clear per-draw state tied to the old tree — see
            // `TreemapChart.uploadAndRender` for the same pattern and
            // rationale.
            this._hoveredNodeId = NULL_NODE;
            this._pinnedNodeId = NULL_NODE;
            this._breadcrumbRegions = [];
            this._facetDrillRoots.clear();
            this._facetGrid = null;
            this._facets = [];

            // Invalidate the instance buffer so a render that fires
            // before the fresh upload draws zero arcs.
            this._instanceCount = 0;

            // Drop any in-flight hover tooltip promise (see treemap).
            this._lazyTooltip.clearHover();
            this._lazyTooltip.invalidatePin();
            dismissSunburstPinnedTooltip(this);
            this._chromeCache?.close();
            this._chromeCache = null;
            this._chromeCacheDirty = true;
            this._chromeCacheGen++;

            resetTreeState(this);
        }

        processTreeChunk(this, columns);
        finalizeTree(this);
        if (this._rootId !== NULL_NODE) {
            await this.requestRender(glManager);
        }
    }

    _fullRender(glManager: WebGLContextManager): void {
        if (this._rootId === NULL_NODE) {
            return;
        }

        this._glManager = glManager;
        renderSunburstFrame(this, glManager);
    }

    protected destroyInternal(): void {
        dismissSunburstPinnedTooltip(this);
        this._chromeCache?.close();
        this._chromeCache = null;
        const gl = this._glManager?.gl;
        if (gl) {
            if (this._stripBuffer) {
                gl.deleteBuffer(this._stripBuffer);
            }

            if (this._instanceBuffer) {
                gl.deleteBuffer(this._instanceBuffer);
            }
        }

        this._stripBuffer = null;
        this._instanceBuffer = null;
        this._program = null;
        this._locations = null;
        this._rootId = NULL_NODE;
        this._currentRootId = NULL_NODE;
        this._breadcrumbIds = [];
        this._childLookup.clear();
        this._visibleNodeIds = null;
        this._visibleNodeCount = 0;
        this._breadcrumbRegions = [];
        this._facetGrid = null;
        this._facetDrillRoots.clear();
        this._facets = [];
    }
}
