#!/usr/bin/env python3
"""根据 comps.yaml 生成 rental-260705 的中间数据表、页面和媒体目录。

- 修复 租房260705/ 下乱码目录名
- 将拆分字段保存为 data.yaml（中间表格）
- HTML 中用手工组织的自然语言展示，不再细碎地列出所有子字段
- 媒体通过 fetch('media.json') 动态加载，使用 BASE_PATH 解析绝对路径
- 生成压缩图、缩略图、media.json、data.yaml 和 comps.html
"""
import json
import re
import shutil
import subprocess
from pathlib import Path

import yaml
from PIL import Image

BASE_DIR = Path(__file__).parent.resolve()  # html_logs/
SRC_DIR = BASE_DIR / "租房260705"
SRC_DIR_260630 = BASE_DIR / "租房260630"
OUT_DIR = BASE_DIR / "rental-260705"

# 乱码目录名修复：原始乱码 -> 正确中文 -> slug
DIR_RENAME = {
    "厂洼2号院5号楼": "changwa-2-5",
    "厂洼一层": "changwa-1-102",
    "厂洼六层": "changwa-1-602",
    "理工附中家属楼南14": "ligong-fushu-nan14",
    "稻西4号楼8-1201": "daoxiang-4-1201",
    "稻西6号楼1207": "daoxiang-6-1207",
    "车道沟10号院": "chedaogou-10",
    "车道沟社区": "chedaogou-shequ-6-13",
    "厂洼17": "changwa-17",
    "汇新家园": "huixin-jiayuan",
}

# 6 月 30 日房源的目录 -> slug（用在 260705 综合页里）
DIR_TO_SLUG_260630 = {
    "万北2-711": "wanquanzhuangbei-2-711",
    "万北4-310": "wanquanzhuangbei-4-310",
    "小南庄24": "xiaonanzhuang-24-762",
    "红5楼": "lixinhong-5-1-6",
    "三义庙": "sanyimiao-1ceng",
    "财智公馆G": "caizhihuiguan-G",
    "财智公馆3": "caizhihuiguan-3",
}

SLUGS = {
    1: "wanquanzhuangbei-2-711",
    2: "wanquanzhuangbei-4-310",
    3: "xiaonanzhuang-24-762",
    4: "lixinhong-5-1-6",
    5: "sanyimiao-1ceng",
    6: "caizhihuiguan-G",
    7: "caizhihuiguan-3",
    8: "nengjia-gongyu",
    9: "ligong-fushu-nan14",
    10: "chedaogou-shequ-6-13",
    11: "chedaogou-10",
    12: "changwa-1-102",
    13: "changwa-1-602",
    14: "changwa-2-5",
    15: "changwa-17",
    16: "daoxiang-4-1201",
    17: "daoxiang-6-1207",
    18: "huixin-jiayuan",
}

# 结构化字段 + 手工组织的自然语言概述
OVERRIDES = {
    1: {
        "小区": "万泉庄北社区",
        "楼号": "2号楼",
        "房号": "711",
        "户型": "两室一厅",
        "面积": "60多平",
        "朝向": "东西向",
        "概述": "万泉庄北社区2号楼711，两室一厅约60多平，东西向。全瓷砖；东卧偏小、墙面有点脏，配的是小床带顶柜和硬床垫，西卧与原先房子差不多大，窗外全是树。",
    },
    2: {
        "小区": "万泉庄北社区",
        "楼号": "4号楼",
        "房号": "310",
        "户型": "一室一厅",
        "面积": "40多平",
        "朝向": "朝南",
        "概述": "万泉庄北社区4号楼310，一室一厅40多平，朝南，有阳台，木地板，装修质量一般。",
    },
    3: {
        "小区": "小南庄",
        "楼号": "24号楼",
        "房号": "762",
        "户型": "一室一厅",
        "面积": "40多平",
        "朝向": "朝南",
        "概述": "小南庄24号楼762，一室一厅40多平，朝南，有阳台，木地板。底价5200，另加10%服务费。",
    },
    4: {
        "小区": "立新红",
        "楼号": "5号楼",
        "单元": "1单元",
        "楼层": "6层",
        "面积": "20来平",
        "朝向": "朝南",
        "概述": "立新红5号楼1单元6层，20来平单间，朝南，窗外是紫金庄园，两户合租。楼道环境一般，一层是商户。",
    },
    5: {
        "小区": "三义庙",
        "楼层": "1层",
        "面积": "十几平",
        "朝向": "朝南",
        "概述": "三义庙1层，十几平狭长隔断房，朝南，木地板，四户合租。",
    },
    6: {
        "小区": "财智会馆",
        "楼层": "G层（二层）",
        "户型": "一室一厅",
        "面积": "45平",
        "朝向": "朝南",
        "概述": "财智会馆G层（二层），一室一厅45平，朝南，瓷砖，只有一小扇窗能开，装修比较新。",
    },
    7: {
        "小区": "财智会馆",
        "楼层": "3层（四层）",
        "户型": "一室一厅",
        "面积": "48平",
        "朝向": "朝东",
        "概述": "财智会馆3层（四层），一室一厅48平，朝东，木地板，柜子有木头味。",
    },
    8: {
        "小区": "展览馆路小区",
        "楼号": "能嘉公寓",
        "户型": "一室一厅公寓",
        "概述": "展览馆路小区能嘉公寓，一室一厅公寓，商电1.5元/度，供暖费400/月，其他免费，不可养宠物。",
    },
    9: {
        "小区": "理工附中家属楼",
        "房号": "南14",
        "户型": "平房（一室一厅+阁楼）",
        "面积": "40平左右",
        "朝向": "门朝北，南边有六层楼",
        "概述": "理工附中家属楼南14，平房带一室一厅和阁楼，共40平左右，门朝北，门外有种菜，墙是砖墙做了保温，南边有六层楼，冬天采光可能差点。",
    },
    10: {
        "小区": "车道沟社区",
        "楼号": "6号楼",
        "楼层": "13层",
        "户型": "一室一厅",
        "面积": "40平左右",
        "朝向": "朝南",
        "概述": "车道沟社区6号楼13层，一室一厅40平左右，朝南，视野宽阔，房东直租。",
    },
    11: {
        "小区": "车道沟10号院",
        "户型": "复式",
        "面积": "200多平",
        "概述": "车道沟10号院，200多平复式，一层住俩女生，二层住俩人加主卧和房东。",
    },
    12: {
        "小区": "厂洼2号院",
        "楼号": "1号楼",
        "房号": "102",
        "户型": "跳台",
        "概述": "厂洼2号院1号楼102，跳台，有霉点。",
    },
    13: {
        "小区": "厂洼2号院",
        "楼号": "1号楼",
        "房号": "602",
        "户型": "打隔断的两室一厅（按一室一厅租）",
        "概述": "厂洼2号院1号楼602，打隔断的两室一厅按一室一厅出租，另一家主卧带独卫不共用厨卫，明厨明卫朝南。",
    },
    14: {
        "小区": "厂洼2号院",
        "楼号": "5号楼",
        "户型": "主卧带独卫",
        "概述": "厂洼2号院5号楼，主卧带独卫，4家合租。",
    },
    15: {
        "小区": "厂洼小区",
        "楼号": "17号楼",
        "概述": "厂洼小区17号楼，3500能谈，还没去看。",
    },
    16: {
        "小区": "稻香园西里",
        "楼号": "4号楼",
        "单元": "8单元",
        "房号": "1201",
        "户型": "一室",
        "朝向": "朝西南",
        "概述": "稻香园西里4号楼8单元1201，一室朝西南，三家合租，前租户养猫。",
    },
    17: {
        "小区": "稻香园西里",
        "楼号": "6号楼",
        "房号": "1207",
        "户型": "未说明",
        "朝向": "朝北/朝西",
        "概述": "稻香园西里6号楼1207，大窗户朝北、小窗户朝西，四家合租，屋里有股味。",
    },
    18: {
        "小区": "汇新家园",
        "楼号": "4号楼",
        "房号": "502",
        "户型": "合租单间（客厅隔断）",
        "朝向": "朝东",
        "概述": "汇新家园4号楼502，朝东，三家合租，两个30多岁男的，采光一般，房间是客厅隔出来的，空间还行。",
    },
}

IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".webp", ".gif"}
VIDEO_EXTS = {".mp4", ".mov", ".webm", ".mkv"}
FULL_MAX_SIZE = 1600
FULL_QUALITY = 80
THUMB_MAX_SIZE = 480
THUMB_QUALITY = 75
VIDEO_MAX_WIDTH = 1280
VIDEO_CRF = 28


def fix_dir_names():
    """把乱码目录重命名为 slug。"""
    if not SRC_DIR.exists():
        print(f"源目录不存在：{SRC_DIR}")
        return []
    renamed = []
    for d in sorted(SRC_DIR.iterdir()):
        if not d.is_dir():
            continue
        decoded = None
        try:
            decoded = d.name.encode('cp866').decode('utf-8')
        except Exception:
            decoded = d.name
        if decoded not in DIR_RENAME:
            print(f"未在映射表中找到：{decoded}")
            continue
        slug = DIR_RENAME[decoded]
        target = SRC_DIR / slug
        if target.exists():
            print(f"目标已存在，跳过：{slug}")
        else:
            d.rename(target)
            print(f"重命名：{decoded} -> {slug}")
        renamed.append(slug)
    return renamed


def compress_image(src: Path, dst: Path) -> bool:
    try:
        dst.parent.mkdir(parents=True, exist_ok=True)
        with Image.open(src) as im:
            if im.mode in ("RGBA", "P"):
                im = im.convert("RGB")
            im.thumbnail((FULL_MAX_SIZE, FULL_MAX_SIZE), Image.LANCZOS)
            im.save(dst, "JPEG", quality=FULL_QUALITY, optimize=True)
        return True
    except Exception as e:
        print(f"  [图片压缩失败] {src}: {e}")
        return False


def make_thumbnail(src: Path, dst: Path) -> bool:
    try:
        dst.parent.mkdir(parents=True, exist_ok=True)
        with Image.open(src) as im:
            if im.mode in ("RGBA", "P"):
                im = im.convert("RGB")
            im.thumbnail((THUMB_MAX_SIZE, THUMB_MAX_SIZE), Image.LANCZOS)
            im.save(dst, "JPEG", quality=THUMB_QUALITY, optimize=True)
        return True
    except Exception as e:
        print(f"  [缩略图失败] {src}: {e}")
        return False


def compress_video(src: Path, dst: Path) -> bool:
    dst.parent.mkdir(parents=True, exist_ok=True)
    cmd = [
        "ffmpeg", "-y", "-i", str(src),
        "-c:v", "libx264", "-crf", str(VIDEO_CRF), "-preset", "medium",
        "-vf", f"scale='min({VIDEO_MAX_WIDTH},iw)':-2,format=yuv420p",
        "-c:a", "aac", "-b:a", "128k", "-movflags", "+faststart",
        str(dst),
    ]
    try:
        subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        return True
    except subprocess.CalledProcessError as e:
        print(f"  [视频压缩失败] {src}: {e}")
        return False


def build_media():
    """处理 租房260705/ 和 租房260630/ 下的媒体，输出到 rental-260705/media/。"""
    OUT_DIR.mkdir(parents=True, exist_ok=True)
    media_dir = OUT_DIR / "media"
    media_dir.mkdir(exist_ok=True)
    data = {}

    for slug in sorted(DIR_RENAME.values()):
        src = SRC_DIR / slug
        if not src.exists():
            continue
        print(f"处理媒体 {slug}")
        _process_media_dir(src, slug, data, media_dir)

    for slug in sorted(DIR_TO_SLUG_260630.values()):
        # 源目录是中文名，用映射表查找
        src_name = {v: k for k, v in DIR_TO_SLUG_260630.items()}[slug]
        src = SRC_DIR_260630 / src_name
        if not src.exists():
            continue
        print(f"处理媒体 260630/{slug}")
        _process_media_dir(src, slug, data, media_dir)

    with open(OUT_DIR / "media.json", "w", encoding="utf-8") as fp:
        json.dump(data, fp, ensure_ascii=False, indent=2)
    print(f"已生成 {OUT_DIR / 'media.json'}")
    return data


def _process_media_dir(src: Path, slug: str, data: dict, media_dir: Path):
    data[slug] = {"images": [], "thumbnails": [], "videos": []}
    dst_base = media_dir / slug
    thumb_dir = dst_base / "thumbs"
    thumb_dir.mkdir(parents=True, exist_ok=True)

    for f in sorted(src.iterdir()):
        if not f.is_file() or f.parent.name == "thumbs":
            continue
        ext = f.suffix.lower()
        if ext in IMAGE_EXTS:
            out_name = f.stem + ".jpg"
            full_dst = dst_base / out_name
            thumb_dst = thumb_dir / out_name
            if compress_image(f, full_dst):
                data[slug]["images"].append(f"media/{slug}/{out_name}")
            if make_thumbnail(f, thumb_dst):
                data[slug]["thumbnails"].append(f"media/{slug}/thumbs/{out_name}")
        elif ext in VIDEO_EXTS:
            out_name = f.stem + ".mp4"
            video_dst = dst_base / out_name
            if compress_video(f, video_dst):
                data[slug]["videos"].append(f"media/{slug}/{out_name}")


def build_data(media: dict):
    """生成中间表格 data.yaml。"""
    with open(BASE_DIR / "comps.yaml", encoding="utf-8") as f:
        raw = yaml.safe_load(f)

    entries = []
    for i, item in enumerate(raw, start=1):
        e = {
            "id": i,
            "slug": SLUGS[i],
            "位置": item.get("位置", ""),
            "小区": "",
            "楼号": "",
            "单元": "",
            "楼层": "",
            "房号": "",
            "户型": "",
            "面积": "",
            "朝向": "",
            "情况": item.get("情况", ""),
            "厨卫": item.get("厨卫") or "",
            "费用": item.get("费用") or "",
            "其他": item.get("其他") or "",
            "到单位通勤距离": "",
            "到现在居住地通勤距离": "",
        }
        e.update(OVERRIDES.get(i, {}))
        e["media"] = media.get(e["slug"], {"images": [], "thumbnails": [], "videos": []})
        entries.append(e)

    with open(OUT_DIR / "data.yaml", "w", encoding="utf-8") as fp:
        yaml.dump(entries, fp, allow_unicode=True, sort_keys=False)
    print(f"已生成 {OUT_DIR / 'data.yaml'}")
    return entries


def extract_service_fee(fee: str) -> str:
    if not fee:
        return ""
    m = re.search(r'\+(\d+(?:\.\d+)?%)服务费', fee)
    if m:
        return m.group(1)
    m = re.search(r'中介费(\d+个月)', fee)
    if m:
        return f"中介{m.group(1)}"
    if "无其他费用" in fee:
        return "无"
    return ""


def location_summary(e: dict) -> str:
    parts = [e.get("小区", "")]
    for k in ["楼号", "单元", "楼层", "房号"]:
        v = e.get(k, "")
        if v:
            parts.append(v)
    return " ".join(p for p in parts if p)


def type_summary(e: dict) -> str:
    parts = [e.get("户型", ""), e.get("面积", ""), e.get("朝向", "")]
    return "，".join(p for p in parts if p) or "-"


def render_html(entries: list, title: str = "看房对比", subtitle: str = "") -> str:
    rows = []
    for e in entries:
        rows.append(
            f"        <tr data-href=\"#{e['slug']}\">\n"
            f"          <td data-label=\"位置\">{location_summary(e)}</td>\n"
            f"          <td data-label=\"户型/面积/朝向\">{type_summary(e)}</td>\n"
            f"          <td data-label=\"费用\">{e.get('费用', '') or '-'}</td>\n"
            f"          <td data-label=\"服务费\">{extract_service_fee(e.get('费用','')) or '-'}</td>\n"
            f"          <td data-label=\"厨卫\">{e.get('厨卫', '') or '-'}</td>\n"
            f"        </tr>"
        )
    rows_html = "\n".join(rows)

    cards = []
    for e in entries:
        title = e.get("位置", "") or location_summary(e)
        overview = e.get("概述", e.get("情况", ""))
        cards.append(
            f"      <article class=\"card\" id=\"{e['slug']}\">\n"
            f"        <h2>{title}</h2>\n"
            f"        <p class=\"overview\">{overview}</p>\n"
            f"        <div class=\"meta\">\n"
            f"          <p><strong>厨卫：</strong>{e.get('厨卫', '') or '—'}</p>\n"
            f"          <p><strong>费用：</strong>{e.get('费用', '') or '—'}</p>\n"
            f"          <p><strong>到单位通勤：</strong>{e.get('到单位通勤距离', '') or '—'}</p>\n"
            f"          <p><strong>到现居地通勤：</strong>{e.get('到现在居住地通勤距离', '') or '—'}</p>\n"
            f"          <p><strong>其他：</strong>{e.get('其他', '') or '—'}</p>\n"
            f"        </div>\n"
            f"        <section class=\"media\">\n"
            f"          <h3>照片 / 视频</h3>\n"
            f"          <div class=\"media-gallery\" data-slug=\"{e['slug']}\"></div>\n"
            f"        </section>\n"
            f"      </article>"
        )
    cards_html = "\n\n".join(cards)

    return f"""<!DOCTYPE html>
<html lang=\"zh-CN\">
<head>
  <meta charset=\"UTF-8\">
  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
  <title>看房对比</title>
  <style>
    :root {{
      --bg: #f5f5f7;
      --card: #ffffff;
      --text: #333333;
      --muted: #666666;
      --border: #e5e5ea;
      --accent: #007aff;
    }}
    * {{ box-sizing: border-box; }}
    body {{
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "PingFang SC", "Microsoft YaHei", sans-serif;
      line-height: 1.65;
      color: var(--text);
      background: var(--bg);
      margin: 0;
      padding: 1rem;
    }}
    .container {{
      max-width: 900px;
      margin: 0 auto;
    }}
    h1 {{
      text-align: center;
      margin-bottom: .25rem;
      font-size: 1.5rem;
    }}
    .subtitle {{
      text-align: center;
      color: var(--muted);
      margin-bottom: 1.25rem;
      font-size: .9rem;
    }}

    /* 概览表 */
    table {{
      width: 100%;
      border-collapse: collapse;
      background: var(--card);
      margin-bottom: 1.5rem;
      box-shadow: 0 1px 3px rgba(0,0,0,.06);
      border-radius: 10px;
      overflow: hidden;
      font-size: .9rem;
    }}
    th, td {{
      padding: .7rem .75rem;
      text-align: left;
      border-bottom: 1px solid var(--border);
      vertical-align: top;
    }}
    th {{ background: #fafafc; font-weight: 600; color: #444; }}
    tbody tr {{ transition: background .15s; }}
    tbody tr[data-href] {{ cursor: pointer; }}
    tbody tr[data-href]:hover {{ background: #f2f2f7; }}
    tbody tr:last-child td {{ border-bottom: none; }}
    @media (max-width: 640px) {{
      table, thead, tbody, th, td, tr {{ display: block; }}
      thead {{ display: none; }}
      tr {{
        margin-bottom: .75rem;
        border: 1px solid var(--border);
        border-radius: 10px;
        overflow: hidden;
      }}
      td {{
        display: flex;
        justify-content: space-between;
        padding: .5rem .75rem;
        border-bottom: 1px solid #f2f2f5;
      }}
      td:last-child {{ border-bottom: none; }}
      td::before {{
        content: attr(data-label);
        font-weight: 600;
        color: var(--muted);
        margin-right: 1rem;
        flex-shrink: 0;
      }}
    }}

    /* 卡片 */
    .cards {{ display: grid; gap: 1rem; }}
    .card {{
      background: var(--card);
      border-radius: 14px;
      padding: 1.25rem;
      box-shadow: 0 2px 10px rgba(0,0,0,.05);
      scroll-margin-top: 1rem;
    }}
    .card h2 {{
      margin: 0 0 .5rem;
      font-size: 1.15rem;
      color: #111;
    }}
    .card .overview {{
      margin: 0 0 .75rem;
      color: var(--text);
      font-size: .98rem;
      line-height: 1.65;
    }}
    .card .meta {{
      background: #fafafc;
      border-radius: 10px;
      padding: .75rem 1rem;
      margin-bottom: 1rem;
      font-size: .92rem;
    }}
    .card .meta p {{
      margin: .35rem 0;
    }}
    .card .meta strong {{
      color: #555;
      margin-right: .3rem;
    }}

    /* 媒体区 */
    .media {{ margin-top: .25rem; }}
    .media h3 {{
      font-size: .85rem;
      color: var(--muted);
      margin: 0 0 .5rem;
      font-weight: 600;
    }}
    .gallery {{
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
      gap: .5rem;
    }}
    .gallery img, .gallery video {{
      width: 100%;
      height: 130px;
      object-fit: cover;
      border-radius: 8px;
      background: #f2f2f6;
    }}
    .gallery img {{ cursor: pointer; }}
    .gallery-more {{ display: none; margin-top: .5rem; }}
    .gallery-more.visible {{ display: grid; }}
    .gallery-videos {{ margin-top: .5rem; }}
    .gallery-toggle {{
      display: block;
      margin: .6rem auto 0;
      padding: .35rem 1rem;
      background: #f2f2f6;
      border: none;
      border-radius: 999px;
      color: var(--accent);
      font-size: .85rem;
      cursor: pointer;
    }}
    .placeholder {{
      color: #999;
      font-size: .9rem;
      margin: .25rem 0 0;
    }}

    /* 灯箱 */
    .lightbox {{
      display: none;
      position: fixed;
      inset: 0;
      background: rgba(0,0,0,.92);
      z-index: 100;
      align-items: center;
      justify-content: center;
    }}
    .lightbox.active {{ display: flex; }}
    .lightbox img {{
      max-width: 92%;
      max-height: 90vh;
      border-radius: 6px;
    }}
    .lightbox .close {{
      position: absolute;
      top: .75rem;
      right: 1rem;
      color: #fff;
      font-size: 2rem;
      line-height: 1;
      cursor: pointer;
    }}
  </style>
</head>
<body>
  <div class=\"container\">
    <h1>{title}</h1>
    <p class=\"subtitle\">{subtitle or f"共 {len(entries)} 套房源，点击表格可跳转到详情"}</p>

    <table>
      <thead>
        <tr>
          <th>位置</th>
          <th>户型/面积/朝向</th>
          <th>费用</th>
          <th>服务费</th>
          <th>厨卫</th>
        </tr>
      </thead>
      <tbody>
{rows_html}
      </tbody>
    </table>

    <div class=\"cards\">

{cards_html}

    </div>
  </div>

  <div class=\"lightbox\" id=\"lightbox\" onclick=\"this.classList.remove('active')\">
    <span class=\"close\">&times;</span>
    <img id=\"lightbox-img\" src=\"\" alt=\"\">
  </div>

  <script>
    const PREVIEW_COUNT = 6;
    const BASE_PATH = location.pathname.replace(/\\/[^\\/]*$/, '');
    const CACHE_BUST = '?v=3';
    function resolve(p) {{ return p.startsWith('/') ? p : (BASE_PATH + '/' + p); }}

    function imgTag(thumb, full) {{
      return `<img src="${{resolve(thumb)}}" loading="lazy" alt="" onclick="openLightbox('${{resolve(full)}}')">`;
    }}

    function videoTag(src) {{
      return `<video src="${{resolve(src)}}" controls preload="metadata"></video>`;
    }}

    async function loadMedia() {{
      let data = {{}};
      try {{
        const res = await fetch(resolve('media.json') + CACHE_BUST);
        if (!res.ok) throw new Error('media.json ' + res.status);
        data = await res.json();
      }} catch (e) {{
        console.error('加载 media.json 失败', e);
      }}

      document.querySelectorAll('.media-gallery').forEach(el => {{
        const slug = el.dataset.slug;
        const media = data[slug] || {{}};
        const images = media.images || [];
        const thumbnails = media.thumbnails || images;
        const videos = media.videos || [];

        if (images.length === 0 && videos.length === 0) {{
          el.innerHTML = '<p class="placeholder">暂无照片/视频</p>';
          return;
        }}

        const pairs = images.map((src, i) => [thumbnails[i] || src, src]);
        let html = '';

        html += '<div class="gallery gallery-preview">';
        pairs.slice(0, PREVIEW_COUNT).forEach(([thumb, full]) => {{ html += imgTag(thumb, full); }});
        html += '</div>';

        if (pairs.length > PREVIEW_COUNT) {{
          html += '<div class="gallery gallery-more">';
          pairs.slice(PREVIEW_COUNT).forEach(([thumb, full]) => {{ html += imgTag(thumb, full); }});
          html += '</div>';
          html += `<button class="gallery-toggle" onclick="toggleGallery(this)">显示剩余 ${{pairs.length - PREVIEW_COUNT}} 张照片</button>`;
        }}

        if (videos.length) {{
          html += '<div class="gallery gallery-videos">';
          videos.forEach(src => {{ html += videoTag(src); }});
          html += '</div>';
        }}

        el.innerHTML = html;
      }});
    }}

    function toggleGallery(btn) {{
      const more = btn.previousElementSibling;
      more.classList.toggle('visible');
      const remaining = more.querySelectorAll('img').length;
      btn.textContent = more.classList.contains('visible')
        ? '收起照片'
        : `显示剩余 ${{remaining}} 张照片`;
    }}

    function openLightbox(src) {{
      const box = document.getElementById('lightbox');
      document.getElementById('lightbox-img').src = src;
      box.classList.add('active');
    }}

    document.querySelectorAll('tbody tr[data-href]').forEach(row => {{
      row.addEventListener('click', () => {{
        location.hash = row.dataset.href;
      }});
    }});

    loadMedia();
  </script>
</body>
</html>
"""


def main():
    print("=== 修复目录名 ===")
    fix_dir_names()
    print("\n=== 处理媒体 ===")
    media = build_media()
    print("\n=== 生成中间表格 ===")
    entries = build_data(media)
    print("\n=== 生成页面 ===")
    html = render_html(entries)
    with open(OUT_DIR / "comps.html", "w", encoding="utf-8") as f:
        f.write(html)
    print(f"已生成 {OUT_DIR / 'comps.html'}")

    final_slugs = ["ligong-fushu-nan14", "changwa-1-602", "daoxiang-4-1201", "huixin-jiayuan"]
    final_entries = [e for e in entries if e["slug"] in final_slugs]
    final_html = render_html(
        final_entries,
        title="决赛圈",
        subtitle=" shortlisted 4 套，重点对比",
    )
    with open(OUT_DIR / "final.html", "w", encoding="utf-8") as f:
        f.write(final_html)
    print(f"已生成 {OUT_DIR / 'final.html'}")

    print(f"\n本地预览: file://{OUT_DIR / 'comps.html'}")
    print(f"决赛圈预览: file://{OUT_DIR / 'final.html'}")


if __name__ == "__main__":
    main()
