/**
 * 📊 Chart Templates — 25 Metabase-Inspired Visualization Types
 * 
 * AI returns structured schema → frontend matches chart_type → renders with warm palette.
 * 
 * Types: bar, horizontalBar, multiBar, stackedBar, line, area, stackedArea, 
 *        pie, doughnut, scatter, combo, bubble, radar, polarArea, waterfall,
 *        numberCard, gauge, progress, funnel, table,
 *        treemap, heatmap, boxplot, sankey, pivotTable
 */

// ─── Warm Color Palettes ────────────────────────────────────────────

const PALETTES = {
  warm:    ['#C75B39','#E8734F','#B8860B','#2E7D32','#5C6BC0','#AB47BC','#00897B','#F4511E','#6D4C41','#78909C'],
  pastel:  ['#FFB74D','#FF8A65','#A1887F','#81C784','#64B5F6','#BA68C8','#4DB6AC','#FF7043','#8D6E63','#90A4AE'],
  earth:   ['#8D6E63','#BCAAA4','#A5D6A7','#FFE082','#90CAF9','#CE93D8','#80CBC4','#FFAB91','#D7CCC8','#B0BEC5'],
  vibrant: ['#FF6F00','#D84315','#1B5E20','#0D47A1','#4A148C','#880E4F','#004D40','#BF360C','#3E2723','#263238'],
};

function getColors(n, palette) {
  const p = PALETTES[palette] || PALETTES.warm;
  const colors = [];
  for (let i = 0; i < n; i++) colors.push(p[i % p.length]);
  return colors;
}

// ─── Shared Chart.js Defaults ───────────────────────────────────────

const CHART_DEFAULTS = {
  font: { family: "'Outfit', sans-serif", size: 11 },
  gridColor: '#E8DED4',
  textColor: '#3D2B1F',
  mutedColor: '#8B7355',
  bgColor: '#FFFFFF',
};

function applyWarmTheme(config) {
  if (!config.options) config.options = {};
  if (!config.options.plugins) config.options.plugins = {};
  
  // Font
  config.options.font = CHART_DEFAULTS.font;
  
  // Legend
  if (!config.options.plugins.legend) config.options.plugins.legend = {};
  config.options.plugins.legend.labels = { 
    color: CHART_DEFAULTS.textColor, 
    font: { family: CHART_DEFAULTS.font.family, size: 11 },
    padding: 16,
    usePointStyle: true,
    pointStyle: 'rectRounded',
  };
  
  // Tooltip
  config.options.plugins.tooltip = {
    backgroundColor: '#3D2B1F',
    titleColor: '#FFF',
    bodyColor: '#FFF',
    borderColor: '#C75B39',
    borderWidth: 1,
    cornerRadius: 8,
    padding: 10,
    titleFont: { family: CHART_DEFAULTS.font.family, size: 12, weight: '600' },
    bodyFont: { family: CHART_DEFAULTS.font.family, size: 11 },
    displayColors: true,
    boxPadding: 4,
  };

  // Scales
  if (config.options.scales) {
    for (const [key, axis] of Object.entries(config.options.scales)) {
      if (axis.grid) axis.grid.color = CHART_DEFAULTS.gridColor;
      if (axis.ticks) {
        axis.ticks.color = CHART_DEFAULTS.mutedColor;
        axis.ticks.font = { family: CHART_DEFAULTS.font.family, size: 10 };
      }
      axis.border = { display: false };
    }
  }
  
  // Animation
  config.options.animation = { duration: 600, easing: 'easeOutQuart' };
  config.options.responsive = true;
  config.options.maintainAspectRatio = false;
  
  return config;
}

function fmt(val) {
  if (val === null || val === undefined) return '—';
  if (typeof val === 'number') {
    if (Math.abs(val) >= 1e6) return (val / 1e6).toFixed(1) + 'M';
    if (Math.abs(val) >= 1e3) return (val / 1e3).toFixed(1) + 'K';
    return val.toLocaleString('vi-VN');
  }
  return String(val);
}

function escH(s) {
  if (!s) return '';
  return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}

// ─── Card Wrapper ───────────────────────────────────────────────────

function chartCard(data, innerHtml, canvasId) {
  return `
    <div class="chart-card">
      <div class="cc-header">
        <div class="cc-title">${escH(data.title || 'Visualization')}</div>
        ${data.description ? `<div class="cc-desc">${escH(data.description)}</div>` : ''}
      </div>
      ${data.trend ? `
        <div class="cc-trend ${data.trend.direction === 'up' ? 'up' : 'down'}">
          ${data.trend.direction === 'up' ? '↑' : '↓'} ${data.trend.value || data.trend.percentage || 0}%
          ${data.trend.label ? `<span class="cc-trend-label">${escH(data.trend.label)}</span>` : ''}
        </div>` : ''}
      <div class="cc-body">${innerHtml}</div>
      ${data.footer ? `<div class="cc-footer">${escH(data.footer)}</div>` : ''}
      <div class="cc-meta">
        <span>📊 ${escH(data.chart_type || 'chart')}</span>
        <span>📋 ${data.data ? data.data.length : 0} rows</span>
      </div>
    </div>`;
}

function makeCanvas(id) {
  return `<div class="cc-canvas-wrap"><canvas id="${id}"></canvas></div>`;
}

// ─── Global counter ─────────────────────────────────────────────────
let _chartId = 0;
function nextId() { return 'cht_' + (++_chartId); }

// ═══════════════════════════════════════════════════════════════════
// TIER 1 — Chart.js Native (10 types)
// ═══════════════════════════════════════════════════════════════════

function renderBar(data) {
  const id = nextId();
  const xKey = data.x_key || data.xAxisKey || Object.keys(data.data[0])[0];
  const yKeys = data.y_keys || [Object.keys(data.data[0]).find(k => k !== xKey)];
  const colors = getColors(yKeys.length, 'warm');
  
  const config = applyWarmTheme({
    type: 'bar',
    data: {
      labels: data.data.map(r => r[xKey]),
      datasets: yKeys.map((k, i) => ({
        label: (data.y_labels && data.y_labels[k]) || k,
        data: data.data.map(r => r[k]),
        backgroundColor: colors[i] + 'CC',
        borderColor: colors[i],
        borderWidth: 1,
        borderRadius: 6,
        borderSkipped: false,
      })),
    },
    options: {
      scales: {
        x: { grid: { display: false }, ticks: {} },
        y: { grid: {}, ticks: { callback: v => fmt(v) }, beginAtZero: true },
      },
      plugins: { legend: { display: yKeys.length > 1 } },
    },
  });
  
  return { html: chartCard(data, makeCanvas(id), id), config, canvasId: id };
}

function renderHorizontalBar(data) {
  const id = nextId();
  const xKey = data.x_key || Object.keys(data.data[0])[0];
  const yKeys = data.y_keys || [Object.keys(data.data[0]).find(k => k !== xKey)];
  const colors = getColors(yKeys.length, 'warm');
  
  const config = applyWarmTheme({
    type: 'bar',
    data: {
      labels: data.data.map(r => r[xKey]),
      datasets: yKeys.map((k, i) => ({
        label: (data.y_labels && data.y_labels[k]) || k,
        data: data.data.map(r => r[k]),
        backgroundColor: colors[i] + 'CC',
        borderColor: colors[i],
        borderWidth: 1,
        borderRadius: 6,
        borderSkipped: false,
      })),
    },
    options: {
      indexAxis: 'y',
      scales: {
        x: { grid: {}, ticks: { callback: v => fmt(v) }, beginAtZero: true },
        y: { grid: { display: false }, ticks: {} },
      },
      plugins: { legend: { display: yKeys.length > 1 } },
    },
  });
  
  return { html: chartCard(data, makeCanvas(id), id), config, canvasId: id };
}

function renderMultiBar(data) {
  // Same as bar but always shows legend
  const result = renderBar(data);
  result.config.options.plugins.legend.display = true;
  return result;
}

function renderStackedBar(data) {
  const result = renderBar(data);
  result.config.options.scales.x.stacked = true;
  result.config.options.scales.y.stacked = true;
  result.config.options.plugins.legend.display = true;
  return result;
}

function renderLine(data) {
  const id = nextId();
  const xKey = data.x_key || Object.keys(data.data[0])[0];
  const yKeys = data.y_keys || Object.keys(data.data[0]).filter(k => k !== xKey);
  const colors = getColors(yKeys.length, 'warm');
  
  const config = applyWarmTheme({
    type: 'line',
    data: {
      labels: data.data.map(r => r[xKey]),
      datasets: yKeys.map((k, i) => ({
        label: (data.y_labels && data.y_labels[k]) || k,
        data: data.data.map(r => r[k]),
        borderColor: colors[i],
        backgroundColor: colors[i] + '20',
        borderWidth: 2.5,
        pointRadius: 3,
        pointHoverRadius: 6,
        pointBackgroundColor: '#fff',
        pointBorderColor: colors[i],
        pointBorderWidth: 2,
        tension: 0.35,
      })),
    },
    options: {
      scales: {
        x: { grid: { display: false }, ticks: {} },
        y: { grid: {}, ticks: { callback: v => fmt(v) }, beginAtZero: true },
      },
      plugins: { legend: { display: yKeys.length > 1 } },
      interaction: { intersect: false, mode: 'index' },
    },
  });
  
  return { html: chartCard(data, makeCanvas(id), id), config, canvasId: id };
}

function renderArea(data) {
  const result = renderLine(data);
  result.config.data.datasets.forEach(ds => { ds.fill = true; ds.backgroundColor = ds.borderColor + '25'; });
  return result;
}

function renderStackedArea(data) {
  const result = renderArea(data);
  result.config.options.scales.y.stacked = true;
  result.config.data.datasets.forEach(ds => { ds.fill = true; ds.backgroundColor = ds.borderColor + '40'; });
  result.config.options.plugins.legend.display = true;
  return result;
}

function renderPie(data) {
  const id = nextId();
  const xKey = data.x_key || Object.keys(data.data[0])[0];
  const yKey = data.y_keys ? data.y_keys[0] : Object.keys(data.data[0]).find(k => k !== xKey);
  const labels = data.data.map(r => r[xKey]);
  const values = data.data.map(r => r[yKey]);
  const colors = getColors(labels.length, 'pastel');
  
  const config = applyWarmTheme({
    type: 'pie',
    data: {
      labels,
      datasets: [{ data: values, backgroundColor: colors, borderColor: '#fff', borderWidth: 2, hoverOffset: 8 }],
    },
    options: {
      plugins: {
        legend: { position: 'right', labels: { padding: 12 } },
      },
    },
  });
  
  return { html: chartCard(data, makeCanvas(id), id), config, canvasId: id };
}

function renderDoughnut(data) {
  const id = nextId();
  const xKey = data.x_key || Object.keys(data.data[0])[0];
  const yKey = data.y_keys ? data.y_keys[0] : Object.keys(data.data[0]).find(k => k !== xKey);
  const labels = data.data.map(r => r[xKey]);
  const values = data.data.map(r => r[yKey]);
  const total = values.reduce((a, b) => a + (b || 0), 0);
  const colors = getColors(labels.length, 'pastel');
  
  const config = applyWarmTheme({
    type: 'doughnut',
    data: {
      labels,
      datasets: [{ data: values, backgroundColor: colors, borderColor: '#fff', borderWidth: 2, hoverOffset: 8 }],
    },
    options: {
      cutout: '65%',
      plugins: {
        legend: { position: 'right', labels: { padding: 12 } },
        // Center label plugin
      },
    },
  });
  
  // Doughnut center text plugin
  const centerPlugin = {
    id: 'doughnutCenter',
    afterDraw(chart) {
      const { ctx, width, height } = chart;
      ctx.save();
      ctx.font = `bold 22px ${CHART_DEFAULTS.font.family}`;
      ctx.fillStyle = CHART_DEFAULTS.textColor;
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(fmt(total), width / 2, height / 2 - 8);
      ctx.font = `11px ${CHART_DEFAULTS.font.family}`;
      ctx.fillStyle = CHART_DEFAULTS.mutedColor;
      ctx.fillText(data.total_label || 'Total', width / 2, height / 2 + 14);
      ctx.restore();
    }
  };
  config.plugins = [centerPlugin];
  
  return { html: chartCard(data, makeCanvas(id), id), config, canvasId: id };
}

function renderScatter(data) {
  const id = nextId();
  const keys = Object.keys(data.data[0]);
  const xKey = data.x_key || keys[0];
  const yKey = data.y_keys ? data.y_keys[0] : keys[1];
  
  const config = applyWarmTheme({
    type: 'scatter',
    data: {
      datasets: [{
        label: `${(data.y_labels && data.y_labels[yKey]) || yKey} vs ${xKey}`,
        data: data.data.map(r => ({ x: r[xKey], y: r[yKey] })),
        backgroundColor: '#C75B39' + '99',
        borderColor: '#C75B39',
        pointRadius: 5,
        pointHoverRadius: 8,
      }],
    },
    options: {
      scales: {
        x: { grid: {}, ticks: { callback: v => fmt(v) }, title: { display: true, text: xKey, color: CHART_DEFAULTS.mutedColor } },
        y: { grid: {}, ticks: { callback: v => fmt(v) }, title: { display: true, text: yKey, color: CHART_DEFAULTS.mutedColor } },
      },
    },
  });
  
  return { html: chartCard(data, makeCanvas(id), id), config, canvasId: id };
}

// ═══════════════════════════════════════════════════════════════════
// TIER 2 — Chart.js Mixed/Custom (5 types)
// ═══════════════════════════════════════════════════════════════════

function renderCombo(data) {
  const id = nextId();
  const xKey = data.x_key || Object.keys(data.data[0])[0];
  const yKeys = data.y_keys || Object.keys(data.data[0]).filter(k => k !== xKey);
  const colors = getColors(yKeys.length, 'warm');
  // First key = bar, rest = line
  const barKeys = yKeys.slice(0, 1);
  const lineKeys = yKeys.slice(1);
  
  const datasets = [];
  barKeys.forEach((k, i) => {
    datasets.push({
      type: 'bar',
      label: (data.y_labels && data.y_labels[k]) || k,
      data: data.data.map(r => r[k]),
      backgroundColor: colors[i] + 'CC',
      borderColor: colors[i],
      borderWidth: 1,
      borderRadius: 6,
      order: 2,
      yAxisID: 'y',
    });
  });
  lineKeys.forEach((k, i) => {
    datasets.push({
      type: 'line',
      label: (data.y_labels && data.y_labels[k]) || k,
      data: data.data.map(r => r[k]),
      borderColor: colors[barKeys.length + i],
      backgroundColor: 'transparent',
      borderWidth: 2.5,
      pointRadius: 3,
      pointHoverRadius: 6,
      tension: 0.35,
      order: 1,
      yAxisID: lineKeys.length > 0 ? 'y1' : 'y',
    });
  });
  
  const scales = {
    x: { grid: { display: false }, ticks: {} },
    y: { grid: {}, ticks: { callback: v => fmt(v) }, beginAtZero: true, position: 'left' },
  };
  if (lineKeys.length > 0) {
    scales.y1 = { grid: { display: false }, ticks: { callback: v => fmt(v) }, position: 'right', beginAtZero: true };
  }
  
  const config = applyWarmTheme({
    type: 'bar',
    data: { labels: data.data.map(r => r[xKey]), datasets },
    options: { scales, plugins: { legend: { display: true } }, interaction: { intersect: false, mode: 'index' } },
  });
  
  return { html: chartCard(data, makeCanvas(id), id), config, canvasId: id };
}

function renderBubble(data) {
  const id = nextId();
  const keys = Object.keys(data.data[0]);
  const xKey = data.x_key || keys[0];
  const yKey = data.y_keys ? data.y_keys[0] : keys[1];
  const rKey = data.y_keys && data.y_keys[1] ? data.y_keys[1] : keys[2];
  
  const maxR = Math.max(...data.data.map(r => Math.abs(r[rKey] || 1)));
  
  const config = applyWarmTheme({
    type: 'bubble',
    data: {
      datasets: [{
        label: data.title || 'Bubble',
        data: data.data.map(r => ({ x: r[xKey], y: r[yKey], r: Math.max(3, (Math.abs(r[rKey] || 1) / maxR) * 25) })),
        backgroundColor: '#C75B39' + '66',
        borderColor: '#C75B39',
        borderWidth: 1,
      }],
    },
    options: {
      scales: {
        x: { grid: {}, ticks: { callback: v => fmt(v) } },
        y: { grid: {}, ticks: { callback: v => fmt(v) } },
      },
    },
  });
  
  return { html: chartCard(data, makeCanvas(id), id), config, canvasId: id };
}

function renderRadar(data) {
  const id = nextId();
  const xKey = data.x_key || Object.keys(data.data[0])[0];
  const yKeys = data.y_keys || Object.keys(data.data[0]).filter(k => k !== xKey);
  const colors = getColors(yKeys.length, 'warm');
  
  const config = applyWarmTheme({
    type: 'radar',
    data: {
      labels: data.data.map(r => r[xKey]),
      datasets: yKeys.map((k, i) => ({
        label: (data.y_labels && data.y_labels[k]) || k,
        data: data.data.map(r => r[k]),
        backgroundColor: colors[i] + '30',
        borderColor: colors[i],
        borderWidth: 2,
        pointBackgroundColor: colors[i],
        pointRadius: 3,
      })),
    },
    options: {
      scales: {
        r: {
          grid: { color: CHART_DEFAULTS.gridColor },
          ticks: { color: CHART_DEFAULTS.mutedColor, backdropColor: 'transparent', font: { size: 9 } },
          pointLabels: { color: CHART_DEFAULTS.textColor, font: { family: CHART_DEFAULTS.font.family, size: 11 } },
        },
      },
    },
  });
  
  return { html: chartCard(data, makeCanvas(id), id), config, canvasId: id };
}

function renderPolarArea(data) {
  const id = nextId();
  const xKey = data.x_key || Object.keys(data.data[0])[0];
  const yKey = data.y_keys ? data.y_keys[0] : Object.keys(data.data[0]).find(k => k !== xKey);
  const colors = getColors(data.data.length, 'pastel');
  
  const config = applyWarmTheme({
    type: 'polarArea',
    data: {
      labels: data.data.map(r => r[xKey]),
      datasets: [{
        data: data.data.map(r => r[yKey]),
        backgroundColor: colors.map(c => c + '99'),
        borderColor: colors,
        borderWidth: 1,
      }],
    },
    options: {
      scales: {
        r: { grid: { color: CHART_DEFAULTS.gridColor }, ticks: { display: false } },
      },
      plugins: { legend: { position: 'right' } },
    },
  });
  
  return { html: chartCard(data, makeCanvas(id), id), config, canvasId: id };
}

function renderWaterfall(data) {
  const id = nextId();
  const xKey = data.x_key || Object.keys(data.data[0])[0];
  const yKey = data.y_keys ? data.y_keys[0] : Object.keys(data.data[0]).find(k => k !== xKey);
  
  // Calculate waterfall segments
  let cumulative = 0;
  const bases = [], values = [], bgColors = [], bdColors = [];
  data.data.forEach((r, i) => {
    const val = r[yKey] || 0;
    if (i === data.data.length - 1 && (r[xKey] || '').toLowerCase().includes('total')) {
      bases.push(0);
      values.push(cumulative + val);
      bgColors.push('#5C6BC0CC');
      bdColors.push('#5C6BC0');
    } else {
      if (val >= 0) {
        bases.push(cumulative);
        values.push(val);
        bgColors.push('#2E7D32CC');
        bdColors.push('#2E7D32');
        cumulative += val;
      } else {
        cumulative += val;
        bases.push(cumulative);
        values.push(Math.abs(val));
        bgColors.push('#C75B39CC');
        bdColors.push('#C75B39');
      }
    }
  });
  
  const config = applyWarmTheme({
    type: 'bar',
    data: {
      labels: data.data.map(r => r[xKey]),
      datasets: [
        { label: 'Base', data: bases, backgroundColor: 'transparent', borderWidth: 0, borderSkipped: false },
        { label: (data.y_labels && data.y_labels[yKey]) || yKey, data: values, backgroundColor: bgColors, borderColor: bdColors, borderWidth: 1, borderRadius: 4, borderSkipped: false },
      ],
    },
    options: {
      scales: {
        x: { stacked: true, grid: { display: false }, ticks: {} },
        y: { stacked: true, grid: {}, ticks: { callback: v => fmt(v) } },
      },
      plugins: { legend: { display: false } },
    },
  });
  
  return { html: chartCard(data, makeCanvas(id), id), config, canvasId: id };
}

// ═══════════════════════════════════════════════════════════════════
// TIER 3 — Custom HTML/CSS (5 types)
// ═══════════════════════════════════════════════════════════════════

function renderNumberCard(data) {
  const keys = Object.keys(data.data[0] || {});
  const valueKey = data.y_keys ? data.y_keys[0] : keys[keys.length - 1];
  const value = data.data[0] ? data.data[0][valueKey] : 0;
  const label = (data.y_labels && data.y_labels[valueKey]) || valueKey;
  
  const trendHtml = data.trend ? `
    <div class="nc-trend ${data.trend.direction === 'up' ? 'up' : 'down'}">
      ${data.trend.direction === 'up' ? '▲' : '▼'} ${data.trend.value || data.trend.percentage || 0}%
      ${data.trend.label ? `<span>${escH(data.trend.label)}</span>` : ''}
    </div>` : '';
  
  const inner = `
    <div class="number-card">
      <div class="nc-value">${fmt(value)}</div>
      <div class="nc-label">${escH(label)}</div>
      ${trendHtml}
    </div>`;
  
  return { html: chartCard(data, inner), config: null, canvasId: null };
}

function renderGauge(data) {
  const id = nextId();
  const keys = Object.keys(data.data[0] || {});
  const valueKey = data.y_keys ? data.y_keys[0] : keys[keys.length - 1];
  const value = data.data[0] ? data.data[0][valueKey] : 0;
  const max = data.gauge_max || 100;
  const pct = Math.min(100, Math.round((value / max) * 100));
  
  // Use doughnut as gauge
  const color = pct >= 75 ? '#2E7D32' : pct >= 50 ? '#B8860B' : '#C75B39';
  
  const config = applyWarmTheme({
    type: 'doughnut',
    data: {
      labels: ['Value', 'Remaining'],
      datasets: [{
        data: [value, Math.max(0, max - value)],
        backgroundColor: [color + 'CC', '#E8DED4' + '40'],
        borderWidth: 0,
        circumference: 270,
        rotation: 225,
      }],
    },
    options: {
      cutout: '75%',
      plugins: { legend: { display: false }, tooltip: { enabled: false } },
    },
  });
  
  const centerPlugin = {
    id: 'gaugeCenter',
    afterDraw(chart) {
      const { ctx, width, height } = chart;
      ctx.save();
      ctx.font = `bold 28px ${CHART_DEFAULTS.font.family}`;
      ctx.fillStyle = color;
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(fmt(value), width / 2, height / 2 + 10);
      ctx.font = `11px ${CHART_DEFAULTS.font.family}`;
      ctx.fillStyle = CHART_DEFAULTS.mutedColor;
      ctx.fillText(`${pct}% of ${fmt(max)}`, width / 2, height / 2 + 32);
      ctx.restore();
    }
  };
  config.plugins = [centerPlugin];
  
  return { html: chartCard(data, makeCanvas(id), id), config, canvasId: id };
}

function renderProgress(data) {
  const rows = data.data || [];
  const xKey = data.x_key || Object.keys(rows[0] || {})[0];
  const yKey = data.y_keys ? data.y_keys[0] : Object.keys(rows[0] || {}).find(k => k !== xKey);
  const max = data.gauge_max || Math.max(...rows.map(r => r[yKey] || 0));
  const colors = getColors(rows.length, 'warm');
  
  let bars = '';
  rows.forEach((r, i) => {
    const val = r[yKey] || 0;
    const pct = Math.round((val / max) * 100);
    bars += `
      <div class="progress-row">
        <div class="progress-label">${escH(String(r[xKey]))}</div>
        <div class="progress-bar-wrap">
          <div class="progress-bar-fill" style="width:${pct}%;background:${colors[i]}"></div>
        </div>
        <div class="progress-val">${fmt(val)}</div>
      </div>`;
  });
  
  return { html: chartCard(data, `<div class="progress-list">${bars}</div>`), config: null, canvasId: null };
}

function renderFunnel(data) {
  const rows = data.data || [];
  const xKey = data.x_key || Object.keys(rows[0] || {})[0];
  const yKey = data.y_keys ? data.y_keys[0] : Object.keys(rows[0] || {}).find(k => k !== xKey);
  const maxVal = Math.max(...rows.map(r => r[yKey] || 0));
  const colors = getColors(rows.length, 'warm');
  
  let steps = '';
  rows.forEach((r, i) => {
    const val = r[yKey] || 0;
    const pct = Math.round((val / maxVal) * 100);
    const convRate = i > 0 ? Math.round((val / (rows[i - 1][yKey] || 1)) * 100) : 100;
    steps += `
      <div class="funnel-step">
        <div class="funnel-bar-wrap">
          <div class="funnel-bar" style="width:${pct}%;background:${colors[i]}">
            <span class="funnel-bar-text">${escH(String(r[xKey]))} — ${fmt(val)}</span>
          </div>
        </div>
        ${i > 0 ? `<div class="funnel-conv">↓ ${convRate}%</div>` : ''}
      </div>`;
  });
  
  return { html: chartCard(data, `<div class="funnel-list">${steps}</div>`), config: null, canvasId: null };
}

function renderTable(data) {
  const rows = data.data || [];
  if (rows.length === 0) return { html: chartCard(data, '<div style="padding:16px;color:var(--muted)">No data</div>'), config: null, canvasId: null };
  
  const cols = data.columns || Object.keys(rows[0]);
  let tbl = '<div class="data-table-wrap"><table class="data-table"><thead><tr>';
  cols.forEach(c => { tbl += `<th>${escH(c)}</th>`; });
  tbl += '</tr></thead><tbody>';
  rows.slice(0, 100).forEach(r => {
    tbl += '<tr>';
    cols.forEach(c => {
      const v = r[c];
      tbl += `<td>${v === null || v === undefined ? '<span style="color:#ccc">NULL</span>' : escH(String(v))}</td>`;
    });
    tbl += '</tr>';
  });
  tbl += '</tbody></table></div>';
  if (rows.length > 100) tbl += `<div style="font-size:9px;color:var(--muted);text-align:center;margin-top:6px">Showing 100/${rows.length} rows</div>`;
  
  return { html: chartCard(data, tbl), config: null, canvasId: null };
}

// ═══════════════════════════════════════════════════════════════════
// TIER 4 — Chart.js Plugins (5 types, graceful fallback)
// ═══════════════════════════════════════════════════════════════════

function renderTreemap(data) {
  const id = nextId();
  const xKey = data.x_key || Object.keys(data.data[0])[0];
  const yKey = data.y_keys ? data.y_keys[0] : Object.keys(data.data[0]).find(k => k !== xKey);
  const colors = getColors(data.data.length, 'pastel');
  
  // Check if treemap plugin is available
  if (typeof Chart !== 'undefined' && Chart.controllers && Chart.controllers.treemap) {
    const config = {
      type: 'treemap',
      data: {
        datasets: [{
          tree: data.data.map(r => r[yKey]),
          labels: { display: true, formatter: (ctx) => data.data[ctx.dataIndex] ? data.data[ctx.dataIndex][xKey] : '' },
          backgroundColor: colors.map(c => c + 'CC'),
          borderColor: '#fff',
          borderWidth: 2,
        }],
      },
      options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } } },
    };
    return { html: chartCard(data, makeCanvas(id), id), config, canvasId: id };
  }
  // Fallback: render as horizontal bar
  data.chart_type = 'horizontalBar';
  return renderHorizontalBar(data);
}

function renderHeatmap(data) {
  // Fallback: render as table with color-coded cells
  const rows = data.data || [];
  if (rows.length === 0) return renderTable(data);
  
  const cols = data.columns || Object.keys(rows[0]);
  const numCols = cols.filter(c => {
    return rows.some(r => typeof r[c] === 'number');
  });
  
  // Find min/max for color scaling
  let minVal = Infinity, maxVal = -Infinity;
  rows.forEach(r => numCols.forEach(c => {
    if (typeof r[c] === 'number') {
      minVal = Math.min(minVal, r[c]);
      maxVal = Math.max(maxVal, r[c]);
    }
  }));
  
  function heatColor(val) {
    if (typeof val !== 'number') return '';
    const ratio = maxVal === minVal ? 0.5 : (val - minVal) / (maxVal - minVal);
    // Warm gradient: light cream → deep orange
    const r = Math.round(255 - ratio * 56);
    const g = Math.round(248 - ratio * 157);
    const b = Math.round(240 - ratio * 183);
    return `background:rgb(${r},${g},${b})`;
  }
  
  let tbl = '<div class="data-table-wrap"><table class="data-table"><thead><tr>';
  cols.forEach(c => { tbl += `<th>${escH(c)}</th>`; });
  tbl += '</tr></thead><tbody>';
  rows.slice(0, 100).forEach(r => {
    tbl += '<tr>';
    cols.forEach(c => {
      const v = r[c];
      const style = numCols.includes(c) ? heatColor(v) : '';
      tbl += `<td style="${style}">${v === null ? '<span style="color:#ccc">NULL</span>' : escH(String(v))}</td>`;
    });
    tbl += '</tr>';
  });
  tbl += '</tbody></table></div>';
  
  return { html: chartCard(data, tbl), config: null, canvasId: null };
}

function renderBoxplot(data) {
  // Fallback: render as bar chart showing min/avg/max
  const id = nextId();
  const xKey = data.x_key || Object.keys(data.data[0])[0];
  const numKeys = Object.keys(data.data[0]).filter(k => k !== xKey && typeof data.data[0][k] === 'number');
  
  if (typeof Chart !== 'undefined' && Chart.controllers && Chart.controllers.boxplot) {
    // Plugin available — use it
    const config = {
      type: 'boxplot',
      data: {
        labels: data.data.map(r => r[xKey]),
        datasets: numKeys.map((k, i) => ({
          label: k,
          data: data.data.map(r => r[k]),
          backgroundColor: getColors(1, 'warm')[0] + '40',
          borderColor: getColors(1, 'warm')[0],
          borderWidth: 1,
        })),
      },
      options: { responsive: true, maintainAspectRatio: false },
    };
    return { html: chartCard(data, makeCanvas(id), id), config, canvasId: id };
  }
  // Fallback: bar
  return renderBar(data);
}

function renderSankey(data) {
  // Fallback: render as funnel or table
  if (typeof Chart !== 'undefined' && Chart.controllers && Chart.controllers.sankey) {
    const id = nextId();
    const config = {
      type: 'sankey',
      data: {
        datasets: [{
          data: data.data,
          colorFrom: '#C75B39',
          colorTo: '#E8734F',
          colorMode: 'gradient',
        }],
      },
      options: { responsive: true, maintainAspectRatio: false },
    };
    return { html: chartCard(data, makeCanvas(id), id), config, canvasId: id };
  }
  return renderTable(data);
}

function renderPivotTable(data) {
  // Enhanced table with grouped headers
  const rows = data.data || [];
  if (rows.length === 0) return renderTable(data);
  
  const cols = data.columns || Object.keys(rows[0]);
  const groupKey = data.x_key || cols[0];
  
  // Group by first column
  const groups = {};
  rows.forEach(r => {
    const g = r[groupKey] || 'Other';
    if (!groups[g]) groups[g] = [];
    groups[g].push(r);
  });
  
  const otherCols = cols.filter(c => c !== groupKey);
  let tbl = '<div class="data-table-wrap"><table class="data-table"><thead><tr>';
  tbl += `<th>${escH(groupKey)}</th>`;
  otherCols.forEach(c => { tbl += `<th>${escH(c)}</th>`; });
  tbl += '</tr></thead><tbody>';
  
  Object.entries(groups).forEach(([group, gRows]) => {
    gRows.forEach((r, i) => {
      tbl += '<tr>';
      if (i === 0) {
        tbl += `<td rowspan="${gRows.length}" style="font-weight:600;background:var(--card);vertical-align:top">${escH(group)}</td>`;
      }
      otherCols.forEach(c => {
        const v = r[c];
        tbl += `<td>${v === null ? '<span style="color:#ccc">NULL</span>' : escH(String(v))}</td>`;
      });
      tbl += '</tr>';
    });
  });
  tbl += '</tbody></table></div>';
  
  return { html: chartCard(data, tbl), config: null, canvasId: null };
}

// ═══════════════════════════════════════════════════════════════════
// MAIN DISPATCHER
// ═══════════════════════════════════════════════════════════════════

const CHART_RENDERERS = {
  // Tier 1
  bar: renderBar,
  horizontalBar: renderHorizontalBar,
  multiBar: renderMultiBar,
  stackedBar: renderStackedBar,
  line: renderLine,
  area: renderArea,
  stackedArea: renderStackedArea,
  pie: renderPie,
  doughnut: renderDoughnut,
  scatter: renderScatter,
  // Tier 2
  combo: renderCombo,
  bubble: renderBubble,
  radar: renderRadar,
  polarArea: renderPolarArea,
  waterfall: renderWaterfall,
  // Tier 3
  numberCard: renderNumberCard,
  number_card: renderNumberCard,
  gauge: renderGauge,
  progress: renderProgress,
  funnel: renderFunnel,
  table: renderTable,
  // Tier 4
  treemap: renderTreemap,
  heatmap: renderHeatmap,
  boxplot: renderBoxplot,
  sankey: renderSankey,
  pivotTable: renderPivotTable,
  pivot_table: renderPivotTable,
};

/**
 * Main entry: render a chart from structured AI data
 * @param {Object} data - { chart_type, title, description, x_key, y_keys, y_labels, trend, footer, data: [...] }
 * @returns {{ html: string, config: object|null, canvasId: string|null }}
 */
function renderChart(data) {
  const type = data.chart_type || 'bar';
  const renderer = CHART_RENDERERS[type];
  if (!renderer) {
    console.warn(`Unknown chart type: ${type}, falling back to bar`);
    return renderBar(data);
  }
  return renderer(data);
}
