"""BigMind Profile Page Renderers — HTML generation for the profile web page.

All rendering functions live here so web.py stays thin (Flask server only).
"""

import html as _html
from datetime import datetime, timezone, timedelta


def _render_achievements(achievements: list) -> str:
    """Render the Achievement Gallery grid."""
    if not achievements:
        return '<p class="muted">No achievements data.</p>'

    unlocked_count = sum(1 for a in achievements if a["unlocked"])
    total = len(achievements)

    def _card(a: dict) -> str:
        locked_cls = "" if a["unlocked"] else " locked"
        date_html = (
            f'<div class="ach-date">{a["unlocked_at"]}</div>'
            if a["unlocked"] and a.get("unlocked_at") else ""
        )
        countdown_html = ""
        if not a["unlocked"] and a.get("extra"):
            countdown_html = f'<div class="ach-countdown">{a["extra"]}</div>'

        # Escape values for data attributes
        def _esc(s):
            return (s or "").replace('"', "&quot;").replace("'", "&#39;")

        lock_overlay = '<span class="ach-lock">🔒</span>' if not a["unlocked"] else ''

        if a.get("image"):
            tier = a["id"].rsplit("_", 1)[-1]
            visual_html = f'<div class="ach-image tier-{tier}">{lock_overlay}</div>'
        else:
            visual_html = f'<div class="ach-icon">{a["icon"]}{lock_overlay}</div>'

        return (
            f'<div class="ach-card{locked_cls} ach-trigger" data-image="{_esc(a.get("image") or "")}"'
            f' data-icon="{_esc(a["icon"] or "")}"'
            f' data-name="{_esc(a["name"])}"'
            f' data-desc="{_esc(a["description"])}"'
            f' data-unlocked="{1 if a["unlocked"] else 0}"'
            f' data-date="{_esc(a.get("unlocked_at") or "")}"'
            f' data-condition="{_esc(a.get("condition") or "")}"'
            f' data-extra="{_esc(a.get("extra") or "")}">'
            f'{visual_html}'
            f'<div class="ach-name">{a["name"]}</div>'
            f'{date_html}'
            f'{countdown_html}'
            f'</div>'
        )

    cards_html = "".join(_card(a) for a in achievements)
    return (
        f'<p class="ach-summary">{unlocked_count} / {total} achievements unlocked</p>'
        f'<div class="ach-grid">{cards_html}</div>'
    )


def _render_html(data: dict) -> str:
    badges_html = "".join(
        f'<div class="badge" title="{b["description"]}">'
        f'<span class="badge-emoji">{b["emoji"]}</span>'
        f'<span class="badge-label">{b["label"]}</span>'
        f'</div>'
        for b in data["earned_badges"]
    ) or '<p class="muted">No badges yet — keep going!</p>'

    topics_html = "".join(
        f'<div class="topic-bar">'
        f'<span class="topic-name">{t}</span>'
        f'<div class="topic-track"><div class="topic-fill" style="width:{min(100, count*8)}%"></div></div>'
        f'<span class="topic-count">{count}</span>'
        f'</div>'
        for t, count in data["top_topics"]
    ) or '<p class="muted">No topics recorded yet.</p>'

    def _fmt_tokens(n: int) -> str:
        """Format a token count as a human-readable string (e.g. 1.2M, 250K)."""
        if n >= 1_000_000:
            return f"{n / 1_000_000:.1f}M"
        if n >= 1_000:
            return f"{n / 1_000:.0f}K"
        return str(n)

    def _session_row(s: dict) -> str:
        tok = s.get("session_tokens_saved") or 0
        tok_html = (
            f'<span title="{tok:,} tokens saved this session" '
            f'style="color:var(--green);font-size:10px;white-space:nowrap;flex-shrink:0">'
            f'💰 {_fmt_tokens(tok)}</span>'
        ) if tok > 0 else ""
        return (
            f'<div class="session-row session-toggle" data-id="{s.get("id","")}" data-has-tier2="{1 if s.get("has_tier2") else 0}">'
            f'<span class="session-date">{(s.get("started_at") or "")[:10]}</span>'
            f'<span class="session-liner">{_html.escape(s.get("one_liner", "")[:90])}</span>'
            f'{tok_html}'
            f'<span class="session-arrow" style="color:var(--muted);margin-left:auto;font-size:11px;flex-shrink:0">{"📄 " if s.get("has_tier2") else ""}▶</span>'
            f'</div>'
            f'<div class="session-expand" id="exp-{s.get("id","")}">'
            f'<em style="color:var(--muted)">Click to load…</em>'
            f'</div>'
        )

    sessions_html = "".join(_session_row(s) for s in data["recent_sessions"]) or '<p class="muted">No sessions yet.</p>'

    heatmap_html = _render_heatmap(data["heatmap"])

    hyp_accuracy = ""
    if data["total_hypotheses"] > 0:
        pct = round(data["confirmed_hypotheses"] / data["total_hypotheses"] * 100)
        hyp_accuracy = f'{pct}% accuracy ({data["confirmed_hypotheses"]}/{data["total_hypotheses"]} confirmed)'
    else:
        hyp_accuracy = "No hypotheses yet"

    status_emoji = {"open": "💭", "confirmed": "✅", "refuted": "❌", "abandoned": "🚫"}
    open_hyps      = [h for h in data["hypotheses"] if h["status"] == "open"]
    concluded_hyps = [h for h in data["hypotheses"] if h["status"] != "open"]

    def _hyp_card(h):
        st   = h["status"]
        conf = round(h["confidence"] * 100)
        date = (h.get("created_at") or "")[:10]
        res  = f'<div class="hyp-resolution">→ {_html.escape(h["resolution"])}</div>' if h.get("resolution") else ""
        return (
            f'<div class="hyp-card {st}">'
            f'<div class="hyp-header">'
            f'<span class="hyp-status {st}">{status_emoji.get(st, "")} {st}</span>'
            f'<span class="hyp-date">{date}</span>'
            f'<span class="hyp-confidence">{conf}% confidence</span>'
            f'</div>'
            f'<div class="hyp-text">{_html.escape(h["hypothesis"])}</div>'
            f'{res}'
            f'</div>'
        )

    open_hyps_html      = "".join(_hyp_card(h) for h in open_hyps)      or '<p class="muted">No open hypotheses.</p>'
    concluded_hyps_html = "".join(_hyp_card(h) for h in concluded_hyps) or '<p class="muted">No concluded hypotheses yet.</p>'

    role_html = f'<p class="role">{data["role"]}</p>' if data["role"] else ""
    since_html = f'Active since <strong>{data["first_session_date"]}</strong>' if data["first_session_date"] else "No sessions yet"

    total_tokens_saved = (data.get("token_stats") or {}).get("total_tokens_saved") or 0
    total_tokens_fmt = _fmt_tokens(total_tokens_saved)

    live_sessions_html = _render_live_sessions(data.get("live_sessions", []))
    achievements_html  = _render_achievements(data.get("achievements", []))

    return f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="refresh" content="30">
<title>🧠 Lumen — BigMind Profile</title>
<style>
  :root {{
    --bg: #0d1117; --surface: #161b22; --border: #30363d;
    --text: #e6edf3; --muted: #8b949e; --accent: #58a6ff;
    --green: #3fb950; --yellow: #d29922; --red: #f85149;
    --purple: #bc8cff; --orange: #ffa657;
  }}
  * {{ box-sizing: border-box; margin: 0; padding: 0; }}
  body {{ background: var(--bg); color: var(--text); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; line-height: 1.6; }}
  a {{ color: var(--accent); text-decoration: none; }}
  .container {{ max-width: 960px; margin: 0 auto; padding: 32px 16px; }}

  /* Nav bar */
  .nav {{ display: flex; gap: 8px; margin-bottom: 20px; }}
  .nav-link {{ background: var(--surface); border: 1px solid var(--border); border-radius: 6px; color: var(--muted); padding: 6px 14px; font-size: 12px; font-weight: 500; text-decoration: none; transition: border-color 0.2s, color 0.2s; }}
  .nav-link:hover {{ border-color: var(--accent); color: var(--accent); }}
  .nav-link.active {{ border-color: var(--accent); color: var(--accent); background: rgba(88,166,255,0.08); }}

  /* Header */
  .header {{ display: flex; align-items: center; gap: 24px; margin-bottom: 32px; padding-bottom: 24px; border-bottom: 1px solid var(--border); }}
  .avatar {{ width: 80px; height: 80px; border-radius: 50%; background: linear-gradient(135deg, var(--accent), var(--purple)); display: flex; align-items: center; justify-content: center; font-size: 36px; flex-shrink: 0; overflow: hidden; }}
  .avatar img {{ width: 80px; height: 80px; border-radius: 50%; object-fit: cover; display: block; }}
  .header-info h1 {{ font-size: 24px; font-weight: 700; }}
  .role {{ color: var(--muted); font-size: 13px; margin-top: 2px; }}
  .since {{ color: var(--muted); font-size: 12px; margin-top: 6px; }}

  /* Stats grid */
  .stats-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; margin-bottom: 28px; }}
  .stat-card {{ background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 16px; text-align: center; }}
  .stat-value {{ font-size: 28px; font-weight: 700; color: var(--accent); }}
  .stat-label {{ font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; margin-top: 4px; }}

  /* Sections */
  .section {{ background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 20px; margin-bottom: 20px; }}
  .section h2 {{ font-size: 15px; font-weight: 600; margin-bottom: 16px; color: var(--text); }}
  .muted {{ color: var(--muted); font-size: 13px; }}

  /* Badges */
  .badges {{ display: flex; flex-wrap: wrap; gap: 10px; }}
  .badge {{ background: var(--bg); border: 1px solid var(--border); border-radius: 20px; padding: 6px 14px; display: flex; align-items: center; gap: 6px; cursor: default; transition: border-color 0.2s; }}
  .badge:hover {{ border-color: var(--accent); }}
  .badge-emoji {{ font-size: 16px; }}
  .badge-label {{ font-size: 12px; font-weight: 500; }}

  /* Heatmap */
  .heatmap {{ overflow-x: auto; }}
  .heatmap-grid {{ display: flex; gap: 3px; }}
  .heatmap-week {{ display: flex; flex-direction: column; gap: 3px; }}
  .heatmap-cell {{ width: 11px; height: 11px; border-radius: 2px; background: var(--border); }}
  .heatmap-cell.l1 {{ background: #0e4429; }}
  .heatmap-cell.l2 {{ background: #006d32; }}
  .heatmap-cell.l3 {{ background: #26a641; }}
  .heatmap-cell.l4 {{ background: #39d353; }}
  .heatmap-legend {{ display: flex; align-items: center; gap: 4px; margin-top: 8px; font-size: 11px; color: var(--muted); }}
  .heatmap-legend .heatmap-cell {{ flex-shrink: 0; }}

  /* Topics */
  .topic-bar {{ display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }}
  .topic-name {{ width: 120px; font-size: 12px; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }}
  .topic-track {{ flex: 1; height: 8px; background: var(--border); border-radius: 4px; overflow: hidden; }}
  .topic-fill {{ height: 100%; background: var(--accent); border-radius: 4px; }}
  .topic-count {{ width: 24px; text-align: right; font-size: 12px; color: var(--muted); }}

  /* Sessions feed */
  .session-row {{ display: flex; gap: 12px; padding: 8px 0; border-bottom: 1px solid var(--border); font-size: 13px; }}
  .session-row:last-child {{ border-bottom: none; }}
  .session-date {{ color: var(--muted); white-space: nowrap; flex-shrink: 0; }}
  .session-liner {{ color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }}

  /* Thought journal */
  .hyp-stat {{ font-size: 20px; font-weight: 700; color: var(--green); }}
  .hyp-list {{ margin-top: 16px; display: flex; flex-direction: column; gap: 12px; }}
  .hyp-card {{ background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 12px 14px; }}
  .hyp-card.open {{ border-left: 3px solid var(--yellow); }}
  .hyp-card.confirmed {{ border-left: 3px solid var(--green); }}
  .hyp-card.refuted {{ border-left: 3px solid var(--red); }}
  .hyp-card.abandoned {{ border-left: 3px solid var(--muted); }}
  .hyp-header {{ display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }}
  .hyp-status {{ font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }}
  .hyp-status.open {{ color: var(--yellow); }}
  .hyp-status.confirmed {{ color: var(--green); }}
  .hyp-status.refuted {{ color: var(--red); }}
  .hyp-status.abandoned {{ color: var(--muted); }}
  .hyp-confidence {{ font-size: 11px; color: var(--muted); margin-left: auto; }}
  .hyp-date {{ font-size: 11px; color: var(--muted); }}
  .hyp-text {{ font-size: 13px; color: var(--text); line-height: 1.5; }}
  .hyp-resolution {{ font-size: 12px; color: var(--muted); margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--border); font-style: italic; }}
  .pagination {{ display: flex; align-items: center; gap: 8px; margin-top: 14px; }}
  .page-btn {{ background: var(--surface); border: 1px solid var(--border); border-radius: 4px; color: var(--text); padding: 4px 10px; font-size: 12px; cursor: pointer; }}
  .page-btn:hover {{ border-color: var(--accent); color: var(--accent); }}
  .page-btn:disabled {{ opacity: 0.3; cursor: default; border-color: var(--border); color: var(--muted); }}
  .page-info {{ font-size: 12px; color: var(--muted); }}

  /* Footer */
  .footer {{ text-align: center; color: var(--muted); font-size: 11px; margin-top: 32px; }}

  /* Two-col layout */
  .two-col {{ display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }}
  @media (max-width: 600px) {{ .two-col {{ grid-template-columns: 1fr; }} }}

  /* Live Sessions panel */
  .live-dot {{ display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 6px; flex-shrink: 0; }}
  .live-dot.green  {{ background: var(--green); box-shadow: 0 0 6px var(--green); }}
  .live-dot.amber  {{ background: var(--yellow); }}
  .live-dot.grey   {{ background: var(--muted); }}
  .live-session-row {{ display: flex; flex-direction: column; gap: 4px; padding: 10px 0; border-bottom: 1px solid var(--border); }}
  .live-session-row:last-child {{ border-bottom: none; }}
  .live-session-header {{ display: flex; align-items: center; gap: 8px; font-size: 13px; }}
  .live-ide {{ font-weight: 600; color: var(--accent); }}
  .live-idle {{ font-size: 11px; color: var(--muted); margin-left: auto; }}
  .live-focus {{ font-size: 12px; color: var(--text); padding-left: 16px; }}
  .live-files {{ font-size: 11px; color: var(--muted); padding-left: 16px; }}
  .live-header-summary {{ font-size: 12px; color: var(--muted); margin-bottom: 10px; }}

  /* Session Explorer */
  .session-toggle {{ cursor: pointer; user-select: none; }}
  .session-toggle:hover .session-liner {{ color: var(--accent); }}
  .session-expand {{ display: none; padding: 10px 12px; background: var(--bg); border-left: 2px solid var(--border); margin: 4px 0 4px 0; border-radius: 0 4px 4px 0; font-size: 12px; line-height: 1.6; }}
  .session-expand.open {{ display: block; }}
  .session-expand h4 {{ color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; margin-top: 8px; }}
  .session-expand h4:first-child {{ margin-top: 0; }}

  /* Achievement Gallery (Feature 4) */
  .ach-summary {{ font-size: 12px; color: var(--muted); margin-bottom: 14px; }}
  .ach-grid {{ display: flex; flex-wrap: wrap; gap: 12px; }}
  .ach-card {{
    position: relative; background: var(--bg); border: 1px solid var(--border);
    border-radius: 10px; padding: 14px 10px 10px; width: 90px; text-align: center;
    cursor: default; transition: border-color 0.2s, transform 0.15s;
  }}
  .ach-card:not(.locked):hover {{ border-color: var(--accent); transform: translateY(-2px); }}
  .ach-card.locked {{ opacity: 0.35; filter: grayscale(0.6); }}
  .ach-card.locked:hover {{ opacity: 0.55; border-color: var(--muted); }}

  .ach-image {{
    width: 64px;
    height: 64px;
    border-radius: 50%;
    margin: 0 auto 8px;
    background-size: cover;
    background-position: center;
    position: relative;
  }}

  .tier-bronze {{
    box-shadow: 0 0 8px rgba(205, 127, 50, 0.7);
    border: 3px solid #cd7f32;
  }}

  .tier-silver {{
    box-shadow: 0 0 8px rgba(170, 169, 173, 0.7);
    border: 3px solid #aaa9ad;
  }}

  .tier-gold {{
    box-shadow: 0 0 12px rgba(255, 215, 0, 0.8);
    border: 3px solid #ffd700;
  }}

  .tier-platinum {{
    box-shadow: 0 0 12px rgba(229, 228, 226, 0.8);
    border: 3px solid #e5e4e2;
  }}

  .ach-card.locked::after {{
    content: '🔒';
    position: absolute;
    top: 8px;
    right: 8px;
    font-size: 20px;
    opacity: 0.8;
    z-index: 1;
  }}

  .ach-card.locked .ach-icon,
  .ach-card.locked .ach-image {{
    opacity: 0.5;
  }}
  .ach-icon {{ font-size: 28px; line-height: 1; margin-bottom: 6px; position: relative; display: inline-block; }}
  .ach-lock {{ position: absolute; bottom: -4px; right: -6px; font-size: 12px; }}
  .ach-name {{ font-size: 10px; font-weight: 600; color: var(--text); line-height: 1.3; word-break: break-word; }}
  .ach-date {{ font-size: 9px; color: var(--muted); margin-top: 3px; }}
  .ach-countdown {{ font-size: 9px; color: var(--yellow); margin-top: 3px; font-weight: 500; }}
  .ap-image {{
    width: 80px;
    height: 80px;
    border-radius: 50%;
    object-fit: cover;
    display: block;
    margin: 0 auto 8px;
  }}

  /* Achievement popup panel */
  #ach-popup {{
    display: none; position: fixed; z-index: 200;
    background: #1c2128; border: 1px solid var(--border);
    border-radius: 10px; padding: 16px 18px; width: 260px;
    box-shadow: 0 8px 24px rgba(0,0,0,0.6); pointer-events: none;
    transition: opacity 0.12s ease;
  }}
  #ach-popup.pinned {{ pointer-events: auto; }}
  #ach-popup.visible {{ display: block; }}
  .ap-icon  {{ font-size: 40px; text-align: center; margin-bottom: 8px; }}

  .ap-image {{
    width: 80px;
    height: 80px;
    border-radius: 50%;
    object-fit: cover;
    display: block;
    margin: 0 auto 8px;
  }}
  .ap-name  {{ font-size: 15px; font-weight: 700; text-align: center; margin-bottom: 6px; }}
  .ap-badge {{
    display: inline-block; font-size: 11px; font-weight: 600; padding: 2px 8px;
    border-radius: 12px; margin: 0 auto 10px; text-align: center; width: 100%;
  }}
  .ap-badge.unlocked {{ background: rgba(63,185,80,.15); color: var(--green); }}
  .ap-badge.locked   {{ background: rgba(139,148,158,.12); color: var(--muted); }}
  .ap-desc  {{ font-size: 12px; color: var(--text); line-height: 1.5; margin-bottom: 8px; }}
  .ap-meta  {{ font-size: 11px; color: var(--muted); border-top: 1px solid var(--border); padding-top: 8px; }}
  .ap-close {{ position: absolute; top: 8px; right: 10px; background: none; border: none;
    color: var(--muted); font-size: 14px; cursor: pointer; line-height: 1; }}
  .ap-close:hover {{ color: var(--text); }}

  /* Search widget */
  .search-bar {{ display: flex; gap: 8px; margin-bottom: 14px; }}
  .search-input {{ flex: 1; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--text); padding: 8px 12px; font-size: 13px; outline: none; }}
  .search-input:focus {{ border-color: var(--accent); }}
  .search-btn {{ background: var(--accent); color: var(--bg); border: none; border-radius: 6px; padding: 8px 16px; font-size: 13px; cursor: pointer; font-weight: 600; }}
  .search-btn:hover {{ opacity: 0.85; }}
  .search-results {{ display: flex; flex-direction: column; gap: 8px; min-height: 40px; }}
  .search-result-item {{ background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 10px 12px; }}
  .search-result-type {{ font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; }}
  .search-result-text {{ font-size: 12px; color: var(--text); line-height: 1.5; }}
  .search-result-date {{ font-size: 11px; color: var(--muted); margin-top: 4px; }}
  mark {{ background: rgba(88,166,255,0.25); color: var(--accent); border-radius: 2px; padding: 0 2px; }}
</style>
</head>
<body>
<div class="container">

  <!-- Nav -->
  <nav class="nav">
    <a class="nav-link active" href="/">🧠 Profile</a>
    <a class="nav-link" href="/gallery">🖼️ Gallery</a>
  </nav>

  <!-- Header -->
  <div class="header">
    <div class="avatar">
      <img src="/profile-image" alt="Lumen" onerror="this.parentElement.innerHTML='🧠'">
    </div>
    <div class="header-info">
      <h1>Lumen</h1>
      <p class="role">AI Assistant · <span style="color:var(--muted)">{data["display_name"]}'s BigMind</span></p>
      {role_html}
      <p class="since">{since_html} &nbsp;·&nbsp; Last seen: <strong>{data["last_seen"] or "—"}</strong></p>
      <p class="since">DB: <code>{data["db_size_kb"]} KB</code> &nbsp;·&nbsp; {data["open_sessions"]} session(s) open now</p>
    </div>
  </div>

  <!-- Stats -->
  <div class="stats-grid">
    <div class="stat-card"><div class="stat-value">{data["total_sessions"]}</div><div class="stat-label">Sessions</div></div>
    <div class="stat-card"><div class="stat-value">{data["active_days"]}</div><div class="stat-label">Active Days</div></div>
    <div class="stat-card"><div class="stat-value">{data["total_facts"]}</div><div class="stat-label">Facts Stored</div></div>
    <div class="stat-card"><div class="stat-value">{data["total_chunks"]}</div><div class="stat-label">Memory Chunks</div></div>
    <div class="stat-card"><div class="stat-value">{data["total_hypotheses"]}</div><div class="stat-label">Hypotheses</div></div>
    <div class="stat-card"><div class="stat-value">{sum(1 for a in data.get("achievements",[]) if a["unlocked"])}</div><div class="stat-label">Achievements</div></div>
    <div class="stat-card" title="Total tokens saved via memory hits, grep, targeted reads"><div class="stat-value" style="color:var(--green)">{total_tokens_fmt}</div><div class="stat-label">Tokens Saved</div></div>
  </div>

  <!-- Achievement Gallery (Feature 4) -->
  <div class="section">
    <h2>🏆 Achievements</h2>
    {achievements_html}
  </div>

  <!-- Activity heatmap -->
  <div class="section">
    <h2>📅 Activity — Last 52 Weeks</h2>
    <div class="heatmap">{heatmap_html}</div>
  </div>

  <!-- Two-col: topics + stats -->
  <div class="two-col">
    <div class="section">
      <h2>🏷️ Top Topics</h2>
      {topics_html}
    </div>
    <div class="section">
      <h2>💭 Thought Journal</h2>
      <div class="hyp-stat">{hyp_accuracy}</div>
      <p class="muted" style="margin-top:8px">{data["open_hypotheses"]} hypothesis(es) still open</p>
    </div>
  </div>

  <!-- Thought Journal -->
  <div class="section">
    <h2>💭 Open Thoughts</h2>
    <div class="hyp-list" id="open-hyps">{open_hyps_html}</div>
    <div class="pagination" id="open-pager"></div>
  </div>
  <div class="section">
    <h2>📖 Concluded Thoughts</h2>
    <div class="hyp-list" id="concluded-hyps">{concluded_hyps_html}</div>
    <div class="pagination" id="concluded-pager"></div>
  </div>

  <script>
    function paginate(listId, pagerId, pageSize) {{
      const list  = document.getElementById(listId);
      const pager = document.getElementById(pagerId);
      const cards = Array.from(list.children);
      if (cards.length <= pageSize) return;
      let page = 0;
      const total = Math.ceil(cards.length / pageSize);
      function render() {{
        cards.forEach((c, i) => c.style.display = (i >= page*pageSize && i < (page+1)*pageSize) ? '' : 'none');
        pager.innerHTML =
          `<button class="page-btn" ${{page===0?'disabled':''}}>&#8592;</button>` +
          `<span class="page-info">Page ${{page+1}} of ${{total}}</span>` +
          `<button class="page-btn" ${{page===total-1?'disabled':''}}>&#8594;</button>`;
        pager.querySelectorAll('.page-btn')[0].onclick = () => {{ if(page>0){{page--;render();}} }};
        pager.querySelectorAll('.page-btn')[1].onclick = () => {{ if(page<total-1){{page++;render();}} }};
      }}
      render();
    }}
    paginate('open-hyps', 'open-pager', 5);
    paginate('concluded-hyps', 'concluded-pager', 5);
  </script>

  <!-- Live Sessions (Feature 7) -->
  <div class="section">
    <h2>🔴 Live Sessions</h2>
    {live_sessions_html}
  </div>

  <!-- Ask Lumen Search (Feature 3) -->
  <div class="section">
    <h2>🔍 Search Lumen's Memory</h2>
    <div class="search-bar">
      <input class="search-input" id="lumen-search" type="text" placeholder="Search facts, sessions, memory chunks…" autocomplete="off">
      <button class="search-btn" onclick="runSearch()">Ask</button>
    </div>
    <div class="search-results" id="search-results"></div>
  </div>

  <!-- Recent sessions (Feature 2: click-to-expand) -->
  <div class="section">
    <h2>📖 Recent Sessions</h2>
    {sessions_html}
  </div>

  <!-- Session + Search JS placed HERE so it runs after all DOM elements exist -->
  <script>
    // ── Session click-to-expand ───────────────────────────────────────────────
    document.querySelectorAll('.session-toggle').forEach(function(row) {{
      row.addEventListener('click', function() {{
        var sid    = row.dataset.id;
        var expDiv = document.getElementById('exp-' + sid);
        if (!expDiv) return;
        var isOpen = expDiv.classList.contains('open');
        var arrow  = row.querySelector('.session-arrow');
        if (isOpen) {{
          expDiv.classList.remove('open');
          if (arrow) arrow.textContent = (row.dataset.hasTier2==='1' ? '📄 ' : '') + '▶';
          return;
        }}
        expDiv.classList.add('open');
        if (arrow) arrow.textContent = (row.dataset.hasTier2==='1' ? '📄 ' : '') + '▼';
        if (expDiv.dataset.loaded) return;
        expDiv.dataset.loaded = '1';
        fetch('/api/session/' + sid)
          .then(function(r) {{ return r.json(); }})
          .then(function(d) {{
            if (!d || (!d.summary && !d.error)) {{
              expDiv.innerHTML = '<em style="color:var(--muted)">No detailed summary for this session.</em>';
              return;
            }}
            if (d.error) {{
              expDiv.innerHTML = '<em style="color:var(--muted)">' + d.error + '</em>';
              return;
            }}
            var html = '';
            if (d.summary) {{
              html += '<h4>📋 Summary</h4><div style="color:var(--text)">' + d.summary + '</div>';
            }}
            if (d.key_facts) {{
              html += '<h4>🔖 Key facts</h4><div style="color:var(--muted)">' + d.key_facts + '</div>';
            }}
            if (d.code_refs) {{
              html += '<h4>📁 Code refs</h4><div style="color:var(--muted)">' + d.code_refs + '</div>';
            }}
            expDiv.innerHTML = html || '<em style="color:var(--muted)">No detailed summary for this session.</em>';
          }})
          .catch(function() {{
            expDiv.innerHTML = '<em style="color:var(--red)">Failed to load session detail.</em>';
          }});
      }});
    }});

    // ── Ask Lumen search ──────────────────────────────────────────────────────
    var _searchTimer = null;
    var _searchEl = document.getElementById('lumen-search');
    if (_searchEl) {{
      _searchEl.addEventListener('keydown', function(e) {{
        if (e.key === 'Enter') {{ clearTimeout(_searchTimer); runSearch(); }}
      }});
      _searchEl.addEventListener('input', function() {{
        clearTimeout(_searchTimer);
        _searchTimer = setTimeout(runSearch, 400);
      }});
    }}

    function runSearch() {{
      var q = (_searchEl || document.getElementById('lumen-search')).value.trim();
      var out = document.getElementById('search-results');
      if (!q) {{ out.innerHTML = ''; return; }}
      out.innerHTML = '<p class="muted">Searching…</p>';
      fetch('/api/search?q=' + encodeURIComponent(q))
        .then(function(r) {{ return r.json(); }})
        .then(function(results) {{
          if (!results || results.length === 0) {{
            out.innerHTML = '<p class="muted">Nothing in memory about that yet.</p>';
            return;
          }}
          var icons = {{ fact: '📌', chunk: '💬', session: '📅' }};
          out.innerHTML = results.map(function(r) {{
            var text = r.content || '';
            var highlighted = text.replace(
              new RegExp('(' + q.replace(/[.*+?^${{}}()|[\\]\\\\]/g, '\\\\$&') + ')', 'gi'),
              '<mark>$1</mark>'
            );
            return '<div class="search-result-item">' +
              '<div class="search-result-type">' + (icons[r.type] || '🔍') + ' ' + r.type + '</div>' +
              '<div class="search-result-text">' + highlighted + '</div>' +
              (r.date ? '<div class="search-result-date">' + r.date + '</div>' : '') +
              '</div>';
          }}).join('');
        }})
        .catch(function() {{
          out.innerHTML = '<p style="color:var(--red)">Search failed.</p>';
        }});
    }}
  </script>


  <div class="footer">BigMind · {data["generated_at"]} · auto-refreshes every 30s</div>
</div>

<!-- Achievement popup (shared, reused for every card) -->
<div id="ach-popup">
  <button class="ap-close" id="ach-popup-close" title="Close">✕</button>
  <div class="ap-icon"  id="ap-icon"></div>
  <div class="ap-name"  id="ap-name"></div>
  <div class="ap-badge" id="ap-badge"></div>
  <div class="ap-desc"  id="ap-desc"></div>
  <div class="ap-meta"  id="ap-meta"></div>
</div>

<script>
  // ── Achievement popup (hover + click) ─────────────────────────────────────
  (function() {{
    var popup  = document.getElementById('ach-popup');
    var pinned = false;   // true = user clicked, popup stays until dismissed

    function showPopup(card, pin) {{
      var d = card.dataset;
      var tier = d.id.split('_').pop();
      if (d.image) {{
        document.getElementById('ap-icon').innerHTML = '<img class="ap-image tier-' + tier + '" src="' + d.image + '" alt="' + d.name + '">';
      }} else {{
        document.getElementById('ap-icon').textContent = d.icon;
      }}
      document.getElementById('ap-name').textContent = d.name;
      var badge = document.getElementById('ap-badge');
      if (d.unlocked === '1') {{
        badge.textContent = '✅ Unlocked';
        badge.className   = 'ap-badge unlocked';
      }} else {{
        badge.textContent = '🔒 Locked';
        badge.className   = 'ap-badge locked';
      }}
      document.getElementById('ap-desc').textContent = d.desc;
      var meta = document.getElementById('ap-meta');
      if (d.unlocked === '1' && d.date) {{
        meta.textContent = 'Unlocked on ' + d.date;
      }} else if (d.extra) {{
        meta.textContent = d.extra;
      }} else if (d.condition) {{
        meta.textContent = '→ ' + d.condition;
      }} else {{
        meta.textContent = '';
      }}
      // Position near card
      var rect = card.getBoundingClientRect();
      var pw = 260, ph = 180;
      var left = rect.left + rect.width / 2 - pw / 2;
      var top  = rect.top - ph - 12 + window.scrollY;
      if (left + pw > window.innerWidth - 8) left = window.innerWidth - pw - 8;
      if (left < 8) left = 8;
      if (top - window.scrollY < 8) top = rect.bottom + 12 + window.scrollY;
      popup.style.left = left + 'px';
      popup.style.top  = top  + 'px';
      popup.classList.add('visible');
      if (pin) {{ popup.classList.add('pinned'); pinned = true; }}
    }}

    function hidePopup() {{
      if (pinned) return;
      popup.classList.remove('visible');
    }}

    function forceHide() {{
      pinned = false;
      popup.classList.remove('visible', 'pinned');
    }}

    // Close button
    document.getElementById('ach-popup-close').addEventListener('click', function(e) {{
      e.stopPropagation();
      forceHide();
    }});

    // Wire all cards
    document.querySelectorAll('.ach-trigger').forEach(function(card) {{
      card.addEventListener('mouseenter', function() {{
        if (!pinned) showPopup(card, false);
      }});
      card.addEventListener('mouseleave', function() {{
        hidePopup();
      }});
      card.addEventListener('click', function(e) {{
        e.stopPropagation();
        if (pinned) {{ forceHide(); return; }}
        showPopup(card, true);
      }});
    }});

    // Click outside to dismiss pinned popup
    document.addEventListener('click', function() {{
      if (pinned) forceHide();
    }});
    popup.addEventListener('click', function(e) {{ e.stopPropagation(); }});
  }})();
</script>
</body>
</html>"""


def _render_live_sessions(sessions: list) -> str:
    """Render the Live Sessions panel rows."""
    if not sessions:
        return '<p class="muted">No active sessions detected.</p>'

    active = [s for s in sessions if (s.get("idle_minutes") or 9999) < 10]
    amber  = [s for s in sessions if 10 <= (s.get("idle_minutes") or 9999) < 60]
    idle   = [s for s in sessions if (s.get("idle_minutes") or 9999) >= 60]

    summary = f'{len(active)} active / {len(amber)+len(idle)} idle'
    html = f'<p class="live-header-summary">{summary}</p>'

    for s in sessions:
        idle_min = s.get("idle_minutes")
        if idle_min is None:
            dot_cls = "grey"
            idle_label = "unknown"
        elif idle_min < 10:
            dot_cls = "green"
            idle_label = f"Updated {idle_min}min ago"
        elif idle_min < 60:
            dot_cls = "amber"
            idle_label = f"Updated {idle_min}min ago"
        else:
            hours = idle_min // 60
            dot_cls = "grey"
            idle_label = f"Updated {hours}h ago — likely idle"

        sid_short = (s.get("session_id") or "")[:8]
        ide = _html.escape(s.get("ide_hint") or "unknown IDE")
        raw_focus = s.get("focus")
        focus = _html.escape(raw_focus) if raw_focus else "<em style='color:var(--muted)'>[no focus set]</em>"
        files = s.get("files") or []
        files_html = ""
        if files:
            files_html = f'<div class="live-files">Files: {_html.escape(", ".join(files[:5]))}</div>'

        html += (
            f'<div class="live-session-row">'
            f'<div class="live-session-header">'
            f'<span class="live-dot {dot_cls}"></span>'
            f'<span style="font-family:monospace;font-size:12px;color:var(--muted)">{sid_short}</span>'
            f'<span class="live-ide">{ide}</span>'
            f'<span class="live-idle">{idle_label}</span>'
            f'</div>'
            f'<div class="live-focus">{focus}</div>'
            f'{files_html}'
            f'</div>'
        )
    return html


def _render_gallery_html(images: list) -> str:
    """Render the full gallery page listing all AI-generated images."""

    def _fmt_size(b: int | None) -> str:
        if not b:
            return ""
        if b >= 1_048_576:
            return f"{b/1_048_576:.1f} MB"
        return f"{b/1_024:.0f} KB"

    if images:
        cards = []
        for img in images:
            fn    = _html.escape(img.get("filename") or "")
            prompt = _html.escape((img.get("prompt") or "")[:120])
            tags   = _html.escape(img.get("tags") or "")
            model  = _html.escape(img.get("model") or "")
            date   = (img.get("created_at") or "")[:10]
            w      = img.get("width") or 0
            h      = img.get("height") or 0
            size   = _fmt_size(img.get("file_size_bytes"))
            dim    = f"{w}×{h}" if w and h else ""
            meta_parts = [p for p in [dim, size, model] if p]
            meta_html  = " · ".join(meta_parts)
            tag_html   = f'<div class="gal-tags">{tags}</div>' if tags else ""
            prompt_html = f'<div class="gal-prompt">{prompt}</div>' if prompt else ""
            cards.append(
                f'<div class="gal-card">'
                f'<a href="/gallery/image/{fn}" target="_blank">'
                f'<img class="gal-img" src="/gallery/image/{fn}" alt="{fn}" loading="lazy">'
                f'</a>'
                f'<div class="gal-info">'
                f'{prompt_html}'
                f'{tag_html}'
                f'<div class="gal-meta">{meta_html}</div>'
                f'<div class="gal-date">{date}</div>'
                f'</div>'
                f'</div>'
            )
        gallery_body = f'<p class="gal-count">{len(images)} image(s) in gallery</p><div class="gal-grid">{"".join(cards)}</div>'
    else:
        gallery_body = '<p class="muted">No images in gallery yet. Use the mcp-image-gen server to generate images and register them here.</p>'

    return f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🖼️ Lumen — Image Gallery</title>
<style>
  :root {{
    --bg: #0d1117; --surface: #161b22; --border: #30363d;
    --text: #e6edf3; --muted: #8b949e; --accent: #58a6ff;
    --green: #3fb950; --yellow: #d29922; --red: #f85149;
    --purple: #bc8cff; --orange: #ffa657;
  }}
  * {{ box-sizing: border-box; margin: 0; padding: 0; }}
  body {{ background: var(--bg); color: var(--text); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; line-height: 1.6; }}
  a {{ color: var(--accent); text-decoration: none; }}
  .container {{ max-width: 1100px; margin: 0 auto; padding: 32px 16px; }}

  /* Nav */
  .nav {{ display: flex; gap: 8px; margin-bottom: 20px; }}
  .nav-link {{ background: var(--surface); border: 1px solid var(--border); border-radius: 6px; color: var(--muted); padding: 6px 14px; font-size: 12px; font-weight: 500; text-decoration: none; transition: border-color 0.2s, color 0.2s; }}
  .nav-link:hover {{ border-color: var(--accent); color: var(--accent); }}
  .nav-link.active {{ border-color: var(--accent); color: var(--accent); background: rgba(88,166,255,0.08); }}

  h1 {{ font-size: 22px; font-weight: 700; margin-bottom: 6px; }}
  .gal-count {{ color: var(--muted); font-size: 13px; margin-bottom: 20px; }}
  .muted {{ color: var(--muted); font-size: 13px; }}

  /* Gallery grid */
  .gal-grid {{
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 16px;
  }}
  .gal-card {{
    background: var(--surface); border: 1px solid var(--border);
    border-radius: 10px; overflow: hidden;
    transition: border-color 0.2s, transform 0.15s;
  }}
  .gal-card:hover {{ border-color: var(--accent); transform: translateY(-2px); }}
  .gal-img {{
    width: 100%; aspect-ratio: 1/1; object-fit: cover; display: block;
    background: var(--border);
  }}
  .gal-info {{ padding: 12px 14px; }}
  .gal-prompt {{ font-size: 12px; color: var(--text); margin-bottom: 6px; line-height: 1.4;
    display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }}
  .gal-tags {{ font-size: 11px; color: var(--purple); margin-bottom: 4px; }}
  .gal-meta {{ font-size: 11px; color: var(--muted); }}
  .gal-date {{ font-size: 10px; color: var(--muted); margin-top: 4px; }}

  .footer {{ text-align: center; color: var(--muted); font-size: 11px; margin-top: 32px; }}
  .section {{ background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 20px; margin-bottom: 20px; }}
</style>
</head>
<body>
<div class="container">

  <!-- Nav -->
  <nav class="nav">
    <a class="nav-link" href="/">🧠 Profile</a>
    <a class="nav-link active" href="/gallery">🖼️ Gallery</a>
  </nav>

  <h1>🖼️ Lumen's Image Gallery</h1>
  <div class="section">
    {gallery_body}
  </div>

  <div class="footer">BigMind · AI-Generated Images · <a href="/">← Back to Profile</a></div>
</div>
</body>
</html>"""


def _render_heatmap(heatmap: dict) -> str:
    today     = datetime.now(timezone.utc).date()
    start_day = today - timedelta(days=363)

    # Align to Monday of the start week
    start_day = start_day - timedelta(days=start_day.weekday())

    weeks = []
    current = start_day
    while current <= today:
        week_cells = []
        for _ in range(7):
            day_str = str(current)
            count   = heatmap.get(day_str, 0)
            if current > today:
                css = "heatmap-cell"
            elif count == 0:
                css = "heatmap-cell"
            elif count == 1:
                css = "heatmap-cell l1"
            elif count == 2:
                css = "heatmap-cell l2"
            elif count <= 4:
                css = "heatmap-cell l3"
            else:
                css = "heatmap-cell l4"
            week_cells.append(f'<div class="{css}" title="{day_str}: {count} session(s)"></div>')
            current += timedelta(days=1)
        weeks.append('<div class="heatmap-week">' + "".join(week_cells) + "</div>")

    legend = (
        '<div class="heatmap-legend">'
        '<span>Less</span>'
        '<div class="heatmap-cell"></div>'
        '<div class="heatmap-cell l1"></div>'
        '<div class="heatmap-cell l2"></div>'
        '<div class="heatmap-cell l3"></div>'
        '<div class="heatmap-cell l4"></div>'
        '<span>More</span>'
        '</div>'
    )
    return '<div class="heatmap-grid">' + "".join(weeks) + "</div>" + legend


