#!/usr/bin/env python3
"""Generate a podcast RSS feed for one show under ~/Podcasts/<slug>/."""
import os, subprocess, html, datetime, urllib.parse, re

HOST = "https://bobs-mac-studio-2-1.tail24a318.ts.net"
SLUG = "ag-grid-delphi"                             # <-- this show's folder name
BASE_URL = f"{HOST}/{SLUG}"
ROOT = os.path.expanduser(f"~/Podcasts/{SLUG}")
MEDIA = os.path.join(ROOT, "media")
OUT = os.path.join(ROOT, "feed.xml")

# ---- channel metadata ----
PODCAST_TITLE = "Mastering AG Grid for Delphi Developers"
PODCAST_DESC  = ("A study companion podcast for learning AG Grid, the JavaScript data "
                 "grid — concepts, configuration, and practical usage, framed for a "
                 "Delphi developer working in Vue 3.")
AUTHOR        = "Bob Francis"
OWNER_EMAIL   = "bob.francis@steeldynamics.com"
CATEGORY      = "Technology"

# ---- episodes IN PLAY ORDER (filename in media/, title, description) ----
EPISODES = [
    ("ep01_history_and_overview.m4a",
     "Ep 1 — History & Overview",
     "The origin and purpose of AG Grid, what it does, and how it maps onto the "
     "data-aware VCL world of TDBGrid, TDataSet, and TField."),
    ("ep02_columns.m4a",
     "Ep 2 — Columns",
     "Column definitions, sizing, moving, pinning, column groups, and column state."),
    ("ep03_rows.m4a",
     "Ep 3 — Rows",
     "Row data, row IDs, sorting, height, styling, pinning, pagination, and dragging."),
    ("ep04_cells.m4a",
     "Ep 4 — Cells",
     "Value getters and formatters, cell renderers, styling, tooltips, and cell data types."),
    ("ep05_filtering.m4a",
     "Ep 5 — Filtering",
     "Text, number, and date filters, floating and quick filters, and the Set and "
     "Advanced filters."),
    ("ep06_selection.m4a",
     "Ep 6 — Selection",
     "Row selection (single and multi) and cell/range selection with the fill handle."),
    ("ep07_editing.m4a",
     "Ep 7 — Editing",
     "In-cell editing, provided and custom cell editors, validation, and undo/redo."),
    ("ep08_updating_data.m4a",
     "Ep 8 — Updating Data",
     "Replacing row data, single row and cell updates, transactions, and high-frequency updates."),
    ("ep09_interactivity.m4a",
     "Ep 9 — Interactivity",
     "Keyboard navigation, touch, accessibility, RTL, aligned grids, and localisation."),
    ("ep10_row_grouping.m4a",
     "Ep 10 — Row Grouping",
     "Grouping flat rows by column value, group display types, and the row group panel."),
    ("ep11_aggregation.m4a",
     "Ep 11 — Aggregation",
     "Aggregation functions, custom aggregations, and total and grand-total rows."),
    ("ep12_formulas.m4a",
     "Ep 12 — Formulas",
     "Spreadsheet-style formulas in cells, the formula editor, and custom functions."),
    ("ep13_pivoting.m4a",
     "Ep 13 — Pivoting",
     "Pivot mode, pivot result columns and column groups, and pivot totals."),
    ("ep14_tree_data.m4a",
     "Ep 14 — Tree Data",
     "Self-referential hierarchies, data paths, the tree group column, selection, and filtering."),
    ("ep15_master_detail.m4a",
     "Ep 15 — Master Detail",
     "Expandable detail grids nested under master rows, detail height, refresh, and nesting."),
    ("ep16_accessories.m4a",
     "Ep 16 — Accessories",
     "Tool panels, the side bar, column and context menus, the status bar, and overlays."),
    ("ep17_server_side_data.m4a",
     "Ep 17 — Server-Side Data",
     "The four row models and how to choose one for your data volume and workload."),
    ("ep18_import_and_export.m4a",
     "Ep 18 — Import & Export",
     "CSV and Excel export, clipboard, drag and drop, printing, and Excel import."),
    ("ep19_state_and_lifecycle.m4a",
     "Ep 19 — State & Lifecycle",
     "Saving and restoring grid state, the grid context object, and lifecycle events."),
    ("ep20_performance.m4a",
     "Ep 20 — Performance",
     "Change detection, DOM virtualisation, the value cache, and handling massive row counts."),
]

def duration_seconds(path):
    try:
        out = subprocess.run(["afinfo", path], capture_output=True, text=True, timeout=30).stdout
        m = re.search(r"estimated duration:\s*([0-9.]+)\s*sec", out)
        if m: return int(float(m.group(1)))
    except Exception:
        pass
    return 0

def hms(sec):
    h = sec // 3600; m = (sec % 3600) // 60; s = sec % 60
    return f"{h:d}:{m:02d}:{s:02d}" if h else f"{m:d}:{s:02d}"

def rfc2822(dt):
    return dt.strftime("%a, %d %b %Y %H:%M:%S +0000")

# newest episode dated "today", each earlier one a day older -> oldest first, all past
base = datetime.datetime.now().replace(microsecond=0)
N = len(EPISODES)
items = []
for i, (fn, title, desc) in enumerate(EPISODES):
    path = os.path.join(MEDIA, fn)
    size = os.path.getsize(path)
    dur = duration_seconds(path)
    pub = base - datetime.timedelta(days=(N - 1 - i))
    url = BASE_URL + "/media/" + urllib.parse.quote(fn)
    items.append(f"""    <item>
      <title>{html.escape(title)}</title>
      <description>{html.escape(desc)}</description>
      <itunes:summary>{html.escape(desc)}</itunes:summary>
      <itunes:episode>{i+1}</itunes:episode>
      <itunes:episodeType>full</itunes:episodeType>
      <enclosure url="{html.escape(url)}" length="{size}" type="audio/x-m4a"/>
      <guid isPermaLink="false">{html.escape(url)}</guid>
      <pubDate>{rfc2822(pub)}</pubDate>
      <itunes:duration>{hms(dur)}</itunes:duration>
      <itunes:author>{html.escape(AUTHOR)}</itunes:author>
      <itunes:explicit>false</itunes:explicit>
    </item>""")

now = rfc2822(datetime.datetime.utcnow())
feed = f"""<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>{html.escape(PODCAST_TITLE)}</title>
    <link>{BASE_URL}/</link>
    <language>en-us</language>
    <description>{html.escape(PODCAST_DESC)}</description>
    <itunes:author>{html.escape(AUTHOR)}</itunes:author>
    <itunes:summary>{html.escape(PODCAST_DESC)}</itunes:summary>
    <itunes:type>serial</itunes:type>
    <itunes:explicit>false</itunes:explicit>
    <itunes:image href="{BASE_URL}/cover.jpg"/>
    <image><url>{BASE_URL}/cover.jpg</url><title>{html.escape(PODCAST_TITLE)}</title><link>{BASE_URL}/</link></image>
    <itunes:owner><itunes:name>{html.escape(AUTHOR)}</itunes:name><itunes:email>{html.escape(OWNER_EMAIL)}</itunes:email></itunes:owner>
    <itunes:category text="{html.escape(CATEGORY)}"/>
    <lastBuildDate>{now}</lastBuildDate>
{chr(10).join(items)}
  </channel>
</rss>
"""
with open(OUT, "w") as f:
    f.write(feed)
print("WROTE", OUT, len(feed), "bytes,", len(EPISODES), "episodes")
