#!/usr/bin/env python3
"""sync_youtube_shorts.py — YouTube で公開予約したショートの X 投稿を、公開の時刻に合わせて予定表に入れる。

元になるもの:
  sns/SHORT_ep{N}_sns_posts.json   ショートの X の文（posts.x_template。{url} がショートの URL に替わる）
  youtube_videos.json              エイイチの Mac が1時間ごとに書き出す自分のチャンネルの一覧
                                   （_制作ベース/youtube_schedule.py。説明文の manga/ep{N} から manga_ep を拾う）

やること:
  1. manga_ep（なければタイトル）で、ショートの json と YouTube の動画を結びつける
  2. 公開予約（または公開済み）の時刻を X の予約の時刻にする。同じ時刻に別の X の予約があれば、
     19:30 なら 20:00、それ以外は30分ずつ後ろへずらす（2026-10-24 エイイチの指示「19:30が重なったら動画は20:00」）
  3. まだ投稿していない予約は、YouTube 側で時刻を変えていたら合わせて直す
  4. 公開の時刻を過ぎていて、まだ予定表にないものは入れない（古いものを今さら出さない）
LinkedIn には入れない（動画は LinkedIn に直接アップロードする決まり。video_rules.md の 0節の22）。

使い方: python3 tools/sync_youtube_shorts.py --videos youtube_videos.json [--dry-run]
"""
import argparse, glob, json, os, re, sys
from datetime import datetime, timedelta, timezone

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
JST = timezone(timedelta(hours=9))
SCHEDULE = os.path.join(ROOT, "sns", "schedule.json")
POSTED = os.path.join(ROOT, "sns", "posted.json")


def norm(t):
    return re.sub(r"\s+", "", re.split(r"\s#", t or "")[0])


def load(p, d):
    try:
        return json.load(open(p, encoding="utf-8"))
    except FileNotFoundError:
        return d


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--videos", required=True)
    ap.add_argument("--dry-run", action="store_true")
    a = ap.parse_args()
    vids = [v for v in load(a.videos, {}).get("動画", []) if v.get("is_short")]
    sched = load(SCHEDULE, {"posts": []})
    posted = load(POSTED, {})
    done_ids = set(posted.keys()) if isinstance(posted, dict) else {p.get("id") for p in posted.get("posts", posted) if isinstance(p, dict)}
    now = datetime.now(timezone.utc)
    changes = []
    for path in sorted(glob.glob(os.path.join(ROOT, "sns", "SHORT_ep*_sns_posts.json"))):
        src = json.load(open(path, encoding="utf-8"))
        meta, posts = src["meta"], src["posts"]
        ep = meta["episode"]
        cand = [v for v in vids if v.get("manga_ep") == ep] or [v for v in vids if norm(v["title"]) == norm(meta.get("youtube_title"))]
        cand = [v for v in cand if v.get("publishAt") or v.get("published")]
        if not cand:
            continue
        v = sorted(cand, key=lambda v: v.get("publishAt") or v.get("published"))[-1]
        when = datetime.fromisoformat((v.get("publishAt") or v.get("published")).replace("Z", "+00:00"))
        sid = f"SHORT_ep{ep}-x"
        entry = next((e for e in sched["posts"] if e["id"] == sid), None)
        if entry and sid in done_ids:
            continue
        if not entry and when < now:
            continue
        url = f"https://youtube.com/shorts/{v['id']}"
        text = posts["x_template"].replace("{url}", url)
        if posts.get("x") != text or meta.get("url") != url or meta.get("youtube_id") != v["id"]:
            posts["x"], meta["url"], meta["youtube_id"] = text, url, v["id"]
            if not a.dry_run:
                with open(path, "w", encoding="utf-8") as f:
                    json.dump(src, f, ensure_ascii=False, indent=2); f.write("\n")
        # 同じ時刻の X の予約を避ける
        taken = {e["post_at"] for e in sched["posts"] if e["platform"] == "x" and e["id"] != sid}
        t = when.astimezone(JST)
        while t.isoformat() in taken:
            t = t.replace(hour=20, minute=0) if (t.hour, t.minute) == (19, 30) else t + timedelta(minutes=30)
        post_at = t.isoformat()
        if entry is None:
            sched["posts"].append({"id": sid, "source": os.path.relpath(path, ROOT), "platform": "x", "post_at": post_at})
            changes.append(f"足した {sid} {post_at} {v['title'][:30]}")
        elif entry["post_at"] != post_at:
            changes.append(f"時刻を直した {sid} {entry['post_at']} → {post_at}")
            entry["post_at"] = post_at
    sched["posts"].sort(key=lambda e: (e["post_at"], e["id"]))
    if changes and not a.dry_run:
        with open(SCHEDULE, "w", encoding="utf-8") as f:
            json.dump(sched, f, ensure_ascii=False, indent=2); f.write("\n")
    print("\n".join(changes) or "変わったものはありません")
    return 0


if __name__ == "__main__":
    sys.exit(main())
