#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
count_image_budget.py — AI画像の「月あたり必要生成回数」を自分の数字で出す

Canva の AI 利用枠（無料版は月20回）で足りるかどうかは、
「月に何点仕上げるか」ではなく「月に何回生成するか」で決まる。
作り直した回数がそのまま枠を削るため、必要点数に作り直し倍率を掛けないと見積りを外す。

このスクリプトは2つを数える。

  1) 記事・ページ1本あたりに実際に載っている画像の点数（中央値・平均・最大・0点の本数）
  2) 生成ツールが出力した枚数と、実際に採用した点数の比（作り直し倍率）

読み取りのみ。ファイルを書き換えず、外部送信もしない。標準ライブラリだけで動く。

使い方:

    # 1) 記事に載っている画像点数だけを数える
    python count_image_budget.py --content ./content

    # 2) 生成フォルダと採用フォルダを与えて作り直し倍率も出す
    python count_image_budget.py --content ./content \
        --generated ~/.codex/generated_images \
        --adopted ./assets/img \
        --since 2026-08-01 --until 2026-08-31

    # 3) 月に仕上げたい点数を指定して、必要な生成回数を出す
    python count_image_budget.py --content ./content --target-per-month 12

引数の意味:
    --content   記事ファイル（.md / .html / .mdx）が入っているディレクトリ
    --generated 画像生成ツールの出力ディレクトリ（再帰的に画像ファイルを数える）
    --adopted   実際にサイトへ載せた画像を置いているディレクトリ
    --since / --until  対象期間（ファイル更新日で絞る。YYYY-MM-DD）
    --raster-only  採用側をラスタ画像（png/jpg/webp）だけに絞る。手書きのSVG図解を除きたいとき
"""

import argparse
import datetime as dt
import os
import re
import statistics
import sys

ARTICLE_EXT = (".md", ".mdx", ".html", ".htm")
IMAGE_EXT = (".png", ".jpg", ".jpeg", ".webp", ".gif", ".avif")
RASTER_EXT = (".png", ".jpg", ".jpeg", ".webp", ".avif")

IMG_TAG = re.compile(r"<img[^>]*>", re.I)
MD_IMG = re.compile(r"!\[[^\]]*\]\([^)]+\)")


def parse_date(value):
    if not value:
        return None
    return dt.datetime.strptime(value, "%Y-%m-%d").date()


def in_range(path, since, until):
    if since is None and until is None:
        return True
    day = dt.datetime.fromtimestamp(os.path.getmtime(path)).date()
    if since and day < since:
        return False
    if until and day > until:
        return False
    return True


def walk(root, extensions, since=None, until=None):
    found = []
    for base, _dirs, files in os.walk(root):
        for name in files:
            if not name.lower().endswith(extensions):
                continue
            path = os.path.join(base, name)
            if in_range(path, since, until):
                found.append(path)
    return found


def count_images_in_article(path):
    try:
        text = open(path, encoding="utf-8", errors="replace").read()
    except OSError:
        return 0
    return len(IMG_TAG.findall(text)) + len(MD_IMG.findall(text))


def report_articles(content_dir, since, until):
    articles = walk(content_dir, ARTICLE_EXT, since, until)
    if not articles:
        print("記事ファイルが見つかりませんでした: %s" % content_dir)
        return None
    counts = [count_images_in_article(p) for p in articles]
    zero = len([c for c in counts if c == 0])
    print("")
    print("== 記事1本あたりの画像点数 ==")
    print("  対象記事        %d 本" % len(articles))
    print("  画像の総点数    %d 点" % sum(counts))
    print("  中央値          %s 点" % statistics.median(counts))
    print("  平均            %.1f 点" % (sum(counts) / len(counts)))
    print("  最大            %d 点" % max(counts))
    print("  画像0点の記事   %d 本 (%.0f%%)" % (zero, 100.0 * zero / len(articles)))
    heavy = sorted(zip(counts, articles), reverse=True)[:5]
    print("  画像が多い順:")
    for c, p in heavy:
        print("    %3d 点  %s" % (c, os.path.relpath(p, content_dir)))
    return statistics.median(counts)


def report_yield(generated_dir, adopted_dir, since, until, raster_only):
    if not generated_dir or not adopted_dir:
        return None
    if not os.path.isdir(generated_dir):
        print("生成ディレクトリが見つかりません: %s" % generated_dir)
        return None
    if not os.path.isdir(adopted_dir):
        print("採用ディレクトリが見つかりません: %s" % adopted_dir)
        return None
    gen = walk(generated_dir, IMAGE_EXT, since, until)
    ext = RASTER_EXT if raster_only else IMAGE_EXT
    adopted = walk(adopted_dir, ext, since, until)
    print("")
    print("== 生成 → 採用の歩留まり ==")
    print("  生成した枚数    %d 枚" % len(gen))
    print("  採用した点数    %d 点" % len(adopted))
    if not adopted:
        print("  採用が0点のため倍率は出せません")
        return None
    if not gen:
        print("  生成が0枚のため倍率は出せません")
        return None
    rate = 100.0 * len(adopted) / len(gen)
    ratio = float(len(gen)) / len(adopted)
    print("  採用率          %.1f%%" % rate)
    print("  作り直し倍率    %.1f 回 / 1点" % ratio)
    if ratio > 2:
        print("  ※ 捨てた枚数のほうが多い状態です。枠はこの倍率で削られます")
    return ratio


def report_budget(median_per_article, ratio, target_points, articles_per_month):
    print("")
    print("== 月あたりの必要生成回数 ==")
    if ratio is None:
        ratio = 1.0
        print("  作り直し倍率が測れなかったため 1.0 倍で計算します（実際はこれより多くなります）")
    points = target_points
    if points is None:
        if articles_per_month is None or median_per_article is None:
            print("  --target-per-month か --articles-per-month を指定すると必要回数を計算します")
            return
        points = articles_per_month * float(median_per_article)
        print("  月の記事本数 %d 本 × 1本あたり中央値 %s 点 = %.1f 点" %
              (articles_per_month, median_per_article, points))
    need = points * ratio
    print("  仕上げたい点数  %.1f 点" % points)
    print("  必要な生成回数  %.0f 回 / 月" % round(need))
    print("")
    print("  参考: Canva の AI 利用枠（2026年9月13日 公式ヘルプ確認）")
    print("    無料版 / プロLite        月20回（標準・高品質の合算。最高品質は利用不可）")
    print("    Canvaプロ / チームス     高品質 月200回 / 最高品質 月20回（標準は枠を消費しない）")
    print("    Canvaビジネス            高品質 月400回 / 最高品質 月40回（標準は枠を消費しない）")
    print("")
    if need <= 20:
        print("  → 無料版の20回に収まる可能性があります")
    elif need <= 200:
        print("  → 無料版では足りません。Canvaプロの高品質200回が目安になります")
    elif need <= 400:
        print("  → Canvaビジネスの高品質400回が目安になります")
    else:
        print("  → 400回も超えます。AIパスの追加、または生成ツールの併用を検討してください")
    print("")
    print("  ※ 公式の回数は「1種類のAI機能だけを簡単なタスクで使った場合」の目安です。")
    print("     複雑な指示や高機能のAIほど1回あたりの消費が増えるため、実際の回数は下振れします。")


def main():
    parser = argparse.ArgumentParser(description="AI画像の月あたり必要生成回数を実測から出す")
    parser.add_argument("--content", required=True, help="記事ファイルのディレクトリ")
    parser.add_argument("--generated", help="画像生成ツールの出力ディレクトリ")
    parser.add_argument("--adopted", help="実際にサイトへ載せた画像のディレクトリ")
    parser.add_argument("--since", help="対象期間の開始 YYYY-MM-DD")
    parser.add_argument("--until", help="対象期間の終了 YYYY-MM-DD")
    parser.add_argument("--raster-only", action="store_true",
                        help="採用側をpng/jpg/webpに限定する（手書きSVGを除く）")
    parser.add_argument("--target-per-month", type=float, help="月に仕上げたい画像の点数")
    parser.add_argument("--articles-per-month", type=int, help="月に出す記事の本数")
    args = parser.parse_args()

    since = parse_date(args.since)
    until = parse_date(args.until)

    print("count_image_budget.py — 読み取りのみ。ファイルは変更しません")
    if since or until:
        print("対象期間: %s 〜 %s" % (args.since or "指定なし", args.until or "指定なし"))

    median_per_article = report_articles(args.content, since, until)
    ratio = report_yield(args.generated, args.adopted, since, until, args.raster_only)
    report_budget(median_per_article, ratio, args.target_per_month, args.articles_per_month)
    return 0


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