"""記事の図解に共通の形を入れる（STYLE.md の「斜線と塗り」「角丸」・article_rules の HTML図解。2026-11-08）。
外側の枠をやめて白いカード、中の箱は灰の面、ボタンは角丸4px、小さい英字ラベルは灰、斜めの斜線は細かく。
色の意味づけ（青・赤・緑）は入れない（1本ずつ別に入れる）。"""
import re, sys, pathlib

INK = r"(?:#242424|var\(--(?:ink|black|fg|blk|bk)\)|#000|#0a0a0a|#111)"
PAPERS = ("#f7f7f7", "var(--paper)", "var(--bg)")

def split_rules(css):
    out = []; i = 0; n = len(css)
    while i < n:
        j = css.find('{', i)
        if j < 0: out.append(('raw', css[i:])); break
        sel = css[i:j]
        if sel.strip().startswith('@'):
            depth = 1; k = j + 1
            while k < n and depth:
                if css[k] == '{': depth += 1
                elif css[k] == '}': depth -= 1
                k += 1
            out.append(('at', sel, css[j+1:k-1])); i = k; continue
        k = css.find('}', j)
        if k < 0: out.append(('raw', css[i:])); break
        out.append(('rule', sel, css[j+1:k])); i = k + 1
    return out

def join_rules(rs, fix):
    s = []
    for r in rs:
        if r[0] == 'raw': s.append(r[1])
        elif r[0] == 'at':
            inner = r[2]
            if '{' in inner and r[1].strip().startswith('@media'):
                inner = join_rules(split_rules(inner), lambda sel, d: fix(sel, d, media=True))
            s.append(r[1] + '{' + inner + '}')
        else:
            s.append(r[1] + '{' + fix(r[1], r[2]) + '}')
    return ''.join(s)

def has(d, prop): return re.search(r'(^|;)\s*' + prop + r'\s*:', d) is not None
def drop(d, prop): return re.sub(r'(^|;)\s*' + prop + r'\s*:[^;]*', r'\1', d)
def setp(d, prop, val):
    d = drop(d, prop); d = d.rstrip().rstrip(';')
    return (d + ';' if d.strip() else '') + prop + ':' + val

def fix_factory(wrap_sel):
    def fix(sel, d, media=False):
        s = sel.strip()
        # body
        if re.fullmatch(r'(html\s*,\s*)?body', s) and has(d, 'padding'):
            d = setp(d, 'padding', '8px 0')
        # 外側のカード
        if wrap_sel and re.search(r'(^|[\s,])' + re.escape(wrap_sel) + r'\s*$', s):
            d = drop(d, 'border')
            if not media:
                d = setp(d, 'background', '#fff'); d = setp(d, 'border-radius', '8px'); d = setp(d, 'padding', '28px 24px')
            elif has(d, 'padding'):
                d = setp(d, 'padding', '20px 14px')
            return d
        isbtn = 'button' in s or re.search(r'\.(btn|tab|seg|chip)\b', s)
        pressed = re.search(r'aria-pressed|aria-selected|\.on\b|\.active|\.sel\b|:hover|\.cur\b|\.is-', s)
        if isbtn:
            if pressed:
                if re.search(r'background\s*:\s*' + INK, d): d = setp(d, 'border-color', '#242424'); d = re.sub(r'color\s*:\s*(#f7f7f7|var\(--paper\)|var\(--bg\))', 'color:#fff', d)
            else:
                if re.search(r'border\s*:\s*1(\.5)?px\s+solid', d):
                    d = re.sub(r'border\s*:\s*1(\.5)?px\s+solid\s+[^;]+', 'border:1px solid #e6e6e6', d); d = setp(d, 'border-radius', '4px')
                    d = re.sub(r'background\s*:\s*(#f7f7f7|var\(--paper\)|var\(--bg\))', 'background:#fff', d)
            return d
        # 小さい英字のラベル
        m = re.search(r'letter-spacing\s*:\s*\.(1[0-9]|2[0-9])em', d)
        fs = re.search(r'font-size\s*:\s*(\d+(?:\.\d+)?)px', d) or re.search(r'font\s*:[^;]*?(\d+(?:\.\d+)?)px', d)
        if m and fs and float(fs.group(1)) <= 13:
            d = re.sub(r'letter-spacing\s*:\s*\.\d+em', 'letter-spacing:.08em', d)
            if not re.search(r'(^|;)\s*color\s*:', d): d = setp(d, 'color', '#888')
        # 表のマス
        if re.search(r'(^|[\s,>])(td|th)\b', s) and re.search(r'border(-\w+)?\s*:\s*1(\.5)?px\s+solid', d):
            d = re.sub(r'(border(?:-\w+)?)\s*:\s*1(\.5)?px\s+solid\s+[^;]+', r'\1:1px solid #e6e6e6', d)
            return d
        # 枠と余白のある箱 → 灰の面・角丸8px（データの棒などは padding がないので外れる）
        if re.search(r'(^|;)\s*border\s*:\s*1(\.5)?px\s+solid\s+' + INK, d) and has(d, 'padding') and not re.search(r'bar|seg|cell|dot|col\b|fill|chart|svg|canvas|grid', s):
            dark = re.search(r'background(-color)?\s*:\s*' + INK, d)
            d = drop(d, 'border'); d = setp(d, 'border-radius', '8px')
            if not dark: d = setp(d, 'background', '#f7f7f7')
            return d
        # 区切りの線（上か下だけ）
        if re.search(r'border-(top|bottom)\s*:\s*1(\.5)?px\s+solid\s+' + INK, d) and has(d, 'padding') and not re.search(r'chart|axis|ax\b|bar|plot|track|scale|tl\b', s):
            d = re.sub(r'(border-(?:top|bottom))\s*:\s*1(\.5)?px\s+solid\s+' + INK, r'\1:1px solid #e6e6e6', d)
        return d
    return fix

def hatch(css):
    # 斜めの斜線を、1年のページと同じ細かさ（線2px・4px おき・-45°）にする。色は1本目の色のまま
    def r(m):
        ang, col, rest = m.group(1), m.group(2), m.group(3)
        return 'repeating-linear-gradient(-45deg,%s 0 2px,transparent 2px 4px)' % col
    return re.sub(r'repeating-linear-gradient\(\s*(-?45deg|135deg)\s*,\s*((?:#[0-9a-fA-F]{3,8}|var\(--[\w-]+\)|rgba?\([^)]*\)))\s+0(?:px)?\s+[\d.]+px\s*,\s*([^)]*?)\)', r, css)

def process(path):
    h = path.read_text(encoding='utf-8'); o = h
    m = re.search(r'<style>(.*?)</style>', h, re.S)
    if not m: return False
    css = m.group(1)
    w = re.search(r'([.#][A-Za-z_][\w-]*)\s*\{[^}]*max-width\s*:\s*7[0-9]0px', css)
    css2 = join_rules(split_rules(css), fix_factory(w.group(1) if w else None))
    css2 = hatch(css2)
    h = h[:m.start(1)] + css2 + h[m.end(1):]
    h = hatch(h)  # JS の中の style 文字列
    if h != o: path.write_text(h, encoding='utf-8'); return True
    return False

if __name__ == '__main__':
    n = 0
    for name in open(sys.argv[1]).read().split():
        n += process(pathlib.Path('public/embeds') / name)
    print('changed', n)
