//! v3.5 c5 — dogfood usecase #2: open device detail (insight subflow).
//!
//! Port reference: `insight/tools/maestro/subflows/open-device-detail.yaml`
//! (3-step id-heavy flow opening the first online device's detail panel).
//!
//! # Why this dogfood
//!
//! Distinct surface from c4's logged-out language picker — c5 exercises
//! the **logged-in** id-selector path:
//!
//! - Three consecutive `id("btn-device-search")` / `id("btn-device-detail")`
//!   / `id("panel-device-detail")` calls — every single one rides c2's
//!   host-resolve fallback (the runner `/find` route can't query by id).
//! - Confirms the device-list scroll-and-auto-select machinery doesn't
//!   trip the c1 `topmost_modal_filter` (no false-positive modal detect
//!   on the device cards).
//! - 30s + 15s + 30s implicit waits prove the host-resolve poll loop
//!   converges on real-sim with c3's `derive_roles_recursive` lifting
//!   raw_type → role at each /tree fetch.
//!
//! # Reversibility
//!
//! Opening a device detail panel is a pure read — no backend state
//! mutation, no business data change, no permission alert. Closing the
//! panel (or terminating + relaunching) returns to the device list.

use smix_sdk::{App, ExpectationFailure, id, text, text_regex};
use std::time::Duration;

#[tokio::main(flavor = "multi_thread", worker_threads = 4)]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let port: u16 = std::env::var("SMIX_RUNNER_PORT")
        .ok()
        .and_then(|s| s.parse().ok())
        .unwrap_or(22087);
    let udid = std::env::var("SMIX_UDID").ok();

    println!("smix dogfood — insight open device detail (v3.5 c5)");
    println!("====================================================");
    println!("runner port: {port}");
    println!("udid:        {:?}", udid);
    println!();

    let mut app = App::connect_to_runner(port).await?;
    if let Some(u) = udid {
        app = app.with_udid(u);
    }

    let bundle_id =
        std::env::var("INSIGHT_BUNDLE_ID").unwrap_or_else(|_| "com.focusai.app.mobile".to_string());
    println!("foregrounding {bundle_id}");
    app.foreground(&bundle_id).await?;
    tokio::time::sleep(Duration::from_secs(2)).await;

    println!("ensure_login: starting");
    ensure_login(&app).await?;
    println!("ensure_login: done");
    println!();

    println!("reset_to_home: tap Device tab + close any open drawer");
    reset_to_home(&app).await?;
    println!("reset_to_home: done");
    println!();

    println!("close_dev_menu_if_open: probe + dismiss");
    close_dev_menu_if_open(&app).await?;
    println!("close_dev_menu_if_open: done");
    println!();

    match open_device_detail(&app).await {
        Ok(()) => {
            println!();
            println!("✓ open device detail flow passed (1-round dogfood)");
            println!("\n🎉 v3.5 c5 dogfood #2 — open device detail ✅");
            Ok(())
        }
        Err(e) => {
            println!();
            println!("✗ open device detail flow failed");
            for line in e.to_prompt().lines() {
                println!("    {line}");
            }
            Err("dogfood device detail FAIL".into())
        }
    }
}

/// Mirrors `insight/tools/maestro/subflows/dev-bubble-login.yaml`. Same
/// shape as `insight_parity.rs::ensure_login` — copied verbatim so each
/// dogfood example stays self-contained.
async fn ensure_login(app: &App) -> Result<(), ExpectationFailure> {
    // Check both the login screen text AND the dev-bubble id. The login
    // screen flashes briefly on RN bundle re-init even when AsyncStorage
    // has cached auth — see "Log in to Insight" being found by /find for
    // a single tick while the home screen is still mounting. Insist on
    // the dev-bubble before walking the login flow; if it's gone, the
    // session is already authenticated.
    let login_text_visible = app.find(&text("Log in to Insight")).await?;
    let dev_bubble_visible = app.find(&id("dev-bubble")).await?;
    if !(login_text_visible && dev_bubble_visible) {
        println!("  already logged in (login screen not stable)");
        return Ok(());
    }
    println!("  tapping dev-bubble");
    app.tap(&id("dev-bubble")).await?;
    tokio::time::sleep(Duration::from_millis(1500)).await;
    println!("  tapping 'Login'");
    app.tap(&text("Login")).await?;
    println!("  waiting for home tab");
    app.wait_for(
        &text_regex("Open menu|Device|Search|Merlin|Alerts|Tracking"),
        Duration::from_secs(30),
    )
    .await?;
    Ok(())
}

/// Same shape as `insight_parity.rs::reset_to_home`. Closes the drawer
/// if open (back × 3) and taps Device bottom tab — which also dismisses
/// the dev-mode floating overlay that sits above the device list, so
/// the underlying btn-device-* id targets become tappable.
async fn reset_to_home(app: &App) -> Result<(), ExpectationFailure> {
    for _ in 0..3 {
        let drawer_open = app
            .wait_for(&text("Dashboard"), Duration::from_millis(200))
            .await
            .is_ok();
        if !drawer_open {
            break;
        }
        let _ = app.go_back().await;
        tokio::time::sleep(Duration::from_millis(400)).await;
    }
    if app.find(&text("Device")).await? {
        let _ = app.tap(&text("Device")).await;
        tokio::time::sleep(Duration::from_millis(800)).await;
    }
    // Home-screen ready indicator. v3.5+ baseline used `text("Open menu")`
    // (the drawer hamburger's accessibilityLabel). Insight RN bundle has since
    // dropped that label on some screens (e.g. Merlin tab's header lacks it),
    // so fall back to any of the bottom-tab labels — at least one is always
    // visible on a home tab.
    app.wait_for(
        &text_regex("Open menu|Device|Search|Merlin|Alerts|Tracking"),
        Duration::from_secs(5),
    )
    .await?;
    Ok(())
}

/// The expo-dev-launcher floats a dev menu over every screen until the
/// user dismisses it. Its labels stay in the React tree even when the
/// menu is collapsed (pre-mount), so a plain `find(text("Reboot"))`
/// alone isn't enough — but the resolver's visibility filter + topmost
/// hit-test (c1) drop the off-screen labels reliably, so a visible
/// "Reboot" really does mean the menu is up. When it is, tap the
/// dev-bubble toggle to dismiss; otherwise no-op.
async fn close_dev_menu_if_open(app: &App) -> Result<(), ExpectationFailure> {
    // wait_for short timeout → host-resolve path (visibility filter is the
    // one that knows about off-screen pre-mount frames).
    let open = app
        .wait_for(&text("Reboot"), Duration::from_millis(300))
        .await
        .is_ok();
    if open {
        println!("  dev menu visible (Reboot label found on-screen), tapping dev-bubble");
        app.tap(&id("dev-bubble")).await?;
        tokio::time::sleep(Duration::from_millis(800)).await;
    } else {
        println!("  dev menu already dismissed");
    }
    Ok(())
}

/// Port of `open-device-detail.yaml`. Three id selectors back-to-back —
/// every one of them lives on c2's host-resolve fallback path.
async fn open_device_detail(app: &App) -> Result<(), ExpectationFailure> {
    println!("step 1: wait for device list (id btn-device-search, ≤30s)");
    app.wait_for(&id("btn-device-search"), Duration::from_secs(30))
        .await?;

    println!("step 2: wait for auto-selected device's Detail button (id btn-device-detail, ≤15s)");
    app.wait_for(&id("btn-device-detail"), Duration::from_secs(15))
        .await?;

    println!("step 3: tap Detail (id btn-device-detail)");
    app.tap(&id("btn-device-detail")).await?;

    println!("step 4: wait for panel (id panel-device-detail, ≤30s)");
    app.wait_for(&id("panel-device-detail"), Duration::from_secs(30))
        .await?;

    Ok(())
}
