"""
grind.py — autonomous aibat grinder (mode: grind).

Attack model (confirmed in-game): click selects a mob, a SECOND click on the
selected mob attacks it, then the character auto-attacks to the death. So we
two-click ONCE to engage, then HAND OFF — never click again until the target
frame disappears (mob dead). Re-clicking mid-fight cancels the auto-attack.

State machine:
  IDLE  -> find nearest small aibat, two-click to engage -> FIGHT
  FIGHT -> hold while the target frame is present; when it's gone for a couple
           frames (flicker-tolerant) the mob is dead -> IDLE
  no mob in view -> rotate the CAMERA (right-drag) to look around

Requires host.py running. Run:  python -u grind.py   (Ctrl+C to stop)
"""
import time
import cv2
import numpy as np
from playwright.sync_api import sync_playwright
from session import get_page, viewport
from find_aibat import detect

# ---- config ----
FOOD_KEY = "1"
HEAL_AT = 0.50
HEAL_COOLDOWN = 3.0
MAX_SMALL = 600          # skip giant (too-strong) aibats
MIN_ENGAGE = 60          # ignore tiny far blobs (unclickable / noise); prefer close mobs
SELECT_GAP = 0.28        # gap between select-click and attack-click
ENGAGE_SETTLE = 1.0      # wait after engaging so the target frame can appear
FIGHT_TIMEOUT = 25.0     # safety: re-engage if a single fight drags on this long
MISSING_TO_KILL = 2      # target-frame absent this many cycles = mob dead (anti-flicker)
ROTATE_DX = 320          # right-drag distance to orbit the camera when scanning
CYCLE = 0.5

# player HP red bar (absolute px, fixed HUD)
HP_X0, HP_X1, HP_Y0, HP_Y1 = 122, 228, 37, 47
FILL_L, FILL_R = 125, 225
# target-frame mob HP bar (fractions of frame)
MOB_BOX = (0.455, 0.045, 0.650, 0.070)


def hp_fraction(img):
    box = img[HP_Y0:HP_Y1, HP_X0:HP_X1].astype(int)
    r, g, b = box[:, :, 2], box[:, :, 1], box[:, :, 0]
    red = (r > 110) & (r > b + 40) & (r > g + 25)
    xs = np.where(red.any(axis=0))[0]
    if len(xs) == 0:
        return 0.0
    return max(0.0, min(1.0, (xs.max() + HP_X0 - FILL_L) / (FILL_R - FILL_L)))


def has_target(img):
    h, w = img.shape[:2]
    x0, y0, x1, y1 = (int(MOB_BOX[0]*w), int(MOB_BOX[1]*h),
                      int(MOB_BOX[2]*w), int(MOB_BOX[3]*h))
    box = img[y0:y1, x0:x1].astype(int)
    r, g, b = box[:, :, 2], box[:, :, 1], box[:, :, 0]
    return int(((r > 120) & (r > b + 50) & (r > g + 50)).sum()) >= 150


def pick_target(img, w, h):
    """Closest clickable aibat (largest blob within size bounds), or None."""
    _, cands = detect(img)
    ok = [c for c in cands if MIN_ENGAGE <= c[2] <= MAX_SMALL]
    if not ok:
        return None
    ok.sort(key=lambda c: -c[2])   # largest area = closest = shortest walk
    return ok[0]


def grab(page):
    return cv2.imdecode(np.frombuffer(page.screenshot(), np.uint8), cv2.IMREAD_COLOR)


def two_click(page, x, y):
    page.mouse.click(x, y)
    time.sleep(SELECT_GAP)
    page.mouse.click(x, y)


def rotate_view(page, w, h):
    cx, cy = w * 0.5, h * 0.5
    page.mouse.move(cx, cy)
    page.mouse.down(button="right")
    page.mouse.move(cx + ROTATE_DX, cy, steps=8)
    page.mouse.up(button="right")


def main():
    print(f"GRIND — food='{FOOD_KEY}', heal<{int(HEAL_AT*100)}%. Ctrl+C to stop.")
    last_heal = 0.0
    state = "IDLE"
    engage_time = 0.0
    missing = 0
    confirmed = False     # did a target frame actually appear this fight?
    kills = 0
    with sync_playwright() as p:
        browser, page = get_page(p)
        w, h = viewport(page)
        while True:
            img = grab(page)
            hp = hp_fraction(img)
            now = time.time()

            if hp < HEAL_AT and now - last_heal > HEAL_COOLDOWN:
                page.keyboard.press(FOOD_KEY); last_heal = now
                print(f"[heal] HP~{hp*100:3.0f}% -> ate '{FOOD_KEY}'")

            tgt = has_target(img)

            if state == "FIGHT":
                if tgt:
                    missing = 0; confirmed = True
                    if now - engage_time > FIGHT_TIMEOUT:
                        mob = pick_target(img, w, h)
                        if mob:
                            two_click(page, mob[0], mob[1]); engage_time = now
                            print(f"[re-eng] HP~{hp*100:3.0f}% fight too long — re-click")
                            time.sleep(ENGAGE_SETTLE)
                    else:
                        print(f"[fight] HP~{hp*100:3.0f}% engaged")
                else:
                    missing += 1
                    if missing >= MISSING_TO_KILL:
                        if confirmed:
                            kills += 1
                            print(f"[kill ] mob down — total {kills}")
                        else:
                            print("[miss ] engage didn't land — re-scan")
                        state = "IDLE"; missing = 0; confirmed = False
            else:  # IDLE
                mob = pick_target(img, w, h)
                if mob:
                    two_click(page, mob[0], mob[1])
                    engage_time = now; state = "FIGHT"; missing = 0; confirmed = False
                    print(f"[engage] HP~{hp*100:3.0f}% -> ({mob[0]/w:.2f},{mob[1]/h:.2f}) area={int(mob[2])}")
                    time.sleep(ENGAGE_SETTLE)
                else:
                    rotate_view(page, w, h)
                    print(f"[scan] HP~{hp*100:3.0f}% no mob — rotating camera")

            time.sleep(CYCLE)


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\nstopped.")
