"""Test geometric snapping (replaces grid snap):
1. Endpoint snap: drawing a line ending near another line's endpoint snaps
   exactly onto it.
2. Perpendicular/near snap: drawing a line ending near another line's body
   snaps to the perpendicular foot on that line.
3. Snap OFF: no snapping occurs.
4. Start-point snap: a new line starting near an existing endpoint snaps its
   start onto it.
"""
import os, sys, time, json
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.firefox.service import Service

HERE = os.path.dirname(os.path.abspath(__file__))
GECKODRIVER = os.path.join(HERE, "geckodriver.exe")
BASE = "http://localhost:8000/field-capture.html?debug"

# Draw a line from normalized (sx,sy) to (ex,ey), transform-aware.
def draw_line(d, sx, sy, ex, ey, pid):
    d.execute_script("""
        return (function(sx, sy, ex, ey, pid){
          const canvas = document.getElementById('sketchCanvas');
          canvas.setPointerCapture = function(){};
          canvas.releasePointerCapture = function(){};
          canvas.hasPointerCapture = function(){ return false; };
          const shell = document.querySelector('.sketch-shell');
          const sr = shell.getBoundingClientRect();
          const left = sr.left + shell.clientLeft, top = sr.top + shell.clientTop;
          const view = document.getElementById('sketchView');
          const T = window.__test;
          const v = T.ui.viewState[T.ui.sketchId] || { scale: 1, tx: 0, ty: 0 };
          const w = view.offsetWidth, h = view.offsetHeight;
          const toC = (nx, ny) => ({ x: left + v.tx + nx*w*v.scale, y: top + v.ty + ny*h*v.scale });
          const s = toC(sx, sy), e = toC(ex, ey);
          const opts = { bubbles: true, cancelable: true, pointerType: 'touch', isPrimary: true };
          canvas.dispatchEvent(new PointerEvent('pointerdown', Object.assign({}, opts, {pointerId: pid, clientX: s.x, clientY: s.y})));
          for (let i = 1; i <= 5; i++) canvas.dispatchEvent(new PointerEvent('pointermove', Object.assign({}, opts, {pointerId: pid, clientX: s.x+(e.x-s.x)*i/5, clientY: s.y+(e.y-s.y)*i/5})));
          canvas.dispatchEvent(new PointerEvent('pointerup', Object.assign({}, opts, {pointerId: pid, clientX: e.x, clientY: e.y})));
        })(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]);
    """, sx, sy, ex, ey, pid)
    time.sleep(0.5)
    # Close the measurement sheet that opens after drawing.
    try:
        d.execute_script('document.querySelector(".sheet-minimal [data-action=closesheet]").click()')
        time.sleep(0.3)
    except Exception:
        pass

def get_lines(d):
    return json.loads(d.execute_script(
        "const T=window.__test; const sk=T.state.jobs[0].sketches[0]; return JSON.stringify(sk.lines.map(l=>({start:l.start,end:l.end})));"
    ))

def main():
    opts = Options()
    opts.headless = False
    svc = Service(executable_path=GECKODRIVER)
    d = webdriver.Firefox(options=opts, service=svc)
    d.set_window_size(480, 900)
    try:
        d.get(BASE)
        time.sleep(2)
        d.find_element(By.CSS_SELECTOR, '[data-action="newjob"]').click()
        time.sleep(0.8)
        d.find_element(By.CSS_SELECTOR, '[data-action="opensketch"]').click()
        time.sleep(0.8)
        d.execute_script("""
            const T = window.__test;
            const job = T.state.jobs[0]; const sk = job.sketches[0];
            const c = document.createElement('canvas'); c.width=200; c.height=150;
            const ctx = c.getContext('2d'); ctx.fillStyle='#ccc'; ctx.fillRect(0,0,200,150);
            sk.photo = { dataUrl: c.toDataURL('image/jpeg', 0.8), width: 200, height: 150 };
            T.saveState();
        """)
        d.execute_script(
            "const T=window.__test; T.ui.screen='sketch'; T.ui.sketchId=T.state.jobs[0].sketches[0].id; T.render();"
        )
        time.sleep(0.6)
        # Turn Snap ON.
        d.execute_script('const T=window.__test; T.ui.snapEnabled=true; T.render();')
        time.sleep(0.4)

        # Line 1: horizontal from (0.2,0.3) to (0.5,0.3).
        draw_line(d, 0.2, 0.3, 0.5, 0.3, 21)
        # Line 2: end near line 1's END endpoint (0.5,0.3). Touch at (0.51,0.31)
        # which is within snap range of (0.5,0.3). Endpoint snap wins.
        draw_line(d, 0.25, 0.45, 0.51, 0.31, 22)
        lines = get_lines(d)
        print("after endpoint snap:", json.dumps(lines))
        ok1 = lines[1]["end"]["x"] == lines[0]["end"]["x"] and abs(lines[1]["end"]["y"] - lines[0]["end"]["y"]) < 1e-9
        print("PASS endpoint snap" if ok1 else "FAIL endpoint snap")

        # Line 3: end near line 1's BODY (not endpoint). Touch at (0.35,0.32),
        # line 1 is at y=0.3. Perpendicular foot = (0.35,0.3). Snap to it.
        draw_line(d, 0.6, 0.5, 0.35, 0.32, 23)
        lines = get_lines(d)
        print("after perp snap:", json.dumps(lines))
        ok2 = abs(lines[2]["end"]["x"] - 0.35) < 1e-9 and abs(lines[2]["end"]["y"] - 0.3) < 1e-9
        print("PASS perpendicular snap" if ok2 else "FAIL perpendicular snap")

        # Turn Snap OFF.
        d.execute_script('const T=window.__test; T.ui.snapEnabled=false; T.render();')
        time.sleep(0.3)
        # Line 4: same touch near endpoint (0.5,0.3) but snap off -> no snap.
        draw_line(d, 0.25, 0.45, 0.51, 0.31, 24)
        lines = get_lines(d)
        print("after snap off:", json.dumps(lines))
        # Snap off -> the end stays exactly where it was touched (0.51,0.31).
        ok3 = abs(lines[3]["end"]["x"] - 0.51) < 1e-6 and abs(lines[3]["end"]["y"] - 0.31) < 1e-6
        print("PASS no snap when off" if ok3 else "FAIL no snap when off")

        # Start-point snap: turn snap on, draw a line STARTING near line 1's
        # start endpoint (0.2,0.3). Touch start at (0.19,0.29).
        d.execute_script('const T=window.__test; T.ui.snapEnabled=true; T.render();')
        time.sleep(0.3)
        draw_line(d, 0.19, 0.29, 0.3, 0.5, 25)
        lines = get_lines(d)
        print("after start snap:", json.dumps(lines))
        ok4 = lines[4]["start"]["x"] == lines[0]["start"]["x"] and abs(lines[4]["start"]["y"] - lines[0]["start"]["y"]) < 1e-9
        print("PASS start-point snap" if ok4 else "FAIL start-point snap")

        if ok1 and ok2 and ok3 and ok4:
            print("ALL SNAP TESTS PASSED")
        else:
            print("SOME SNAP TESTS FAILED")
    finally:
        d.quit()

if __name__ == "__main__":
    main()