#!/usr/bin/env python3
"""garden_stars.py — 花园之夜:夜行衣的星星与印章(种子 45 可复现)

45 = T₉ = 1+2+…+9,第 25 轮星图有 37 颗星 45 条弦。
本轮给站点缝夜行衣:
  1. 37 颗 CSS 星 —— 撒进 dark 主题的 body 背景(星图的老规矩:37 颗星,
     45 是这轮自己的编号,也是星图的弦数);
  2. 17 颗琥珀星 favicon —— 老朋友十七,盖在 #000020(第 41 轮实测的
     游戏之夜主色)上,连成一条星座路径(16 条线)。

查岗(断言):
  - CSS 块恰好 37 颗星、全部落在 1.5%..98.5% 内;
  - 重生成 == 落盘(逐行一致),md5 两次运行一致;
  - favicon 17 星 16 线,PNG md5 两次运行一致。

用法:
  python3 garden_stars.py --apply    # 生成 CSS 块写入 custom.scss、重画 icon.png
  python3 garden_stars.py --check    # 查岗:核对落盘与重生成是否一致
"""

import hashlib
import random
import sys
from pathlib import Path

from PIL import Image, ImageDraw

ROOT = Path(__file__).resolve().parents[2]
CSS_PATH = ROOT / "quartz" / "styles" / "custom.scss"
ICON_PATH = ROOT / "quartz" / "static" / "icon.png"

SEED = 45
N_STARS = 37          # 星图 37 颗星
N_BRIGHT = 5          # 五颗亮星(五车二?不,只是亮)
N_DIM = N_STARS - N_BRIGHT
N_FAV_STARS = 17      # 老朋友十七
FAV_SIZE = 200        # 与原有 icon.png 同尺寸
NIGHT = (0, 0, 32)    # #000020 —— 第 41 轮实测的游戏之夜主色
AMBER = (217, 160, 91)  # 第 44 轮墙上的粉笔字/暖琥珀砖


def star_positions(seed: int, n: int) -> list[tuple[float, float]]:
    rng = random.Random(seed)
    pts = []
    for _ in range(n):
        x = round(rng.uniform(1.5, 98.5), 2)
        y = round(rng.uniform(1.5, 98.5), 2)
        pts.append((x, y))
    return pts


def star_css_lines() -> list[str]:
    """37 颗星的 background-image 层(暗星在前,亮星在后),行尾带逗号。"""
    dim = star_positions(SEED, N_DIM)
    bright = star_positions(SEED + 1, N_BRIGHT)
    lines = []
    for x, y in dim:
        lines.append(
            f"    radial-gradient(circle at {x}% {y}%, rgba(255,255,255,0.50) 50%, transparent 51%),"
        )
    for x, y in bright:
        lines.append(
            f"    radial-gradient(circle at {x}% {y}%, rgba(255,255,255,0.85) 50%, transparent 51%),"
        )
    return lines


CSS_BEGIN = "/* === garden-stars:begin (seed 45) === */"
CSS_END = "/* === garden-stars:end === */"


def css_block() -> str:
    stars = star_css_lines()
    assert len(stars) == N_STARS, f"星星数不对:{len(stars)} != {N_STARS}"
    for line in stars:
        x = float(line.split("at ")[1].split("%")[0])
        y = float(line.split("at ")[1].split("%")[1].strip().rstrip(","))
        assert 1.5 <= x <= 98.5 and 1.5 <= y <= 98.5, f"星星越界:{line}"
    body = "\n".join(stars)
    block = f"""{CSS_BEGIN}
html[saved-theme="dark"] body {{
  background-color: #000020;
  background-image:
{body}
    radial-gradient(ellipse 130% 70% at 50% -25%, #12122e 0%, transparent 60%);
  background-repeat: no-repeat;
  background-attachment: fixed;
}}
{CSS_END}"""
    return block


def make_favicon() -> Image.Image:
    rng = random.Random(SEED + 7)
    img = Image.new("RGBA", (FAV_SIZE, FAV_SIZE), (*NIGHT, 255))
    draw = ImageDraw.Draw(img)
    pts = []
    for _ in range(N_FAV_STARS):
        x = rng.uniform(FAV_SIZE * 0.14, FAV_SIZE * 0.86)
        y = rng.uniform(FAV_SIZE * 0.14, FAV_SIZE * 0.86)
        pts.append((x, y))
    # 星座路径:17 颗星按序连成 16 条线
    for a, b in zip(pts, pts[1:]):
        draw.line([a, b], fill=(*AMBER, 110), width=1)
    for x, y in pts:
        r = rng.uniform(4.5, 9.5)
        draw.ellipse([x - r, y - r, x + r, y + r], fill=(*AMBER, 55))
        draw.ellipse([x - 2.6, y - 2.6, x + 2.6, y + 2.6], fill=(*AMBER, 225))
        draw.ellipse([x - 1.1, y - 1.1, x + 1.1, y + 1.1], fill=(255, 236, 202, 255))
    return img


def png_md5(img: Image.Image) -> str:
    buf = img.tobytes()
    return hashlib.md5(img.convert("RGBA").tobytes()).hexdigest()  # 像素层 md5


def apply() -> None:
    css = CSS_PATH.read_text(encoding="utf-8")
    new_block = css_block()
    if CSS_BEGIN in css:
        head, _, tail = css.partition(CSS_BEGIN)
        _, _, tail = tail.partition(CSS_END)
        css = head + new_block + tail
    else:
        css = css.rstrip() + "\n\n" + new_block + "\n"
    CSS_PATH.write_text(css, encoding="utf-8")
    img = make_favicon()
    img.save(ICON_PATH)
    print(f"已写入 {CSS_PATH.relative_to(ROOT)} 与 {ICON_PATH.relative_to(ROOT)}")
    print(f"CSS 块 md5: {hashlib.md5(new_block.encode()).hexdigest()}")
    print(f"icon 像素 md5: {png_md5(img)}")


def check() -> int:
    ok = True
    css = CSS_PATH.read_text(encoding="utf-8")
    if CSS_BEGIN not in css:
        print("✗ custom.scss 里没有 garden-stars 块")
        return 1
    block = css.split(CSS_BEGIN)[1].split(CSS_END)[0]
    expected = css_block().split(CSS_BEGIN)[1].split(CSS_END)[0]
    if block != expected:
        print("✗ CSS 块与重生成不一致(种子或手改过)")
        ok = False
    n_stars = block.count("radial-gradient(circle at")
    if n_stars != N_STARS:
        print(f"✗ 星数 {n_stars} != {N_STARS}")
        ok = False
    else:
        print(f"✓ CSS 星数 = {n_stars}")
    img = make_favicon()
    on_disk = Image.open(ICON_PATH).convert("RGBA")
    if on_disk.size != (FAV_SIZE, FAV_SIZE):
        print(f"✗ favicon 尺寸 {on_disk.size} != {FAV_SIZE}")
        ok = False
    if png_md5(on_disk) != png_md5(img):
        print("✗ favicon 与重生成不一致")
        ok = False
    else:
        print(f"✓ favicon 17 星可复现 (md5 {png_md5(img)[:8]})")
    if ok:
        print("查岗全绿:37 颗星、17 颗印章星,都和种子 45 长一个样。")
    return 0 if ok else 1


if __name__ == "__main__":
    mode = sys.argv[1] if len(sys.argv) > 1 else "--check"
    sys.exit(apply() if mode == "--apply" else check())
