"""Test browser back-button / swipe-back navigation.

The app is a single-page app, so the browser's own history has nothing to walk
back through. We seed a guard history entry and interpret each back press
against live app state:
  1. cancel an in-progress drawing / copy-drag / endpoint drag
  2. close the open dimension sheet
  3. close the tips sheet, or cancel "add sketch"
  4. step back a screen (sketch -> job -> jobs)
At the jobs root we let the browser exit.

We drive the back gesture by dispatching a real 'popstate' event (which is what
the browser fires on swipe-back) and assert the app state stepped back one
level each time.
"""
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"

def back(d):
    # Simulate the browser firing popstate (swipe-back). Our handler reads
    # history.state; if it isn't the guard it re-seeds, otherwise it steps back.
    d.execute_script("window.dispatchEvent(new PopStateEvent('popstate'));")
    time.sleep(0.3)

def get_screen(d):
    return d.execute_script("return window.__test.ui.screen;")

def has_sheet(d):
    return d.execute_script("return !!window.__test.ui.pinModal;")

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();
            T.ui.screen='sketch'; T.ui.sketchId=sk.id; T.render();
        """)
        time.sleep(0.6)

        # ---- 1. Back from sketch screen -> job screen ----
        assert get_screen(d) == "sketch", "should start on sketch"
        back(d)
        assert get_screen(d) == "job", f"expected job after back, got {get_screen(d)}"
        print("PASS back: sketch -> job")

        # ---- 2. Back from job screen -> jobs screen ----
        back(d)
        assert get_screen(d) == "jobs", f"expected jobs after back, got {get_screen(d)}"
        print("PASS back: job -> jobs")

        # ---- 3. Back at jobs root: browser should exit (state unchanged) ----
        back(d)
        assert get_screen(d) == "jobs", "jobs root should stay put"
        print("PASS back: jobs root stays (browser would exit)")

        # ---- 4. Back cancels an in-progress drawing ----
        d.find_element(By.CSS_SELECTOR, '[data-action="openjob"]').click()
        time.sleep(0.6)
        d.find_element(By.CSS_SELECTOR, '[data-action="opensketch"]').click()
        time.sleep(0.6)
        # Begin a drawing (pointerdown + a small move) so a draft is active.
        d.execute_script("""
            const T = window.__test;
            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 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(0.2, 0.3), e = toC(0.5, 0.4);
            const opts = { bubbles: true, cancelable: true, pointerType: 'touch', isPrimary: true };
            canvas.dispatchEvent(new PointerEvent('pointerdown', Object.assign({}, opts, {pointerId: 1, clientX: s.x, clientY: s.y})));
            canvas.dispatchEvent(new PointerEvent('pointermove', Object.assign({}, opts, {pointerId: 1, clientX: s.x+10, clientY: s.y+10})));
        """)
        time.sleep(0.3)
        draftActive = d.execute_script("return window.__test.ui.sketchDraft && window.__test.ui.sketchDraft.active;")
        assert draftActive, "drawing should be active"
        # Back cancels the drawing (no line created).
        back(d)
        draftActive = d.execute_script("return window.__test.ui.sketchDraft && window.__test.ui.sketchDraft.active;")
        lines = d.execute_script("return window.__test.state.jobs[0].sketches[0].lines.length;")
        assert not draftActive, "draft should be cancelled"
        assert lines == 0, f"no line should be created, got {lines}"
        assert get_screen(d) == "sketch", "should stay on sketch after cancelling draft"
        print("PASS back: cancels in-progress drawing")

        # ---- 5. Back closes the dimension sheet ----
        # Create a real line so a sheet opens.
        d.execute_script("""
            const T = window.__test;
            const canvas = document.getElementById('sketchCanvas');
            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 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(0.2, 0.3), e = toC(0.5, 0.4);
            const opts = { bubbles: true, cancelable: true, pointerType: 'touch', isPrimary: true };
            canvas.dispatchEvent(new PointerEvent('pointerdown', Object.assign({}, opts, {pointerId: 2, clientX: s.x, clientY: s.y})));
            for (let i=1;i<=5;i++) canvas.dispatchEvent(new PointerEvent('pointermove', Object.assign({}, opts, {pointerId: 2, 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: 2, clientX: e.x, clientY: e.y})));
        """)
        time.sleep(0.6)
        assert has_sheet(d), "sheet should be open after drawing"
        back(d)
        assert not has_sheet(d), "sheet should close on back"
        assert get_screen(d) == "sketch", "should stay on sketch after closing sheet"
        print("PASS back: closes dimension sheet")

        # ---- 6. Back closes tips sheet ----
        d.execute_script("const T=window.__test; T.ui.infoOpen=true; T.render();")
        time.sleep(0.3)
        assert d.execute_script("return window.__test.ui.infoOpen;") is True
        back(d)
        assert d.execute_script("return window.__test.ui.infoOpen;") is False
        print("PASS back: closes tips sheet")

        print("ALL BACK-NAV TESTS PASSED")
    finally:
        d.quit()

if __name__ == "__main__":
    main()