#!/usr/bin/env -S uv run
# /// script
# dependencies = []
# ///
"""flomo-takeout: flomo HTML export -> monthly markdown files.

Usage: flomo-takeout <input.zip|input.html> [output_dir]
"""
import re, sys, zipfile
from collections import defaultdict
from html.parser import HTMLParser
from pathlib import Path

class H2M(HTMLParser):
  def __init__(self):
    super().__init__()
    self.o, self.n, self.ord = [], 0, False
  def handle_starttag(self, t, a):
    if t == "br": self.o.append("\n")
    elif t == "ul": self.ord, self.n = False, 0
    elif t == "ol": self.ord, self.n = True, 0
    elif t == "li":
      self.n += 1
      self.o.append(f"{self.n}. " if self.ord else "- ")
    elif t == "strong": self.o.append("**")
  def handle_endtag(self, t):
    if t in ("p", "li"): self.o.append("\n")
    elif t == "strong": self.o.append("**")
  def handle_data(self, d): self.o.append(d)
  def md(self):
    text = "".join(self.o).strip()
    # strip leading whitespace per line (flomo indents metadata with spaces)
    text = "\n".join(line.strip() for line in text.split("\n"))
    return re.sub(r"\n{3,}", "\n\n", text)

def h2m(html):
  p = H2M(); p.feed(html); return p.md()

def extract(path):
  """-> (html_str, {relative_path: bytes})"""
  assets = {}
  if path.suffix == ".zip":
    html = ""
    with zipfile.ZipFile(path) as z:
      for n in z.namelist():
        if n.endswith(".html"): html = z.read(n).decode("utf-8")
        elif "/file/" in n and not n.endswith("/"):
          i = n.find("file/")
          if i >= 0: assets[n[i:]] = z.read(n)
    assert html, "no html in zip"
    return html, assets
  html = path.read_text(encoding="utf-8")
  fd = path.parent / "file"
  if fd.is_dir():
    for f in fd.rglob("*"):
      if f.is_file(): assets[str(f.relative_to(path.parent))] = f.read_bytes()
  return html, assets

def parse(html):
  """split html into memo dicts"""
  R = re.compile
  t_re, c_re = R(r'class="time">([\d\- :]+)<'), R(r'class="content">(.*?)</div>', re.S)
  img_re, aud_re = R(r'<img[^>]+src="(file/[^"]+)"'), R(r'<audio[^>]+src="(file/[^"]+)"')
  tr_re = R(r'class="audio-player__content">(.*?)</div>', re.S)
  out = []
  for part in html.split('<div class="memo">')[1:]:
    t = t_re.search(part)
    if not t: continue
    ts = t[1].strip()
    c = c_re.search(part)
    tr = tr_re.search(part)
    out.append(dict(ts=ts, date=ts[:10], month=ts[:7],
      html=c[1].strip() if c else "",
      imgs=img_re.findall(part), auds=aud_re.findall(part),
      transcript=tr[1].strip() if tr else ""))
  return out

def memo2md(m, mo):
  """single memo -> markdown chunk"""
  parts = []
  md = h2m(m["html"])
  if md: parts.append(md)
  for p in m["imgs"]:
    f = Path(p).name; parts.append(f"\n![{f}]({mo}.assets/{f})")
  for p in m["auds"]:
    f = Path(p).name; parts.append(f"\n[{f}]({mo}.assets/{f})")
    if m["transcript"]: parts.append(f"\n> {m['transcript']}")
  return "\n".join(parts)

def convert(src, dst):
  html, assets = extract(src)
  memos = parse(html)
  assert memos, "no memos found"

  # group: month -> date -> [memo]
  by_month = defaultdict(lambda: defaultdict(list))
  for m in memos: by_month[m["month"]][m["date"]].append(m)
  dst.mkdir(parents=True, exist_ok=True)

  n, na = 0, 0
  for mo in sorted(by_month):
    sections = []
    for date in sorted(by_month[mo]):
      day = sorted(by_month[mo][date], key=lambda m: m["ts"])
      chunks = [memo2md(m, mo) for m in day]
      chunks = [c for c in chunks if c]
      n += len(chunks)
      if chunks: sections.append(f"## {date}\n\n" + "\n\n".join(chunks))
    (dst / f"{mo}.md").write_text("\n\n".join(sections) + "\n", encoding="utf-8")

    # copy assets for this month
    for m in [m for d in by_month[mo].values() for m in d]:
      for p in m["imgs"] + m["auds"]:
        data = assets.get(p)
        if data:
          ad = dst / f"{mo}.assets"; ad.mkdir(exist_ok=True)
          (ad / Path(p).name).write_bytes(data); na += 1

  print(f"{n} memos -> {len(by_month)} files" + (f", {na} assets" if na else ""))

def main():
  if len(sys.argv) < 2 or sys.argv[1] in ("-h", "--help"):
    print(__doc__.strip()); sys.exit(0)
  src = Path(sys.argv[1]).expanduser().resolve()
  assert src.exists(), f"not found: {src}"
  dst = Path(sys.argv[2]) if len(sys.argv) >= 3 else Path("flomo")
  convert(src, dst)

if __name__ == "__main__": main()
