// ════════════════════════════════════════════════════════════════════════
//  tableColumns.js — the single source of truth for the hourly table's
//  metric columns.
//
//  WHY THIS EXISTS
//  ──────────────────────────────────────────────────────────────────────
//  The table can be rendered in two orientations:
//    • normal  — rows = hours,   columns = metrics
//    • rotated — rows = metrics, columns = hours
//  Both read from the same definitions here, so a metric is described
//  once (label, unit, group, colouring, formatting) and both views stay
//  in step. Adding a new metric means adding one entry to the array in
//  buildColumnDefs().
//
//  SHAPE OF A DEFINITION
//  ──────────────────────────────────────────────────────────────────────
//    key        column id, matches visibleCols / COL_DESCRIPTIONS
//    label      heading text
//    unit       the small print under the heading ("°C", "mm/h", …)
//    unitNone   true → render the unit muted+italic (no data for this day)
//    group      felt | surface | ambient | precip | sky | wind | airqual | solar
//    visible    resolved from visibleCols / indoorMode
//    headClass  extra class on the heading cell ('utci-tight-head', …)
//    cellClass  extra class on the data cell ('utci-dir-cell', …)
//    headExtra  optional extra node inside the heading (the SunSoak badge)
//    render(r, prev, next, dir) → { content, style, title }
//      r/prev/next are the hour row and its neighbours; `dir` is the CSS
//      gradient direction ('to bottom' normally, 'to right' rotated) so
//      the temperature heatmaps flow along whichever axis time runs on.
// ════════════════════════════════════════════════════════════════════════

import { h } from '../vendor/preact.js';
import htm from '../vendor/htm.js';
import { petCategory, sunburnMinutes, burnLabel } from './utils.js';
import { WindVane, CloudIcon, PrecipIcon } from './components.js';
import { POLLEN_TYPES, UTCI_ENVIRONMENTS } from './config.js';

const html = htm.bind(h);

// ── COLOUR SCALE ────────────────────────────────────────────────────────
// Continuous temperature colour scale — shared by every temperature column
// and by the thermal-stress legend in app.js.
export const TEMP_STOPS = [
  [-10, [ 90, 155, 220]],
  [  0, [140, 195, 235]],
  [ 10, [155, 215, 195]],
  [ 16, [140, 210, 140]],
  [ 20, [195, 225, 110]],
  [ 24, [240, 225,  80]],
  [ 28, [250, 175,  65]],
  [ 32, [240, 120,  55]],
  [ 36, [220,  70,  50]],
  [ 40, [185,  30,  30]],
  [ 50, [130,   0,  20]],
];

export const airTempRgb = (t, whiteMix = 0.58) => {
  if (t == null) return null;
  const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
  let i = TEMP_STOPS.findIndex(s => t < s[0]);
  if (i === -1) i = TEMP_STOPS.length;
  const raw = i === 0 ? TEMP_STOPS[0][1]
    : i >= TEMP_STOPS.length ? TEMP_STOPS[TEMP_STOPS.length-1][1]
    : (() => {
        const [t0, c0] = TEMP_STOPS[i-1];
        const [t1, c1] = TEMP_STOPS[i];
        const x = clamp((t - t0) / (t1 - t0), 0, 1);
        const lerp = (a, b) => Math.round(a + (b - a) * x);
        return [lerp(c0[0],c1[0]), lerp(c0[1],c1[1]), lerp(c0[2],c1[2])];
      })();
  return raw.map(c => Math.min(255, Math.round(c + (255 - c) * whiteMix)));
};

// Pet columns (Fur Colour, Pet Shade, Pet Home, Paw) reuse airTempRgb
// exactly as-is - identical stops, identical whiteMix blend, identical
// neighbour blending. The only thing that differs is which temperature
// gets handed to it: petEquivHumanTemp() remaps a pet reading to "the
// human felt-temp this severity is equivalent to" first, using the exact
// same anchor pairs PET_BANDS was calibrated against (same tier, same
// ordinal position in UTCI_BANDS vs PET_BANDS - see utils.js). So a -2 °C
// pet reading (mild "Cold", not "Freezing") gets looked up as if it were
// a few degrees warmer on the human scale, and a 46 °C paw reading (mid
// "Extreme", not "Danger") looks up around human "Extreme" too - never a
// different colour-computation, just a different input to the same one.
const PET_TO_HUMAN_TEMP = [
  [-28, -20], [-18, -10], [-8, 0], [-3, 5], [2, 10], [7, 15], [11, 19],
  [25, 24], [32, 27], [40, 32], [52, 41],
];
const petEquivHumanTemp = (t) => {
  if (t == null) return null;
  const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
  const pts = PET_TO_HUMAN_TEMP;
  const slopeBetween = (a, b) => (b[1] - a[1]) / (b[0] - a[0]);
  if (t <= pts[0][0]) {
    const slope = slopeBetween(pts[0], pts[1]);
    return pts[0][1] + (t - pts[0][0]) * slope;
  }
  if (t >= pts[pts.length - 1][0]) {
    const last = pts[pts.length - 1], prev = pts[pts.length - 2];
    const slope = slopeBetween(prev, last);
    return last[1] + (t - last[0]) * slope;
  }
  for (let i = 1; i < pts.length; i++) {
    const [p0, h0] = pts[i - 1], [p1, h1] = pts[i];
    if (t <= p1) {
      const x = clamp((t - p0) / (p1 - p0), 0, 1);
      return h0 + (h1 - h0) * x;
    }
  }
};
export const petAirTempRgb = (t, whiteMix = 0.58) => airTempRgb(petEquivHumanTemp(t), whiteMix);

// ── CELL BACKGROUND HELPERS ─────────────────────────────────────────────
const toRgb = (rgb) => rgb ? `rgb(${rgb[0]},${rgb[1]},${rgb[2]})` : 'transparent';

// Builds a gradient that flows from the previous hour's colour through
// this hour's colour to the next hour's. The end colours are the midpoint
// temperatures either side, so the boundary colour is identical on both
// sides of every cell edge — the whole line of cells reads as one seamless
// heatmap. `dir` picks the axis: 'to bottom' when hours run down the
// table, 'to right' when they run across it.
const makeTempBg = (rgbFn) => (t, tPrev, tNext, dir = 'to bottom') => {
  if (t == null) return 'transparent';
  const tStart = tPrev != null ? (tPrev + t) / 2 : t;
  const tEnd   = tNext != null ? (t + tNext) / 2 : t;
  return `linear-gradient(${dir}, ${toRgb(rgbFn(tStart))} 0%, ${toRgb(rgbFn(tEnd))} 100%)`;
};
const airTempBg    = makeTempBg(airTempRgb);
const petAirTempBg = makeTempBg(petAirTempRgb);

const airTempFontColor = () => '#1a1a1a';

// Linear alpha ramp between min and max — used by the non-temperature
// columns (humidity, wind, solar…) that tint rather than heatmap.
const scaleBg = (v, min, max, rgb, maxAlpha = 0.14) => {
  if (v == null || isNaN(v)) return 'transparent';
  if (v <= min) return 'transparent';
  const x = Math.max(0, Math.min(1, (v - min) / (max - min)));
  const a = 0.035 + x * maxAlpha;
  return `rgba(${rgb},${a.toFixed(3)})`;
};

const soilMoistureBg = (v) => {
  if (v == null || isNaN(v)) return { bg: 'transparent', fg: '#555' };
  const pct = v * 100;
  // stops: [pct, r, g, b]
  const stops = [
    [0,  212, 180, 131],  // sandy tan  – bone dry
    [10, 160, 120,  64],  // mid brown  – dry
    [22, 138, 122,  80],  // olive-brown – damp/firm
    [32, 200, 160,  64],  // amber       – soft, caution
    [38,  58, 138,  92],  // green       – wet
    [50,  42, 106, 144],  // blue        – saturated
  ];
  let s0 = stops[0], s1 = stops[stops.length - 1];
  for (let i = 0; i < stops.length - 1; i++) {
    if (pct >= stops[i][0] && pct <= stops[i+1][0]) { s0 = stops[i]; s1 = stops[i+1]; break; }
  }
  const t  = s1[0] === s0[0] ? 1 : Math.max(0, Math.min(1, (pct - s0[0]) / (s1[0] - s0[0])));
  const r  = Math.round(s0[1] + t * (s1[1] - s0[1]));
  const g  = Math.round(s0[2] + t * (s1[2] - s0[2]));
  const b  = Math.round(s0[3] + t * (s1[3] - s0[3]));
  return { bg: `rgba(${r},${g},${b},0.18)`, fg: '#3a2a10' };
};

const deltaBg = (v) => {
  if (v == null || isNaN(v)) return 'transparent';
  if (Math.abs(v) < 1) return 'transparent';
  const x = Math.min(1, Math.abs(v) / 12);
  const a = 0.035 + x * 0.13;
  return v > 0 ? `rgba(230,140,50,${a.toFixed(3)})` : `rgba(80,135,210,${a.toFixed(3)})`;
};

const burnBg = (mins, uv) => {
  if (!isFinite(mins) || uv <= 0) return 'transparent';
  if (mins >= 240) return 'transparent';
  const x = Math.max(0, Math.min(1, (240 - mins) / 220));
  return `rgba(210,70,50,${(0.04 + x * 0.15).toFixed(3)})`;
};

// ── THE REGISTRY ────────────────────────────────────────────────────────
// Order here IS the left-to-right column order in the normal view, and the
// top-to-bottom row order in the rotated view.
export function buildColumnDefs(ctx) {
  const {
    visibleCols, indoorMode, indoorManaged,
    showDecimals, showUnits,
    skinType, utciEnv, pollenType,
    aqBeyond, aqBeyondNote,
  } = ctx;

  const fmt = (v, dp = 1) => v == null ? '—' : (showDecimals ? v.toFixed(dp) : String(Math.round(v)));
  const u   = (unit) => showUnits ? unit : '';

  // A plain temperature column: heatmap background, dark text, "N°C" body.
  const tempCol = (key, label, unit, group, get, opts = {}) => ({
    key, label, unit, group,
    visible: !!visibleCols[key],
    ...opts,
    render: (r, prev, next, dir) => ({
      content: get(r) != null ? `${fmt(get(r))}${u('°C')}` : '—',
      style: {
        color: airTempFontColor(),
        background: (opts.pet ? petAirTempBg : airTempBg)(get(r), get(prev ?? {}), get(next ?? {}), dir),
      },
      title: opts.petTitle ? (petCategory(get(r))?.label ?? '') : undefined,
    }),
  });

  const defs = [
    {
      key: 'utciP', label: 'SunSoak', unit: '°C felt', group: 'felt',
      visible: !!visibleCols.utciP,
      headExtra: UTCI_ENVIRONMENTS[utciEnv]?.shortLabel
        ? html`<span class="col-env-badge">${UTCI_ENVIRONMENTS[utciEnv].shortLabel}</span>`
        : null,
      render: (r, prev, next, dir) => ({
        content: `${fmt(r.utciAdj)}${u('°C')}`,
        style: { background: airTempBg(r.utciAdj, prev?.utciAdj, next?.utciAdj, dir), color: airTempFontColor(), fontSize: '13px' },
      }),
    },
    tempCol('shadeT', 'Shade', '°C felt', 'felt', r => r.shadeT, { headClass: 'utci-tight-head' }),
    tempCol('vehicleT', 'Vehicle', '°C peak', 'felt', r => r.vehicleT),
    {
      ...tempCol('indoorT', 'Indoors', '°C est.', 'felt', r => r.indoorT),
      visible: indoorMode === 'on' && !indoorManaged,
    },
    {
      ...tempCol('managedT', 'Managed', '°C est.', 'felt', r => r.managedT),
      visible: indoorMode === 'on' && indoorManaged,
    },
    {
      key: 'burn', label: 'Burn', unit: 'to MED', group: 'felt',
      visible: !!visibleCols.burn,
      render: (r) => {
        const mins = sunburnMinutes(r.uv, skinType);
        return {
          content: burnLabel(mins),
          style: {
            color: r.uv > 0 ? (mins < 30 ? '#c44a3a' : '#c8601a') : '#4a3218',
            background: burnBg(mins, r.uv),
          },
        };
      },
    },
    tempCol('utci', 'UTCI', '°C felt', 'felt', r => r.utci),
    {
      key: 'delta', label: 'Δ', unit: 'UTCI−Air', group: 'felt',
      visible: !!visibleCols.delta,
      render: (r) => {
        const d = r.utci - r.Ta;
        return {
          content: `${d > 0 ? '+' : ''}${fmt(d)}${u('°C')}`,
          style: {
            color: d > 3 ? '#c8601a' : d < -3 ? '#3f73c4' : '#4a3218',
            background: deltaBg(d),
          },
        };
      },
    },
    tempCol('tmrt', 'Tmrt', '°C', 'felt', r => r.Tmrt),
    tempCol('concreteT', 'Concrete', '°C surface', 'surface', r => r.concreteT),
    tempCol('furSurfaceT', 'Fur', '°C surface', 'surface', r => r.furSurfaceT, { pet: true, petTitle: true }),
    tempCol('pawT', 'Paw', '°C surface', 'surface', r => r.pawT, { pet: true, petTitle: true }),
    tempCol('soilT', 'Soil °C', 'surface', 'surface', r => r.soilT0),
    tempCol('soilT6', 'Soil 6cm', '°C root', 'surface', r => r.soilT6),
    {
      key: 'soilM', label: 'Soil moist', unit: '%', group: 'surface',
      visible: !!visibleCols.soilM,
      render: (r) => {
        const sm = soilMoistureBg(r.soilM);
        return {
          content: r.soilM != null ? fmt(r.soilM * 100, 1) + u('%') : '—',
          style: { color: sm.fg, background: sm.bg },
        };
      },
    },
    tempCol('petShadeT', 'Pet Shade', '°C', 'ambient', r => r.petShadeT, { pet: true, petTitle: true, headClass: 'utci-tight-head' }),
    // Pet Home reads the same indoorT figure as the Indoors column, just on
    // the pet-calibrated colour scale.
    tempCol('petHomeT', 'Pet Home', '°C est.', 'ambient', r => r.indoorT, { pet: true, petTitle: true }),
    tempCol('air', 'Air', '°C', 'ambient', r => r.Ta, { headClass: 'utci-tight-head' }),
    {
      key: 'rh', label: 'RH', unit: '%', group: 'ambient',
      visible: !!visibleCols.rh,
      render: (r) => ({
        content: `${Math.round(r.RH)}${u('%')}`,
        style: { background: scaleBg(r.RH, 30, 100, '70,145,200') },
      }),
    },
    tempCol('dew', 'Dew', '°C', 'ambient', r => r.dew),
    {
      key: 'precip', label: 'Pcpt', unit: 'mm/h', group: 'precip',
      visible: !!visibleCols.precip, headClass: 'utci-tight-head',
      render: (r) => ({
        style: { background: r.snow > 0 ? scaleBg(r.snow, 0, 4, '90,140,210') : scaleBg(r.precip, 0, 8, '70,145,200') },
        content: html`
          <span style=${{ display: 'inline-flex', alignItems: 'center', gap: '5px', verticalAlign: 'middle' }}>
            <${PrecipIcon} precip=${r.precipProb > 0 ? r.precip : 0} snow=${r.precipProb > 0 ? r.snow : 0} size=${28} />
            <span style=${{ color: r.snow > 0 ? '#2a5fa8' : r.precip > 0 ? '#2a6a90' : '#7a5c30' }}>
              ${r.precipProb > 0
                ? (r.snow > 0 ? fmt(r.snow) + u('cm') : r.precip > 0 ? fmt(r.precip) + u('mm') : '—')
                : '—'}
            </span>
          </span>`,
      }),
    },
    {
      key: 'precipProb', label: 'Rain', unit: '%', group: 'precip',
      visible: !!visibleCols.precipProb, headClass: 'utci-tight-head',
      render: (r) => ({
        content: `${r.precipProb}${u('%')}`,
        style: {
          background: scaleBg(r.precipProb, 0, 100, '70,145,200'),
          color: r.precipProb >= 50 ? '#1a4a70' : r.precipProb > 0 ? '#2a6a90' : '#7a8a90',
        },
      }),
    },
    {
      key: 'lightning', label: '⚡', unit: 'LPI', group: 'precip',
      visible: !!visibleCols.lightning, headClass: 'utci-tight-head',
      render: (r) => {
        const lpi = r.lightning ?? 0;
        return {
          content: lpi > 0 ? `${Math.round(lpi)}` : '',
          style: {
            background: lpi >= 25 ? 'rgba(255,180,0,0.25)' : lpi >= 5 ? 'rgba(255,210,0,0.15)' : 'transparent',
            color: lpi >= 25 ? '#7a4800' : lpi >= 5 ? '#6a5800' : '#7a8a90',
            textAlign: 'center',
          },
        };
      },
    },
    {
      key: 'cloud', label: 'Cloud', unit: '%', group: 'sky',
      visible: !!visibleCols.cloud,
      render: (r) => ({
        style: { background: scaleBg(r.cc, 0, 100, '110,130,150', 0.07), verticalAlign: 'middle' },
        content: html`
          <span style=${{ display: 'inline-flex', alignItems: 'center', gap: '6px', verticalAlign: 'middle' }}>
            <span style=${{ position: 'relative', top: '6px' }}>
              <${CloudIcon} category=${r.cloudCat} elev=${r.elev} dt=${r.dt} size=${28} />
            </span>
            <span>${Math.round(r.cc)}${u('%')}</span>
          </span>`,
      }),
    },
    {
      key: 'vis', label: 'Vis', unit: 'km', group: 'sky',
      visible: !!visibleCols.vis, headClass: 'utci-tight-head',
      render: (r) => {
        const v = r.visKm;
        return {
          content: v != null ? fmt(v) + u('km') : '—',
          style: {
            background: v == null ? 'transparent'
              : v < 1  ? 'rgba(180,80,80,0.18)'
              : v < 4  ? 'rgba(210,140,50,0.15)'
              : v < 10 ? 'rgba(200,190,80,0.12)'
              : 'transparent',
            color: v != null && v < 4 ? '#8a3a1a' : '#4a3218',
          },
        };
      },
    },
    {
      key: 'wind', label: 'Wind', unit: 'mph (gust)', group: 'wind',
      visible: !!visibleCols.wind,
      render: (r) => ({
        style: { background: scaleBg((r.gust ?? r.va) * 2.237, 0, 40, '85,130,180') },
        content: html`${Math.round(r.va * 2.237)}${u('mph')}${r.gust != null && r.gust > r.va + 0.5
          ? (gm => html`<span style=${{ marginLeft: '4px', fontWeight: gm >= 25 ? 700 : 'normal', opacity: gm >= 25 ? 1 : 0.65, color: gm >= 55 ? '#b81010' : gm >= 40 ? '#d44010' : gm >= 25 ? '#c47a00' : 'inherit' }}>(${Math.round(gm)}${u('mph')})</span>`)(r.gust * 2.237)
          : ''}`,
      }),
    },
    {
      key: 'dir', label: 'Dir', unit: '-', group: 'wind',
      visible: !!visibleCols.dir, headClass: 'utci-dir-cell', cellClass: 'utci-dir-cell',
      render: (r) => ({
        content: html`
          <span style=${{ display: 'inline-flex', alignItems: 'center', gap: '6px', verticalAlign: 'middle' }}>
            <${WindVane} bearing=${r.wd} size=${28} />
            <span class="wind-dir-label" style=${{ fontFamily: 'Manrope, sans-serif', fontSize: '11px', fontWeight: 700 }}>${r.compass.label}</span>
          </span>`,
      }),
    },
    {
      key: 'aqi', label: 'AQI', unit: aqBeyond ? 'no data' : 'EU idx', unitNone: aqBeyond, group: 'airqual',
      visible: !!visibleCols.aqi,
      render: (r) => {
        const v = r.aqi == null ? null : Math.round(r.aqi);
        return {
          title: v == null && aqBeyondNote ? aqBeyondNote : null,
          content: v == null ? '—'
            : v < 20  ? `${v} Good`
            : v < 40  ? `${v} Fair`
            : v < 60  ? `${v} Mod`
            : v < 80  ? `${v} Poor`
            : v < 100 ? `${v} V.Poor`
            : `${v} Hazard`,
          style: {
            background: v == null ? 'transparent'
              : v < 20  ? 'rgba(80,180,100,0.15)'
              : v < 40  ? 'rgba(140,200,100,0.13)'
              : v < 60  ? 'rgba(220,200,60,0.15)'
              : v < 80  ? 'rgba(220,130,50,0.18)'
              : v < 100 ? 'rgba(200,70,50,0.18)'
              : 'rgba(160,30,100,0.20)',
            color: v != null && v >= 60 ? '#7a2010' : v != null && v >= 40 ? '#7a4a10' : '#2a4a20',
            fontWeight: v != null && v >= 60 ? 600 : 400,
          },
        };
      },
    },
    {
      key: 'pollen',
      label: pollenType === 'all_pollen' ? 'Pollen' : POLLEN_TYPES[pollenType]?.name.replace(' pollen', '') ?? 'Pollen',
      unit: aqBeyond ? 'no data' : 'grains/m³', unitNone: aqBeyond, group: 'airqual',
      visible: !!visibleCols.pollen,
      render: (r) => {
        const pollenMap = {
          all_pollen:     [r.grassPollen, r.birchPollen, r.alderPollen, r.mugwortPollen, r.olivePollen, r.ragweedPollen].reduce((s, x) => x != null ? s + x : s, null),
          grass_pollen:   r.grassPollen,
          birch_pollen:   r.birchPollen,
          alder_pollen:   r.alderPollen,
          mugwort_pollen: r.mugwortPollen,
          olive_pollen:   r.olivePollen,
          ragweed_pollen: r.ragweedPollen,
        };
        const v = pollenMap[pollenType] ?? null;
        return {
          title: v == null && aqBeyondNote ? aqBeyondNote : null,
          content: v == null ? '—'
            : v < 10  ? `${Math.round(v)} Low`
            : v < 50  ? `${Math.round(v)} Mod`
            : v < 200 ? `${Math.round(v)} High`
            : `${Math.round(v)} V.High`,
          style: {
            background: v == null ? 'transparent'
              : v < 10  ? 'transparent'
              : v < 50  ? 'rgba(180,200,80,0.13)'
              : v < 200 ? 'rgba(210,150,50,0.16)'
              : 'rgba(200,70,50,0.18)',
            color: v != null && v >= 200 ? '#8a2010' : v != null && v >= 50 ? '#7a4a10' : '#4a3218',
            fontWeight: v != null && v >= 50 ? 600 : 400,
          },
        };
      },
    },
    {
      key: 'uvA', label: 'UV-A', unit: 'est. idx', group: 'solar',
      visible: !!visibleCols.uvA,
      render: (r) => ({
        content: r.uvA > 0 ? fmt(r.uvA) + u(' idx') : '—',
        style: { color: r.uvA > 0 ? '#c8922a' : '#4a3218', background: scaleBg(r.uvA, 0, 8, '220,155,40') },
      }),
    },
    {
      key: 'uvB', label: 'UV-B', unit: 'est. idx', group: 'solar',
      visible: !!visibleCols.uvB,
      render: (r) => ({
        content: r.uvB > 0 ? fmt(r.uvB, 2) + u(' idx') : '—',
        style: { color: r.uvB > 0 ? '#c44a3a' : '#4a3218', background: scaleBg(r.uvB, 0, 1.2, '210,70,50') },
      }),
    },
    {
      key: 'sun', label: 'Sun', unit: 'elev°', group: 'solar',
      visible: !!visibleCols.sun,
      render: (r) => ({
        content: r.elev > 0 ? fmt(r.elev) + u('°') : '—',
        style: { background: scaleBg(r.elev > 0 ? r.elev : null, 0, 70, '225,160,45') },
      }),
    },
    {
      key: 'direct', label: 'Direct', unit: 'W/m²', group: 'solar',
      visible: !!visibleCols.direct,
      render: (r) => ({
        content: `${Math.round(r.dir)}${u('W/m²')}`,
        style: { background: scaleBg(r.dir, 0, 850, '230,155,35') },
      }),
    },
    {
      key: 'diffuse', label: 'Diffuse', unit: 'W/m²', group: 'solar',
      visible: !!visibleCols.diffuse,
      render: (r) => ({
        content: `${Math.round(r.dif)}${u('W/m²')}`,
        style: { background: scaleBg(r.dif, 0, 450, '230,190,70') },
      }),
    },
  ];

  return defs;
}
