"""
Multi-stimulus timeline replay (Phase 5).

Loads a recorded session and lets you scrub/play through it: a moving playhead
sweeps every synchronized signal at once, the active stimulus is highlighted, and
the per-stimulus segments are listed. This is the iMotions "replay the timeline"
view — proof, visually, that everything shares one clock.

Two entry points:
  * `export_replay_html(session_path, out)` — a SELF-CONTAINED interactive HTML
    (Plotly + a vanilla-JS scrubber/play button). No server, opens anywhere.
  * `serve_replay(session_path)` — a Dash app version with a live slider.
"""

from __future__ import annotations

import json

import numpy as np

from ..analysis import load as A

PALETTE = ["#4C78A8", "#F58518", "#54A24B", "#E45756", "#72B7B2", "#B279A2"]


# --------------------------------------------------------------------------
def _downsample(t, x, max_points=1500):
    """Uniform decimation to keep the HTML small but faithful."""
    n = len(t)
    if n <= max_points:
        return t, x
    idx = np.linspace(0, n - 1, max_points).astype(int)
    return t[idx], x[idx]


def replay_data(session: dict, max_points=1500) -> dict:
    """Build a compact, JSON-serializable replay payload from a session."""
    t0, t_end = A.common_window(session)
    streams = []
    for name, s in session["streams"].items():
        if s["type"] == "Markers" or not len(s["t"]):
            continue
        t = np.asarray(s["t"], float) - t0
        x = np.asarray(s["x"], float)
        x = x[:, 0] if x.ndim > 1 else x
        td, xd = _downsample(t, x)
        streams.append({"name": name, "type": s["type"], "srate": s["srate"],
                        "t": [round(float(v), 4) for v in td],
                        "y": [round(float(v), 4) for v in xd]})
    # stimulus segments from on/off marker pairs
    segments, open_on = [], {}
    for t, label in A.markers(session):
        label = str(label)
        rel = float(t) - t0
        if label.startswith("stim_on"):
            open_on[label.split(":", 1)[-1]] = rel
        elif label.startswith("stim_off"):
            name = label.split(":", 1)[-1]
            if name in open_on:
                segments.append({"name": name, "start": open_on.pop(name), "end": rel})
    return {"duration": float(t_end - t0), "streams": streams, "segments": segments}


# --------------------------------------------------------------------------
def export_replay_html(session_path: str, out_path: str, max_points=1500, *,
                       back_href: str | None = None, subtitle: str | None = None,
                       screen=None) -> str:
    """Write a self-contained interactive replay HTML.

    `back_href` adds the shared Back/Home bar; `screen=(url, offset)` embeds the
    screen recording synced to the playhead (video.currentTime = offset + playhead).
    """
    from ..app.ui import THEME_CSS, topbar
    data = replay_data(A.load_session(session_path), max_points)
    if screen:
        data["screen"] = {"url": screen[0], "offset": float(screen[1])}
    payload = json.dumps(data)
    html = (_REPLAY_TEMPLATE
            .replace("/*__DATA__*/", payload)
            .replace("/*__THEME__*/", THEME_CSS)
            .replace("<!--__TOPBAR__-->", topbar(subtitle=subtitle or "Timeline replay",
                                                 back_href=back_href)))
    with open(out_path, "w") as f:
        f.write(html)
    return out_path


def serve_replay(session_path: str, host="127.0.0.1", port=8051):  # pragma: no cover
    """Dash version with a live time slider."""
    from dash import Dash, dcc, html, Input, Output
    import plotly.graph_objects as go
    from plotly.subplots import make_subplots

    data = replay_data(A.load_session(session_path))
    app = Dash(__name__)
    app.layout = html.Div([
        html.H1(f"Replay — {session_path}"),
        dcc.Slider(0, data["duration"], value=0, id="playhead",
                   tooltip={"placement": "bottom"}),
        dcc.Graph(id="replay"),
    ])

    @app.callback(Output("replay", "figure"), Input("playhead", "value"))
    def _draw(t):
        fig = make_subplots(rows=len(data["streams"]), cols=1, shared_xaxes=True,
                            subplot_titles=[s["name"] for s in data["streams"]])
        for i, s in enumerate(data["streams"], 1):
            fig.add_trace(go.Scattergl(x=s["t"], y=s["y"], mode="lines",
                                       line=dict(width=1, color=PALETTE[i % len(PALETTE)])),
                          row=i, col=1)
        for seg in data["segments"]:
            fig.add_vrect(x0=seg["start"], x1=seg["end"], line_width=0,
                          fillcolor="rgba(76,120,168,.08)", row="all", col=1)
        fig.add_vline(x=t, line=dict(color="#E45756", width=2))
        fig.update_layout(height=160 * len(data["streams"]), showlegend=False)
        return fig

    app.run(host=host, port=port)


# --------------------------------------------------------------------------
_REPLAY_TEMPLATE = r"""<!doctype html><html lang="en"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>biosync — timeline replay</title>
<script src="https://cdn.plot.ly/plotly-2.35.2.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>/*__THEME__*/
 #controls{display:flex;gap:14px;align-items:center;margin:0 0 14px}
 #scrub{flex:1;accent-color:var(--accent)}
 #clock{font-variant-numeric:tabular-nums;min-width:120px;color:var(--muted);font-size:14px}
 #stim{font-weight:650;color:var(--primary);min-width:150px}
 #plot{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
       padding:8px;box-shadow:var(--shadow)}
</style></head><body>
<!--__TOPBAR__-->
<div class="wrap">
<div class="card" id="screencard" style="display:none">
  <video id="screenvid" muted playsinline preload="auto"
    style="width:100%;border-radius:10px;background:#000"></video>
</div>
<div class="card">
<div id="controls">
  <button class="btn" id="play">&#9654;&nbsp; Play</button>
  <input id="scrub" type="range" min="0" max="100" value="0" step="0.05">
  <span id="clock">0.00 / 0.00 s</span>
  <span id="stim">&mdash;</span>
</div>
<div id="plot"></div>
</div></div>
<script>
const DATA = /*__DATA__*/;
const PAL = ["#4C78A8","#F58518","#54A24B","#E45756","#72B7B2","#B279A2"];
const S = DATA.streams, DUR = DATA.duration, SEG = DATA.segments;
const n = S.length;

// build stacked subplots
const traces = [], layout = {height: 150*n+60, margin:{l:55,r:20,t:30,b:35},
  showlegend:false, shapes:[]};
S.forEach((s,i)=>{
  const ax = i===0 ? "" : (i+1);
  traces.push({x:s.t,y:s.y,type:"scattergl",mode:"lines",
    line:{width:1,color:PAL[i%PAL.length]}, xaxis:"x"+ax, yaxis:"y"+ax});
  layout["yaxis"+ax]={title:{text:s.name,font:{size:11}}, domain:[1-(i+1)/n+0.03, 1-i/n-0.0]};
  layout["xaxis"+ax]={range:[0,DUR], anchor:"y"+ax, matches:"x"};
});
// shade stimulus windows on every subplot
SEG.forEach(seg=>{ for(let i=0;i<n;i++){ const ax=i===0?"":(i+1);
  layout.shapes.push({type:"rect",xref:"x"+ax,yref:"y"+ax+" domain",
    x0:seg.start,x1:seg.end,y0:0,y1:1,line:{width:0},fillcolor:"rgba(76,120,168,.08)"});}});
// the playhead line (one per subplot), tagged so we can move them
function playheadShapes(t){ const sh=[]; for(let i=0;i<n;i++){ const ax=i===0?"":(i+1);
  sh.push({type:"line",xref:"x"+ax,yref:"y"+ax+" domain",x0:t,x1:t,y0:0,y1:1,
    line:{color:"#E45756",width:2}}); } return sh; }
layout.shapes = layout.shapes.concat(playheadShapes(0));
const baseShapes = layout.shapes.length - n;  // index where playhead lines start

Plotly.newPlot("plot", traces, layout, {displayModeBar:false, responsive:true});

const scrub=document.getElementById("scrub"), clock=document.getElementById("clock"),
      stim=document.getElementById("stim"), playBtn=document.getElementById("play");
scrub.max = DUR;
function activeStim(t){ const s=SEG.find(s=>t>=s.start&&t<=s.end); return s?s.name:"—"; }
function setHead(t){
  const shapes = Plotly.d3 ? null : null; // noop guard
  const all = layout.shapes.slice(0, baseShapes).concat(playheadShapes(t));
  Plotly.relayout("plot", {shapes: all});
  clock.textContent = t.toFixed(2)+" / "+DUR.toFixed(2)+" s";
  stim.textContent = activeStim(t);
  stim.style.color = activeStim(t)==="—" ? "#999" : "#E45756";
}
// optional screen-recording video synced to the playhead
const SCREEN = DATA.screen || null;
let vid = null;
if(SCREEN){ document.getElementById("screencard").style.display="block";
  vid = document.getElementById("screenvid"); vid.src = SCREEN.url; }
function syncVideo(t){ if(!vid) return; const vt = SCREEN.offset + t;
  if(vt>=0 && isFinite(vt) && Math.abs(vid.currentTime - vt) > 0.15){
    try{ vid.currentTime = Math.max(0, vt); }catch(e){} } }

scrub.addEventListener("input", e=>{ const t=parseFloat(e.target.value); setHead(t); syncVideo(t); });

let playing=false, raf=null, t0=0, startWall=0;
function tick(){ const t = t0 + (performance.now()-startWall)/1000;
  if(t>=DUR){ setHead(DUR); scrub.value=DUR; stop(); return; }
  scrub.value=t; setHead(t); syncVideo(t); raf=requestAnimationFrame(tick); }
function play(){ playing=true; playBtn.textContent="⏸ Pause";
  t0=parseFloat(scrub.value)>=DUR?0:parseFloat(scrub.value);
  startWall=performance.now(); if(vid){ syncVideo(t0); vid.play().catch(()=>{}); }
  raf=requestAnimationFrame(tick); }
function stop(){ playing=false; playBtn.textContent="▶ Play"; cancelAnimationFrame(raf);
  if(vid) vid.pause(); }
playBtn.addEventListener("click", ()=> playing?stop():play());
setHead(0);
</script></body></html>"""
