Skip to content
//! Maud HTML helpers for the charts UI.

use actix_web::HttpRequest;
use maud::{Markup, PreEscaped, html};

use crate::db::ColumnInfo;

use super::types::ChartType;

pub fn no_connection_html(req: &HttpRequest) -> Markup {
    let content = html! {
        div class="w-100 flex flex-column items-center justify-center" style="height:100%;" {
            div class="bg-near-black ba b--white-20 br2 pa4 mw6 w-100 tc" {
                p class="f4 white-70 mb3" { "No active database connection" }
                a href="/connections"
                  hx-get="/connections"
                  hx-target="#feature"
                  hx-swap="innerHTML"
                  hx-push-url="true"
                  class="dib bg-orange white br2 ph3 pv2 no-underline f6 fw6 hover-bg-dark-orange" {
                    "Configure a connection"
                }
            }
        }
    };
    if req.headers().get("HX-Request").is_some() {
        content
    } else {
        crate::view::render_layout(&content)
    }
}

pub fn select_class() -> &'static str {
    "input-reset ba b--white-30 ph2 pv1 br2 f7 bg-white-10 white"
}

pub fn option_style() -> &'static str {
    "background-color: #1a1a1a; color: white;"
}

/// CSS for the chart-area loading overlay. Tachyons doesn't ship anything
/// for `.htmx-indicator`, so we inline a tiny stylesheet once on the panel.
const CHART_INDICATOR_CSS: &str = r"
.chart-indicator {
    position: absolute; inset: 0;
    display: none;
    align-items: center; justify-content: center;
    background: rgba(18,18,18,0.55);
    pointer-events: none;
}
.htmx-request .chart-indicator,
.chart-indicator.htmx-request { display: flex; }
.chart-spinner {
    width: 36px; height: 36px;
    border: 3px solid rgba(255,255,255,0.15);
    border-top-color: #E07000;
    border-radius: 50%;
    animation: chart-spin 0.8s linear infinite;
}
@keyframes chart-spin { to { transform: rotate(360deg); } }
";

/// Renders the chart SVG + data-point count badge + loading overlay.
///
/// The overlay is wired to HTMX via `hx-indicator="#chart-area"`, which adds
/// the `htmx-request` class while a request is in flight.
pub fn chart_area(svg: &str, data_len: usize, error: Option<&str>) -> Markup {
    html! {
        div id="chart-area"
            class="w-100 flex-auto flex flex-column relative"
            style="overflow:hidden;" {
            @if let Some(err) = error {
                div class="pa3 bg-dark-red br2 mb2 f7 white" { (err) }
            }
            div class="f7 white-50 mb1 tr pr2" {
                (data_len) " data points"
            }
            div class="w-100 flex-auto" style="overflow:hidden;" {
                (PreEscaped(svg))
            }
            div class="chart-indicator" aria-hidden="true" {
                div class="chart-spinner" {}
            }
        }
    }
}

/// Full chart panel: controls + chart area. Returned when a table is selected.
#[allow(clippy::too_many_arguments)]
pub fn chart_panel(
    cols: &[ColumnInfo],
    chart_type: &ChartType,
    x_col: &str,
    y_col: &str,
    limit: usize,
    database: &str,
    table: &str,
    svg: &str,
    data_len: usize,
    error: Option<&str>,
) -> Markup {
    let is_histogram = *chart_type == ChartType::Histogram;
    let y_style = if is_histogram {
        "opacity:0.35; pointer-events:none;"
    } else {
        ""
    };

    // Common HTMX attributes for every control. Triggers debounce briefly to
    // avoid firing a fetch on every keystroke / quick click.
    let trigger = "change delay:200ms";

    html! {
        style { (PreEscaped(CHART_INDICATOR_CSS)) }

        // ── Controls bar ──────────────────────────────────────────────────
        div id="chart-controls"
            class="flex flex-wrap items-end bg-black-70 ph3 pv2 bb b--white-10"
            style="flex-shrink:0; gap:0.5rem;" {

            // Chart type
            div {
                label class="db f7 white-50 mb1" { "Chart type" }
                select
                    name="chart_type"
                    class=(select_class())
                    style="color:white;"
                    hx-get="/charts/render"
                    hx-include="#chart-form"
                    hx-target="#chart-area"
                    hx-swap="outerHTML"
                    hx-indicator="#chart-area"
                    hx-disabled-elt="this"
                    hx-trigger=(trigger) {
                    @for ct in ChartType::all() {
                        option
                            value=(ct.value())
                            selected[ct == chart_type]
                            style=(option_style()) {
                            (ct.label())
                        }
                    }
                }
            }

            // X column
            div {
                label class="db f7 white-50 mb1" { "X axis / column" }
                select
                    name="x_col"
                    class=(select_class())
                    style="color:white;"
                    hx-get="/charts/render"
                    hx-include="#chart-form"
                    hx-target="#chart-area"
                    hx-swap="outerHTML"
                    hx-indicator="#chart-area"
                    hx-disabled-elt="this"
                    hx-trigger=(trigger) {
                    @for c in cols {
                        option
                            value=(c.name)
                            selected[c.name == x_col]
                            style=(option_style()) {
                            (c.name) " (" (c.column_type) ")"
                        }
                    }
                }
            }

            // Y column (dimmed for histogram)
            div style=(y_style) {
                label class="db f7 white-50 mb1" { "Y axis / value" }
                select
                    name="y_col"
                    class=(select_class())
                    style="color:white;"
                    hx-get="/charts/render"
                    hx-include="#chart-form"
                    hx-target="#chart-area"
                    hx-swap="outerHTML"
                    hx-indicator="#chart-area"
                    hx-disabled-elt="this"
                    hx-trigger=(trigger) {
                    @for c in cols {
                        option
                            value=(c.name)
                            selected[c.name == y_col]
                            style=(option_style()) {
                            (c.name) " (" (c.column_type) ")"
                        }
                    }
                }
            }

            // Row limit
            div {
                label class="db f7 white-50 mb1" { "Limit" }
                select
                    name="limit"
                    class=(select_class())
                    style="color:white;"
                    hx-get="/charts/render"
                    hx-include="#chart-form"
                    hx-target="#chart-area"
                    hx-swap="outerHTML"
                    hx-indicator="#chart-area"
                    hx-disabled-elt="this"
                    hx-trigger=(trigger) {
                    @for &l in &[100usize, 500, 1000, 5000, 10000] {
                        option value=(l) selected[l == limit] style=(option_style()) { (l) }
                    }
                }
            }
        }

        // Hidden form fields so hx-include picks them all up together
        form id="chart-form" style="display:none;" {
            input type="hidden" name="database" value=(database);
            input type="hidden" name="table" value=(table);
        }

        // Chart area
        (chart_area(svg, data_len, error))
    }
}