/* ── the phone, turned over: the two settings that do it ───────────────
 *  `settings get system accelerometer_rotation` and `user_rotation` are the
 *  only two things on the device this walk ever writes, and both are READ
 *  before either is written. The saved pair is latched at module scope on
 *  purpose: the step loop's `still()` and its PROBE call are wrapped in
 *  nothing at all, so a WebView that stops answering rejects after cdp.mjs's
 *  timeout and throws clean out of the walk into main's `finally`. That is
 *  the exact path that would otherwise leave Kyle's phone locked sideways,
 *  which is why the restore is a latch reachable from four places — the
 *  rotation block's own finally, main's finally, process 'exit' and a
 *  signal — rather than a step in a list. */
let rotationSaved = null;

/** Both rotation settings as the device has them now, or null when either
 *  will not read as something we recognise. null is the SKIP signal: the
 *  block never writes what it could not read first. `user_rotation` comes
 *  back as the literal "null" on a phone where it has never been set. */
function rotationSettings() {
    try {
        const accel = adb("shell", "settings", "get", "system", "accelerometer_rotation").toString().trim();
        const user = adb("shell", "settings", "get", "system", "user_rotation").toString().trim();
        if (!/^\d+$/.test(accel)) return null;
        if (!/^\d+$/.test(user) && user !== "null") return null;
        return { accel, user };
    } catch { return null; }
}

/** Read once and latch. Returns the pair, or null when the device would not
 *  say — in which case nothing has been written and there is nothing to put
 *  back. */
function rotationArm() {
    const was = rotationSettings();
    if (was) rotationSaved = was;
    return was;
}

/** Drop the latch WITHOUT writing anything. For the skip paths: the pair has
 *  been read but nothing has been put, and restoring there would write two
 *  settings onto Kyle's phone that this walk never changed. */
function rotationDisarm() { rotationSaved = null; }

/** Hold the phone at one rotation. Auto-rotate goes off FIRST: with the
 *  sensor still in charge it takes `user_rotation` straight back and the
 *  turn never happens. Returns null, or the message it failed with. */
function rotationPut(n) {
    try {
        adb("shell", "settings", "put", "system", "accelerometer_rotation", "0");
        adb("shell", "settings", "put", "system", "user_rotation", String(n));
        return null;
    } catch (e) { return e.message; }
}

/** Put both back, once. The latch is cleared BEFORE anything is written, so
 *  the first caller is the only one that acts and the three behind it are
 *  no-ops; a restore that fails puts the latch back so the next caller can
 *  try again. The rotation goes first and auto-rotate last — a phone whose
 *  auto-rotate was on must hand control back to the sensor only once the
 *  angle underneath it is the one it started at. Synchronous, because
 *  `adb()` is spawnSync and nothing asynchronous survives an 'exit'
 *  handler. Returns true, null when there was nothing to restore, or the
 *  message it failed with. */
function rotationRestore() {
    const was = rotationSaved;
    rotationSaved = null;
    if (!was) return null;
    try {
        if (was.user === "null") {
            /* never set on this phone: delete it rather than inventing a 0 */
            try { adb("shell", "settings", "delete", "system", "user_rotation"); }
            catch { adb("shell", "settings", "put", "system", "user_rotation", "0"); }
        } else {
            adb("shell", "settings", "put", "system", "user_rotation", was.user);
        }
        adb("shell", "settings", "put", "system", "accelerometer_rotation", was.accel);
        return true;
    } catch (e) { rotationSaved = was; return e.message; }
}

/** The system's own view of the turn, for the report only. The two settings
 *  read back, plus the display's current size and rotation out of `dumpsys
 *  window displays` — whose spelling moves between Android versions, so a
 *  parse failure comes back empty and is printed, never asserted. */
function rotationSystem() {
    const out = { setAccel: "", setUser: "", dispCur: "", dispRot: "" };
    try {
        out.setAccel = adb("shell", "settings", "get", "system", "accelerometer_rotation").toString().trim();
        out.setUser = adb("shell", "settings", "get", "system", "user_rotation").toString().trim();
    } catch { /* empty is the reading */ }
    try {
        const d = adb("shell", "dumpsys", "window", "displays").toString();
        out.dispCur = (d.match(/\bcur=(\d+x\d+)/) || [])[1] || "";
        out.dispRot = (d.match(/mCurrentRotation=(?:ROTATION_)?(\w+)/) || d.match(/\brotation=(\d+)/) || [])[1] || "";
    } catch { /* empty is the reading */ }
    return out;
}

/* 'exit' does not run on a signal and a signal does not run 'exit', so
   both. Ctrl-C in the middle of the turn is precisely the case that would
   otherwise leave the phone sideways until Kyle found the Quick Settings
   tile. Registered only for the phone walk — the mock never writes a
   setting, and a SIGINT handler on the mock run would change how Ctrl-C
   behaves there for nothing. */
if (PHONE) {
    process.on("exit", () => { rotationRestore(); });
    for (const sig of ["SIGINT", "SIGTERM", "SIGBREAK"]) {
        process.on(sig, () => { rotationRestore(); process.exit(130); });
    }
}
