"""予約投稿と YouTube の公開を、Google カレンダーに並べるための「あるべき予定の一覧」を出す（2026-10-25）。

カレンダーへの書き込みはしない。予定のタスク「SNS予約をカレンダーにそろえる」（毎日 18:10）の Claude が、
この一覧と Google カレンダーの予定を見比べて、足りない予定を足し・時刻のずれを直し・なくなった予定を消す。

使い方: python3 tools/calendar_plan.py [--days 60]   → 標準出力に JSON（予定の配列）

読むもの
  sns/schedule.json                 予定表（まだ出していない・時刻がまだ来ていない X / LinkedIn）
  sns/posted.json                   出したもの（ここにある id は並べない）
  sns/SHORT_ep*_sns_posts.json      ショートの YouTube の公開の時刻（meta.publish_at。tools/sync_youtube_shorts.py が入れる）
  sns/VIDEO_*_sns_posts.json        本編の YouTube の公開の時刻（同上）

予定の色（Google カレンダーの colorId。2026-10-25 エイイチの指示「X黒系・LinkedInブルー系・YouTube赤系」）
  X = 8（グラファイト） / LinkedIn = 9（ブルーベリー） / YouTube = 11（トマト）

予定の見分け方（key）。予定の説明の最後の行に「p2m-key: {key}」を入れる
  x:{id} / linkedin:{id}            予定表の1行（id は schedule.json の id）
  yt:{youtube_id}                   YouTube の公開
  li-video:{youtube_id}             LinkedIn に動画を手で上げる（動画は LinkedIn に直接アップロードする決まりのため）
"""
import argparse, glob, json, os, re
from datetime import datetime, timedelta, timezone

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
JST = timezone(timedelta(hours=9))
COLOR = {"x": "8", "linkedin": "9", "yt": "11", "li-video": "9"}


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


def short_title(t):
    """YouTube の題名の「 ― 」「｜」より前（カレンダーの予定の名前に使う）"""
    return re.split(r"\s*[―｜|]\s*", t or "")[0].strip()


def label(src, path):
    m = src.get("meta", {})
    base = os.path.basename(path)
    if base.startswith("SHORT_ep"):
        return f"ショート 第{m.get('episode')}話", "short"
    if base.startswith("VIDEO_"):
        return f"本編「{short_title(m.get('youtube_title') or m.get('title'))}」", "video"
    name = re.sub(r"(_linkedin_ja)?_sns_posts\.json$|_linkedin_ja\.json$", "", base)
    return name, "article"


def ev(key, summary, start, desc):
    s = start.astimezone(JST)
    return {"key": key, "summary": summary, "colorId": COLOR[key.split(":")[0]],
            "startTime": s.strftime("%Y-%m-%dT%H:%M:%S"), "endTime": (s + timedelta(minutes=15)).strftime("%Y-%m-%dT%H:%M:%S"),
            "description": desc.rstrip() + f"\n\np2m-key: {key}"}


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--days", type=int, default=60)
    a = ap.parse_args()
    now = datetime.now(timezone.utc)
    until = now + timedelta(days=a.days)
    sched = load("sns/schedule.json", {"posts": []})
    posted = load("sns/posted.json", {})
    out = []
    for e in sched.get("posts", []):
        t = datetime.fromisoformat(e["post_at"])
        if e["id"] in posted or not (now < t <= until):
            continue
        src = load(e["source"], {})
        m, p = src.get("meta", {}), src.get("posts", {})
        name, _ = label(src, e["source"])
        en = e.get("lang") == "en"
        if e["platform"] == "x":
            body = p.get("x_en" if en else "x", "")
            desc = f"{m.get('title_en' if en else 'title', '') or m.get('youtube_title', '')}\n{m.get('url_en' if en else 'url', '')}\n\nX の本文:\n{body}"
            out.append(ev(f"x:{e['id']}", f"SNS予約 {name}（X{'・英語' if en else ''}）", t, desc))
        else:
            body = p.get("linkedin_en" if en else "linkedin", "")
            head = body[:120] + ("…" if len(body) > 120 else "")
            desc = f"{m.get('title_en' if en else 'title', '')}\n{m.get('url_en' if en else 'url', '')}\n\nLinkedIn の本文（冒頭）:\n{head}"
            out.append(ev(f"linkedin:{e['id']}", f"SNS予約 {name}（LinkedIn{'・英語' if en else ''}）", t, desc))
    paths = sorted(glob.glob(os.path.join(ROOT, "sns", "SHORT_ep*_sns_posts.json"))) + \
        sorted(glob.glob(os.path.join(ROOT, "sns", "VIDEO_*_sns_posts.json")))
    for path in paths:
        src = json.load(open(path, encoding="utf-8"))
        m = src.get("meta", {})
        if not m.get("publish_at") or not m.get("youtube_id"):
            continue
        t = datetime.fromisoformat(m["publish_at"])
        if not (now < t <= until):
            continue
        name, kind = label(src, path)
        yid = m["youtube_id"]
        watch = f"https://www.youtube.com/watch?v={yid}"
        title = m.get("youtube_title") or m.get("title", "")
        out.append(ev(f"yt:{yid}", f"YouTube公開 {name}", t, f"{title}\n{watch}"))
        note = "YouTube の公開と同じ時刻。LinkedIn には動画を直接アップロードする決まり（自動では出ない）。"
        if kind == "video":
            note += f"文は iCloud の 動画/{m.get('title', '')}/投稿文_X_LinkedIn.txt"
        out.append(ev(f"li-video:{yid}", f"LinkedIn 動画を上げる（手で）{name}", t, f"{note}\nYouTube: {watch}"))
    out.sort(key=lambda x: (x["startTime"], x["key"]))
    print(json.dumps(out, ensure_ascii=False, indent=1))


if __name__ == "__main__":
    main()
