#!/usr/bin/env python3
"""
Music build script.

For each *-music.md in this folder:
  - Parse title, optional first-load lightbox, and track entries
  - Two entry types detected by URL:
      YouTube  → youtube.com/watch?v=VIDEO_ID  → video embed card
      Audio    → files.exopraxist.org/share/TOKEN → audio card + floating player
  - Generate a self-contained HTML page

Then update music-hub.html subtitle with current totals.

Output filenames: strip '-music' suffix, lowercase the stem.
  OldMusicandMVs-music.md  →  oldmusicandmvs.html

Run after adding or editing any *-music.md file.
"""

import re
from pathlib import Path
from html import escape

HERE = Path(__file__).parent
HUB  = HERE / "music-hub.html"

ALLOW = (
    "accelerometer; autoplay; clipboard-write; "
    "encrypted-media; gyroscope; picture-in-picture; web-share"
)


# ── MD parsing ────────────────────────────────────────────────────────────────

def slug_from_path(path: Path) -> str:
    stem = path.stem
    stem = re.sub(r'-music$', '', stem, flags=re.IGNORECASE)
    return stem.lower().replace('-', '').replace('_', '').replace(' ', '')


def parse_md(path: Path) -> dict:
    text = path.read_text(encoding='utf-8')

    m = re.search(r'^#\s+(.+)$', text, re.MULTILINE)
    title = m.group(1).strip() if m else path.stem

    m = re.search(r'>>on-first-load\s+-\s+lightbox\s+-\s+"([^"]+)"', text)
    lightbox = m.group(1).strip() if m else None

    entries = []
    for m in re.finditer(
        r'-\s+\*\*(.+?)\*\*\s*\n\s+-\s+URL:\s+(https://[^\s]+)',
        text
    ):
        name = m.group(1).strip()
        # Strip trailing \- Source label from name (e.g. "Title \- YouTube")
        name = re.sub(r'\s*\\-\s*.+$', '', name).strip()
        url  = m.group(2).strip()

        if 'youtube.com/watch' in url:
            vid = re.search(r'[?&]v=([A-Za-z0-9_-]+)', url)
            if vid:
                entries.append({'type': 'youtube', 'name': name,
                                 'video_id': vid.group(1)})
        elif 'files.exopraxist.org/share/' in url:
            token = url.rstrip('/').split('/')[-1]
            entries.append({'type': 'audio', 'name': name,
                             'share_url': url,
                             'direct_url': f"https://files.exopraxist.org/api/public/dl/{token}"})

    return {'title': title, 'lightbox': lightbox, 'entries': entries}


# ── HTML blocks ───────────────────────────────────────────────────────────────

def build_lightbox_html(text: str, slug: str) -> tuple[str, str]:
    if not text:
        return '', ''

    # Preserve — JL attribution if present at the end
    body = text
    attribution = ''
    m = re.search(r'\s*-\s*JL\s*$', text)
    if m:
        body = text[:m.start()].strip()
        attribution = '<p class="lightbox-attribution">— JL</p>'

    safe_body = escape(body)

    div = f"""
    <div id="lightbox">
        <div class="lightbox-content">
            <span class="lightbox-close">&times;</span>
            <p class="lightbox-quote">{safe_body}</p>
            {attribution}
            <span class="lightbox-hint">CLICK ANYWHERE TO DISMISS</span>
        </div>
    </div>"""

    js = f"""
            const lightbox = document.getElementById('lightbox');
            lightbox.style.display = 'flex';
            const closeLightbox = () => {{ lightbox.style.display = 'none'; }};
            lightbox.addEventListener('click', (e) => {{
                if (e.target === lightbox) closeLightbox();
            }});
            document.querySelector('.lightbox-close').addEventListener('click', closeLightbox);
            document.addEventListener('keydown', (e) => {{
                if (e.key === 'Escape') closeLightbox();
            }});
"""
    return div, js


def build_video_card(entry: dict) -> str:
    embed_url = f"https://www.youtube.com/embed/{entry['video_id']}"
    title_esc = escape(entry['name'])
    return f"""
        <div class="card">
            <div class="card-header"><h2 class="card-title">{title_esc}</h2></div>
            <div class="video-container">
                <iframe
                    data-src="{embed_url}"
                    allow="{ALLOW}"
                    allowfullscreen>
                </iframe>
            </div>
        </div>"""


def build_audio_card(entry: dict, audio_index: int) -> str:
    title_esc    = escape(entry['name'])
    direct_url   = entry['direct_url']
    share_url    = entry['share_url']
    return f"""
        <div class="card" id="track-{audio_index}">
            <div class="card-header">
                <h2 class="card-title">{title_esc}</h2>
            </div>
            <div class="audio-card">
                <div class="audio-controls">
                    <button class="play-btn"
                        data-src="{direct_url}"
                        data-title="{title_esc}">▶</button>
                    <div class="audio-info">
                        <a href="{share_url}" target="_blank" rel="noopener"
                           class="download-link">⬇ DOWNLOAD</a>
                        <div class="progress-container">
                            <div class="progress-bar"></div>
                        </div>
                        <div class="duration-meta">
                            <span class="current-time">0:00</span>
                            <span class="total-time">--:--</span>
                        </div>
                    </div>
                </div>
            </div>
        </div>"""


def build_entries_html(entries: list) -> str:
    parts = []
    audio_idx = 1
    for e in entries:
        if e['type'] == 'youtube':
            parts.append(build_video_card(e))
        else:
            parts.append(build_audio_card(e, audio_idx))
            audio_idx += 1
    return '\n'.join(parts)


def build_stagger_css(count: int) -> str:
    lines = []
    for i in range(1, min(count, 12) + 1):
        lines.append(
            f"        .card:nth-child({i}) {{ animation-delay: {(i-1)*50}ms; }}"
        )
    return '\n'.join(lines)


# ── Full page generation ──────────────────────────────────────────────────────

def generate_html(data: dict, slug: str) -> str:
    title       = data['title']
    entries     = data['entries']
    count       = len(entries)
    has_audio   = any(e['type'] == 'audio' for e in entries)
    title_upper = title.upper()
    count_label = f"{count} TRACK{'S' if count != 1 else ''}"

    lightbox_div, lightbox_js = build_lightbox_html(data['lightbox'], slug)
    entries_html = build_entries_html(entries)
    stagger_css  = build_stagger_css(count)

    lightbox_css = """
        /* --- Lightbox --- */
        #lightbox {
            position: fixed;
            top: 0; left: 0;
            width: 100%; height: 100%;
            background: rgba(4, 6, 11, 0.9);
            display: none;
            align-items: center;
            justify-content: center;
            z-index: 1000;
            padding: 2rem;
            cursor: pointer;
        }
        .lightbox-content {
            background: #0c0a08;
            border: 2px solid var(--mu-red);
            border-top: 8px solid var(--mu-orange);
            max-width: 600px;
            width: 100%;
            max-height: 90vh;
            overflow-y: auto;
            padding: 2.5rem;
            position: relative;
            cursor: default;
        }
        .lightbox-close {
            position: absolute;
            top: 0.5rem; right: 0.75rem;
            font-size: 2rem;
            color: var(--mu-orange);
            cursor: pointer;
            line-height: 1;
        }
        .lightbox-quote {
            font-style: italic;
            font-size: 1.1rem;
            color: var(--text-warm);
            margin-bottom: 1.5rem;
            padding-right: 1.5rem;
        }
        .lightbox-attribution {
            font-weight: 700;
            text-transform: uppercase;
            color: var(--mu-orange);
            text-align: right;
        }
        .lightbox-hint {
            display: block;
            text-align: center;
            margin-top: 2rem;
            font-weight: 700;
            text-transform: uppercase;
            font-size: 0.65rem;
            letter-spacing: 0.1em;
            color: rgba(232, 213, 184, 0.4);
        }
""" if data['lightbox'] else ''

    audio_css = """
        /* --- Audio Card --- */
        .audio-card {
            padding: 1.5rem;
        }
        .audio-controls {
            display: flex;
            align-items: center;
            gap: 1.5rem;
        }
        .play-btn {
            background: none;
            border: 2px solid var(--mu-orange);
            color: var(--mu-orange);
            width: 48px;
            height: 48px;
            flex-shrink: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 1.2rem;
            cursor: pointer;
            transition: var(--transition);
            font-family: inherit;
        }
        .play-btn:hover {
            background: var(--mu-orange);
            color: var(--bg-void);
        }
        .audio-info { flex-grow: 1; }
        .download-link {
            font-weight: 700;
            text-transform: uppercase;
            font-size: 0.75rem;
            color: var(--mu-orange);
            text-decoration: none;
            display: inline-block;
            margin-bottom: 0.5rem;
            transition: var(--transition);
        }
        .download-link:hover { text-decoration: underline; }
        .progress-container {
            height: 4px;
            background: var(--mu-red);
            width: 100%;
            cursor: pointer;
        }
        .progress-bar {
            height: 100%;
            background: var(--mu-orange);
            width: 0%;
            transition: width 100ms linear;
        }
        .duration-meta {
            font-weight: 700;
            text-transform: uppercase;
            font-size: 0.7rem;
            color: rgba(232, 213, 184, 0.5);
            margin-top: 0.25rem;
            display: flex;
            justify-content: space-between;
        }

        /* --- Floating Player --- */
        .floating-player {
            position: fixed;
            bottom: 0; left: 0;
            width: 100%;
            height: 64px;
            background: var(--bg-void);
            border-top: 2px solid var(--mu-red);
            z-index: 500;
            display: none;
            align-items: center;
            padding: 0 1rem;
            gap: 1rem;
        }
        .floating-player.active { display: flex; }
        .fp-info { flex: 1; min-width: 0; }
        .fp-title {
            font-family: 'Rancho', cursive;
            font-size: 1.2rem;
            color: var(--mu-orange);
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        .fp-controls {
            display: flex;
            align-items: center;
            gap: 1rem;
        }
        .fp-btn {
            background: none;
            border: none;
            color: var(--mu-orange);
            font-family: 'Rambla', sans-serif;
            font-weight: 700;
            font-size: 1rem;
            cursor: pointer;
            transition: var(--transition);
        }
        .fp-btn:hover { color: var(--text-warm); }
        .fp-progress-wrap {
            position: absolute;
            top: -2px; left: 0;
            width: 100%;
            height: 2px;
            background: var(--mu-red);
        }
        .fp-progress-fill {
            height: 100%;
            background: var(--mu-orange);
            width: 0%;
            transition: width 100ms linear;
        }
""" if has_audio else ''

    floating_player_html = """
    <div class="floating-player">
        <div class="fp-progress-wrap"><div class="fp-progress-fill"></div></div>
        <div class="fp-info"><div class="fp-title">No Track Selected</div></div>
        <div class="fp-controls">
            <button class="fp-btn" id="fp-prev">◀◀</button>
            <button class="fp-btn" id="fp-play">▶</button>
            <button class="fp-btn" id="fp-next">▶▶</button>
        </div>
    </div>
    <audio id="main-audio"></audio>""" if has_audio else ''

    audio_js = """
            // --- Audio Logic ---
            const audio        = document.getElementById('main-audio');
            const floatingPlayer = document.querySelector('.floating-player');
            const fpPlayBtn    = document.getElementById('fp-play');
            const fpTitle      = document.querySelector('.fp-title');
            const fpFill       = document.querySelector('.fp-progress-fill');
            let currentTrackBtn = null;

            const formatTime = (t) => {
                const m = Math.floor(t / 60);
                const s = Math.floor(t % 60);
                return `${m}:${s.toString().padStart(2, '0')}`;
            };

            const updateUI = () => {
                const playing = !audio.paused;
                const icon = playing ? '▐▐' : '▶';
                if (currentTrackBtn) currentTrackBtn.textContent = icon;
                fpPlayBtn.textContent = icon;
                if (playing) {
                    floatingPlayer.classList.add('active');
                    document.body.classList.add('player-active');
                }
            };

            document.querySelectorAll('.play-btn').forEach(btn => {
                btn.addEventListener('click', () => {
                    const src   = btn.dataset.src;
                    const title = btn.dataset.title;
                    if (audio.getAttribute('data-loaded') === src) {
                        if (audio.paused) audio.play(); else audio.pause();
                    } else {
                        if (currentTrackBtn) currentTrackBtn.textContent = '▶';
                        audio.src = src;
                        audio.setAttribute('data-loaded', src);
                        fpTitle.textContent = title;
                        currentTrackBtn = btn;
                        audio.play();
                    }
                });
            });

            fpPlayBtn.addEventListener('click', () => {
                if (audio.paused) audio.play(); else audio.pause();
            });

            audio.addEventListener('play',  updateUI);
            audio.addEventListener('pause', updateUI);

            audio.addEventListener('timeupdate', () => {
                if (!audio.duration) return;
                const pct = (audio.currentTime / audio.duration) * 100;
                if (fpFill) fpFill.style.width = pct + '%';
                if (currentTrackBtn) {
                    const card = currentTrackBtn.closest('.audio-card');
                    const bar  = card.querySelector('.progress-bar');
                    const cur  = card.querySelector('.current-time');
                    if (bar) bar.style.width = pct + '%';
                    if (cur) cur.textContent = formatTime(audio.currentTime);
                }
            });

            audio.addEventListener('loadedmetadata', () => {
                if (currentTrackBtn) {
                    const card = currentTrackBtn.closest('.audio-card');
                    const tot  = card.querySelector('.total-time');
                    if (tot) tot.textContent = formatTime(audio.duration);
                }
            });

            audio.addEventListener('ended', () => {
                const allBtns = [...document.querySelectorAll('.play-btn')];
                const idx  = allBtns.indexOf(currentTrackBtn);
                const next = allBtns[idx + 1];
                if (next) {
                    next.click();
                } else {
                    audio.pause();
                    if (currentTrackBtn) currentTrackBtn.textContent = '▶';
                    fpPlayBtn.textContent = '▶';
                    floatingPlayer.classList.remove('active');
                    document.body.classList.remove('player-active');
                }
            });

            document.getElementById('fp-prev').addEventListener('click', () => {
                const all = [...document.querySelectorAll('.play-btn')];
                const prev = all[all.indexOf(currentTrackBtn) - 1];
                if (prev) prev.click();
            });

            document.getElementById('fp-next').addEventListener('click', () => {
                const all = [...document.querySelectorAll('.play-btn')];
                const next = all[all.indexOf(currentTrackBtn) + 1];
                if (next) next.click();
            });

            document.querySelectorAll('.progress-container').forEach(bar => {
                bar.addEventListener('click', (e) => {
                    const rect = bar.getBoundingClientRect();
                    audio.currentTime = ((e.clientX - rect.left) / rect.width) * audio.duration;
                });
            });
""" if has_audio else ''

    return f"""<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{escape(title)} | MUSIC</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Rancho&family=Rambla:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet">
    <style>
        :root {{
            --bg-void:        #04060b;
            --text-warm:      #e8d5b8;
            --mu-red:         #8b2020;
            --mu-orange:      #ff7f3f;
            --mu-red-glow:    rgba(139, 32, 32, 0.25);
            --mu-orange-glow: rgba(255, 127, 63, 0.25);
            --transition:     100ms ease;
        }}

        * {{ box-sizing: border-box; margin: 0; padding: 0; border-radius: 0; }}

        body {{
            background-color: var(--bg-void);
            color: var(--text-warm);
            font-family: 'Rambla', sans-serif;
            line-height: 1.6;
            padding-top: 64px;
        }}

        body.player-active {{ padding-bottom: 64px; }}

        /* --- Sticky header --- */
        .sticky-header {{
            position: fixed;
            top: 0; left: 0; right: 0;
            z-index: 100;
            background: var(--bg-void);
            border-bottom: 2px solid var(--mu-red);
            padding: 1rem;
            display: flex;
            align-items: center;
            justify-content: space-between;
        }}

        .header-title {{
            font-family: 'Rancho', cursive;
            font-size: 1.5rem;
            color: var(--mu-orange);
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            margin-right: 1rem;
        }}

        .header-nav {{
            display: flex;
            align-items: center;
            gap: 1.5rem;
        }}

        .nav-link {{
            font-weight: 700;
            text-transform: uppercase;
            font-size: 0.85rem;
            color: var(--mu-orange);
            text-decoration: none;
            transition: var(--transition);
        }}

        .nav-link:hover {{ color: #ff9f6f; }}

        .count {{
            font-weight: 700;
            text-transform: uppercase;
            font-size: 0.75rem;
            letter-spacing: 0.1em;
            color: rgba(232, 213, 184, 0.5);
        }}

        .menu-toggle {{
            display: none;
            background: none;
            border: none;
            color: var(--mu-orange);
            font-size: 1.5rem;
            cursor: pointer;
            font-family: inherit;
        }}

        @media (max-width: 600px) {{
            .menu-toggle {{ display: block; }}
            .header-nav {{
                display: none;
                position: absolute;
                top: 100%; right: 0;
                background: var(--bg-void);
                border: 2px solid var(--mu-red);
                border-top: none;
                flex-direction: column;
                padding: 1rem;
                gap: 1rem;
                width: 200px;
            }}
            .header-nav.open {{ display: flex; }}
        }}
{lightbox_css}{audio_css}
        /* --- Cards --- */
        @keyframes cardIn {{
            from {{ opacity: 0; transform: translateY(16px); }}
            to   {{ opacity: 1; transform: translateY(0); }}
        }}

        main {{
            max-width: 900px;
            margin: 2rem auto;
            padding: 0 1rem 4rem;
        }}

        .section-title {{
            font-family: 'Rancho', cursive;
            font-size: 3rem;
            color: var(--mu-orange);
            margin-bottom: 2rem;
            text-align: center;
        }}

        .card {{
            margin-bottom: 3rem;
            border: 2px solid var(--mu-red);
            background: rgba(139, 32, 32, 0.05);
            transition: border-color var(--transition), box-shadow var(--transition);
            animation: cardIn 320ms ease both;
        }}

{stagger_css}

        .card:hover {{
            border-color: var(--mu-orange);
            box-shadow: 0 0 8px 2px var(--mu-orange-glow);
        }}

        .card-header {{
            padding: 0.75rem 1rem;
            border-bottom: 1px solid var(--mu-red);
        }}

        .card-title {{
            font-family: 'Rancho', cursive;
            font-size: 1.6rem;
            color: var(--mu-orange);
        }}

        .video-container {{
            position: relative;
            padding-bottom: 56.25%;
            height: 0;
            overflow: hidden;
            background: #000;
        }}

        .video-container iframe {{
            position: absolute;
            top: 0; left: 0;
            width: 100%; height: 100%;
            border: 0;
        }}
    </style>
</head>
<body>
{lightbox_div}
    <header class="sticky-header">
        <div class="header-title">{escape(title_upper)}</div>
        <button class="menu-toggle">☰</button>
        <nav class="header-nav">
            <a href="music-hub.html" class="nav-link">← MUSIC</a>
            <span class="count">{count_label}</span>
        </nav>
    </header>

    <main>
        <h1 class="section-title">{escape(title.upper())}</h1>
{entries_html}
    </main>
{floating_player_html}
    <script>
        document.addEventListener('DOMContentLoaded', () => {{
{lightbox_js}
            // --- Mobile nav ---
            const menuToggle = document.querySelector('.menu-toggle');
            const nav = document.querySelector('.header-nav');
            menuToggle.addEventListener('click', (e) => {{
                e.stopPropagation();
                nav.classList.toggle('open');
            }});
            document.addEventListener('click', (e) => {{
                if (!nav.contains(e.target) && e.target !== menuToggle)
                    nav.classList.remove('open');
            }});

            // --- Lazy-load iframes ---
            const observer = new IntersectionObserver((entries, obs) => {{
                entries.forEach(entry => {{
                    if (entry.isIntersecting) {{
                        const iframe = entry.target;
                        if (iframe.dataset.src) {{
                            iframe.src = iframe.dataset.src;
                            iframe.removeAttribute('data-src');
                            obs.unobserve(iframe);
                        }}
                    }}
                }});
            }}, {{ rootMargin: '200px' }});
            document.querySelectorAll('iframe[data-src]').forEach(f => observer.observe(f));
{audio_js}
        }});
    </script>
</body>
</html>
"""


# ── Hub subtitle update ───────────────────────────────────────────────────────

def update_hub_subtitle(collection_count: int, entry_total: int):
    hub_text = HUB.read_text(encoding='utf-8')
    subtitle = (
        f'<span class="subtitle">'
        f'{collection_count} COLLECTION{"S" if collection_count != 1 else ""}'
        f' · PERSONAL ARCHIVE'
        f' · {entry_total} TRACK{"S" if entry_total != 1 else ""}'
        f'</span>'
    )
    hub_text = re.sub(
        r'<!-- SUBTITLE-START -->.*?<!-- SUBTITLE-END -->',
        f'<!-- SUBTITLE-START -->{subtitle}<!-- SUBTITLE-END -->',
        hub_text,
        flags=re.DOTALL
    )
    HUB.write_text(hub_text, encoding='utf-8')


# ── Main ──────────────────────────────────────────────────────────────────────

def main():
    md_files = sorted(HERE.glob('*-music.md'))
    if not md_files:
        print("No *-music.md files found.")
        return

    total_entries = 0

    for md_path in md_files:
        slug  = slug_from_path(md_path)
        out   = HERE / f"{slug}.html"
        data  = parse_md(md_path)
        count = len(data['entries'])

        if count == 0:
            print(f"  SKIP  {md_path.name} — no entries found")
            continue

        audio_count = sum(1 for e in data['entries'] if e['type'] == 'audio')
        video_count = count - audio_count

        html = generate_html(data, slug)
        out.write_text(html, encoding='utf-8')
        total_entries += count
        print(f"  BUILD {out.name} — {data['title']} ({video_count} videos, {audio_count} audio)")

    update_hub_subtitle(len(md_files), total_entries)
    print(f"\nUpdated hub — {len(md_files)} collections, {total_entries} tracks total")


if __name__ == '__main__':
    main()
