#!/usr/bin/env python3
"""原生 CDP 截图器 v2:屏蔽教学箭头;可模拟放置 K 块碎片后重绘
用法: python3 cdpsnap.py N TAG [dsf] [texIdx] [placeK] [WxH]"""
import sys, json, time, base64, urllib.request
import websocket

N = int(sys.argv[1]) if len(sys.argv) > 1 else 3
TAG = sys.argv[2] if len(sys.argv) > 2 else "before"
DSF = int(sys.argv[3]) if len(sys.argv) > 3 else 3
TEX = sys.argv[4] if len(sys.argv) > 4 else "0"
PLACEK = int(sys.argv[5]) if len(sys.argv) > 5 else 0
WH = sys.argv[6] if len(sys.argv) > 6 else "390x844"
W, H = (int(v) for v in WH.split("x"))

def http_json(path, method="GET"):
    req = urllib.request.Request("http://127.0.0.1:9222" + path, method=method)
    return json.loads(urllib.request.urlopen(req, timeout=10).read().decode())

t = http_json("/json/new?url=about:blank", method="PUT")
ws = websocket.create_connection(t["webSocketDebuggerUrl"], timeout=180)
_id = 0
def raw_send(method, **params):
    global _id
    _id += 1
    ws.send(json.dumps({"id": _id, "method": method, "params": params}))
    return _id

def cmd(method, **params):
    my = raw_send(method, **params)
    while True:
        msg = json.loads(ws.recv())
        if msg.get("id") == my:
            if "error" in msg:
                raise RuntimeError(f"{method}: {msg['error']}")
            return msg.get("result", {})
        m = msg.get("method")
        if m == "Fetch.requestPaused":
            rid = msg["params"]["requestId"]
            url = msg["params"]["request"]["url"]
            if "127.0.0.1" in url or "localhost" in url:
                raw_send("Fetch.continueRequest", requestId=rid)
            else:
                raw_send("Fetch.failRequest", requestId=rid, errorReason="BlockedByClient")

def eval_js(expr):
    r = cmd("Runtime.evaluate", expression=expr, awaitPromise=True,
            returnByValue=True, userGesture=True)
    if r.get("exceptionDetails"):
        raise RuntimeError(json.dumps(r["exceptionDetails"])[:500])
    return r["result"].get("value")

cmd("Fetch.enable", patterns=[{"urlPattern": "*"}])
cmd("Emulation.setDeviceMetricsOverride", width=W, height=H, deviceScaleFactor=DSF, mobile=(W < 500))
cmd("Page.enable")
cmd("Page.navigate", url=f"http://127.0.0.1:8123/index.html")
time.sleep(3)

geo = eval_js(f"""(async()=>{{
  try{{sessionStorage.setItem('pp_hint','1');}}catch(e){{}} /* 屏蔽教学箭头 */
  document.querySelector('#diff button[data-n="{N}"]').click();
  await new Promise(r=>setTimeout(r,200));
  document.getElementById('go-gallery').click();
  await new Promise(r=>setTimeout(r,250));
  document.querySelectorAll('#gal-grid-cards .spec')[{TEX}].click();
  const t0=Date.now();
  while(Date.now()-t0<15000){{
    const g=document.getElementById('ghost');
    if(g&&g.width>0&&!document.getElementById('board-loading').classList.contains('show')) break;
    await new Promise(r=>setTimeout(r,120));
  }}
  /* 可选:模拟放置前 {PLACEK} 块(含边缘槽位,验证重绘与碎片贴边) */
  for(let i=0;i<{PLACEK};i++){{ try{{ placePiece(state.pieces[i]); }}catch(e){{}} }}
  await new Promise(r=>setTimeout(r,900));
  const R=e=>{{const r=e.getBoundingClientRect();return{{x:r.x,y:r.y,w:r.width,h:r.height}}}};
  const w=document.getElementById('board-wrap'), g=document.getElementById('ghost');
  return {{wrap:R(w), ghost:R(g), ghostPx:[g.width,g.height], placed:state.placed}};
}})()""")
print(json.dumps(geo))

full = cmd("Page.captureScreenshot", format="png")["data"]
open(f"/home/user/shots/{TAG}_n{N}.png", "wb").write(base64.b64decode(full))
wr = geo["wrap"]
clip = {"x": max(0, wr["x"]-30), "y": max(0, wr["y"]-30),
        "width": wr["w"]+60, "height": wr["h"]+60, "scale": 1}
board = cmd("Page.captureScreenshot", format="png", clip=clip)["data"]
open(f"/home/user/shots/{TAG}_n{N}_board.png", "wb").write(base64.b64decode(board))
cmd("Target.closeTarget", targetId=t["id"])
print("SNAP-OK", TAG, N)
