"""Risk-of-Rain-flavoured pixel theme for the Streamlit screener.

Injects a chunky, CRT-ish pixel skin (dark night-sky palette, pixel fonts,
loot-card metrics, health-bar progress) and renders a pixel-art Gup — the
friendly blue blob — as a bouncing mascot in the header.

Kept out of app.py so the dashboard logic stays readable.
"""
from __future__ import annotations

import re
from pathlib import Path

# ---------------------------------------------------------------- Gup sprite --
# The Gup is drawn as a symmetric pixel blob: each row below is the LEFT half
# (8px). The right half is mirrored in code, so the sprite is always 16px wide
# and can never go lopsided. Chars map to colours in _GUP_PALETTE.
_GUP_LEFT = [
    "       ###",
    "     #####",
    "    #ooooo",
    "   #oo****",
    "   #o*****",
    "  #oo*****",
    "  #oow@ooo",
    "  #oo@@ooo",
    "  #ooooooo",
    "###ooooooo",  # arm: top outline
    "#ooooooooo",  # arm: hand + stub (pokes out each side when mirrored)
    "###xxooooo",  # arm: bottom outline
    "   #xxxxoo",
    "    #xxxxx",
    "     #####",
]
_GUP_PALETTE = {
    "#": "#4a1f07",  # dark outline
    "o": "#ff8f33",  # orange jelly body
    "x": "#cf6a1c",  # body shadow
    "*": "#ffe0b0",  # highlight / shine
    "@": "#1a0f08",  # eye
    "w": "#ffffff",  # eye glint
}


def gup_svg(scale: int = 6) -> str:
    """Return the Gup as crisp pixel-art SVG, each source pixel `scale`px wide."""
    rows = [left + left[::-1] for left in _GUP_LEFT]
    w = len(rows[0])
    h = len(rows)
    rects = []
    for y, row in enumerate(rows):
        for x, ch in enumerate(row):
            fill = _GUP_PALETTE.get(ch)
            if fill:
                rects.append(f'<rect x="{x}" y="{y}" width="1" height="1" fill="{fill}"/>')
    return (
        f'<svg class="gup" viewBox="0 0 {w} {h}" '
        f'width="{w * scale}" height="{h * scale}" '
        f'shape-rendering="crispEdges" xmlns="http://www.w3.org/2000/svg">'
        + "".join(rects)
        + "</svg>"
    )


# --------------------------------------------------------------- money rain --
# Falling dollar bills, shown only while a RAIN_IDS track is actually playing.
# Offsets come from modular arithmetic rather than random(), so a Streamlit
# rerun re-renders the identical rain instead of reshuffling it mid-fall.
# The negative animation-delay staggers the columns so they don't all start
# bunched at the top edge on first paint.
RAIN_HTML = '<div class="cash-rain">' + "".join(
    f'<span style="left:{i * 2.5:.1f}%;font-size:{16 + (i * 7) % 16}px;'
    f'animation-duration:{5 + (i * 3) % 5}s;animation-delay:-{(i * 11) % 9}s">&#128181;</span>'
    for i in range(40)
) + "</div>"


# --------------------------------------------------------------------- CSS ----
CSS = """
<style>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=VT323&display=swap');

:root {
  --ror-bg:      #0a0e1a;
  --ror-panel:   #161f38;
  --ror-panel2:  #1e2a47;
  --ror-line:    #33456e;
  --ror-text:    #e7ecfb;
  --ror-muted:   #8ea0c6;
  --ror-gold:    #ffb638;   /* loot / money */
  --ror-cyan:    #5fe0ff;   /* gup / ui */
  --ror-green:   #77dd77;   /* uncommon */
  --ror-red:     #ff5d5d;   /* legendary / danger */
  --ror-purple:  #b78bff;
  --pix: 'Press Start 2P', monospace;
  --term: 'VT323', 'Courier New', monospace;
}

/* ---- base canvas: deep night sky with a couple of glows ---- */
.stApp {
  background:
    radial-gradient(900px 500px at 15% -10%, rgba(95,224,255,0.10), transparent 60%),
    radial-gradient(900px 600px at 90% 0%, rgba(183,139,255,0.10), transparent 55%),
    linear-gradient(180deg, #0b1022 0%, var(--ror-bg) 55%, #06080f 100%);
  color: var(--ror-text);
  font-family: var(--term);
  font-size: 1.15rem;
}
/* faint CRT scanlines over everything */
.stApp::before {
  content: "";
  position: fixed; inset: 0; pointer-events: none; z-index: 9999;
  background: repeating-linear-gradient(0deg, rgba(0,0,0,0.16) 0 1px, transparent 1px 3px);
  mix-blend-mode: multiply;
}
[data-testid="stHeader"] { background: transparent; }
.block-container { padding-top: 2.2rem; }

/* ---- pixel typography ---- */
h1, h2, h3, h4 {
  font-family: var(--pix) !important;
  color: var(--ror-gold) !important;
  text-shadow: 3px 3px 0 #000, 0 0 14px rgba(255,182,56,0.35);
  letter-spacing: 1px;
  line-height: 1.5 !important;
}
h2, h3 { color: var(--ror-cyan) !important; text-shadow: 2px 2px 0 #000, 0 0 12px rgba(95,224,255,0.35); }
p, span, label, li, .stMarkdown, .stCaption, [data-testid="stCaptionContainer"] {
  font-family: var(--term);
}
code, .stCode, pre { font-family: var(--term) !important; color: var(--ror-green) !important; }

/* ---- hero banner + bouncing gup ---- */
.ror-hero {
  display: flex; align-items: center; gap: 22px;
  background: linear-gradient(180deg, var(--ror-panel2), var(--ror-panel));
  border: 4px solid var(--ror-line);
  box-shadow: 8px 8px 0 #000, inset 0 0 0 2px rgba(95,224,255,0.12);
  padding: 20px 26px; margin-bottom: 18px;
}
.ror-hero h1 { margin: 0; font-size: 1.7rem; }
.ror-hero .sub {
  font-family: var(--term); font-size: 1.35rem; color: var(--ror-muted);
  margin: 8px 0 0; line-height: 1.2;
}
.ror-hero .sub b { color: var(--ror-red); }
.gup-wrap { flex: 0 0 auto; filter: drop-shadow(5px 6px 0 rgba(0,0,0,0.55)); }
.gup { image-rendering: pixelated; display: block; animation: gup-hop 1.4s steps(2, jump-none) infinite; transform-origin: bottom center; }
@keyframes gup-hop {
  0%, 100% { transform: translateY(0) scaleY(1); }
  30%      { transform: translateY(-14px) scaleY(1.06); }
  55%      { transform: translateY(0) scaleY(0.92); }
  70%      { transform: translateY(0) scaleY(1); }
}

/* ---- free-roaming Gup that bounces around the whole screen ---- */
.gup-roam {
  position: fixed; top: 0; left: 0; z-index: 9998; pointer-events: none;
  filter: drop-shadow(4px 5px 0 rgba(0,0,0,0.5));
  animation: gup-roam 26s linear infinite;
}
.gup-roam .gup { animation: gup-hop 0.9s steps(2, jump-none) infinite; }
@keyframes gup-roam {
  0%   { transform: translate(2vw, 10vh)  scaleX( 1); }
  12%  { transform: translate(82vw, 26vh) scaleX( 1); }
  13%  { transform: translate(82vw, 26vh) scaleX(-1); }
  27%  { transform: translate(16vw, 74vh) scaleX(-1); }
  28%  { transform: translate(16vw, 74vh) scaleX( 1); }
  43%  { transform: translate(84vw, 58vh) scaleX( 1); }
  44%  { transform: translate(84vw, 58vh) scaleX(-1); }
  58%  { transform: translate(6vw, 20vh)  scaleX(-1); }
  59%  { transform: translate(6vw, 20vh)  scaleX( 1); }
  73%  { transform: translate(70vw, 82vh) scaleX( 1); }
  74%  { transform: translate(70vw, 82vh) scaleX(-1); }
  88%  { transform: translate(24vw, 40vh) scaleX(-1); }
  89%  { transform: translate(24vw, 40vh) scaleX( 1); }
  100% { transform: translate(2vw, 10vh)  scaleX( 1); }
}
@media (prefers-reduced-motion: reduce) { .gup-roam { display: none; } }

/* ---- money rain: only visible while a rain track is playing ----
   z-index 9997 keeps the bills under the roaming Gup (9998) and under the
   CRT scanline overlay (9999), so the theme still reads on top of them.
   The reduced-motion rule needs !important: `html.cash-on .cash-rain` is
   specificity (0,2,1) and would otherwise outrank a plain `.cash-rain`. */
.cash-rain {
  position: fixed; inset: 0; z-index: 9997; pointer-events: none;
  overflow: hidden; display: none;
}
html.cash-on .cash-rain { display: block; }
.cash-rain span {
  position: absolute; top: -12vh;
  animation-name: cash-fall; animation-timing-function: linear;
  animation-iteration-count: infinite;
}
@keyframes cash-fall {
  0%   { transform: translateY(0) rotate(-12deg); opacity: 0; }
  10%  { opacity: 1; }
  100% { transform: translateY(118vh) rotate(14deg); }
}
@media (prefers-reduced-motion: reduce) { .cash-rain { display: none !important; } }
.ror-tag {
  display:inline-block; font-family: var(--pix); font-size:0.6rem; color:#04121b;
  background: var(--ror-cyan); padding:6px 9px; margin-top:12px;
  box-shadow: 3px 3px 0 #000;
}

/* ---- sidebar as a HUD panel ---- */
[data-testid="stSidebar"] {
  background: linear-gradient(180deg, #10192f, #0b1223);
  border-right: 4px solid var(--ror-line);
}
[data-testid="stSidebar"] .stHeadingContainer h2,
[data-testid="stSidebar"] h1, [data-testid="stSidebar"] h2, [data-testid="stSidebar"] h3 {
  font-size: 1rem;
}

/* ---- chunky pixel buttons ---- */
.stButton > button, .stDownloadButton > button {
  font-family: var(--pix) !important; font-size: 0.72rem !important;
  color: #1a0f00 !important;
  background: var(--ror-gold) !important;
  border: 3px solid #000 !important; border-radius: 0 !important;
  box-shadow: 5px 5px 0 #000 !important;
  padding: 12px 16px !important;
  transition: transform .05s steps(1), box-shadow .05s steps(1);
}
.stButton > button:hover, .stDownloadButton > button:hover {
  background: #ffcf6b !important; transform: translate(2px,2px);
  box-shadow: 3px 3px 0 #000 !important;
}
.stButton > button:active, .stDownloadButton > button:active {
  transform: translate(5px,5px); box-shadow: 0 0 0 #000 !important;
}

/* ---- inputs / selects ---- */
[data-baseweb="select"] > div, .stTextInput input, .stNumberInput input {
  background: #0c1426 !important; border: 2px solid var(--ror-line) !important;
  border-radius: 0 !important; color: var(--ror-text) !important;
  font-family: var(--term) !important; font-size: 1.15rem !important;
}
.stSlider [data-baseweb="slider"] div[role="slider"] {
  background: var(--ror-gold) !important; border-radius: 0 !important;
  box-shadow: 2px 2px 0 #000;
}

/* ---- metrics as loot cards ---- */
[data-testid="stMetric"] {
  background: linear-gradient(180deg, var(--ror-panel2), var(--ror-panel));
  border: 3px solid var(--ror-gold);
  box-shadow: 5px 5px 0 #000;
  padding: 14px 16px;
}
[data-testid="stMetricLabel"] { font-family: var(--pix) !important; font-size: 0.6rem !important; color: var(--ror-muted) !important; }
[data-testid="stMetricValue"] { font-family: var(--pix) !important; font-size: 1.25rem !important; color: var(--ror-gold) !important; text-shadow: 2px 2px 0 #000; }
[data-testid="stMetricDelta"] { font-family: var(--term) !important; font-size: 1.2rem !important; }
/* rarity-coloured loot cards across a 4-metric row */
[data-testid="stHorizontalBlock"] > div:nth-child(1) [data-testid="stMetric"] { border-color: var(--ror-gold);  }
[data-testid="stHorizontalBlock"] > div:nth-child(2) [data-testid="stMetric"] { border-color: var(--ror-red);   }
[data-testid="stHorizontalBlock"] > div:nth-child(2) [data-testid="stMetricValue"] { color: var(--ror-red);   }
[data-testid="stHorizontalBlock"] > div:nth-child(3) [data-testid="stMetric"] { border-color: var(--ror-green); }
[data-testid="stHorizontalBlock"] > div:nth-child(3) [data-testid="stMetricValue"] { color: var(--ror-green); }
[data-testid="stHorizontalBlock"] > div:nth-child(4) [data-testid="stMetric"] { border-color: var(--ror-cyan);  }
[data-testid="stHorizontalBlock"] > div:nth-child(4) [data-testid="stMetricValue"] { color: var(--ror-cyan);  }

/* ---- progress = teleporter charge / health bar ---- */
.stProgress > div > div > div > div {
  background: linear-gradient(90deg, var(--ror-gold), var(--ror-red)) !important;
  border-radius: 0 !important;
}
.stProgress > div > div > div {
  background: #0c1426 !important; border: 2px solid #000 !important; border-radius: 0 !important;
}

/* ---- expander / alerts as panels ---- */
[data-testid="stExpander"] details {
  background: var(--ror-panel); border: 3px solid var(--ror-line); border-radius: 0;
  box-shadow: 5px 5px 0 #000;
}
[data-testid="stExpander"] summary { font-family: var(--pix) !important; font-size: 0.7rem !important; color: var(--ror-cyan) !important; }
[data-testid="stAlert"] {
  border-radius: 0 !important; border: 3px solid var(--ror-line) !important;
  box-shadow: 5px 5px 0 #000; font-family: var(--term); font-size: 1.15rem;
}
hr { border-color: var(--ror-line) !important; }

/* dataframe frame */
[data-testid="stDataFrame"] { border: 3px solid var(--ror-line); box-shadow: 6px 6px 0 #000; }

/* ---- soundtrack player ---- */
.ost-hdr { font-family: var(--pix); font-size: 0.72rem; color: var(--ror-cyan);
           text-shadow: 2px 2px 0 #000; margin: 4px 0 10px; }
.ost-now { font-family: var(--term); font-size: 1.15rem; color: var(--ror-muted);
           line-height: 1.15; margin-bottom: 8px; }
.ost-now b { color: var(--ror-gold); }
[data-testid="stAudio"] {
  background: #0c1426; border: 2px solid var(--ror-line);
  box-shadow: 3px 3px 0 #000; padding: 6px; border-radius: 0;
}
[data-testid="stAudio"] audio { width: 100%; }
[data-testid="stVideo"] {
  border: 2px solid var(--ror-line); box-shadow: 3px 3px 0 #000; background: #000;
}
[data-testid="stFileUploaderDropzone"] {
  background: #0c1426 !important; border: 2px dashed var(--ror-line) !important;
  border-radius: 0 !important;
}
[data-testid="stFileUploader"] section { padding: 10px !important; }
</style>
"""


# --------------------------------------------------------------- soundtrack --
TRACK_TITLE = "The Rain Formerly Known as Purple"
# Swappable soundtrack: pick one in the sidebar, or paste any YouTube link.
# Ids in RAIN_IDS make it rain dollar bills while they play. Gating on the
# video id (not the dropdown label) means a pasted link to the same track
# rains too, and there's no per-entry flag to keep in sync.
TRACKS = {
    "Money Song": "HMuYfScGpbE",
    TRACK_TITLE: "Io_yUfZQ2v4",  # Chris Christodoulou, official upload via IIP-DDS
}
RAIN_IDS = {"HMuYfScGpbE"}
# Fallback when no track is selected; editable in the sidebar, persisted to _URL_FILE.
DEFAULT_YT_URL = "https://www.youtube.com/watch?v=Io_yUfZQ2v4"
_ASSETS = Path(__file__).parent / "assets"
_URL_FILE = _ASSETS / "soundtrack_url.txt"
_AUDIO_MIME = {
    ".mp3": "audio/mpeg", ".ogg": "audio/ogg", ".opus": "audio/ogg",
    ".m4a": "audio/mp4", ".wav": "audio/wav", ".flac": "audio/flac",
}

# Hidden, looping, auto-playing YouTube background player. Rendered inside a
# component iframe so it can carry its own <script> (Streamlit strips those
# from st.markdown). Browsers block audio autoplay until the first user
# gesture, so we also arm a one-shot "start on first click anywhere" listener
# and expose a ♪ pill that always works. It also toggles the money rain on the
# parent page in step with real playback. __ID__/__TITLE__/__RAIN__ filled in below.
_PLAYER_HTML = """
<style>
  html, body { margin: 0; overflow: hidden; background: transparent; }
  #yt { position: absolute; left: -9999px; top: -9999px; width: 1px; height: 1px; }
  #bar {
    display: inline-flex; align-items: center; gap: 9px; cursor: pointer;
    font-family: 'VT323', 'Courier New', monospace; font-size: 18px; color: #8ea0c6;
    background: #0c1426; border: 2px solid #33456e; box-shadow: 3px 3px 0 #000;
    padding: 6px 11px; user-select: none;
  }
  #bar .eq { color: #5fe0ff; text-shadow: 0 0 8px rgba(95,224,255,0.6); }
  #bar.paused .eq { color: #8ea0c6; text-shadow: none; }
  #bar b { color: #ffb638; }
</style>
<div id="yt"></div>
<div id="bar" class="paused" title="Play / pause soundtrack">
  <span class="eq">&#9835;</span><span id="label">LOADING&hellip;</span>
</div>
<script>
  var player, ready = false;
  var bar = document.getElementById('bar'), label = document.getElementById('label');
  function paint() {
    var playing = ready && player.getPlayerState && player.getPlayerState() === 1;
    bar.classList.toggle('paused', !playing);
    label.innerHTML = (playing ? 'NOW PLAYING &mdash; ' : 'PAUSED &mdash; ') + '<b>__TITLE__</b>';
    // Make it rain on the real page: this component lives in a srcdoc iframe
    // that inherits the parent origin, so reaching out is allowed.
    try {
      window.parent.document.documentElement.classList.toggle('cash-on', playing && __RAIN__);
    } catch (e) { /* cross-origin frame — no rain, music still fine */ }
  }
  bar.addEventListener('click', function () {
    if (!ready) return;
    if (player.getPlayerState() === 1) player.pauseVideo(); else player.playVideo();
  });
  // Best-effort: start on the first interaction anywhere in the app.
  try {
    window.parent.document.addEventListener('pointerdown',
      function () { if (ready) player.playVideo(); }, { once: true, capture: true });
  } catch (e) { /* cross-origin frame — the ♪ pill still starts it */ }

  var tag = document.createElement('script');
  tag.src = 'https://www.youtube.com/iframe_api';
  document.head.appendChild(tag);
  window.onYouTubeIframeAPIReady = function () {
    player = new YT.Player('yt', {
      videoId: '__ID__',
      playerVars: { autoplay: 1, loop: 1, playlist: '__ID__', controls: 0,
                    modestbranding: 1, playsinline: 1 },
      events: {
        onReady: function (e) { ready = true; e.target.setVolume(35); e.target.playVideo(); paint(); },
        onStateChange: paint
      }
    });
  };
</script>
"""


def _yt_id(url: str) -> str | None:
    """Extract an 11-char YouTube video id from common URL forms (or a bare id)."""
    m = re.search(r"(?:v=|youtu\.be/|embed/|shorts/)([A-Za-z0-9_-]{11})", url)
    if m:
        return m.group(1)
    return url if re.fullmatch(r"[A-Za-z0-9_-]{11}", url) else None


def _saved_url() -> str:
    """The persisted soundtrack URL, or the official default."""
    if _URL_FILE.is_file():
        saved = _URL_FILE.read_text().strip()
        if saved:
            return saved
    return DEFAULT_YT_URL


def _find_track() -> Path | None:
    """The audio file to play, or None. Prefers a name hinting at the track."""
    if not _ASSETS.is_dir():
        return None
    audio = [p for p in sorted(_ASSETS.iterdir()) if p.suffix.lower() in _AUDIO_MIME]
    if not audio:
        return None
    for p in audio:
        if "rain" in p.stem.lower() or "purple" in p.stem.lower():
            return p
    return audio[0]


def _display_title(track: Path) -> str:
    """Show the OST title for a matching file, else the file's own name."""
    n = track.stem.lower()
    if "rain" in n or "purple" in n:
        return TRACK_TITLE
    return track.stem.replace("_", " ").replace("-", " ").strip().title()


def soundtrack() -> None:
    """Themed OST player that plays in the background, on loop.

    A YouTube link (the official upload by default, editable + persisted) is
    played via a hidden, auto-playing IFrame player. A local audio file dropped
    into assets/ still takes priority if present. Browsers block audio autoplay
    until the first user gesture, so playback also starts on the first click.
    """
    import streamlit as st

    st.markdown('<div class="ost-hdr">♪ SOUNDTRACK</div>', unsafe_allow_html=True)

    # A local audio file, if dropped into assets/, wins.
    track = _find_track()
    if track is not None:
        st.markdown(
            f'<div class="ost-now">NOW PLAYING<br><b>{_display_title(track)}</b></div>',
            unsafe_allow_html=True,
        )
        st.audio(track.read_bytes(), format=_AUDIO_MIME[track.suffix.lower()],
                 loop=True, autoplay=True)
        st.caption("Chris Christodoulou — *Risk of Rain* OST")
        return

    # Otherwise, pick a track — or paste any YouTube link, which wins.
    name = st.selectbox("Track", list(TRACKS), help="Swap the soundtrack")
    custom = st.text_input("or paste a YouTube link", value="",
                           placeholder="https://youtu.be/…").strip()
    url = custom or f"https://www.youtube.com/watch?v={TRACKS[name]}"
    title = TRACK_TITLE if custom else name

    if custom:
        _ASSETS.mkdir(exist_ok=True)
        if _saved_url() != url:
            try:
                _URL_FILE.write_text(url + "\n")  # persist across reloads
            except OSError:
                pass  # assets/ may not be writable; persistence is a nicety

    vid = _yt_id(url)
    if vid is None:
        # Not a YouTube URL — fall back to a plain looping player.
        st.video(url, loop=True)
        st.caption("Chris Christodoulou — *Risk of Rain* OST · press ▶ to play")
        return

    import streamlit.components.v1 as components
    components.html(
        _PLAYER_HTML.replace("__ID__", vid).replace("__TITLE__", title)
                    .replace("__RAIN__", "true" if vid in RAIN_IDS else "false"),
        height=58,
    )
    st.caption("Autoplays on loop; tap ♪ if your browser blocked it."
               + (" Makes it rain 💵" if vid in RAIN_IDS else ""))


def inject() -> None:
    """Apply the pixel theme + render the hero banner with the bouncing Gup."""
    import streamlit as st

    st.markdown(CSS, unsafe_allow_html=True)
    # a second Gup roams free, bouncing around the whole viewport
    st.markdown(f'<div class="gup-roam">{gup_svg(scale=3)}</div>', unsafe_allow_html=True)
    # dormant until the soundtrack player flips `cash-on` on <html>
    st.markdown(RAIN_HTML, unsafe_allow_html=True)
    st.markdown(
        f"""
        <div class="ror-hero">
          <div class="gup-wrap">{gup_svg()}</div>
          <div>
            <h1>STOCK SCREENER</h1>
            <p class="sub">Scan the market through Buffett · Burry · Roaring Kitty · Simons lenses.
            Free Yahoo data. <b>Educational tool — not investment advice.</b></p>
            <span class="ror-tag">DIFFICULTY: HAHAHA</span>
          </div>
        </div>
        """,
        unsafe_allow_html=True,
    )
