"""
DSM 瀏覽器代理(部署用)。

長駐 Chromium 視窗,讓使用者能看到 + 操作,
同時接受外部指令(寫 command.json)→ 自動執行 → 寫 result.json + live.png

使用:
  ./.venv/Scripts/python.exe tools/dsm/daemon.py

等 daemon 啟動後,Claude 透過寫 command.json 發指令,讀 live.png 看狀態。
"""

import asyncio
import json
import sys
import time
from pathlib import Path

try:
    sys.stdout.reconfigure(encoding="utf-8")
except (AttributeError, ValueError):
    pass

from playwright.async_api import async_playwright


HERE = Path(__file__).resolve().parent
PROFILE = HERE / "profile"
PROFILE.mkdir(exist_ok=True, parents=True)

COMMAND_FILE = HERE / "command.json"
RESULT_FILE = HERE / "result.json"
LIVE_PNG = HERE / "live.png"
STATE_FILE = HERE / "state.json"

URL = "https://192-168-1-121.ssbb30529.direct.quickconnect.to:5001/"


async def execute(page, cmd: dict):
    """執行單一指令。"""
    action = cmd.get("action")
    timeout = cmd.get("timeout", 10000)

    if action == "screenshot":
        await page.screenshot(path=str(LIVE_PNG))
        return {"path": str(LIVE_PNG)}

    if action == "url":
        return {"url": page.url, "title": await page.title()}

    if action == "click":
        await page.click(cmd["selector"], timeout=timeout)
        return {"clicked": cmd["selector"]}

    if action == "fill":
        await page.fill(cmd["selector"], cmd["value"], timeout=timeout)
        return {"filled": cmd["selector"]}

    if action == "type":
        await page.keyboard.type(cmd["text"], delay=cmd.get("delay", 30))
        return {"typed": True}

    if action == "press":
        await page.keyboard.press(cmd["key"])
        return {"pressed": cmd["key"]}

    if action == "navigate":
        await page.goto(cmd["url"], wait_until="domcontentloaded", timeout=60000)
        return {"navigated": cmd["url"]}

    if action == "reload":
        await page.reload(wait_until="domcontentloaded")
        return {"reloaded": True}

    if action == "wait":
        await asyncio.sleep(cmd.get("seconds", 1))
        return {"waited": cmd.get("seconds", 1)}

    if action == "wait_for":
        await page.wait_for_selector(cmd["selector"], timeout=timeout, state=cmd.get("state", "visible"))
        return {"found": cmd["selector"]}

    if action == "eval":
        result = await page.evaluate(cmd["script"])
        return {"result": result}

    if action == "find":
        count = await page.locator(cmd["selector"]).count()
        texts = []
        if count > 0:
            for i in range(min(count, 20)):
                try:
                    texts.append((await page.locator(cmd["selector"]).nth(i).inner_text()).strip()[:200])
                except Exception:
                    texts.append(None)
        return {"count": count, "texts": texts}

    if action == "find_text":
        # 找含某文字的元素,回傳那些元素的可點選資訊
        text = cmd["text"]
        result = await page.evaluate("""
            (text) => {
                const all = document.querySelectorAll('*');
                const out = [];
                for (const el of all) {
                    if (el.children.length === 0 && el.textContent && el.textContent.includes(text)) {
                        const r = el.getBoundingClientRect();
                        if (r.width > 0 && r.height > 0) {
                            out.push({
                                tag: el.tagName,
                                text: el.textContent.trim().slice(0, 100),
                                x: Math.round(r.x + r.width/2),
                                y: Math.round(r.y + r.height/2),
                            });
                        }
                    }
                    if (out.length >= 10) break;
                }
                return out;
            }
        """, text)
        return {"matches": result}

    if action == "click_at":
        await page.mouse.click(cmd["x"], cmd["y"], click_count=cmd.get("count", 1))
        return {"clicked_at": [cmd["x"], cmd["y"]]}

    if action == "dblclick_at":
        await page.mouse.dblclick(cmd["x"], cmd["y"])
        return {"dblclicked_at": [cmd["x"], cmd["y"]]}

    return {"unknown_action": action}


async def main():
    print(f"[daemon] starting, profile={PROFILE}")
    async with async_playwright() as pw:
        ctx = await pw.chromium.launch_persistent_context(
            user_data_dir=str(PROFILE),
            headless=False,
            ignore_https_errors=True,    # QuickConnect 自簽憑證
            args=[
                "--disable-blink-features=AutomationControlled",
            ],
            viewport={"width": 1400, "height": 900},
        )
        page = ctx.pages[0] if ctx.pages else await ctx.new_page()

        try:
            await page.goto(URL, wait_until="domcontentloaded", timeout=60000)
            print(f"[daemon] navigated to {URL}")
        except Exception as e:
            print(f"[daemon] navigation error: {e}")

        # 初始截圖
        try:
            await page.screenshot(path=str(LIVE_PNG))
        except Exception:
            pass

        # 寫 ready 標記
        STATE_FILE.write_text(
            json.dumps({"ready": True, "started_at": time.time()}, ensure_ascii=False),
            encoding="utf-8",
        )

        # 主迴圈
        last_auto_screenshot = 0
        AUTO_SHOT_INTERVAL = 4.0

        while True:
            try:
                # 自動定期 screenshot,讓 Claude 能看到使用者操作後的狀態
                now = time.time()
                if now - last_auto_screenshot > AUTO_SHOT_INTERVAL:
                    try:
                        await page.screenshot(path=str(LIVE_PNG))
                    except Exception:
                        pass
                    last_auto_screenshot = now

                # 檢查指令
                if COMMAND_FILE.exists():
                    raw = COMMAND_FILE.read_text(encoding="utf-8")
                    try:
                        cmd = json.loads(raw)
                    except Exception as e:
                        cmd = None
                        result = {"ok": False, "error": f"bad json: {e}"}

                    if cmd is not None:
                        try:
                            r = await execute(page, cmd)
                            result = {"ok": True, "result": r}
                            # 執行完強制 screenshot 一次
                            try:
                                await page.screenshot(path=str(LIVE_PNG))
                            except Exception:
                                pass
                        except Exception as e:
                            result = {"ok": False, "error": str(e), "type": type(e).__name__}

                    # 寫結果、刪 command
                    RESULT_FILE.write_text(
                        json.dumps(result, ensure_ascii=False, indent=2),
                        encoding="utf-8",
                    )
                    try:
                        COMMAND_FILE.unlink()
                    except Exception:
                        pass
                    print(f"[daemon] cmd done: {result.get('ok')}")

                await asyncio.sleep(0.4)
            except KeyboardInterrupt:
                break
            except Exception as e:
                print(f"[daemon] loop err: {e}")
                await asyncio.sleep(1)

        await ctx.close()


if __name__ == "__main__":
    asyncio.run(main())
