// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ 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 { Canvas2D, Context2D } from "../charts/canvas-types";
import type { PlotLayout, PlotRect } from "../layout/plot-layout";
import { formatTickValue } from "../layout/ticks";
import {
    colorValueToT,
    sampleGradient,
    type GradientStop,
} from "../theme/gradient";
import type { Theme } from "../theme/theme";

function rgbCss(c: [number, number, number, number]): string {
    return `rgb(${Math.round(c[0] * 255)},${Math.round(c[1] * 255)},${Math.round(c[2] * 255)})`;
}

/**
 * Render a vertical color gradient legend on the Canvas2D overlay.
 * Only call when a color column is active. When `colorDomain` crosses
 * zero the 50% stop (sign pivot) is annotated with a tick + `0` label.
 *
 * Per-facet wrapper; computes the anchor from `layout` and delegates
 * to {@link renderLegendAt}. Facet grids render one shared gradient
 * legend and pass an explicit rect to `renderLegendAt` directly.
 */
export function renderLegend(
    canvas: Canvas2D,
    layout: PlotLayout,
    colorDomain: { min: number; max: number; label: string },
    stops: GradientStop[],
    theme: Theme,
    formatter?: (v: number) => string,
): void {
    const rect: PlotRect = {
        x: layout.plotRect.x + layout.plotRect.width + 12,
        y: layout.margins.top + 20,
        width: Math.max(
            1,
            layout.cssWidth - layout.plotRect.x - layout.plotRect.width - 12,
        ),
        height: Math.max(1, layout.plotRect.height),
    };
    renderLegendAt(canvas, rect, colorDomain, stops, theme, formatter);
}

/**
 * Render a gradient legend at an explicit canvas-absolute rect.
 * Used by facet grids that paint one legend for the whole grid and
 * by single-plot charts through {@link renderLegend}.
 */
export function renderLegendAt(
    canvas: Canvas2D,
    rect: PlotRect,
    colorDomain: { min: number; max: number; label: string },
    stops: GradientStop[],
    theme: Theme,
    formatter: (v: number) => string = formatTickValue,
): void {
    const ctx = canvas.getContext("2d") as Context2D | null;
    if (!ctx) {
        return;
    }

    const textColor = theme.legendText;
    const borderColor = theme.legendBorder;
    const fontFamily = theme.fontFamily;

    const barWidth = 16;
    const barHeight = Math.min(120, rect.height * 0.4);
    const x = rect.x;
    const y = rect.y;

    ctx.fillStyle = textColor;
    ctx.font = `9px ${fontFamily}`;
    ctx.textAlign = "left";
    ctx.textBaseline = "bottom";
    ctx.fillText(colorDomain.label, x, y - 4);

    // Paint the gradient by walking `colorDomain.min..max` top→bottom and
    // feeding each value through `colorValueToT` so the legend matches the
    // sign-aware mapping used by the GPU / treemap paths.
    const topVal = colorDomain.max;
    const bottomVal = colorDomain.min;
    const gradient = ctx.createLinearGradient(0, y, 0, y + barHeight);
    const SAMPLES = 16;
    for (let i = 0; i <= SAMPLES; i++) {
        const offset = i / SAMPLES;
        const v = topVal + offset * (bottomVal - topVal);
        const t = colorValueToT(v, colorDomain.min, colorDomain.max);
        const rgba = sampleGradient(stops, t);
        gradient.addColorStop(offset, rgbCss(rgba));
    }

    ctx.fillStyle = gradient;
    ctx.fillRect(x, y, barWidth, barHeight);

    ctx.strokeStyle = borderColor;
    ctx.lineWidth = 1;
    ctx.strokeRect(x, y, barWidth, barHeight);

    ctx.fillStyle = textColor;
    ctx.font = `10px ${fontFamily}`;
    ctx.textAlign = "left";
    ctx.textBaseline = "middle";

    const labelX = x + barWidth + 5;
    ctx.fillText(formatter(colorDomain.max), labelX, y + 2);
    ctx.fillText(
        formatter((colorDomain.min + colorDomain.max) / 2),
        labelX,
        y + barHeight / 2,
    );
    ctx.fillText(formatter(colorDomain.min), labelX, y + barHeight - 2);

    // Sign-pivot marker when the data crosses zero: a small tick on the
    // right edge of the bar + a "0" label.
    if (colorDomain.min < 0 && colorDomain.max > 0) {
        const zeroOffset =
            (colorDomain.max - 0) / (colorDomain.max - colorDomain.min);
        const zeroY = y + zeroOffset * barHeight;
        ctx.strokeStyle = textColor;
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.moveTo(x + barWidth, zeroY);
        ctx.lineTo(x + barWidth + 4, zeroY);
        ctx.stroke();
        ctx.fillStyle = textColor;
        ctx.fillText("0", labelX, zeroY);
    }
}

/**
 * Render a categorical legend with discrete colored swatches.
 * Used when split_by or string color columns produce distinct categories.
 *
 * The per-facet wrapper; computes the anchor from `layout` and delegates
 * to {@link renderCategoricalLegendAt}. Facet grids that render one
 * shared legend pass an explicit rect to `renderCategoricalLegendAt`
 * directly.
 */
export function renderCategoricalLegend(
    canvas: Canvas2D,
    layout: PlotLayout,
    labels: Map<string, number>,
    palette: [number, number, number][],
    theme: Theme,
): void {
    const rect: PlotRect = {
        x: layout.plotRect.x + layout.plotRect.width + 12,
        y: layout.margins.top + 10,
        width: Math.max(
            1,
            layout.cssWidth - layout.plotRect.x - layout.plotRect.width - 12,
        ),
        height: Math.max(1, layout.plotRect.height),
    };
    renderCategoricalLegendAt(canvas, rect, labels, palette, theme);
}

/**
 * Render a categorical legend at an explicit canvas-absolute rect.
 * Used by facet grids that paint one legend for the whole grid and by
 * single-plot charts through {@link renderCategoricalLegend}.
 */
export function renderCategoricalLegendAt(
    canvas: Canvas2D,
    rect: PlotRect,
    labels: Map<string, number>,
    palette: [number, number, number][],
    theme: Theme,
): void {
    const ctx = canvas.getContext("2d") as Context2D | null;
    if (!ctx) {
        return;
    }

    if (labels.size === 0) {
        return;
    }

    const textColor = theme.legendText;
    const fontFamily = theme.fontFamily;

    const swatchSize = 10;
    const lineHeight = 18;
    const x = rect.x;
    let y = rect.y + lineHeight / 2;

    ctx.font = `11px ${fontFamily}`;
    ctx.textAlign = "left";
    ctx.textBaseline = "middle";

    for (const [label, idx] of labels) {
        if (y + swatchSize / 2 > rect.y + rect.height) {
            break;
        }

        const color = palette[idx] ??
            palette[idx % palette.length] ?? [0, 0, 0];
        ctx.fillStyle = `rgb(${Math.round(color[0] * 255)},${Math.round(color[1] * 255)},${Math.round(color[2] * 255)})`;
        ctx.fillRect(x, y - swatchSize / 2, swatchSize, swatchSize);

        ctx.fillStyle = textColor;
        ctx.fillText(label, x + swatchSize + 6, y);

        y += lineHeight;
    }
}
