// Copyright (C) 2026 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import m from 'mithril';
import {
  BarChart,
  type BarChartData,
  aggregateBarChartData,
} from '../../../components/widgets/charts/bar_chart';
import {
  type ChartAggregation,
  isIntegerAggregation,
} from '../../../components/widgets/charts/chart_utils';
import {
  SQLBarChartLoader,
  type BarChartLoaderConfig,
} from '../../../components/widgets/charts/bar_chart_loader';
import {Histogram} from '../../../components/widgets/charts/histogram';
import type {LegendPosition} from '../../../components/widgets/charts/common';
import {
  InMemoryHistogramLoader,
  SQLHistogramLoader,
  type HistogramLoaderConfig,
} from '../../../components/widgets/charts/histogram_loader';
import {
  LineChart,
  type LineChartData,
} from '../../../components/widgets/charts/line_chart';
import {
  SQLLineChartLoader,
  type LineChartLoaderConfig,
} from '../../../components/widgets/charts/line_chart_loader';
import {
  PieChart,
  type PieChartData,
} from '../../../components/widgets/charts/pie_chart';
import {
  SQLPieChartLoader,
  type PieChartLoaderConfig,
} from '../../../components/widgets/charts/pie_chart_loader';
import {
  Scatterplot,
  type ScatterChartData,
} from '../../../components/widgets/charts/scatterplot';
import {
  SQLScatterChartLoader,
  type ScatterChartLoaderConfig,
} from '../../../components/widgets/charts/scatterplot_loader';
import {
  Treemap,
  type TreemapData,
} from '../../../components/widgets/charts/treemap';
import {
  SQLTreemapLoader,
  type TreemapLoaderConfig,
} from '../../../components/widgets/charts/treemap_loader';
import {
  SQLCdfLoader,
  type CdfLoaderConfig,
} from '../../../components/widgets/charts/cdf_loader';
import {
  BoxplotChart,
  type BoxplotData,
} from '../../../components/widgets/charts/boxplot';
import {
  SQLBoxplotLoader,
  type BoxplotLoaderConfig,
} from '../../../components/widgets/charts/boxplot_loader';
import {
  HeatmapChart,
  type HeatmapData,
} from '../../../components/widgets/charts/heatmap';
import {
  Sankey,
  type SankeyData,
} from '../../../components/widgets/charts/sankey';
import {
  SQLSankeyLoader,
  type SankeyLoaderConfig,
} from '../../../components/widgets/charts/sankey_loader';
import {
  SQLHeatmapLoader,
  type HeatmapLoaderConfig,
} from '../../../components/widgets/charts/heatmap_loader';
import {Gauge} from '../../../components/widgets/charts/gauge';
import {Scorecard} from '../../../components/widgets/charts/scorecard';
import {
  SQLSingleValueLoader,
  type SingleValueLoaderConfig,
} from '../../../components/widgets/charts/single_value_loader';
import type {App} from '../../../public/app';
import {EnumOption, renderWidgetShowcase} from '../widgets_page_utils';
import type {Trace} from '../../../public/trace';
import {LineChartSvg} from '../../../components/widgets/charts_svg/line_chart_svg';
import {HistogramSvg} from '../../../components/widgets/charts_svg/histogram_svg';

// Generate sample data with normal distribution
function generateNormalData(
  count: number,
  mean: number,
  stdDev: number,
  integer = false,
): number[] {
  const data: number[] = [];
  for (let i = 0; i < count; i++) {
    // Box-Muller transform for normal distribution
    const u1 = Math.random();
    const u2 = Math.random();
    const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
    const v = mean + z * stdDev;
    data.push(integer ? Math.round(v) : v);
  }
  return data;
}

interface SampleSlice {
  readonly process: string;
  readonly dur: number;
}

// Simulated raw trace data: slices with process name and duration.
const SAMPLE_SLICES: readonly SampleSlice[] = [
  {process: 'Chrome', dur: 1500},
  {process: 'Chrome', dur: 2300},
  {process: 'Chrome', dur: 800},
  {process: 'Chrome', dur: 1500},
  {process: 'Chrome', dur: 4200},
  {process: 'SurfaceFlinger', dur: 500},
  {process: 'SurfaceFlinger', dur: 1200},
  {process: 'SurfaceFlinger', dur: 900},
  {process: 'SurfaceFlinger', dur: 500},
  {process: 'SystemUI', dur: 3100},
  {process: 'SystemUI', dur: 700},
  {process: 'SystemUI', dur: 2200},
  {process: 'Launcher', dur: 1800},
  {process: 'Launcher', dur: 600},
  {process: 'InputDispatcher', dur: 200},
  {process: 'InputDispatcher', dur: 350},
  {process: 'InputDispatcher', dur: 200},
  {process: 'AudioFlinger', dur: 900},
  {process: 'AudioFlinger', dur: 1100},
  {process: 'CameraService', dur: 5000},
];

export function renderCharts(app: App): m.Children {
  return [
    m(
      '.pf-widget-intro',
      m('h1', 'Charts'),
      m('p', [
        'ECharts-based chart components for visualizing data. ',
        'Includes Bar, Line, Pie/Donut, Histogram, Scatter, Treemap, Sankey, CDF, Boxplot, Heatmap, and Stat Card charts.',
      ]),
    ),

    // LineChart section
    m('h2', 'LineChart'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(LineChartDemo, {
          height: opts.height,
          brushMode: opts.brushMode,
          logScale: opts.logScale,
          showPoints: opts.showPoints,
          multiSeries: opts.multiSeries,
          stacked: opts.stacked,
          gridLines: opts.gridLines,
          legendPosition: opts.legendPosition,
        });
      },
      initialOpts: {
        height: 250,
        brushMode: new EnumOption('filter', [
          'off',
          'filter',
          'select',
        ] as const),
        logScale: false,
        showPoints: true,
        multiSeries: false,
        stacked: false,
        gridLines: new EnumOption('none', [
          'none',
          'horizontal',
          'vertical',
          'both',
        ] as const),
        legendPosition: new EnumOption('top', [
          'top',
          'right',
          'bottom',
        ] as const),
      },
    }),

    // LineChartSvg section
    m('h2', {style: {marginTop: '32px'}}, 'LineChartSvg'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(LineChartDemo, {
          height: opts.height,
          brushMode: opts.brushMode,
          logScale: opts.logScale,
          showPoints: opts.showPoints,
          multiSeries: opts.multiSeries,
          stacked: opts.stacked,
          gridLines: opts.gridLines,
          legendPosition: opts.legendPosition,
          markers: opts.markers,
          useSvg: true,
        });
      },
      initialOpts: {
        height: 250,
        brushMode: new EnumOption('filter', [
          'off',
          'filter',
          'select',
        ] as const),
        logScale: false,
        showPoints: true,
        multiSeries: false,
        stacked: false,
        gridLines: new EnumOption('none', [
          'none',
          'horizontal',
          'vertical',
          'both',
        ] as const),
        legendPosition: new EnumOption('top', [
          'top',
          'right',
          'bottom',
        ] as const),
        markers: false,
      },
    }),

    // PieChart section
    m('h2', {style: {marginTop: '32px'}}, 'PieChart'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(PieChartDemo, {
          height: opts.height,
          showLabels: opts.showLabels,
          showLegend: opts.showLegend,
          donut: opts.donut,
          legendPosition: opts.legendPosition,
        });
      },
      initialOpts: {
        height: 250,
        showLabels: false,
        showLegend: true,
        donut: false,
        legendPosition: new EnumOption('right', [
          'top',
          'right',
          'bottom',
        ] as const),
      },
    }),

    // BarChart section
    m('h2', {style: {marginTop: '32px'}}, 'BarChart'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(BarChartDemo, {
          height: opts.height,
          logScale: opts.logScale,
          brushMode: opts.brushMode,
          horizontal: opts.horizontal,
          aggregation: opts.aggregation,
          gridLines: opts.gridLines,
        });
      },
      initialOpts: {
        height: 250,
        logScale: false,
        brushMode: new EnumOption('filter', [
          'off',
          'filter',
          'select',
        ] as const),
        horizontal: false,
        aggregation: new EnumOption('SUM', [
          'SUM',
          'AVG',
          'MIN',
          'MAX',
          'COUNT_DISTINCT',
        ] as const),
        gridLines: new EnumOption('none', [
          'none',
          'horizontal',
          'vertical',
          'both',
        ] as const),
      },
    }),

    // Histogram section
    m('h2', {style: {marginTop: '32px'}}, 'Histogram'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(HistogramDemo, {
          bucketCount: opts.bucketCount,
          height: opts.height,
          brushMode: opts.brushMode,
          logScale: opts.logScale,
          integer: opts.integer,
          useSvg: false,
        });
      },
      initialOpts: {
        bucketCount: 20,
        height: 250,
        brushMode: new EnumOption('filter', [
          'off',
          'filter',
          'select',
        ] as const),
        logScale: false,
        integer: false,
      },
    }),

    // HistogramSvg section
    m('h2', {style: {marginTop: '32px'}}, 'HistogramSvg'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(HistogramDemo, {
          bucketCount: opts.bucketCount,
          height: opts.height,
          brushMode: opts.brushMode,
          logScale: opts.logScale,
          integer: opts.integer,
          useSvg: true,
        });
      },
      initialOpts: {
        bucketCount: 20,
        height: 250,
        brushMode: new EnumOption('filter', [
          'off',
          'filter',
          'select',
        ] as const),
        logScale: false,
        integer: false,
      },
    }),

    // ScatterChart section
    m('h2', {style: {marginTop: '32px'}}, 'ScatterChart'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(ScatterChartDemo, {
          height: opts.height,
          showLegend: opts.showLegend,
          bubbleMode: opts.bubbleMode,
          scaleAxes: opts.scaleAxes,
          brushMode: opts.brushMode,
          gridLines: opts.gridLines,
          legendPosition: opts.legendPosition,
        });
      },
      initialOpts: {
        height: 250,
        showLegend: true,
        bubbleMode: false,
        scaleAxes: false,
        brushMode: new EnumOption('filter', [
          'off',
          'filter',
          'select',
        ] as const),
        gridLines: new EnumOption('none', [
          'none',
          'horizontal',
          'vertical',
          'both',
        ] as const),
        legendPosition: new EnumOption('top', [
          'top',
          'right',
          'bottom',
        ] as const),
      },
    }),

    // TreemapChart section
    m('h2', {style: {marginTop: '32px'}}, 'TreemapChart'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(TreemapChartDemo, {
          height: opts.height,
          showLabels: opts.showLabels,
          hierarchical: opts.hierarchical,
        });
      },
      initialOpts: {
        height: 300,
        showLabels: true,
        hierarchical: true,
      },
    }),

    // SankeyChart section
    m('h2', {style: {marginTop: '32px'}}, 'SankeyChart'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(SankeyChartDemo, {
          height: opts.height,
        });
      },
      initialOpts: {
        height: 300,
      },
    }),

    // BoxplotChart section
    m('h2', {style: {marginTop: '32px'}}, 'BoxplotChart'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(BoxplotChartDemo, {
          height: opts.height,
          horizontal: opts.horizontal,
          gridLines: opts.gridLines,
        });
      },
      initialOpts: {
        height: 300,
        horizontal: false,
        gridLines: new EnumOption('none', [
          'none',
          'horizontal',
          'vertical',
          'both',
        ] as const),
      },
    }),

    // HeatmapChart section
    m('h2', {style: {marginTop: '32px'}}, 'HeatmapChart'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(HeatmapChartDemo, {
          height: opts.height,
          brushMode: opts.brushMode,
        });
      },
      initialOpts: {
        height: 300,
        brushMode: new EnumOption('select', [
          'off',
          'filter',
          'select',
        ] as const),
      },
    }),

    // Gauge section
    m('h2', {style: {marginTop: '32px'}}, 'Gauge'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(Gauge, {
          data: {value: opts.value},
          isPending: false,
          label: opts.label,
          height: opts.height,
          min: opts.min,
          max: opts.max,
          diameter: opts.diameter,
        });
      },
      initialOpts: {
        height: 300,
        value: 70,
        min: 0,
        max: 100,
        label: 'CPU Usage %',
        diameter: '75%',
      },
    }),

    // Scorecard section
    m('h2', {style: {marginTop: '32px'}}, 'Scorecard'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(Scorecard, {
          label: opts.label,
          value: opts.value,
          fillParent: opts.fillParent,
        });
      },
      initialOpts: {
        value: 1234,
        label: 'Total Events',
        fillParent: false,
      },
    }),

    // SQL loader demos (only shown when a trace is loaded)
    ...renderSQLDemos(app),
    app.trace === undefined &&
      m(
        'p',
        {style: {marginTop: '32px', color: 'var(--pf-color-text-muted)'}},
        'Load a trace to see the SQL loader demos.',
      ),
  ];
}

function renderSQLDemos(app: App): m.Children[] {
  const trace = app.trace;
  if (trace === undefined) return [];
  return [
    m('h3', {style: {marginTop: '32px'}}, 'SQLBarChartLoader'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(SQLBarChartDemo, {
          trace,
          height: opts.height,
          brushMode: opts.brushMode,
          logScale: opts.logScale,
          horizontal: opts.horizontal,
          aggregation: opts.aggregation,
          gridLines: opts.gridLines,
        });
      },
      initialOpts: {
        height: 250,
        brushMode: new EnumOption('filter', [
          'off',
          'filter',
          'select',
        ] as const),
        logScale: false,
        horizontal: false,
        aggregation: new EnumOption('SUM', [
          'SUM',
          'AVG',
          'MIN',
          'MAX',
          'COUNT_DISTINCT',
        ] as const),
        gridLines: new EnumOption('none', [
          'none',
          'horizontal',
          'vertical',
          'both',
        ] as const),
      },
    }),
    m('h3', {style: {marginTop: '32px'}}, 'SQLLineChartLoader'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(SQLLineChartDemo, {
          trace,
          height: opts.height,
          brushMode: opts.brushMode,
          showPoints: opts.showPoints,
          maxPoints: opts.maxPoints,
          scaleAxes: opts.scaleAxes,
          gridLines: opts.gridLines,
        });
      },
      initialOpts: {
        height: 250,
        brushMode: new EnumOption('filter', [
          'off',
          'filter',
          'select',
        ] as const),
        showPoints: true,
        maxPoints: 200,
        scaleAxes: true,
        gridLines: new EnumOption('none', [
          'none',
          'horizontal',
          'vertical',
          'both',
        ] as const),
      },
    }),
    m('h3', {style: {marginTop: '32px'}}, 'SQLLineChartLoader (Svg)'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(SQLLineChartDemo, {
          trace,
          height: opts.height,
          brushMode: opts.brushMode,
          showPoints: opts.showPoints,
          maxPoints: opts.maxPoints,
          scaleAxes: opts.scaleAxes,
          gridLines: opts.gridLines,
          useSvg: true,
        });
      },
      initialOpts: {
        height: 250,
        brushMode: new EnumOption('filter', [
          'off',
          'filter',
          'select',
        ] as const),
        showPoints: true,
        maxPoints: 200,
        scaleAxes: true,
        gridLines: new EnumOption('none', [
          'none',
          'horizontal',
          'vertical',
          'both',
        ] as const),
      },
    }),
    m('h3', {style: {marginTop: '32px'}}, 'SQLPieChartLoader'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(SQLPieChartDemo, {
          trace,
          height: opts.height,
          showLegend: opts.showLegend,
          donut: opts.donut,
          aggregation: opts.aggregation,
          limit: opts.limit,
        });
      },
      initialOpts: {
        height: 250,
        showLegend: true,
        donut: false,
        aggregation: new EnumOption('SUM', [
          'SUM',
          'AVG',
          'MIN',
          'MAX',
          'COUNT_DISTINCT',
        ] as const),
        limit: 8,
      },
    }),
    m('h3', {style: {marginTop: '32px'}}, 'SQLHistogramLoader'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(SQLHistogramDemo, {
          trace,
          bucketCount: opts.bucketCount,
          height: opts.height,
          brushMode: opts.brushMode,
          logScale: opts.logScale,
        });
      },
      initialOpts: {
        bucketCount: 20,
        height: 250,
        brushMode: new EnumOption('filter', [
          'off',
          'filter',
          'select',
        ] as const),
        logScale: false,
      },
    }),
    m('h3', {style: {marginTop: '32px'}}, 'SQLHistogramLoader (Svg)'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(SQLHistogramDemo, {
          trace,
          bucketCount: opts.bucketCount,
          height: opts.height,
          brushMode: opts.brushMode,
          logScale: opts.logScale,
          useSvg: true,
        });
      },
      initialOpts: {
        bucketCount: 20,
        height: 250,
        brushMode: new EnumOption('filter', [
          'off',
          'filter',
          'select',
        ] as const),
        logScale: false,
      },
    }),
    m('h3', {style: {marginTop: '32px'}}, 'SQLScatterChartLoader'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(SQLScatterChartDemo, {
          trace,
          height: opts.height,
          showLegend: opts.showLegend,
          maxPoints: opts.maxPoints,
          scaleAxes: opts.scaleAxes,
          brushMode: opts.brushMode,
          gridLines: opts.gridLines,
        });
      },
      initialOpts: {
        height: 250,
        showLegend: true,
        maxPoints: 500,
        scaleAxes: true,
        brushMode: new EnumOption('filter', [
          'off',
          'filter',
          'select',
        ] as const),
        gridLines: new EnumOption('none', [
          'none',
          'horizontal',
          'vertical',
          'both',
        ] as const),
      },
    }),
    m('h3', {style: {marginTop: '32px'}}, 'SQLTreemapLoader'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(SQLTreemapDemo, {
          trace,
          height: opts.height,
          showLabels: opts.showLabels,
          limit: opts.limit,
        });
      },
      initialOpts: {
        height: 300,
        showLabels: true,
        limit: 10,
      },
    }),
    m('h3', {style: {marginTop: '32px'}}, 'SQLSankeyLoader'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(SQLSankeyDemo, {
          trace,
          height: opts.height,
          limit: opts.limit,
        });
      },
      initialOpts: {
        height: 300,
        limit: 20,
      },
    }),
    m('h3', {style: {marginTop: '32px'}}, 'SQLCdfLoader'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(SQLCdfDemo, {
          trace,
          height: opts.height,
          maxPoints: opts.maxPoints,
          brushMode: opts.brushMode,
          gridLines: opts.gridLines,
        });
      },
      initialOpts: {
        height: 250,
        maxPoints: 500,
        brushMode: new EnumOption('filter', [
          'off',
          'filter',
          'select',
        ] as const),
        gridLines: new EnumOption('none', [
          'none',
          'horizontal',
          'vertical',
          'both',
        ] as const),
      },
    }),
    m('h3', {style: {marginTop: '32px'}}, 'SQLCdfLoader (Svg)'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(SQLCdfDemo, {
          trace,
          height: opts.height,
          maxPoints: opts.maxPoints,
          brushMode: opts.brushMode,
          gridLines: opts.gridLines,
          useSvg: true,
        });
      },
      initialOpts: {
        height: 250,
        maxPoints: 500,
        brushMode: new EnumOption('filter', [
          'off',
          'filter',
          'select',
        ] as const),
        gridLines: new EnumOption('none', [
          'none',
          'horizontal',
          'vertical',
          'both',
        ] as const),
      },
    }),
    m('h3', {style: {marginTop: '32px'}}, 'SQLBoxplotLoader'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(SQLBoxplotDemo, {
          trace,
          height: opts.height,
          limit: opts.limit,
          gridLines: opts.gridLines,
        });
      },
      initialOpts: {
        height: 300,
        limit: 10,
        gridLines: new EnumOption('none', [
          'none',
          'horizontal',
          'vertical',
          'both',
        ] as const),
      },
    }),
    m('h3', {style: {marginTop: '32px'}}, 'SQLHeatmapLoader'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(SQLHeatmapDemo, {
          trace,
          height: opts.height,
          xLimit: opts.xLimit,
          yLimit: opts.yLimit,
          brushMode: opts.brushMode,
        });
      },
      initialOpts: {
        height: 300,
        xLimit: 15,
        yLimit: 15,
        brushMode: new EnumOption('select', [
          'off',
          'filter',
          'select',
        ] as const),
      },
    }),
    m('h3', {style: {marginTop: '32px'}}, 'SQLSingleValueLoader (Gauge)'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(SQLGaugeDemo, {
          trace,
          aggregation: opts.aggregation,
          column: opts.column,
          label: opts.label,
          height: opts.height,
          min: opts.min,
          max: opts.max,
        });
      },
      initialOpts: {
        aggregation: new EnumOption('COUNT', [
          'COUNT',
          'SUM',
          'AVG',
          'MIN',
          'MAX',
        ] as const),
        column: new EnumOption('dur', ['dur', 'ts', 'depth'] as const),
        label: 'Slice count',
        height: 300,
        min: 0,
        max: 10000,
      },
    }),
    m('h3', {style: {marginTop: '32px'}}, 'SQLSingleValueLoader (Scorecard)'),
    renderWidgetShowcase({
      renderWidget: (opts) => {
        return m(SQLScorecardDemo, {
          trace,
          aggregation: opts.aggregation,
          column: opts.column,
          label: opts.label,
          fillParent: opts.fillParent,
        });
      },
      initialOpts: {
        aggregation: new EnumOption('COUNT', [
          'COUNT',
          'SUM',
          'AVG',
          'MIN',
          'MAX',
        ] as const),
        column: new EnumOption('dur', ['dur', 'ts', 'depth'] as const),
        label: 'Slice duration',
        fillParent: false,
      },
    }),
  ];
}

function HistogramDemo(): m.Component<{
  bucketCount: number;
  height: number;
  brushMode: 'off' | 'filter' | 'select';
  logScale: boolean;
  integer: boolean;
  useSvg?: boolean;
}> {
  const continuousData = generateNormalData(1000, 50, 15);
  const integerData = generateNormalData(1000, 50, 15, true);

  const continuousLoader = new InMemoryHistogramLoader(continuousData);
  const integerLoader = new InMemoryHistogramLoader(integerData);

  let brushedRange: {start: number; end: number} | undefined;

  return {
    view: ({attrs}) => {
      const isFilter = attrs.brushMode === 'filter';
      const loader = attrs.integer ? integerLoader : continuousLoader;
      const config: HistogramLoaderConfig = {
        bucketCount: attrs.bucketCount,
        integer: attrs.integer || undefined,
        filter:
          isFilter && brushedRange !== undefined
            ? {min: brushedRange.start, max: brushedRange.end}
            : undefined,
      };
      const {data} = loader.use(config);
      return m('div', [
        m(attrs.useSvg ? HistogramSvg : Histogram, {
          data,
          height: attrs.height,
          xAxisLabel: attrs.integer ? 'Thread Count' : 'Value',
          yAxisLabel: 'Count',
          logScale: attrs.logScale,
          integerDimension: attrs.integer,
          onBrush:
            attrs.brushMode !== 'off'
              ? (range) => {
                  brushedRange = range;
                }
              : undefined,
          selection: attrs.brushMode === 'select' ? brushedRange : undefined,
        }),
        m(
          'pre',
          {
            style: {
              marginTop: '8px',
              fontSize: '11px',
              background: 'var(--pf-color-background-secondary)',
              padding: '8px',
              borderRadius: '4px',
            },
          },
          [
            `loader.use(${JSON.stringify(config, null, 2)})`,
            brushedRange
              ? `\n\nBrushed: [${brushedRange.start.toFixed(2)}, ${brushedRange.end.toFixed(2)}]`
              : attrs.brushMode !== 'off'
                ? '\nDrag to brush-select a range'
                : '',
            !isFilter && brushedRange ? '\n(select mode — data unchanged)' : '',
          ],
        ),
        brushedRange &&
          m(
            'button',
            {
              style: {marginTop: '8px', fontSize: '12px'},
              onclick: () => {
                brushedRange = undefined;
              },
            },
            isFilter ? 'Clear filter' : 'Clear selection',
          ),
      ]);
    },
  };
}

function toGridLines(
  value: string,
): 'horizontal' | 'vertical' | 'both' | undefined {
  if (value === 'horizontal' || value === 'vertical' || value === 'both') {
    return value;
  }
  return undefined;
}

function BarChartDemo(): m.Component<{
  height: number;
  logScale: boolean;
  brushMode: 'off' | 'filter' | 'select';
  horizontal: boolean;
  aggregation: ChartAggregation;
  gridLines: string;
}> {
  let brushedLabels: Array<string | number> | undefined;

  return {
    view: ({attrs}) => {
      const isFilter = attrs.brushMode === 'filter';
      const {aggregation} = attrs;
      let data: BarChartData = aggregateBarChartData(
        SAMPLE_SLICES,
        (s) => s.process,
        (s) => s.dur,
        aggregation,
      );

      // Filter by brushed labels (only in filter mode)
      const labels = brushedLabels;
      if (isFilter && labels !== undefined) {
        data = {
          items: data.items.filter((item) => labels.includes(item.label)),
        };
      }

      const measureLabels: Record<ChartAggregation, string> = {
        ANY: 'Any Duration',
        COUNT: 'Count',
        SUM: 'Total Duration',
        AVG: 'Avg Duration',
        MIN: 'Min Duration',
        MAX: 'Max Duration',
        COUNT_DISTINCT: 'Distinct Durations',
        P25: 'p25 Duration',
        P50: 'p50 Duration',
        P75: 'p75 Duration',
        P90: 'p90 Duration',
        P95: 'p95 Duration',
        P99: 'p99 Duration',
      };

      return m('div', [
        m(BarChart, {
          data,
          height: attrs.height,
          dimensionLabel: 'Process',
          measureLabel: measureLabels[aggregation],
          integerMeasure: isIntegerAggregation(aggregation),
          logScale: attrs.logScale,
          orientation: attrs.horizontal ? 'horizontal' : 'vertical',
          gridLines: toGridLines(attrs.gridLines),
          onBrush:
            attrs.brushMode !== 'off'
              ? (newLabels) => {
                  brushedLabels = newLabels;
                }
              : undefined,
          selection: attrs.brushMode === 'select' ? brushedLabels : undefined,
        }),
        m(
          'pre',
          {
            style: {
              marginTop: '8px',
              fontSize: '11px',
              background: 'var(--pf-color-background-secondary)',
              padding: '8px',
              borderRadius: '4px',
            },
          },
          [
            brushedLabels
              ? `Brushed: [${brushedLabels.join(', ')}]`
              : attrs.brushMode !== 'off'
                ? 'Drag to brush-select bars'
                : '',
            !isFilter && brushedLabels
              ? '\n(select mode — data unchanged)'
              : '',
          ],
        ),
        brushedLabels &&
          m(
            'button',
            {
              style: {marginTop: '8px', fontSize: '12px'},
              onclick: () => {
                brushedLabels = undefined;
              },
            },
            isFilter ? 'Clear filter' : 'Clear selection',
          ),
      ]);
    },
  };
}

function SQLBarChartDemo(): m.Component<{
  trace: Trace;
  height: number;
  brushMode: 'off' | 'filter' | 'select';
  logScale: boolean;
  horizontal: boolean;
  aggregation: ChartAggregation;
  gridLines: string;
}> {
  let loader: SQLBarChartLoader | undefined;
  let brushedLabels: Array<string | number> | undefined;

  return {
    view: ({attrs}) => {
      if (!loader) {
        loader = new SQLBarChartLoader({
          engine: attrs.trace.engine,
          query: 'SELECT name, dur FROM slice WHERE dur > 0',
          dimensionColumn: 'name',
          measureColumn: 'dur',
        });
      }

      const isFilter = attrs.brushMode === 'filter';
      const {aggregation} = attrs;
      const config: BarChartLoaderConfig = {
        aggregation,
        limit: 10,
        filter: isFilter ? brushedLabels : undefined,
      };
      const {data, isPending} = loader.use(config);

      const measureLabels: Record<ChartAggregation, string> = {
        ANY: 'Any Duration',
        COUNT: 'Count',
        SUM: 'Total Duration',
        AVG: 'Avg Duration',
        MIN: 'Min Duration',
        MAX: 'Max Duration',
        COUNT_DISTINCT: 'Distinct Durations',
        P25: 'p25 Duration',
        P50: 'p50 Duration',
        P75: 'p75 Duration',
        P90: 'p90 Duration',
        P95: 'p95 Duration',
        P99: 'p99 Duration',
      };

      return m('div', [
        m(BarChart, {
          data,
          height: attrs.height,
          dimensionLabel: 'Slice Name',
          measureLabel: measureLabels[aggregation],
          integerMeasure: isIntegerAggregation(aggregation),
          logScale: attrs.logScale,
          orientation: attrs.horizontal ? 'horizontal' : 'vertical',
          gridLines: toGridLines(attrs.gridLines),
          onBrush:
            attrs.brushMode !== 'off'
              ? (labels) => {
                  brushedLabels = labels;
                }
              : undefined,
          selection: attrs.brushMode === 'select' ? brushedLabels : undefined,
        }),
        m(
          'pre',
          {
            style: {
              marginTop: '8px',
              fontSize: '11px',
              background: 'var(--pf-color-background-secondary)',
              padding: '8px',
              borderRadius: '4px',
            },
          },
          [
            `query: 'SELECT name, dur FROM slice WHERE dur > 0'\n`,
            `dimensionColumn: 'name', measureColumn: 'dur'\n`,
            `loader.use(${JSON.stringify(config, null, 2)})`,
            isPending ? '\n(loading...)' : '',
            brushedLabels ? `\nBrushed: [${brushedLabels.join(', ')}]` : '',
            !isFilter && brushedLabels
              ? '\n(select mode — data unchanged)'
              : '',
          ],
        ),
        brushedLabels &&
          m(
            'button',
            {
              style: {marginTop: '8px', fontSize: '12px'},
              onclick: () => {
                brushedLabels = undefined;
              },
            },
            isFilter ? 'Clear filter' : 'Clear selection',
          ),
      ]);
    },
    onremove: () => {
      loader?.dispose();
      loader = undefined;
    },
  };
}

function SQLHistogramDemo(): m.Component<{
  trace: Trace;
  bucketCount: number;
  height: number;
  brushMode: 'off' | 'filter' | 'select';
  logScale: boolean;
  useSvg?: boolean;
}> {
  let loader: SQLHistogramLoader | undefined;
  let brushedRange: {start: number; end: number} | undefined;

  return {
    view: ({attrs}) => {
      const isFilter = attrs.brushMode === 'filter';
      // Create loader on first render (or if trace changes)
      if (!loader) {
        loader = new SQLHistogramLoader({
          engine: attrs.trace.engine,
          query: 'SELECT dur FROM slice WHERE dur > 0',
          valueColumn: 'dur',
        });
      }

      const config: HistogramLoaderConfig = {
        bucketCount: attrs.bucketCount,
        filter:
          isFilter && brushedRange !== undefined
            ? {min: brushedRange.start, max: brushedRange.end}
            : undefined,
      };
      const {data, isPending} = loader.use(config);

      return m('div', [
        m(attrs.useSvg ? HistogramSvg : Histogram, {
          data,
          height: attrs.height,
          xAxisLabel: 'Duration (ns)',
          yAxisLabel: 'Count',
          logScale: attrs.logScale,
          onBrush:
            attrs.brushMode !== 'off'
              ? (range) => {
                  brushedRange = range;
                }
              : undefined,
          selection: attrs.brushMode === 'select' ? brushedRange : undefined,
        }),
        m(
          'pre',
          {
            style: {
              marginTop: '8px',
              fontSize: '11px',
              background: 'var(--pf-color-background-secondary)',
              padding: '8px',
              borderRadius: '4px',
            },
          },
          [
            `query: 'SELECT dur FROM slice WHERE dur > 0'\n`,
            `valueColumn: 'dur'\n`,
            `loader.use(${JSON.stringify(config, null, 2)})`,
            isPending ? '\n(loading...)' : '',
            brushedRange
              ? `\nBrushed: [${brushedRange.start.toFixed(2)}, ${brushedRange.end.toFixed(2)}]`
              : '',
            !isFilter && brushedRange ? '\n(select mode — data unchanged)' : '',
          ],
        ),
        brushedRange &&
          m(
            'button',
            {
              style: {marginTop: '8px', fontSize: '12px'},
              onclick: () => {
                brushedRange = undefined;
              },
            },
            isFilter ? 'Clear filter' : 'Clear selection',
          ),
      ]);
    },
    onremove: () => {
      loader?.dispose();
      loader = undefined;
    },
  };
}

function SQLLineChartDemo(): m.Component<{
  trace: Trace;
  height: number;
  brushMode: 'off' | 'filter' | 'select';
  showPoints: boolean;
  maxPoints: number;
  scaleAxes: boolean;
  gridLines: string;
  useSvg?: boolean;
}> {
  let loader: SQLLineChartLoader | undefined;
  let brushedRange: {start: number; end: number} | undefined;

  return {
    view: ({attrs}) => {
      const isFilter = attrs.brushMode === 'filter';
      if (!loader) {
        loader = new SQLLineChartLoader({
          engine: attrs.trace.engine,
          query: 'SELECT ts, dur, name FROM slice WHERE dur > 0 LIMIT 500',
          xColumn: 'ts',
          yColumn: 'dur',
        });
      }

      const config: LineChartLoaderConfig = {
        xRange:
          isFilter && brushedRange !== undefined
            ? {min: brushedRange.start, max: brushedRange.end}
            : undefined,
        maxPoints: attrs.maxPoints,
      };
      const {data, isPending} = loader.use(config);

      const sqlLineChartProps = {
        data,
        height: attrs.height,
        xAxisLabel: 'Timestamp',
        yAxisLabel: 'Value',
        showPoints: attrs.showPoints,
        scaleAxes: attrs.scaleAxes,
        gridLines: toGridLines(attrs.gridLines),
        onBrush:
          attrs.brushMode !== 'off'
            ? (range: {start: number; end: number}) => {
                brushedRange = range;
              }
            : undefined,
        selection: attrs.brushMode === 'select' ? brushedRange : undefined,
      };

      return m('div', [
        m(attrs.useSvg ? LineChartSvg : LineChart, sqlLineChartProps),
        m(
          'pre',
          {
            style: {
              marginTop: '8px',
              fontSize: '11px',
              background: 'var(--pf-color-background-secondary)',
              padding: '8px',
              borderRadius: '4px',
            },
          },
          [
            `query: 'SELECT ts, dur, name FROM slice WHERE dur > 0 LIMIT 500'\n`,
            `xColumn: 'ts', yColumn: 'dur'\n`,
            `loader.use(${JSON.stringify(config, null, 2)})`,
            isPending ? '\n(loading...)' : '',
            brushedRange
              ? `\nBrushed: [${brushedRange.start.toFixed(2)}, ${brushedRange.end.toFixed(2)}]`
              : '',
            !isFilter && brushedRange ? '\n(select mode — data unchanged)' : '',
          ],
        ),
        brushedRange &&
          m(
            'button',
            {
              style: {marginTop: '8px', fontSize: '12px'},
              onclick: () => {
                brushedRange = undefined;
              },
            },
            isFilter ? 'Clear filter' : 'Clear selection',
          ),
      ]);
    },
    onremove: () => {
      loader?.dispose();
      loader = undefined;
    },
  };
}

function SQLPieChartDemo(): m.Component<{
  trace: Trace;
  height: number;
  showLegend: boolean;
  donut: boolean;
  aggregation: ChartAggregation;
  limit: number;
}> {
  let loader: SQLPieChartLoader | undefined;

  return {
    view: ({attrs}) => {
      if (!loader) {
        loader = new SQLPieChartLoader({
          engine: attrs.trace.engine,
          query: 'SELECT name, dur FROM slice WHERE dur > 0',
          dimensionColumn: 'name',
          measureColumn: 'dur',
        });
      }

      const config: PieChartLoaderConfig = {
        aggregation: attrs.aggregation,
        limit: attrs.limit,
      };
      const {data, isPending} = loader.use(config);

      return m('div', [
        m(PieChart, {
          data,
          height: attrs.height,
          showLegend: attrs.showLegend,
          innerRadiusRatio: attrs.donut ? 0.5 : 0,
        }),
        m(
          'pre',
          {
            style: {
              marginTop: '8px',
              fontSize: '11px',
              background: 'var(--pf-color-background-secondary)',
              padding: '8px',
              borderRadius: '4px',
            },
          },
          [
            `query: 'SELECT name, dur FROM slice WHERE dur > 0'\n`,
            `dimensionColumn: 'name', measureColumn: 'dur'\n`,
            `loader.use(${JSON.stringify(config, null, 2)})`,
            isPending ? '\n(loading...)' : '',
          ],
        ),
      ]);
    },
    onremove: () => {
      loader?.dispose();
      loader = undefined;
    },
  };
}

// Static sample data for LineChart demo (generated once)
const LINE_CHART_SAMPLE_DATA = generateLineChartSampleData();
const LINE_CHART_MULTI_SERIES_DATA = generateMultiSeriesLineData();

function generateLineChartSampleData(): LineChartData {
  const points = [];
  for (let i = 0; i < 20; i++) {
    points.push({
      x: i,
      y: Math.sin(i * 0.5) * 50 + 50 + Math.random() * 10,
    });
  }
  return {
    series: [{name: 'Values', points}],
  };
}

function generateMultiSeriesLineData(): LineChartData {
  // Shared irregular x-values (e.g. simulating real timestamps).
  const xValues = [0, 2, 3, 7, 8, 12, 15, 18, 20, 25, 28, 30, 35, 42, 50];
  const series1 = xValues.map((x) => ({
    x,
    y: Math.abs(Math.sin(x * 0.15)) * 30 + Math.random() * 5,
  }));
  const series2 = xValues.map((x) => ({
    x,
    y: Math.abs(Math.cos(x * 0.1)) * 20 + Math.random() * 5,
  }));
  return {
    series: [
      {name: 'Series A', points: series1},
      {name: 'Series B', points: series2},
    ],
  };
}

function LineChartDemo(): m.Component<{
  height: number;
  brushMode: 'off' | 'filter' | 'select';
  logScale: boolean;
  showPoints: boolean;
  multiSeries: boolean;
  stacked: boolean;
  gridLines: string;
  legendPosition: LegendPosition;
  markers?: boolean;
  useSvg?: boolean;
}> {
  let brushRange: {start: number; end: number} | undefined;

  return {
    view: ({attrs}) => {
      const isFilter = attrs.brushMode === 'filter';
      const fullData = attrs.multiSeries
        ? LINE_CHART_MULTI_SERIES_DATA
        : LINE_CHART_SAMPLE_DATA;

      // Pass full data through; xAxisMin/xAxisMax handles the visible window.
      const range = brushRange;
      const data: LineChartData = fullData;

      const lineChartProps = {
        data,
        height: attrs.height,
        xAxisLabel: 'Time',
        yAxisLabel: 'Value',
        logScale: attrs.logScale,
        showPoints: attrs.showPoints,
        xAxisMin: isFilter ? range?.start : undefined,
        xAxisMax: isFilter ? range?.end : undefined,
        stacked: attrs.stacked,
        gridLines: toGridLines(attrs.gridLines),
        legendPosition: attrs.legendPosition,
        onBrush:
          attrs.brushMode !== 'off'
            ? (newRange: {start: number; end: number}) => {
                brushRange = newRange;
              }
            : undefined,
        selection: attrs.brushMode === 'select' ? brushRange : undefined,
        markers: attrs.markers
          ? [{x: 4}, {x: 9, color: 'var(--pf-color-warning)'}, {x: 14}]
          : undefined,
      };

      return m('div', [
        m(attrs.useSvg ? LineChartSvg : LineChart, lineChartProps),
        m(
          'pre',
          {
            style: {
              marginTop: '8px',
              fontSize: '11px',
              background: 'var(--pf-color-background-secondary)',
              padding: '8px',
              borderRadius: '4px',
            },
          },
          [
            brushRange
              ? `Brushed range: [${brushRange.start.toFixed(2)}, ${brushRange.end.toFixed(2)}]`
              : attrs.brushMode !== 'off'
                ? 'Drag to brush-select a range'
                : '',
            !isFilter && brushRange ? '\n(select mode — data unchanged)' : '',
          ],
        ),
        brushRange &&
          m(
            'button',
            {
              style: {marginTop: '8px', fontSize: '12px'},
              onclick: () => {
                brushRange = undefined;
              },
            },
            isFilter ? 'Clear filter' : 'Clear selection',
          ),
      ]);
    },
  };
}

// Static sample data for PieChart demo
const PIE_CHART_SAMPLE_DATA: PieChartData = {
  slices: [
    {label: 'Chrome', value: 35},
    {label: 'Firefox', value: 25},
    {label: 'Safari', value: 20},
    {label: 'Edge', value: 15},
    {label: 'Other', value: 5},
  ],
};

function PieChartDemo(): m.Component<{
  height: number;
  showLabels: boolean;
  showLegend: boolean;
  donut: boolean;
  legendPosition: LegendPosition;
}> {
  let clickedSlice: string | undefined;

  return {
    view: ({attrs}) => {
      return m('div', [
        m(PieChart, {
          data: PIE_CHART_SAMPLE_DATA,
          height: attrs.height,
          showLabels: attrs.showLabels,
          showLegend: attrs.showLegend,
          legendPosition: attrs.legendPosition,
          innerRadiusRatio: attrs.donut ? 0.5 : 0,
          onSliceClick: (slice) => {
            clickedSlice = slice.label;
          },
        }),
        m(
          'pre',
          {
            style: {
              marginTop: '8px',
              fontSize: '11px',
              background: 'var(--pf-color-background-secondary)',
              padding: '8px',
              borderRadius: '4px',
            },
          },
          clickedSlice
            ? `Clicked: ${clickedSlice}`
            : 'Click a slice to select it',
        ),
        clickedSlice &&
          m(
            'button',
            {
              style: {marginTop: '8px', fontSize: '12px'},
              onclick: () => {
                clickedSlice = undefined;
              },
            },
            'Clear selection',
          ),
      ]);
    },
  };
}

// Simple seeded pseudo-random number generator for reproducible demo data.
function seededRandom(seed: number): () => number {
  let s = seed;
  return () => {
    s = (s * 16807) % 2147483647;
    return s / 2147483647;
  };
}

// Static sample data for ScatterChart demo
const SCATTER_SAMPLE_DATA: ScatterChartData = (() => {
  const rng = seededRandom(42);
  return {
    series: [
      {
        name: 'Group A',
        points: Array.from({length: 30}, () => ({
          x: rng() * 100,
          y: rng() * 50 + 25,
        })),
      },
      {
        name: 'Group B',
        points: Array.from({length: 25}, () => ({
          x: rng() * 100,
          y: rng() * 50 + 50,
        })),
      },
    ],
  };
})();

const SCATTER_BUBBLE_DATA: ScatterChartData = {
  series: [
    {
      name: 'Processes',
      points: [
        {x: 10, y: 20, size: 100, label: 'Chrome'},
        {x: 25, y: 45, size: 200, label: 'Firefox'},
        {x: 40, y: 30, size: 150, label: 'Safari'},
        {x: 55, y: 60, size: 80, label: 'Edge'},
        {x: 70, y: 35, size: 300, label: 'System'},
        {x: 85, y: 50, size: 120, label: 'Launcher'},
      ],
    },
  ],
};

function ScatterChartDemo(): m.Component<{
  height: number;
  showLegend: boolean;
  bubbleMode: boolean;
  scaleAxes: boolean;
  brushMode: 'off' | 'filter' | 'select';
  gridLines: string;
  legendPosition: LegendPosition;
}> {
  let brushRange:
    | {xMin: number; xMax: number; yMin: number; yMax: number}
    | undefined;

  return {
    view: ({attrs}) => {
      const isFilter = attrs.brushMode === 'filter';
      const fullData = attrs.bubbleMode
        ? SCATTER_BUBBLE_DATA
        : SCATTER_SAMPLE_DATA;

      // Filter points only in filter mode
      const range = brushRange;
      const data: ScatterChartData =
        isFilter && range !== undefined
          ? {
              series: fullData.series.map((s) => ({
                ...s,
                points: s.points.filter(
                  (p) =>
                    p.x >= range.xMin &&
                    p.x <= range.xMax &&
                    p.y >= range.yMin &&
                    p.y <= range.yMax,
                ),
              })),
            }
          : fullData;

      return m('div', [
        m(Scatterplot, {
          data,
          height: attrs.height,
          xAxisLabel: 'X Value',
          yAxisLabel: 'Y Value',
          showLegend: attrs.showLegend,
          legendPosition: attrs.legendPosition,
          scaleAxes: attrs.scaleAxes,
          gridLines: toGridLines(attrs.gridLines),
          onBrush:
            attrs.brushMode !== 'off'
              ? (r) => {
                  brushRange = r;
                }
              : undefined,
          selection: attrs.brushMode === 'select' ? brushRange : undefined,
        }),
        m(
          'pre',
          {
            style: {
              marginTop: '8px',
              fontSize: '11px',
              background: 'var(--pf-color-background-secondary)',
              padding: '8px',
              borderRadius: '4px',
            },
          },
          [
            attrs.bubbleMode
              ? 'Bubble mode: size encodes a third dimension'
              : 'Regular scatter plot with two series',
            attrs.scaleAxes
              ? '\nscaleAxes: true (axis range from data min/max)'
              : '\nscaleAxes: false (axis range includes zero)',
            range
              ? `\n\nBrushed: X=[${range.xMin.toFixed(1)}, ${range.xMax.toFixed(1)}]  Y=[${range.yMin.toFixed(1)}, ${range.yMax.toFixed(1)}]`
              : attrs.brushMode !== 'off'
                ? '\n\nDrag to brush-select a region'
                : '',
            !isFilter && range ? '\n(select mode — data unchanged)' : '',
          ],
        ),
        range &&
          m(
            'button',
            {
              style: {marginTop: '8px', fontSize: '12px'},
              onclick: () => {
                brushRange = undefined;
              },
            },
            isFilter ? 'Clear filter' : 'Clear selection',
          ),
      ]);
    },
  };
}

// Static sample data for TreemapChart demo
const TREEMAP_FLAT_DATA: TreemapData = {
  nodes: [
    {name: 'Chrome', value: 350},
    {name: 'SurfaceFlinger', value: 150},
    {name: 'SystemUI', value: 200},
    {name: 'Launcher', value: 100},
    {name: 'InputDispatcher', value: 75},
    {name: 'AudioFlinger', value: 125},
  ],
};

const TREEMAP_HIERARCHICAL_DATA: TreemapData = {
  nodes: [
    {
      name: 'UI Processes',
      value: 650, // Sum of children: 350 + 200 + 100
      children: [
        {name: 'Chrome', value: 350},
        {name: 'SystemUI', value: 200},
        {name: 'Launcher', value: 100},
      ],
    },
    {
      name: 'System Services',
      value: 350, // Sum of children: 150 + 75 + 125
      children: [
        {name: 'SurfaceFlinger', value: 150},
        {name: 'InputDispatcher', value: 75},
        {name: 'AudioFlinger', value: 125},
      ],
    },
  ],
};

function TreemapChartDemo(): m.Component<{
  height: number;
  showLabels: boolean;
  hierarchical: boolean;
}> {
  let clickedNode: string | undefined;

  return {
    view: ({attrs}) => {
      const data = attrs.hierarchical
        ? TREEMAP_HIERARCHICAL_DATA
        : TREEMAP_FLAT_DATA;
      return m('div', [
        m(Treemap, {
          data,
          height: attrs.height,
          showLabels: attrs.showLabels,
          onNodeClick: (node) => {
            clickedNode = node.name;
          },
        }),
        m(
          'pre',
          {
            style: {
              marginTop: '8px',
              fontSize: '11px',
              background: 'var(--pf-color-background-secondary)',
              padding: '8px',
              borderRadius: '4px',
            },
          },
          clickedNode ? `Clicked: ${clickedNode}` : 'Click a node to select it',
        ),
        clickedNode &&
          m(
            'button',
            {
              style: {marginTop: '8px', fontSize: '12px'},
              onclick: () => {
                clickedNode = undefined;
              },
            },
            'Clear selection',
          ),
      ]);
    },
  };
}

// Static sample data for SankeyChart demo
const SANKEY_DATA: SankeyData = {
  nodes: [
    {name: 'App'},
    {name: 'SurfaceFlinger'},
    {name: 'HWC'},
    {name: 'GPU'},
    {name: 'Display'},
    {name: 'Vsync'},
  ],
  links: [
    {source: 'App', target: 'SurfaceFlinger', value: 400},
    {source: 'App', target: 'GPU', value: 200},
    {source: 'SurfaceFlinger', target: 'HWC', value: 300},
    {source: 'SurfaceFlinger', target: 'GPU', value: 100},
    {source: 'GPU', target: 'Display', value: 300},
    {source: 'HWC', target: 'Display', value: 300},
    {source: 'Vsync', target: 'App', value: 600},
    {source: 'Vsync', target: 'SurfaceFlinger', value: 400},
  ],
};

function SankeyChartDemo(): m.Component<{
  height: number;
}> {
  let clickedNode: string | undefined;

  return {
    view: ({attrs}) => {
      return m('div', [
        m(Sankey, {
          data: SANKEY_DATA,
          height: attrs.height,
          onNodeClick: (node) => {
            clickedNode = node.name;
          },
        }),
        m(
          'pre',
          {
            style: {
              marginTop: '8px',
              fontSize: '11px',
              background: 'var(--pf-color-background-secondary)',
              padding: '8px',
              borderRadius: '4px',
            },
          },
          clickedNode ? `Clicked: ${clickedNode}` : 'Click a node to select it',
        ),
        clickedNode &&
          m(
            'button',
            {
              style: {marginTop: '8px', fontSize: '12px'},
              onclick: () => {
                clickedNode = undefined;
              },
            },
            'Clear selection',
          ),
      ]);
    },
  };
}

function SQLSankeyDemo(): m.Component<{
  trace: Trace;
  height: number;
  limit: number;
}> {
  let loader: SQLSankeyLoader | undefined;
  let clickedNode: string | undefined;

  return {
    view: ({attrs}) => {
      if (!loader) {
        loader = new SQLSankeyLoader({
          engine: attrs.trace.engine,
          query: 'SELECT utid, state, dur FROM thread_state WHERE dur > 0',
          sourceColumn: 'utid',
          targetColumn: 'state',
          valueColumn: 'dur',
        });
      }

      const config: SankeyLoaderConfig = {
        aggregation: 'SUM',
        limit: attrs.limit,
      };
      const {data, isPending} = loader.use(config);

      return m('div', [
        m(Sankey, {
          data,
          height: attrs.height,
          onNodeClick: (node) => {
            clickedNode = node.name;
          },
        }),
        m(
          'pre',
          {
            style: {
              marginTop: '8px',
              fontSize: '11px',
              background: 'var(--pf-color-background-secondary)',
              padding: '8px',
              borderRadius: '4px',
            },
          },
          [
            `query: 'SELECT upid, state, dur FROM thread_state WHERE dur > 0'\n`,
            `sourceColumn: 'upid', targetColumn: 'state', valueColumn: 'dur'\n`,
            `loader.use(${JSON.stringify(config, null, 2)})`,
            isPending ? '\n(loading...)' : '',
            clickedNode ? `\nClicked: ${clickedNode}` : '',
          ],
        ),
        clickedNode &&
          m(
            'button',
            {
              style: {marginTop: '8px', fontSize: '12px'},
              onclick: () => {
                clickedNode = undefined;
              },
            },
            'Clear selection',
          ),
      ]);
    },
    onremove: () => {
      loader?.dispose();
      loader = undefined;
    },
  };
}

function SQLScatterChartDemo(): m.Component<{
  trace: Trace;
  height: number;
  showLegend: boolean;
  maxPoints: number;
  scaleAxes: boolean;
  brushMode: 'off' | 'filter' | 'select';
  gridLines: string;
}> {
  let loader: SQLScatterChartLoader | undefined;
  let brushRange:
    | {xMin: number; xMax: number; yMin: number; yMax: number}
    | undefined;

  return {
    view: ({attrs}) => {
      if (!loader) {
        loader = new SQLScatterChartLoader({
          engine: attrs.trace.engine,
          query: 'SELECT ts, dur, name FROM slice WHERE dur > 0 LIMIT 1000',
          xColumn: 'ts',
          yColumn: 'dur',
          seriesColumn: 'name',
        });
      }

      const isFilter = attrs.brushMode === 'filter';
      // Always load with just maxPoints — brush filtering is applied
      // client-side on the loaded points rather than via SQL-level range
      // filters to avoid mismatches between floating-point brush coordinates
      // and the integer values stored in SQLite.
      const config: ScatterChartLoaderConfig = {
        maxPoints: attrs.maxPoints,
      };
      const {data: rawData, isPending} = loader.use(config);

      // Filter points only in filter mode
      const range = brushRange;
      const data: ScatterChartData | undefined =
        isFilter && rawData !== undefined && range !== undefined
          ? {
              series: rawData.series.map((s) => ({
                ...s,
                points: s.points.filter(
                  (p) =>
                    p.x >= range.xMin &&
                    p.x <= range.xMax &&
                    p.y >= range.yMin &&
                    p.y <= range.yMax,
                ),
              })),
            }
          : rawData;

      return m('div', [
        m(Scatterplot, {
          data,
          height: attrs.height,
          xAxisLabel: 'Timestamp',
          yAxisLabel: 'Duration',
          showLegend: attrs.showLegend,
          scaleAxes: attrs.scaleAxes,
          gridLines: toGridLines(attrs.gridLines),
          onBrush:
            attrs.brushMode !== 'off'
              ? (r) => {
                  brushRange = r;
                }
              : undefined,
          selection: attrs.brushMode === 'select' ? brushRange : undefined,
        }),
        m(
          'pre',
          {
            style: {
              marginTop: '8px',
              fontSize: '11px',
              background: 'var(--pf-color-background-secondary)',
              padding: '8px',
              borderRadius: '4px',
            },
          },
          [
            `query: 'SELECT ts, dur, name FROM slice WHERE dur > 0 LIMIT 1000'\n`,
            `xColumn: 'ts', yColumn: 'dur', seriesColumn: 'name'\n`,
            `loader.use(${JSON.stringify(config, null, 2)})`,
            range
              ? `\n\nBrushed: X=[${range.xMin.toFixed(0)}, ${range.xMax.toFixed(0)}]  Y=[${range.yMin.toFixed(0)}, ${range.yMax.toFixed(0)}]`
              : attrs.brushMode !== 'off'
                ? '\n\nDrag to brush-select a region'
                : '',
            !isFilter && range ? '\n(select mode — data unchanged)' : '',
            isPending ? '\n(loading...)' : '',
          ],
        ),
        range &&
          m(
            'button',
            {
              style: {marginTop: '8px', fontSize: '12px'},
              onclick: () => {
                brushRange = undefined;
              },
            },
            isFilter ? 'Clear filter' : 'Clear selection',
          ),
      ]);
    },
    onremove: () => {
      loader?.dispose();
      loader = undefined;
    },
  };
}

function SQLTreemapDemo(): m.Component<{
  trace: Trace;
  height: number;
  showLabels: boolean;
  limit: number;
}> {
  let loader: SQLTreemapLoader | undefined;
  let clickedNode: string | undefined;

  return {
    view: ({attrs}) => {
      if (!loader) {
        loader = new SQLTreemapLoader({
          engine: attrs.trace.engine,
          query: 'SELECT name, dur, category FROM slice WHERE dur > 0',
          labelColumn: 'name',
          sizeColumn: 'dur',
          groupColumn: 'category',
        });
      }

      const config: TreemapLoaderConfig = {
        aggregation: 'SUM',
        limit: attrs.limit,
      };
      const {data, isPending} = loader.use(config);

      return m('div', [
        m(Treemap, {
          data,
          height: attrs.height,
          showLabels: attrs.showLabels,
          onNodeClick: (node) => {
            clickedNode = node.name;
          },
        }),
        m(
          'pre',
          {
            style: {
              marginTop: '8px',
              fontSize: '11px',
              background: 'var(--pf-color-background-secondary)',
              padding: '8px',
              borderRadius: '4px',
            },
          },
          [
            `query: 'SELECT name, dur, category FROM slice WHERE dur > 0'\n`,
            `labelColumn: 'name', sizeColumn: 'dur', groupColumn: 'category'\n`,
            `loader.use(${JSON.stringify(config, null, 2)})`,
            isPending ? '\n(loading...)' : '',
            clickedNode ? `\nClicked: ${clickedNode}` : '',
          ],
        ),
        clickedNode &&
          m(
            'button',
            {
              style: {marginTop: '8px', fontSize: '12px'},
              onclick: () => {
                clickedNode = undefined;
              },
            },
            'Clear selection',
          ),
      ]);
    },
    onremove: () => {
      loader?.dispose();
      loader = undefined;
    },
  };
}

// ---------------------------------------------------------------------------
// Static sample data for BoxplotChart demo
// ---------------------------------------------------------------------------

const BOXPLOT_SAMPLE_DATA: BoxplotData = {
  items: [
    {label: 'Chrome', min: 200, q1: 800, median: 1500, q3: 2300, max: 4200},
    {
      label: 'SurfaceFlinger',
      min: 400,
      q1: 500,
      median: 850,
      q3: 1050,
      max: 1200,
    },
    {label: 'SystemUI', min: 600, q1: 700, median: 2200, q3: 2650, max: 3100},
    {label: 'Launcher', min: 500, q1: 600, median: 1200, q3: 1500, max: 1800},
    {
      label: 'AudioFlinger',
      min: 800,
      q1: 900,
      median: 1000,
      q3: 1050,
      max: 1100,
    },
  ],
};

function BoxplotChartDemo(): m.Component<{
  height: number;
  horizontal: boolean;
  gridLines: string;
}> {
  return {
    view: ({attrs}) => {
      return m('div', [
        m(BoxplotChart, {
          data: BOXPLOT_SAMPLE_DATA,
          height: attrs.height,
          categoryLabel: 'Process',
          valueLabel: 'Duration (ns)',
          orientation: attrs.horizontal ? 'horizontal' : 'vertical',
          gridLines: toGridLines(attrs.gridLines),
        }),
        m(
          'pre',
          {
            style: {
              marginTop: '8px',
              fontSize: '11px',
              background: 'var(--pf-color-background-secondary)',
              padding: '8px',
              borderRadius: '4px',
            },
          },
          'Static boxplot with quartile statistics per process',
        ),
      ]);
    },
  };
}

// ---------------------------------------------------------------------------
// Static sample data for HeatmapChart demo
// ---------------------------------------------------------------------------

const HEATMAP_SAMPLE_DATA: HeatmapData = (() => {
  const xLabels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'];
  const yLabels = ['Chrome', 'SurfaceFlinger', 'SystemUI', 'Launcher'];
  const rng = seededRandom(123);
  const values: Array<readonly [number, number, number]> = [];
  let min = Infinity;
  let max = -Infinity;
  for (let x = 0; x < xLabels.length; x++) {
    for (let y = 0; y < yLabels.length; y++) {
      const val = Math.floor(rng() * 100);
      min = Math.min(min, val);
      max = Math.max(max, val);
      values.push([x, y, val]);
    }
  }
  return {xLabels, yLabels, values, min, max};
})();

function HeatmapChartDemo(): m.Component<{
  height: number;
  brushMode: 'off' | 'filter' | 'select';
}> {
  let brushedSelection: {xLabels: string[]; yLabels: string[]} | undefined;

  return {
    view: ({attrs}) => {
      const isFilter = attrs.brushMode === 'filter';
      let data = HEATMAP_SAMPLE_DATA;

      // Filter by brushed selection (only in filter mode)
      if (isFilter && brushedSelection !== undefined) {
        const xSet = new Set(brushedSelection.xLabels);
        const ySet = new Set(brushedSelection.yLabels);
        const xIndices = new Map<string, number>();
        const yIndices = new Map<string, number>();
        const filteredXLabels = data.xLabels.filter((l) => xSet.has(l));
        const filteredYLabels = data.yLabels.filter((l) => ySet.has(l));
        filteredXLabels.forEach((l, i) => xIndices.set(l, i));
        filteredYLabels.forEach((l, i) => yIndices.set(l, i));
        let min = Infinity;
        let max = -Infinity;
        const filteredValues: Array<readonly [number, number, number]> = [];
        for (const [xIdx, yIdx, val] of data.values) {
          const xLabel = data.xLabels[xIdx];
          const yLabel = data.yLabels[yIdx];
          const newX = xIndices.get(xLabel);
          const newY = yIndices.get(yLabel);
          if (newX !== undefined && newY !== undefined) {
            filteredValues.push([newX, newY, val]);
            min = Math.min(min, val);
            max = Math.max(max, val);
          }
        }
        if (filteredValues.length > 0) {
          data = {
            xLabels: filteredXLabels,
            yLabels: filteredYLabels,
            values: filteredValues,
            min,
            max,
          };
        }
      }

      return m('div', [
        m(HeatmapChart, {
          data,
          height: attrs.height,
          xAxisLabel: 'Day',
          yAxisLabel: 'Process',
          onBrush:
            attrs.brushMode !== 'off'
              ? (sel: {xLabels: string[]; yLabels: string[]}) => {
                  brushedSelection = sel;
                }
              : undefined,
          selection:
            attrs.brushMode === 'select' ? brushedSelection : undefined,
        }),
        m(
          'pre',
          {
            style: {
              marginTop: '8px',
              fontSize: '11px',
              background: 'var(--pf-color-background-secondary)',
              padding: '8px',
              borderRadius: '4px',
            },
          },
          [
            brushedSelection
              ? `Brushed: x=[${brushedSelection.xLabels.join(', ')}] y=[${brushedSelection.yLabels.join(', ')}]`
              : attrs.brushMode !== 'off'
                ? 'Drag to brush-select cells'
                : 'Static heatmap: process activity by day of week',
            !isFilter && brushedSelection
              ? '\n(select mode — data unchanged)'
              : '',
          ],
        ),
        brushedSelection &&
          m(
            'button',
            {
              style: {marginTop: '8px', fontSize: '12px'},
              onclick: () => {
                brushedSelection = undefined;
              },
            },
            isFilter ? 'Clear filter' : 'Clear selection',
          ),
      ]);
    },
  };
}

// ---------------------------------------------------------------------------
// SQL CDF demo
// ---------------------------------------------------------------------------

function SQLCdfDemo(): m.Component<{
  trace: Trace;
  height: number;
  maxPoints: number;
  brushMode: 'off' | 'filter' | 'select';
  gridLines: string;
  useSvg?: boolean;
}> {
  let loader: SQLCdfLoader | undefined;
  let brushedRange: {start: number; end: number} | undefined;

  return {
    view: ({attrs}) => {
      const isFilter = attrs.brushMode === 'filter';
      if (!loader) {
        loader = new SQLCdfLoader({
          engine: attrs.trace.engine,
          query: 'SELECT dur FROM slice WHERE dur > 0',
          valueColumn: 'dur',
        });
      }

      const config: CdfLoaderConfig = {
        maxPoints: attrs.maxPoints,
        filter:
          isFilter && brushedRange !== undefined
            ? {min: brushedRange.start, max: brushedRange.end}
            : undefined,
      };
      const {data, isPending} = loader.use(config);

      const cdfProps = {
        data,
        height: attrs.height,
        xAxisLabel: 'Duration (ns)',
        yAxisLabel: 'Cumulative %',
        showPoints: false,
        scaleAxes: true,
        gridLines: toGridLines(attrs.gridLines),
        onBrush:
          attrs.brushMode !== 'off'
            ? (range: {start: number; end: number}) => {
                brushedRange = range;
              }
            : undefined,
        selection: attrs.brushMode === 'select' ? brushedRange : undefined,
      };

      return m('div', [
        m(attrs.useSvg ? LineChartSvg : LineChart, cdfProps),
        m(
          'pre',
          {
            style: {
              marginTop: '8px',
              fontSize: '11px',
              background: 'var(--pf-color-background-secondary)',
              padding: '8px',
              borderRadius: '4px',
            },
          },
          [
            `query: 'SELECT dur FROM slice WHERE dur > 0'\n`,
            `valueColumn: 'dur'\n`,
            `loader.use(${JSON.stringify(config, null, 2)})`,
            isPending ? '\n(loading...)' : '',
            brushedRange
              ? `\nBrushed: [${brushedRange.start.toFixed(2)}, ${brushedRange.end.toFixed(2)}]`
              : '',
            !isFilter && brushedRange ? '\n(select mode — data unchanged)' : '',
          ],
        ),
        brushedRange &&
          m(
            'button',
            {
              style: {marginTop: '8px', fontSize: '12px'},
              onclick: () => {
                brushedRange = undefined;
              },
            },
            isFilter ? 'Clear filter' : 'Clear selection',
          ),
      ]);
    },
    onremove: () => {
      loader?.dispose();
      loader = undefined;
    },
  };
}

// ---------------------------------------------------------------------------
// SQL Boxplot demo
// ---------------------------------------------------------------------------

function SQLBoxplotDemo(): m.Component<{
  trace: Trace;
  height: number;
  limit: number;
  gridLines: string;
}> {
  let loader: SQLBoxplotLoader | undefined;

  return {
    view: ({attrs}) => {
      if (!loader) {
        loader = new SQLBoxplotLoader({
          engine: attrs.trace.engine,
          query: 'SELECT name, dur FROM slice WHERE dur > 0',
          categoryColumn: 'name',
          valueColumn: 'dur',
        });
      }

      const config: BoxplotLoaderConfig = {
        limit: attrs.limit,
      };
      const {data, isPending} = loader.use(config);

      return m('div', [
        m(BoxplotChart, {
          data,
          height: attrs.height,
          categoryLabel: 'Slice Name',
          valueLabel: 'Duration (ns)',
          gridLines: toGridLines(attrs.gridLines),
        }),
        m(
          'pre',
          {
            style: {
              marginTop: '8px',
              fontSize: '11px',
              background: 'var(--pf-color-background-secondary)',
              padding: '8px',
              borderRadius: '4px',
            },
          },
          [
            `query: 'SELECT name, dur FROM slice WHERE dur > 0'\n`,
            `categoryColumn: 'name', valueColumn: 'dur'\n`,
            `loader.use(${JSON.stringify(config, null, 2)})`,
            isPending ? '\n(loading...)' : '',
          ],
        ),
      ]);
    },
    onremove: () => {
      loader?.dispose();
      loader = undefined;
    },
  };
}

// ---------------------------------------------------------------------------
// SQL Heatmap demo
// ---------------------------------------------------------------------------

function SQLHeatmapDemo(): m.Component<{
  trace: Trace;
  height: number;
  xLimit: number;
  yLimit: number;
  brushMode: 'off' | 'filter' | 'select';
}> {
  let loader: SQLHeatmapLoader | undefined;
  let brushedSelection: {xLabels: string[]; yLabels: string[]} | undefined;

  return {
    view: ({attrs}) => {
      if (!loader) {
        loader = new SQLHeatmapLoader({
          engine: attrs.trace.engine,
          query: 'SELECT priority, end_state, dur FROM sched WHERE dur > 0',
          xColumn: 'priority',
          yColumn: 'end_state',
          valueColumn: 'dur',
        });
      }

      const isFilter = attrs.brushMode === 'filter';

      const config: HeatmapLoaderConfig = {
        aggregation: 'SUM',
        xLimit: attrs.xLimit,
        yLimit: attrs.yLimit,
      };
      let {data} = loader.use(config);
      const {isPending} = loader.use(config);

      // Filter by brushed selection (only in filter mode)
      if (isFilter && brushedSelection !== undefined && data !== undefined) {
        const xSet = new Set(brushedSelection.xLabels);
        const ySet = new Set(brushedSelection.yLabels);
        const xIndices = new Map<string, number>();
        const yIndices = new Map<string, number>();
        const filteredXLabels = data.xLabels.filter((l) => xSet.has(l));
        const filteredYLabels = data.yLabels.filter((l) => ySet.has(l));
        filteredXLabels.forEach((l, i) => xIndices.set(l, i));
        filteredYLabels.forEach((l, i) => yIndices.set(l, i));
        let min = Infinity;
        let max = -Infinity;
        const filteredValues: Array<readonly [number, number, number]> = [];
        for (const [xIdx, yIdx, val] of data.values) {
          const xLabel = data.xLabels[xIdx];
          const yLabel = data.yLabels[yIdx];
          const newX = xIndices.get(xLabel);
          const newY = yIndices.get(yLabel);
          if (newX !== undefined && newY !== undefined) {
            filteredValues.push([newX, newY, val]);
            min = Math.min(min, val);
            max = Math.max(max, val);
          }
        }
        if (filteredValues.length > 0) {
          data = {
            xLabels: filteredXLabels,
            yLabels: filteredYLabels,
            values: filteredValues,
            min,
            max,
          };
        }
      }

      return m('div', [
        m(HeatmapChart, {
          data,
          height: attrs.height,
          xAxisLabel: 'Priority',
          yAxisLabel: 'End State',
          onBrush:
            attrs.brushMode !== 'off'
              ? (sel: {xLabels: string[]; yLabels: string[]}) => {
                  brushedSelection = sel;
                }
              : undefined,
          selection:
            attrs.brushMode === 'select' ? brushedSelection : undefined,
        }),
        m(
          'pre',
          {
            style: {
              marginTop: '8px',
              fontSize: '11px',
              background: 'var(--pf-color-background-secondary)',
              padding: '8px',
              borderRadius: '4px',
            },
          },
          [
            `query: 'SELECT priority, end_state, dur FROM sched WHERE dur > 0'\n`,
            `xColumn: 'priority', yColumn: 'end_state', valueColumn: 'dur'\n`,
            `loader.use(${JSON.stringify(config, null, 2)})`,
            isPending ? '\n(loading...)' : '',
            brushedSelection
              ? `\nBrushed: x=[${brushedSelection.xLabels.join(', ')}] y=[${brushedSelection.yLabels.join(', ')}]`
              : '',
          ],
        ),
        brushedSelection &&
          m(
            'button',
            {
              style: {marginTop: '8px', fontSize: '12px'},
              onclick: () => {
                brushedSelection = undefined;
              },
            },
            isFilter ? 'Clear filter' : 'Clear selection',
          ),
      ]);
    },
    onremove: () => {
      loader?.dispose();
      loader = undefined;
    },
  };
}

// ---------------------------------------------------------------------------
// SQL Scorecard demo
// ---------------------------------------------------------------------------

function SQLScorecardDemo(): m.Component<{
  trace: Trace;
  aggregation: ChartAggregation;
  column: string;
  label: string;
  fillParent?: boolean;
}> {
  let loader: SQLSingleValueLoader | undefined;
  let currentColumn: string | undefined;

  return {
    view: ({attrs}) => {
      if (!loader || currentColumn !== attrs.column) {
        loader?.dispose();
        currentColumn = attrs.column;
        loader = new SQLSingleValueLoader({
          engine: attrs.trace.engine,
          query: `SELECT ${attrs.column} FROM slice WHERE ${attrs.column} > 0`,
          measureColumn: attrs.column,
        });
      }

      const config: SingleValueLoaderConfig = {
        aggregation: attrs.aggregation,
      };
      const {data, isPending} = loader.use(config);

      return m('div', [
        m(
          'div',
          {
            style: {
              width: '250px',
              height: '120px',
              border: '1px solid var(--pf-color-border)',
              borderRadius: '8px',
            },
          },
          m(Scorecard, {
            value: data?.value,
            isPending,
            label: attrs.label,
            fillParent: attrs.fillParent,
          }),
        ),
        m(
          'pre',
          {
            style: {
              marginTop: '8px',
              fontSize: '11px',
              background: 'var(--pf-color-background-secondary)',
              padding: '8px',
              borderRadius: '4px',
            },
          },
          [
            `query: 'SELECT ${attrs.column} FROM slice WHERE ${attrs.column} > 0'\n`,
            `measureColumn: '${attrs.column}'\n`,
            `loader.use(${JSON.stringify(config, null, 2)})`,
            isPending ? '\n(loading...)' : '',
          ],
        ),
      ]);
    },
    onremove: () => {
      loader?.dispose();
      loader = undefined;
    },
  };
}

// ---------------------------------------------------------------------------
// SQL Gauge demo
// ---------------------------------------------------------------------------

function SQLGaugeDemo(): m.Component<{
  trace: Trace;
  aggregation: ChartAggregation;
  column: string;
  label: string;
  height: number;
  min: number;
  max: number;
}> {
  let loader: SQLSingleValueLoader | undefined;
  let currentColumn: string | undefined;

  return {
    view: ({attrs}) => {
      if (!loader || currentColumn !== attrs.column) {
        loader?.dispose();
        currentColumn = attrs.column;
        loader = new SQLSingleValueLoader({
          engine: attrs.trace.engine,
          query: `SELECT ${attrs.column} FROM slice WHERE ${attrs.column} > 0`,
          measureColumn: attrs.column,
        });
      }

      const config: SingleValueLoaderConfig = {
        aggregation: attrs.aggregation,
      };
      const {data, isPending} = loader.use(config);

      return m('div', [
        m(Gauge, {
          data,
          isPending,
          label: attrs.label,
          height: attrs.height,
          min: attrs.min,
          max: attrs.max,
        }),
        m(
          'pre',
          {
            style: {
              marginTop: '8px',
              fontSize: '11px',
              background: 'var(--pf-color-background-secondary)',
              padding: '8px',
              borderRadius: '4px',
            },
          },
          [
            `query: 'SELECT ${attrs.column} FROM slice WHERE ${attrs.column} > 0'\n`,
            `measureColumn: '${attrs.column}'\n`,
            `loader.use(${JSON.stringify(config, null, 2)})`,
            isPending ? '\n(loading...)' : '',
          ],
        ),
      ]);
    },
    onremove: () => {
      loader?.dispose();
      loader = undefined;
    },
  };
}
