# geney/visualization/preview.py — Generate an HTML preview of dashboard cards.
#
# Usage:
#     from geney.visualization.preview import generate_preview
#     generate_preview(result, "preview.html")
#
# Or from CLI:
#     python -m geney.visualization.preview KRAS:12:25227343:G:T
from __future__ import annotations

import json
import os
from pathlib import Path

from ..results import MutationResult
from . import card_data


def generate_preview(result: MutationResult, output_path: str = "card_preview.html") -> str:
    """Generate a self-contained HTML file previewing all dashboard cards.

    Args:
        result: MutationResult from pipeline.
        output_path: Where to write the HTML file.

    Returns:
        Absolute path to the generated HTML file.
    """
    data = card_data(result)
    data_json = json.dumps(data, default=str)

    html = _HTML_TEMPLATE.replace("__CARD_DATA__", data_json)

    out = Path(output_path).resolve()
    out.write_text(html)
    return str(out)


_HTML_TEMPLATE = r"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Geney Dashboard Preview</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #f5f5f7; color: #1d1d1f; padding: 24px; }
.header { text-align: center; margin-bottom: 32px; }
.header h1 { font-size: 24px; font-weight: 600; }
.header .subtitle { color: #86868b; font-size: 14px; margin-top: 4px; }
.header .overall { font-size: 48px; font-weight: 700; margin: 16px 0 4px; }
.header .overall-label { font-size: 13px; color: #86868b; text-transform: uppercase; letter-spacing: 1px; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; max-width: 1200px; margin: 0 auto; }
@media (max-width: 800px) { .grid { grid-template-columns: 1fr; } }
.card { background: #fff; border-radius: 16px; padding: 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); }
.card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.card-title { font-size: 16px; font-weight: 600; text-transform: capitalize; }
.card-score { font-size: 28px; font-weight: 700; }
.card-status { font-size: 12px; color: #86868b; margin-top: 2px; }
.score-high { color: #ff3b30; }
.score-med { color: #ff9500; }
.score-low { color: #34c759; }
.score-none { color: #c7c7cc; }
svg { width: 100%; overflow: visible; }
.label { font-size: 11px; fill: #86868b; }
.tick-label { font-size: 10px; fill: #86868b; }
.not-run { text-align: center; padding: 40px 0; color: #c7c7cc; font-size: 14px; }

/* Tooltip */
.tooltip { position: absolute; background: rgba(29,29,31,0.9); color: #fff; padding: 6px 10px;
  border-radius: 6px; font-size: 12px; pointer-events: none; opacity: 0; transition: opacity 0.15s;
  white-space: nowrap; z-index: 10; }
</style>
</head>
<body>

<div class="header">
  <h1 id="title"></h1>
  <div class="subtitle" id="subtitle"></div>
  <div class="overall" id="overall-score"></div>
  <div class="overall-label">Overall Disruption Score</div>
</div>

<div class="grid" id="grid"></div>
<div class="tooltip" id="tooltip"></div>

<script>
const DATA = __CARD_DATA__;

// -- Helpers --
function scoreClass(s) { return s >= 7 ? 'score-high' : s >= 3 ? 'score-med' : s > 0 ? 'score-low' : 'score-none'; }

function showTooltip(evt, text) {
  const tt = document.getElementById('tooltip');
  tt.textContent = text;
  tt.style.left = evt.pageX + 12 + 'px';
  tt.style.top = evt.pageY - 28 + 'px';
  tt.style.opacity = 1;
}
function hideTooltip() { document.getElementById('tooltip').style.opacity = 0; }

// SVG namespace helper
function svgEl(tag, attrs = {}) {
  const el = document.createElementNS('http://www.w3.org/2000/svg', tag);
  for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, v);
  return el;
}

// -- Header --
document.getElementById('title').textContent = `${DATA.gene} — ${DATA.mut_id}`;
document.getElementById('subtitle').textContent = Object.entries(DATA.statuses)
  .map(([k,v]) => `${k}: ${v}`).join(' · ');
const ov = document.getElementById('overall-score');
ov.textContent = DATA.overall_score.toFixed(1);
ov.className = 'overall ' + scoreClass(DATA.overall_score);

// -- Card factory --
function makeCard(name, cardData) {
  const div = document.createElement('div');
  div.className = 'card';

  const hdr = document.createElement('div');
  hdr.className = 'card-header';
  const title = document.createElement('div');
  title.innerHTML = `<div class="card-title">${name}</div><div class="card-status">${cardData.status}</div>`;
  const scoreDiv = document.createElement('div');
  scoreDiv.className = 'card-score ' + scoreClass(cardData.score);
  scoreDiv.textContent = cardData.score.toFixed(1);
  hdr.appendChild(title);
  hdr.appendChild(scoreDiv);
  div.appendChild(hdr);

  if (cardData.status === 'not_run' || cardData.status === 'error') {
    const nr = document.createElement('div');
    nr.className = 'not-run';
    nr.textContent = cardData.reason || 'Not run';
    div.appendChild(nr);
    return div;
  }

  const vizDiv = document.createElement('div');
  vizDiv.id = `viz-${name}`;
  div.appendChild(vizDiv);
  return div;
}

// -- Splicing visualization --
function drawSplicing(container, card) {
  const W = 520, margin = {top: 10, right: 20, bottom: 30, left: 20};
  const exons = card.exons || [];
  const sites = card.splice_sites || [];

  if (exons.length === 0 && sites.length === 0) {
    container.innerHTML = '<div class="not-run">No exon/splice data</div>';
    return;
  }

  // Determine coordinate range
  let allPos = sites.map(s => s.position);
  exons.forEach(e => { allPos.push(e.start, e.end); });
  if (card.mutation_position) allPos.push(card.mutation_position);
  const minP = Math.min(...allPos), maxP = Math.max(...allPos);
  const range = maxP - minP || 1;
  const w = W - margin.left - margin.right;
  const scale = p => margin.left + ((p - minP) / range) * w;

  // Heights
  const exonY = 40, exonH = 20, siteAreaH = 60;
  const H = exonY + exonH + siteAreaH + margin.bottom + 10;

  const svg = svgEl('svg', {viewBox: `0 0 ${W} ${H}`, preserveAspectRatio: 'xMidYMid meet'});

  // Intron line
  if (exons.length > 0) {
    const line = svgEl('line', {x1: scale(exons[0].start), y1: exonY + exonH/2,
      x2: scale(exons[exons.length-1].end), y2: exonY + exonH/2,
      stroke: '#d2d2d7', 'stroke-width': 2});
    svg.appendChild(line);
  }

  // Exons
  exons.forEach(e => {
    const x = scale(e.start), ew = Math.max(4, scale(e.end) - scale(e.start));
    const rect = svgEl('rect', {x, y: exonY, width: ew, height: exonH, rx: 3,
      fill: e.disrupted ? '#ff3b30' : '#007aff', opacity: e.disrupted ? 0.85 : 0.7});
    rect.addEventListener('mouseover', ev => showTooltip(ev,
      `Exon ${e.number}: ${e.start.toLocaleString()}-${e.end.toLocaleString()}${e.disrupted ? ' (DISRUPTED)' : ''}`));
    rect.addEventListener('mouseout', hideTooltip);
    svg.appendChild(rect);

    const lbl = svgEl('text', {x: x + ew/2, y: exonY + exonH/2 + 4,
      'text-anchor': 'middle', 'font-size': 10, fill: '#fff', 'font-weight': 600});
    lbl.textContent = e.number;
    svg.appendChild(lbl);
  });

  // Mutation marker
  if (card.mutation_position) {
    const mx = scale(card.mutation_position);
    svg.appendChild(svgEl('line', {x1: mx, y1: exonY - 8, x2: mx, y2: exonY + exonH + 8,
      stroke: '#ff9500', 'stroke-width': 2, 'stroke-dasharray': '3,2'}));
    const tri = svgEl('polygon', {points: `${mx-4},${exonY-8} ${mx+4},${exonY-8} ${mx},${exonY-2}`,
      fill: '#ff9500'});
    svg.appendChild(tri);
  }

  // Splice site lollipops
  const siteBaseY = exonY + exonH + 15;
  const maxDelta = Math.max(0.01, ...sites.map(s => Math.abs(s.delta)));
  sites.forEach(s => {
    const x = scale(s.position);
    const h = (Math.abs(s.delta) / maxDelta) * siteAreaH * 0.8;
    const isGain = s.delta > 0;
    const y1 = siteBaseY, y2 = siteBaseY + (isGain ? -h : h);
    const color = isGain ? '#34c759' : '#ff3b30';

    svg.appendChild(svgEl('line', {x1: x, y1, x2: x, y2, stroke: color, 'stroke-width': 2}));
    const circ = svgEl('circle', {cx: x, cy: y2, r: 3.5, fill: color});
    circ.addEventListener('mouseover', ev => showTooltip(ev,
      `${s.type} @ ${s.position.toLocaleString()}: ${s.ref_prob.toFixed(3)} → ${s.var_prob.toFixed(3)} (Δ${s.delta >= 0 ? '+' : ''}${s.delta.toFixed(3)})${s.annotated ? ' [annotated]' : ' [novel]'}`));
    circ.addEventListener('mouseout', hideTooltip);
    svg.appendChild(circ);
  });

  // Legend
  const legendY = H - 10;
  svg.appendChild(svgEl('circle', {cx: margin.left, cy: legendY, r: 3, fill: '#34c759'}));
  const g = svgEl('text', {x: margin.left + 6, y: legendY + 3, class: 'tick-label'}); g.textContent = 'Gained'; svg.appendChild(g);
  svg.appendChild(svgEl('circle', {cx: margin.left + 50, cy: legendY, r: 3, fill: '#ff3b30'}));
  const l = svgEl('text', {x: margin.left + 56, y: legendY + 3, class: 'tick-label'}); l.textContent = 'Lost'; svg.appendChild(l);

  // Summary text
  const iso = card.top_isoform;
  if (iso) {
    const summ = svgEl('text', {x: W - margin.right, y: legendY + 3, 'text-anchor': 'end', class: 'tick-label'});
    summ.textContent = `Top: ${iso.summary || '?'} (prevalence ${((iso.prevalence||0)*100).toFixed(0)}%)`;
    svg.appendChild(summ);
  }

  container.appendChild(svg);
}

// -- TIS visualization --
function drawTIS(container, card) {
  const sites = card.sites || [];
  if (sites.length === 0) {
    container.innerHTML = '<div class="not-run">No TIS data</div>';
    return;
  }

  const W = 520, margin = {top: 10, right: 20, bottom: 25, left: 40};
  const w = W - margin.left - margin.right;
  const barH = 70;
  const H = margin.top + barH + margin.bottom + 10;

  // Only show sites with meaningful probability (top 15)
  const filtered = sites.filter(s => Math.max(s.ref_prob, s.var_prob) > 0.01).slice(0, 15);
  const maxProb = Math.max(0.1, ...filtered.map(s => Math.max(s.ref_prob, s.var_prob)));

  const svg = svgEl('svg', {viewBox: `0 0 ${W} ${H}`, preserveAspectRatio: 'xMidYMid meet'});

  // mRNA track line
  const trackY = margin.top + barH / 2;
  svg.appendChild(svgEl('line', {x1: margin.left, y1: trackY, x2: W - margin.right, y2: trackY,
    stroke: '#e5e5ea', 'stroke-width': 2}));

  // Position scale (mRNA coords)
  const positions = filtered.map(s => s.position);
  const minPos = Math.min(...positions), maxPos = Math.max(...positions);
  const posRange = maxPos - minPos || 1;
  const scaleX = p => margin.left + ((p - minPos) / posRange) * w;
  const scaleH = p => (p / maxProb) * (barH / 2 - 4);

  // Probability bars — ref on top, var below
  const barW = Math.max(3, Math.min(12, w / filtered.length * 0.35));

  filtered.forEach(s => {
    const x = scaleX(s.position);
    const refH = scaleH(s.ref_prob);
    const varH = scaleH(s.var_prob);
    const isCanonical = s.location === 'canonical';

    // Ref bar (above track)
    const refBar = svgEl('rect', {x: x - barW/2 - 1, y: trackY - refH, width: barW, height: Math.max(1, refH),
      fill: '#007aff', opacity: 0.7, rx: 1});
    svg.appendChild(refBar);

    // Var bar (below track)
    const varBar = svgEl('rect', {x: x + 1, y: trackY, width: barW, height: Math.max(1, varH),
      fill: '#ff3b30', opacity: 0.7, rx: 1});
    svg.appendChild(varBar);

    // Canonical marker
    if (isCanonical) {
      svg.appendChild(svgEl('line', {x1: x, y1: trackY - refH - 6, x2: x, y2: trackY + varH + 6,
        stroke: '#007aff', 'stroke-width': 1, 'stroke-dasharray': '2,2', opacity: 0.5}));
    }

    // Tooltip region
    const hitArea = svgEl('rect', {x: x - barW, y: trackY - barH/2, width: barW * 2,
      height: barH, fill: 'transparent', cursor: 'pointer'});
    hitArea.addEventListener('mouseover', ev => showTooltip(ev,
      `ATG @ ${s.position} (${s.location}${s.in_frame ? ', in-frame' : ', out-of-frame'})\nRef: ${s.ref_prob.toFixed(3)}  Var: ${s.var_prob.toFixed(3)}  Δ${s.delta >= 0 ? '+' : ''}${s.delta.toFixed(3)}`));
    hitArea.addEventListener('mouseout', hideTooltip);
    svg.appendChild(hitArea);
  });

  // Mutation position markers
  (card.mutation_positions || []).forEach(mp => {
    if (mp >= minPos && mp <= maxPos) {
      const mx = scaleX(mp);
      svg.appendChild(svgEl('line', {x1: mx, y1: margin.top, x2: mx, y2: margin.top + barH,
        stroke: '#ff9500', 'stroke-width': 1.5, 'stroke-dasharray': '3,2'}));
    }
  });

  // Y axis labels
  const topLbl = svgEl('text', {x: margin.left - 4, y: margin.top + 8, 'text-anchor': 'end', class: 'tick-label'});
  topLbl.textContent = 'Ref'; svg.appendChild(topLbl);
  const botLbl = svgEl('text', {x: margin.left - 4, y: margin.top + barH - 4, 'text-anchor': 'end', class: 'tick-label'});
  botLbl.textContent = 'Var'; svg.appendChild(botLbl);

  // Legend
  const ly = H - 6;
  svg.appendChild(svgEl('rect', {x: margin.left, y: ly - 6, width: 8, height: 8, fill: '#007aff', opacity: 0.7, rx: 1}));
  const rl = svgEl('text', {x: margin.left + 12, y: ly + 1, class: 'tick-label'}); rl.textContent = 'Reference'; svg.appendChild(rl);
  svg.appendChild(svgEl('rect', {x: margin.left + 70, y: ly - 6, width: 8, height: 8, fill: '#ff3b30', opacity: 0.7, rx: 1}));
  const vl = svgEl('text', {x: margin.left + 82, y: ly + 1, class: 'tick-label'}); vl.textContent = 'Variant'; svg.appendChild(vl);

  const intact = svgEl('text', {x: W - margin.right, y: ly + 1, 'text-anchor': 'end', class: 'tick-label'});
  intact.textContent = card.canonical_intact ? 'Canonical initiation preserved' : 'Canonical initiation DISRUPTED';
  intact.setAttribute('fill', card.canonical_intact ? '#34c759' : '#ff3b30');
  svg.appendChild(intact);

  container.appendChild(svg);
}

// -- Folding visualization (arc diagram) --
function drawFolding(container, card) {
  const win = card.window;
  if (!win) {
    container.innerHTML = '<div class="not-run">No folding window data</div>';
    return;
  }

  const seqLen = win.sequence_length;
  const W = 520, margin = {top: 8, right: 15, bottom: 30, left: 15};
  const w = W - margin.left - margin.right;
  const arcH = 60;
  const seqY = margin.top + arcH + 5;
  const H = seqY + arcH + margin.bottom + 15;

  const svg = svgEl('svg', {viewBox: `0 0 ${W} ${H}`, preserveAspectRatio: 'xMidYMid meet'});

  const charW = w / seqLen;
  const posX = i => margin.left + i * charW + charW / 2;

  // Draw arcs: ref above, var below
  function drawArcs(arcs, baseY, direction, colorMap) {
    arcs.forEach(a => {
      const x1 = posX(a.i), x2 = posX(a.j);
      const span = x2 - x1;
      const midX = (x1 + x2) / 2;
      const height = Math.min(arcH - 5, span * 0.45);
      const cy = baseY + direction * height;
      const color = colorMap[a.status] || '#86868b';

      const path = svgEl('path', {
        d: `M ${x1},${baseY} Q ${midX},${cy} ${x2},${baseY}`,
        fill: 'none', stroke: color, 'stroke-width': a.status === 'kept' ? 1.2 : 2,
        opacity: a.status === 'kept' ? 0.35 : 0.85,
      });
      path.addEventListener('mouseover', ev => showTooltip(ev,
        `Base pair ${a.i}–${a.j} (${a.status})`));
      path.addEventListener('mouseout', hideTooltip);
      svg.appendChild(path);
    });
  }

  const refColors = {kept: '#007aff', lost: '#ff3b30'};
  const varColors = {kept: '#007aff', gained: '#34c759'};

  drawArcs(win.ref_arcs, seqY, -1, refColors);
  drawArcs(win.var_arcs, seqY, 1, varColors);

  // Sequence strip
  const seq = win.ref_sequence;
  for (let i = 0; i < seqLen; i++) {
    const x = posX(i);
    const isMut = i === win.mutation_offset;

    if (isMut) {
      svg.appendChild(svgEl('rect', {x: x - charW/2 - 1, y: seqY - 7, width: charW + 2, height: 14,
        rx: 2, fill: '#ff9500', opacity: 0.25}));
    }

    if (charW > 6) {
      const t = svgEl('text', {x, y: seqY + 4, 'text-anchor': 'middle',
        'font-size': Math.min(11, charW * 0.9), 'font-family': 'monospace',
        fill: isMut ? '#ff9500' : '#1d1d1f', 'font-weight': isMut ? 700 : 400});
      t.textContent = seq[i];
      svg.appendChild(t);
    }
  }

  // Labels
  const refLbl = svgEl('text', {x: margin.left - 2, y: seqY - arcH/2, class: 'label', 'text-anchor': 'start'});
  refLbl.textContent = 'Ref'; svg.appendChild(refLbl);
  const varLbl = svgEl('text', {x: margin.left - 2, y: seqY + arcH/2 + 10, class: 'label', 'text-anchor': 'start'});
  varLbl.textContent = 'Var'; svg.appendChild(varLbl);

  // MFE comparison
  const mfeY = H - 8;
  const mfeTxt = svgEl('text', {x: W/2, y: mfeY, 'text-anchor': 'middle', class: 'tick-label'});
  mfeTxt.textContent = `MFE: ${win.ref_mfe} → ${win.var_mfe} kcal/mol (Δ${win.delta_mfe >= 0 ? '+' : ''}${win.delta_mfe})`;
  svg.appendChild(mfeTxt);

  // Legend
  const ly = mfeY;
  svg.appendChild(svgEl('line', {x1: W - margin.right - 100, y1: ly - 3, x2: W - margin.right - 88, y2: ly - 3, stroke: '#ff3b30', 'stroke-width': 2}));
  const ll = svgEl('text', {x: W - margin.right - 85, y: ly, class: 'tick-label'}); ll.textContent = 'Lost'; svg.appendChild(ll);
  svg.appendChild(svgEl('line', {x1: W - margin.right - 55, y1: ly - 3, x2: W - margin.right - 43, y2: ly - 3, stroke: '#34c759', 'stroke-width': 2}));
  const gl = svgEl('text', {x: W - margin.right - 40, y: ly, class: 'tick-label'}); gl.textContent = 'Gained'; svg.appendChild(gl);

  container.appendChild(svg);
}

// -- Transcription visualization --
function drawTranscription(container, card) {
  const tissues = card.tissues || [];
  if (tissues.length === 0) {
    container.innerHTML = '<div class="not-run">No tissue data</div>';
    return;
  }

  const top = tissues.slice(0, 12);
  const W = 520, margin = {top: 5, right: 20, bottom: 5, left: 130};
  const rowH = 18;
  const H = margin.top + top.length * rowH + margin.bottom;
  const w = W - margin.left - margin.right;

  const svg = svgEl('svg', {viewBox: `0 0 ${W} ${H}`, preserveAspectRatio: 'xMidYMid meet'});

  const maxLFC = Math.max(0.1, ...top.map(t => Math.abs(t.log2fc)));
  const centerX = margin.left + w / 2;
  const scaleBar = v => (v / maxLFC) * (w / 2);

  // Center axis
  svg.appendChild(svgEl('line', {x1: centerX, y1: margin.top, x2: centerX, y2: H - margin.bottom,
    stroke: '#e5e5ea', 'stroke-width': 1}));

  top.forEach((t, i) => {
    const y = margin.top + i * rowH + rowH / 2;
    const barLen = scaleBar(Math.abs(t.log2fc));
    const isPos = t.log2fc >= 0;
    const x = isPos ? centerX : centerX - barLen;
    const color = isPos ? '#ff3b30' : '#007aff';

    // Bar
    const bar = svgEl('rect', {x, y: y - 6, width: Math.max(1, barLen), height: 12,
      fill: color, opacity: t.affected ? 0.75 : 0.3, rx: 2});
    bar.addEventListener('mouseover', ev => showTooltip(ev,
      `${t.name}: log2FC = ${t.log2fc >= 0 ? '+' : ''}${t.log2fc.toFixed(4)}${t.affected ? '' : ' (below threshold)'}`));
    bar.addEventListener('mouseout', hideTooltip);
    svg.appendChild(bar);

    // Label
    const label = svgEl('text', {x: margin.left - 4, y: y + 4, 'text-anchor': 'end', 'font-size': 10,
      fill: t.affected ? '#1d1d1f' : '#c7c7cc'});
    label.textContent = t.name.length > 20 ? t.name.slice(0, 18) + '...' : t.name;
    svg.appendChild(label);
  });

  // Axis labels
  const axY = H - 1;
  const negLbl = svgEl('text', {x: margin.left, y: axY, class: 'tick-label'}); negLbl.textContent = 'Down'; svg.appendChild(negLbl);
  const posLbl = svgEl('text', {x: W - margin.right, y: axY, 'text-anchor': 'end', class: 'tick-label'}); posLbl.textContent = 'Up'; svg.appendChild(posLbl);
  const zLbl = svgEl('text', {x: centerX, y: axY, 'text-anchor': 'middle', class: 'tick-label'}); zLbl.textContent = '0'; svg.appendChild(zLbl);

  container.appendChild(svg);
}

// -- Render all cards --
const grid = document.getElementById('grid');
const renderers = { splicing: drawSplicing, tis: drawTIS, folding: drawFolding, transcription: drawTranscription };

for (const [name, cardData] of Object.entries(DATA.cards)) {
  const cardEl = makeCard(name, cardData);
  grid.appendChild(cardEl);
  const vizDiv = cardEl.querySelector(`#viz-${name}`);
  if (vizDiv && renderers[name]) {
    renderers[name](vizDiv, cardData);
  }
}
</script>
</body>
</html>
"""


# CLI entry point
if __name__ == "__main__":
    import sys

    mut_id = sys.argv[1] if len(sys.argv) > 1 else "KRAS:12:25227343:G:T"
    out_path = sys.argv[2] if len(sys.argv) > 2 else "card_preview.html"

    print(f"Running pipeline for {mut_id}...")
    from ..pipelines import mutation_pipeline
    result = mutation_pipeline(mut_id, run_transcription=False)

    path = generate_preview(result, out_path)
    print(f"Preview written to: {path}")
    print(f"Open in browser: file://{path}")
