#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""把 H5 定稿版的数据＋素材同步进小程序项目。

用法：
    /opt/venv/bin/python tools/sync_data_to_miniprogram.py

背景：小程序 XnzxsProject 是"改造前"的版本 —— 用的是演示数据（少量房间）+ 四个近似矩形分区；
H5 xnzxs-h5 是用户逐轮评审后的定稿版 —— 真实 30 间（来自统计表）+ 西宁四城区真实 GeoJSON。

本脚本做三件事（只写小程序侧，**不动 H5 的任何文件**）：
  1) 生成 XnzxsProject/data/xining-geo.js  —— 四城区真实轮廓（取自 H5 assets/xining4.js）
  2) 压缩并拷贝素材到 XnzxsProject/assets/  —— 房间图、地图底图、Banner
     ⚠ 小程序单包上限 2MB，而 H5 的 82 张房间图共 6.6MB（平均 82KB）→ 必须压。
       策略：**每间只带 1 张**，压到 640px 宽 / q78（约 30KB），30 间 ≈ 0.9MB。
       详情页掠影因此只有 1 张；要更多就后续走网络图（域名需加下载白名单）或分包。
       → 关键：**图片列表按"实际拷进来的文件"回填**，否则数据里留着 404 路径（小程序端还不报错）。
  3) 生成 XnzxsProject/data/rooms.js       —— 30 间真实数据 + 区色/标注框配置 + 口径说明

⚠ 字段口径：统计表里**没有**的字段（配套设施、联盟示范点、管理员职务、状态、所属党支部）
   一律留空并在文件头注明"需由台账补齐"，不编造 —— 这是本项目一贯口径。
⚠ 图片路径：统计表/H5 用相对路径 assets/rooms/xxx.jpg，小程序里 / 相对项目根，需改写成
   /assets/rooms/xxx.jpg，否则小程序端全部 404（控制台还不报错）。
"""
import json
import os
import re
import shutil

from PIL import Image

H5 = "/data/www/files/xnzxs-h5"
MP = "/data/www/files/XnzxsProject"
ASSETS = os.path.join(MP, "assets")
ROOM_DIR = os.path.join(ASSETS, "rooms")
ROOM_W, ROOM_Q = 560, 75
BG_W, BG_Q = 750, 80
BANNER_W, BANNER_Q = 750, 78

# 四区配色：逐色取自用户附件（在其图片上按颜色聚类取样），H5 与小程序共用同一套
DIST_COLORS = {
    "城北区": "#6CD2C0",
    "城西区": "#D8EA66",
    "城中区": "#FCB46C",
    "城东区": "#A8DE78",
}
# 数量标注框位置：rf = 框中心在该区包围盒中的相对位置（<0 / >1 表示落在区外）
CALLS = [
    {"name": "城北区", "rf": [0.144, 0.154]},
    {"name": "城西区", "rf": [-0.548, 0.80]},
    {"name": "城东区", "rf": [1.633, 0.62]},
    {"name": "城中区", "rf": [0.5, 1.162]},
]
DIST_BORDER = "#FFFFFF"


def load_h5_data():
    s = open(os.path.join(H5, "data.js"), encoding="utf-8").read()
    return json.loads(s[s.index("=") + 1:].rstrip().rstrip(";"))


def load_geo():
    s = open(os.path.join(H5, "assets", "xining4.js"), encoding="utf-8").read()
    i = s.index("=", s.index("window.XN_XINING_GEO"))
    body = s[i + 1:].rstrip().rstrip(";")
    return json.loads(body)


def copy_assets(rooms):
    """压缩 + 拷贝素材；返回 {房间id: [小程序内图片路径]}（只含真正拷成功的文件）"""
    os.makedirs(ROOM_DIR, exist_ok=True)
    out = {}
    for r in rooms:
        paths = []
        for p in (r.get("images") or [])[:1]:        # 每间只带 1 张，控制包体
            src = os.path.join(H5, p)
            if not os.path.exists(src):
                continue
            name = os.path.basename(p)
            im = Image.open(src).convert("RGB")
            if im.width > ROOM_W:
                im = im.resize((ROOM_W, round(im.height * ROOM_W / im.width)), Image.LANCZOS)
            im.save(os.path.join(ROOM_DIR, name), "JPEG", quality=ROOM_Q, optimize=True)
            paths.append("/assets/rooms/" + name)
        if paths:
            out[r["id"]] = paths
    for src, name, w, q in [
        (os.path.join(H5, "assets", "map-bg-5.jpg"), "map-bg.jpg", BG_W, BG_Q),
        (os.path.join(H5, "assets", "banner-photo.jpg"), "banner-photo.jpg", BANNER_W, BANNER_Q),
    ]:
        if not os.path.exists(src):
            print("  ! 找不到 %s，跳过" % src)
            continue
        im = Image.open(src).convert("RGB")
        if im.width > w:
            im = im.resize((w, round(im.height * w / im.width)), Image.LANCZOS)
        im.save(os.path.join(ASSETS, name), "JPEG", quality=q, optimize=True)
    return out


def main():
    d = load_h5_data()
    geo = load_geo()
    rooms = d["rooms"]
    print("H5 数据：%d 间；GeoJSON：%d 个区" % (len(rooms), len(geo["features"])))

    # ---------- 1) 四区轮廓 ----------
    feats = []
    for f in geo["features"]:
        # ⚠ 统一成「环数组」：rings = [环1, 环2, ...]，每个环是 [ [lng,lat], ... ]
        #   原始 GeoJSON 的 Polygon → coordinates[0] 是外环；MultiPolygon → 各多边形的外环都收进来。
        #   渲染器（utils/xmap.js）始终按 rings[0] 取外环、按 rings 遍历所有环，
        #   层级必须一致，否则坐标会算成 NaN（本轮踩过：层级差一层，四个区算出同一个包围盒）。
        gtype = f["geometry"]["type"]
        coords = f["geometry"]["coordinates"]
        if gtype == "Polygon":
            rings = [coords[0]]
        else:
            rings = [poly[0] for poly in coords]
        feats.append({
            "name": f["properties"]["name"],
            "cp": f["properties"]["cp"],
            "rings": rings,
        })
    vtx = sum(len(r) for f in feats for r in f["rings"])
    print("四区轮廓：%d 个环 / %d 个顶点" % (sum(len(f["rings"]) for f in feats), vtx))
    out_geo = '''/**
 * 西宁市四城区真实轮廓（城东 / 城西 / 城中 / 城北）
 *
 * 来源：西宁市党建门户站点地图数据（明文经纬度，GCJ-02 坐标系），
 *      与高德地理编码得到的自习室坐标同一坐标系，可直接叠点，无需纠偏。
 * 用途：首页「区域分布图」——由 utils/xmap.js 用 Canvas 2D 自绘（小程序端不可用 ECharts，
 *      也无法从 CDN 注入脚本，所以按同一套投影参数原生绘制）。
 *
 * 结构：features[].rings 是**环数组** —— rings = [环1, 环2, ...]，每个环是 [ [lng,lat], ... ]；
 *      cp 为该区「文字标注锚点」。
 * 【注意】cp 只是"区名写在哪"，不等于图形几何中心 —— 点区放大取景要用几何中心，
 *        否则像城东区那样 cp 偏高的区会被顶出容器上沿（H5 端实测 y 到 -10 被裁）。
 *
 * 体积：约 %dKB。若后续要换更精细的区界，替换本文件即可，绘制逻辑不用动。
 */
const XINING_GEO = %s;

module.exports = { XINING_GEO: XINING_GEO };
''' % (round(os.path.getsize(os.path.join(H5, "assets", "xining4.js")) / 1024),
       json.dumps(feats, ensure_ascii=False, separators=(",", ":")))

    sha = os.path.join(MP, "data", "xining-geo.js")
    with open(sha, "w", encoding="utf-8") as f:
        f.write(out_geo)
    print("已写出 data/xining-geo.js  (%.1fKB)" % (os.path.getsize(sha) / 1024))

    # ---------- 2) 素材（先做，图片列表要按实际拷进来的文件回填）----------
    copied = copy_assets(rooms)
    n_img = sum(len(v) for v in copied.values())
    img_kb = sum(os.path.getsize(os.path.join(ROOM_DIR, os.path.basename(p)))
                 for v in copied.values() for p in v) / 1024
    print("已压缩拷贝房间图 %d 张（共 %.0fKB）" % (n_img, img_kb))

    # ---------- 3) 房间数据 ----------
    out_rooms = []
    for r in rooms:
        imgs = copied.get(r["id"], [])
        out_rooms.append({
            "id": r["id"],
            "no": r["no"],
            "sortNo": r.get("sortNo", r["no"]),
            "name": r["name"],
            "district": r["district"],
            "address": r["address"],
            "latitude": r["latitude"],
            "longitude": r["longitude"],
            "geoLevel": r.get("geoLevel", ""),
            "reason": r["reason"],
            "note": r.get("note", ""),
            "seatTotal": r.get("capacity", 0),
            "seatText": r.get("capacityText", ""),
            "staff": r.get("staff", 0),
            "staffText": r.get("staffText", ""),
            "popText": r.get("popText", ""),
            "contactName": r.get("contact", ""),
            "contactPhone": r.get("phone", ""),
            "phoneMask": r.get("phoneMask", ""),
            # 统计表没有「职务」列 → 留空，上线前由台账补
            "contactTitle": "",
            "openTime": r.get("openHours", ""),
            "notice": r.get("notice", []),
            "images": imgs,
            "video": r.get("video", ""),
            # 以下三项统计表里没有，留空占位（不编造）
            "facilities": [],
            "allianceFlag": False,
            "status": "",
            "org": "",
            "updatedAt": r.get("sourceUpdated", ""),
        })

    districts = d["districts"]
    # 按"城东→城中→城西→城北"的顺序（与 H5 的筛选条一致）；H5 里没有"全部"项，点已选中即取消
    order = ["城东区", "城中区", "城西区", "城北区"]
    districts = [x for x in order if x in districts] or districts

    body = json.dumps(out_rooms, ensure_ascii=False, indent=2)
    src_note = d.get("source") or {}
    out_rooms_js = '''/**
 * 自习室数据 —— 与 H5 定稿版同源（《西宁市自习室（党群服务中心）统计表》）
 *
 * 【本文件由 tools/sync_data_to_miniprogram.py 生成，不要手改】
 *   改数据请改 xnzxs-h5/tools/parse_et.py（或直接改统计表），再重跑本脚本，
 *   这样小程序与 H5 两个交付物永远同源。
 *
 * 数据来源：%(src)s
 * 共 %(n)d 间。
 *
 * 【上线前必须替换的 3 项】
 *   1) latitude / longitude —— 统计表坐标是「%(levels)s」，上线前建议到店门口实采一次
 *   2) contactName / contactPhone / openTime / address —— 以社区填报 + 区委组织部审核后的台账为准
 *      （联系电话当前是明显占位的号段，展示时已脱敏为 138****NNNN）
 *   3) 统计表**没有**「配套设施 / 联盟示范点 / 管理员职务 / 开放状态 / 所属党支部」
 *      这几列 —— 本文件里一律留空，不编造，等台账补齐后再填。
 *
 * 四区配色与数量标注框位置：逐色取自用户提供的效果图，H5 与小程序共用这一套
 * （改色只改这里；绘制逻辑在 utils/xmap.js）。
 */
const DIST_COLORS = %(colors)s;

/** 数量标注框：rf=[rx,ry] 是「框中心」在该区包围盒中的相对位置，<0 / >1 即落在区外 */
const CALLS = %(calls)s;

/** 地图轮廓描边色（白），与 H5 一致 */
const DIST_BORDER = '%(border)s';

const districts = %(districts)s;

const notices = %(notices)s;

const source = %(source)s;

const rooms = %(rooms)s;

module.exports = {
  districts: districts,
  rooms: rooms,
  notices: notices,
  DIST_COLORS: DIST_COLORS,
  CALLS: CALLS,
  DIST_BORDER: DIST_BORDER,
  source: source
};
''' % {
        "src": src_note.get("table", "《西宁市自习室（党群服务中心）统计表》"),
        "n": len(out_rooms),
        "levels": "、".join(sorted({r.get("geoLevel", "") for r in out_rooms if r.get("geoLevel")})),
        "colors": json.dumps(DIST_COLORS, ensure_ascii=False, indent=2),
        "calls": json.dumps(CALLS, ensure_ascii=False, indent=2),
        "border": DIST_BORDER,
        "districts": json.dumps(districts, ensure_ascii=False, indent=2),
        "notices": json.dumps(d.get("notices") or [], ensure_ascii=False, indent=2),
        "source": json.dumps(src_note, ensure_ascii=False, indent=2),
        "rooms": body,
    }

    dst = os.path.join(MP, "data", "rooms.js")
    if os.path.exists(dst) and not os.path.exists(dst + ".bak"):
        shutil.copy(dst, dst + ".bak")            # 旧演示数据留一份备份，不直接丢
        print("旧 data/rooms.js 已备份为 rooms.js.bak")
    with open(dst, "w", encoding="utf-8") as f:
        f.write(out_rooms_js)
    print("已写出 data/rooms.js  (%.1fKB, %d 间)" % (os.path.getsize(dst) / 1024, len(out_rooms)))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
