// 07 · The Synthesis
// LLM-powered plain-language findings grounded in pre-computed weather data.
// Calls the ohy-synthesis Cloudflare Worker, which proxies to Claude API.

const SYNTH_WORKER_URL = 'https://ohy-synthesis.jeremyhaynes.workers.dev';

// ── Query matching: extract relevant data slice from window globals ──

function matchWeatherQuery(query) {
  const q = query.trim().toLowerCase();
  if (!q) return null;

  const data = {
    annualAnomaly: window.annualAnomaly || [],
    STATIONS: window.STATIONS || [],
    STRANGEST: window.STRANGEST || {},
    RATIO_BY_DECADE: window.RATIO_BY_DECADE || [],
    RETURN_PERIOD: window.RETURN_PERIOD || {},
    EVENT_CLUSTERS: window.EVENT_CLUSTERS || [],
    BASELINE_STATS: window.BASELINE_STATS || [],
  };

  // Match station by name or city
  const stationMatch = data.STATIONS.find(s =>
    (s.name || '').toLowerCase().includes(q) ||
    (s.city || '').toLowerCase().includes(q) ||
    (s.id || '').toLowerCase() === q
  );
  if (stationMatch) {
    return {
      type: 'station',
      label: stationMatch.city || stationMatch.name,
      context: {
        station: stationMatch,
        baseline: data.BASELINE_STATS,
        headline: `Station profile for ${stationMatch.city || stationMatch.name}`,
      },
    };
  }

  // Match "strangest day"
  if (q.includes('strangest') || q.includes('weird') || q.includes('anomalous')) {
    const strangest = data.STRANGEST.default || Object.values(data.STRANGEST)[0];
    return {
      type: 'strangest',
      label: 'Strangest Day',
      context: {
        strangest,
        baseline: data.BASELINE_STATS,
        headline: `The strangest day in the record: ${strangest?.city || 'unknown'}`,
      },
    };
  }

  // Match year
  const yearNum = parseInt(q, 10);
  if (yearNum >= 1850 && yearNum <= 2030) {
    const yearAnomaly = data.annualAnomaly.find(d => d.year === yearNum);
    const yearEvents = data.EVENT_CLUSTERS.filter(e =>
      String(e.date || '').startsWith(String(yearNum)) ||
      String(e.year || '') === String(yearNum)
    );
    return {
      type: 'year',
      label: String(yearNum),
      context: {
        year: yearNum,
        anomaly: yearAnomaly,
        events: yearEvents,
        annualAnomaly: data.annualAnomaly,
        baseline: data.BASELINE_STATS,
        headline: `Temperature anomaly analysis for ${yearNum}`,
      },
    };
  }

  // Match decade (e.g. "1990s" or "1990")
  const decadeMatch = q.match(/^(\d{3})0s?$/);
  if (decadeMatch) {
    const decadeStart = parseInt(decadeMatch[1] + '0', 10);
    const decadeLabel = decadeStart + 's';
    const decadeRatio = data.RATIO_BY_DECADE.find(d =>
      String(d.decade || d.label || '').includes(String(decadeStart))
    );
    const decadeAnomalies = data.annualAnomaly.filter(d =>
      d.year >= decadeStart && d.year < decadeStart + 10
    );
    return {
      type: 'decade',
      label: decadeLabel,
      context: {
        decade: decadeLabel,
        ratio: decadeRatio,
        anomalies: decadeAnomalies,
        allRatios: data.RATIO_BY_DECADE,
        baseline: data.BASELINE_STATS,
        headline: `Decade analysis: ${decadeLabel}`,
      },
    };
  }

  // Match "return period" queries
  if (q.includes('return') || q.includes('recurrence') || q.includes('extreme value')) {
    return {
      type: 'return_period',
      label: 'Return Periods',
      context: {
        returnPeriod: data.RETURN_PERIOD,
        baseline: data.BASELINE_STATS,
        headline: `Non-stationary return period analysis`,
      },
    };
  }

  // Match event clusters
  const eventMatch = data.EVENT_CLUSTERS.find(e =>
    (e.label || '').toLowerCase().includes(q) ||
    (e.type || '').toLowerCase().includes(q) ||
    (e.description || '').toLowerCase().includes(q)
  );
  if (eventMatch) {
    return {
      type: 'event',
      label: eventMatch.label || eventMatch.type || 'Event',
      context: {
        event: eventMatch,
        allEvents: data.EVENT_CLUSTERS,
        baseline: data.BASELINE_STATS,
        headline: `Extreme event cluster: ${eventMatch.label || eventMatch.type}`,
      },
    };
  }

  // Match weather terms (heat, cold, etc.)
  const weatherTerms = {
    heat: { filter: e => (e.type || e.label || '').toLowerCase().includes('heat') || (e.anomaly_mean || 0) > 0, label: 'Heat Events' },
    cold: { filter: e => (e.type || e.label || '').toLowerCase().includes('cold') || (e.anomaly_mean || 0) < 0, label: 'Cold Events' },
    warm: { filter: e => (e.anomaly_mean || 0) > 0, label: 'Warming Signal' },
    cool: { filter: e => (e.anomaly_mean || 0) < 0, label: 'Cooling Signal' },
  };
  const termDef = weatherTerms[q];
  if (termDef) {
    const matched = data.EVENT_CLUSTERS.filter(termDef.filter);
    return {
      type: 'term',
      label: termDef.label,
      context: {
        events: matched,
        annualAnomaly: data.annualAnomaly,
        baseline: data.BASELINE_STATS,
        headline: `Analysis of ${termDef.label.toLowerCase()} in the record`,
      },
    };
  }

  // Fuzzy: city match in stations
  const fuzzyStation = data.STATIONS.find(s =>
    (s.city || '').toLowerCase().includes(q)
  );
  if (fuzzyStation) {
    return {
      type: 'station',
      label: fuzzyStation.city || fuzzyStation.name,
      context: {
        station: fuzzyStation,
        baseline: data.BASELINE_STATS,
        headline: `Station profile for ${fuzzyStation.city || fuzzyStation.name}`,
      },
    };
  }

  // Fallback: send everything as general context
  return {
    type: 'general',
    label: query.trim(),
    context: {
      annualAnomaly: data.annualAnomaly.slice(-20),
      stationCount: data.STATIONS.length,
      baseline: data.BASELINE_STATS,
      eventCount: data.EVENT_CLUSTERS.length,
      ratioByDecade: data.RATIO_BY_DECADE,
      headline: `General query: ${query.trim()}`,
    },
  };
}

// ── Suggestions ─────────────────────────────────────────────────────

const SYNTH_SUGGESTIONS = [
  { label: 'Phoenix', type: 'station' },
  { label: '1936', type: 'year' },
  { label: '2023', type: 'year' },
  { label: 'strangest day', type: 'strangest' },
  { label: 'return periods', type: 'return_period' },
  { label: 'heat', type: 'term' },
  { label: '1990s', type: 'decade' },
];

// ── Confidence badge (Atmosphere design system) ─────────────────────

function SynthConfidenceBadge({ level }) {
  const colors = {
    'HIGH': { color: '#74ADD1', label: 'HIGH CONFIDENCE' },
    'HIGH CONFIDENCE': { color: '#74ADD1', label: 'HIGH CONFIDENCE' },
    'MODERATE': { color: '#FDAE61', label: 'MODERATE CONFIDENCE' },
    'MODERATE CONFIDENCE': { color: '#FDAE61', label: 'MODERATE CONFIDENCE' },
    'CANDIDATE': { color: '#D73027', label: 'CANDIDATE FINDING' },
  };
  const c = colors[(level || '').toUpperCase()] || colors['CANDIDATE'];
  return (
    <span className="mono" style={{
      display: 'inline-flex', alignItems: 'center', gap: 6,
      border: `1px solid ${c.color}66`, color: c.color, padding: '3px 7px',
      fontSize: 9, letterSpacing: 0.18, textTransform: 'uppercase',
    }}>
      <span style={{ width: 6, height: 6, background: c.color, borderRadius: '50%' }} />
      {c.label}
    </span>
  );
}

// ── Raw data display ────────────────────────────────────────────────

function SynthRawDataTable({ context }) {
  const rows = [];
  function flatten(obj, prefix) {
    if (Array.isArray(obj)) {
      obj.slice(0, 20).forEach((item, i) => {
        if (typeof item === 'object' && item !== null) {
          flatten(item, prefix + '[' + i + ']');
        } else {
          rows.push({ key: prefix + '[' + i + ']', value: String(item) });
        }
      });
    } else if (typeof obj === 'object' && obj !== null) {
      for (const [k, v] of Object.entries(obj)) {
        if (typeof v === 'object' && v !== null) {
          flatten(v, prefix ? prefix + '.' + k : k);
        } else {
          rows.push({ key: prefix ? prefix + '.' + k : k, value: String(v) });
        }
      }
    }
  }
  flatten(context, '');
  const display = rows.slice(0, 40);

  return (
    <div style={{
      maxHeight: 300, overflowY: 'auto',
      border: '1px solid #1B2740', background: 'rgba(10,15,26,0.5)',
      fontSize: 11,
    }}>
      <table style={{ width: '100%', borderCollapse: 'collapse' }}>
        <thead>
          <tr style={{ borderBottom: '1px solid #1B2740', position: 'sticky', top: 0, background: '#0E1422' }}>
            <th className="mono" style={{ textAlign: 'left', padding: '6px 10px', color: '#4E6A82', fontWeight: 500, fontSize: 10, letterSpacing: 0.1, textTransform: 'uppercase' }}>Field</th>
            <th className="mono" style={{ textAlign: 'left', padding: '6px 10px', color: '#4E6A82', fontWeight: 500, fontSize: 10, letterSpacing: 0.1, textTransform: 'uppercase' }}>Value</th>
          </tr>
        </thead>
        <tbody>
          {display.map((r, i) => (
            <tr key={i} style={{ borderBottom: '1px solid #1B274033' }}>
              <td className="mono" style={{ padding: '4px 10px', color: '#8BAFC7', whiteSpace: 'nowrap', fontSize: 10.5 }}>{r.key}</td>
              <td className="mono" style={{ padding: '4px 10px', color: '#E6ECF2', fontSize: 10.5 }}>{r.value}</td>
            </tr>
          ))}
        </tbody>
      </table>
      {rows.length > 40 && (
        <div className="mono" style={{ padding: '6px 10px', color: '#4E6A82', fontSize: 10 }}>
          + {rows.length - 40} more fields
        </div>
      )}
    </div>
  );
}

// ── Main Synthesis Tab ──────────────────────────────────────────────

function TabSynthesis() {
  const [input, setInput] = React.useState('');
  const [loading, setLoading] = React.useState(false);
  const [result, setResult] = React.useState(null);
  const [error, setError] = React.useState(null);
  const [matchInfo, setMatchInfo] = React.useState(null);
  const [showRaw, setShowRaw] = React.useState(false);
  const inputRef = React.useRef(null);

  async function runSynthesis(queryStr) {
    const q = queryStr || input;
    if (!q.trim()) return;

    setLoading(true);
    setError(null);
    setResult(null);
    setShowRaw(false);

    const match = matchWeatherQuery(q);
    setMatchInfo(match);

    if (!match) {
      setError('No matching data found for "' + q + '." Try a station name, year, decade, event, or weather term.');
      setLoading(false);
      return;
    }

    try {
      const resp = await fetch(SYNTH_WORKER_URL, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ query: q.trim(), context: match.context }),
      });

      if (!resp.ok) {
        const body = await resp.json().catch(() => ({}));
        throw new Error(body.error || 'Synthesis service returned ' + resp.status);
      }

      const synthesis = await resp.json();
      setResult(synthesis);
    } catch (err) {
      setError(err.message || 'Failed to reach synthesis service');
    } finally {
      setLoading(false);
    }
  }

  function handleKeyDown(e) {
    if (e.key === 'Enter') {
      e.preventDefault();
      runSynthesis();
    }
  }

  function handleSuggestion(label) {
    setInput(label);
    runSynthesis(label);
  }

  return (
    <div style={{ padding: '32px 56px 80px' }}>
      <Eyebrow id="07 · SYNTHESIS"
        title="Ask the record a question."
        right="Claude-powered · grounded in pre-computed data" />

      {/* Synthesis engine panel */}
      <div style={{ border: '1px solid #1B2740', background: 'rgba(14,20,34,0.6)', padding: 28, marginBottom: 24 }}>
        <Tick color="#C9A84C">&#9612; SYNTHESIS ENGINE</Tick>
        <div className="serif" style={{
          fontSize: 15, color: '#8BAFC7', lineHeight: 1.6, maxWidth: 720,
          marginTop: 12, marginBottom: 20,
        }}>
          Type a station name, year, decade, or weather term. The synthesis engine translates
          pre-computed model outputs into plain-language findings at any resolution.
        </div>

        {/* Search input */}
        <div style={{ display: 'flex', gap: 8, alignItems: 'center', maxWidth: 600 }}>
          <div style={{
            flex: 1, display: 'flex', alignItems: 'center', gap: 8,
            background: 'rgba(10,15,26,0.6)', border: '1px solid #243353',
            padding: '10px 14px',
          }}>
            <Crosshair size={14} color="#4E6A82" />
            <input
              ref={inputRef}
              type="text"
              value={input}
              onChange={e => setInput(e.target.value)}
              onKeyDown={handleKeyDown}
              placeholder="e.g. Phoenix, 1936, strangest day, return periods, heat"
              className="mono"
              style={{
                flex: 1, background: 'none', border: 'none', outline: 'none',
                fontSize: 12, color: '#E6ECF2', letterSpacing: '0.02em',
                fontFamily: "'Space Mono', ui-monospace, monospace",
              }}
            />
          </div>
          <button
            onClick={() => runSynthesis()}
            disabled={loading || !input.trim()}
            style={{
              padding: '10px 18px', border: '1px solid #C9A84C',
              background: loading ? 'transparent' : '#C9A84C',
              color: loading ? '#4E6A82' : '#0A0F1A',
              fontFamily: "'Space Mono', ui-monospace, monospace",
              fontSize: 10, fontWeight: 700,
              letterSpacing: '0.12em', textTransform: 'uppercase',
              cursor: loading ? 'wait' : 'pointer',
              transition: 'all 180ms',
            }}
          >
            {loading ? 'SYNTHESIZING...' : 'SYNTHESIZE'}
          </button>
        </div>

        {/* Suggestions */}
        <div style={{ display: 'flex', gap: 6, marginTop: 12, flexWrap: 'wrap', alignItems: 'center' }}>
          <span className="mono" style={{ color: '#4E6A82', fontSize: 9, letterSpacing: '0.15em', marginRight: 4, textTransform: 'uppercase' }}>Try:</span>
          {SYNTH_SUGGESTIONS.map((s, i) => (
            <button key={i} onClick={() => handleSuggestion(s.label)} style={{
              padding: '3px 9px',
              border: '1px solid #1B2740', background: 'rgba(14,20,34,0.4)',
              color: '#8BAFC7', fontFamily: "'Space Mono', ui-monospace, monospace",
              fontSize: 10.5, cursor: 'pointer', letterSpacing: '0.02em',
              transition: 'border-color 180ms',
            }}>
              {s.label}
            </button>
          ))}
        </div>

        {/* Error */}
        {error && (
          <div style={{
            padding: '14px 18px', marginTop: 20,
            border: '1px solid rgba(215,48,39,0.3)', background: 'rgba(215,48,39,0.06)',
            fontSize: 12, color: '#D73027',
            fontFamily: "'Space Mono', ui-monospace, monospace",
          }}>
            {error}
          </div>
        )}

        {/* Loading */}
        {loading && (
          <div style={{
            padding: '28px 18px', textAlign: 'center', marginTop: 20,
            fontFamily: "'Space Mono', ui-monospace, monospace",
          }}>
            <div className="mono" style={{
              fontSize: 11, color: '#8BAFC7', letterSpacing: '0.1em',
              animation: 'pulse 1.5s ease-in-out infinite',
            }}>
              analyzing weather record...
            </div>
            {matchInfo && (
              <div className="mono" style={{ fontSize: 10, color: '#4E6A82', marginTop: 8 }}>
                matched: {matchInfo.type} &rarr; {matchInfo.label}
              </div>
            )}
          </div>
        )}

        {/* Result */}
        {result && !loading && (
          <div style={{ marginTop: 20 }}>
            {/* Query match info */}
            <div className="mono" style={{
              display: 'flex', gap: 12, alignItems: 'center', marginBottom: 14,
              fontSize: 10, color: '#4E6A82', letterSpacing: '0.08em',
            }}>
              <span>QUERY TYPE: {(result.query_type || matchInfo?.type || '').toUpperCase()}</span>
              <span>&middot;</span>
              <span>MATCHED: {matchInfo?.label}</span>
              {result.cached && <React.Fragment><span>&middot;</span><span>CACHED</span></React.Fragment>}
            </div>

            {/* Synthesis text */}
            <div style={{
              padding: '20px 24px',
              border: '1px solid #1B2740', background: 'rgba(10,15,26,0.5)',
              marginBottom: 14,
            }}>
              <div className="serif" style={{
                fontSize: 15, lineHeight: 1.7, color: '#E6ECF2',
              }}>
                {result.synthesis}
              </div>
            </div>

            {/* Confidence + sources row */}
            <div style={{ display: 'flex', gap: 16, alignItems: 'center', marginBottom: 16, flexWrap: 'wrap' }}>
              <SynthConfidenceBadge level={result.confidence || 'CANDIDATE'} />
              {result.sources && result.sources.length > 0 && (
                <span className="mono" style={{ fontSize: 10, color: '#4E6A82', letterSpacing: 0.08 }}>
                  SOURCES: {result.sources.join(', ')}
                </span>
              )}
            </div>

            {/* Raw data toggle */}
            <button onClick={() => setShowRaw(!showRaw)} style={{
              padding: '5px 12px',
              border: '1px solid #1B2740', background: 'rgba(14,20,34,0.4)',
              color: '#8BAFC7', fontFamily: "'Space Mono', ui-monospace, monospace",
              fontSize: 10.5, cursor: 'pointer', letterSpacing: '0.05em',
            }}>
              {showRaw ? 'HIDE RAW DATA \u25B4' : 'SHOW RAW DATA \u25BE'}
            </button>

            {showRaw && matchInfo && (
              <div style={{ marginTop: 12 }}>
                <SynthRawDataTable context={matchInfo.context} />
              </div>
            )}
          </div>
        )}
      </div>

      {/* Methodology note */}
      <div style={{ border: '1px solid #1B2740', background: 'rgba(14,20,34,0.6)', padding: 28 }}>
        <Tick color="#C9A84C">&#9612; HOW IT WORKS</Tick>
        <div className="serif" style={{
          fontSize: 14.5, color: '#8BAFC7', marginTop: 14, lineHeight: 1.65,
          maxWidth: 720,
        }}>
          <p style={{ margin: '0 0 12px' }}>
            The synthesis engine is not a chatbot. It receives pre-computed data from the GHCN
            analysis pipeline and translates it into plain-language findings using Claude (Anthropic).
          </p>
          <p style={{ margin: '0 0 12px' }}>
            Every numerical claim in a synthesis is grounded in the data visible in the raw data panel
            below each finding. The model cannot invent statistics or make claims beyond what the data supports.
          </p>
          <p style={{ margin: '0 0 16px' }}>
            Findings are labeled with confidence levels inherited from the series-wide system:
          </p>
        </div>
        <div style={{ display: 'flex', gap: 24, flexWrap: 'wrap', marginBottom: 16 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <SynthConfidenceBadge level="HIGH" />
            <span className="mono" style={{ fontSize: 10, color: '#4E6A82' }}>directly in the data</span>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <SynthConfidenceBadge level="MODERATE" />
            <span className="mono" style={{ fontSize: 10, color: '#4E6A82' }}>minimal inference</span>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <SynthConfidenceBadge level="CANDIDATE" />
            <span className="mono" style={{ fontSize: 10, color: '#4E6A82' }}>requires assumptions</span>
          </div>
        </div>
        <div className="mono" style={{ fontSize: 10, color: '#4E6A82', letterSpacing: 0.08 }}>
          Responses are cached for 24 hours. The same query always returns the same synthesis.
        </div>
      </div>

      {/* Citations */}
      <div style={{ marginTop: 40, display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderTop: '1px solid #1B2740', paddingTop: 18 }}>
        <Tick>SYNTHESIS · EXTENSION 03 · CLAUDE (ANTHROPIC)</Tick>
        <Tick color="#C9A84C">GROUNDED IN GHCN-DAILY PRE-COMPUTED DATA</Tick>
      </div>
    </div>
  );
}

Object.assign(window, { TabSynthesis });
