//! v3.17 c1 — dogfood usecase: dashboard (scroll round-trip).
//!
//! Port reference: `insight/tools/maestro/flows/main/dashboard.yaml`
//! & `insight/tools/maestro/subflows/go-to-dashboard.yaml`
//! & `insight/tools/maestro/subflows/open-menu.yaml`. Drives Open menu →
//! Dashboard → verify top cards → scroll down to Data Visualization →
//! scroll down to Recent events → scroll back up to Device Overview.
//!
//! # Why this dogfood
//!
//! v3.17 c1 first round bakes the **scroll-until-visible loop pattern**
//! against insight's real RN-rendered dashboard scroll view. `App::scroll`
//! is exposed but its internal retry/termination behavior is unspecified
//! at the SDK contract level, so this example explicitly hand-rolls the
//! `for _ in 0..N { find/break/scroll }` loop. That keeps the
//! escape-hatch surface narrow (caller decides budget) and matches
//! maestro's `scrollUntilVisible` semantics 1:1 (poll-then-swipe-then-
//! poll) for AI test-author predictability.
//!
//! Also exercises:
//! - subflow inline-port pattern (`open_menu` + `go_to_dashboard`)
//! - SwipeDirection::Up reveals lower content (content moves up); ::Down
//!   reveals upper content. Maestro `direction: DOWN` = scroll page
//!   downward = swipe-up gesture, so DOWN → SwipeDirection::Up.
//!
//! # Reversibility
//!
//! Pure read. Dashboard is a static layout of cards + lists. Scroll is
//! local view scope; round-trip back to Device Overview restores initial
//! scroll position by definition.
//!
//! # Capability gap surfaced
//!
//! None planned. If `App::scroll` turns out to consistently overshoot or
//! the find-loop fails to converge in 10 rounds on real RN list, file
//! cap-gap candidate "scroll-until-visible needs SDK-level loop with
//! anchor-stability detection". v3.17 c1 first-cycle real-sim run is the
//! confirming probe.

use smix_sdk::{App, ExpectationFailure, Selector, SwipeDirection, 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 dashboard (v3.17 c1)");
    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!("launch-warm: terminate + openurl deep link");
    let _ = app.terminate(&bundle_id).await;
    tokio::time::sleep(Duration::from_millis(800)).await;
    app.open_url("exp+focus-ai-app://expo-development-client/?url=http%3A%2F%2Flocalhost%3A8081")
        .await?;
    app.wait_for(
        &text_regex("Log in to Insight|Device|Open menu"),
        Duration::from_secs(45),
    )
    .await?;

    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\n");

    println!("reset_to_home");
    reset_to_home(&app).await?;
    println!("reset_to_home: done\n");

    println!("close_dev_menu_if_open");
    close_dev_menu_if_open(&app).await?;
    println!("close_dev_menu_if_open: done\n");

    match dashboard_flow(&app).await {
        Ok(()) => {
            println!("\n✓ dashboard flow passed (1-round dogfood)");
            println!("\n🎉 v3.17 c1 dogfood — dashboard ✅");
            Ok(())
        }
        Err(e) => {
            println!("\n✗ dashboard flow failed");
            for line in e.to_prompt().lines() {
                println!("    {line}");
            }
            Err("dogfood dashboard FAIL".into())
        }
    }
}

// ---- inline helpers (each example self-contained) -----------------------

async fn ensure_login(app: &App) -> Result<(), ExpectationFailure> {
    // v3.14 c2 robust pattern: double-check login_text + dev_bubble both
    // visible. `find` (Apple element query) can flash login text for a
    // single tick during RN home-screen mount even when session cached.
    // Insist on dev-bubble; if gone, 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(())
}

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;
    }
    // v3.14 c2 robust fallback: any bottom-tab label (insight RN bundle
    // dropped `Open menu` label on some screens; Merlin tab header lacks it).
    app.wait_for(
        &text_regex("Open menu|Device|Search|Merlin|Alerts|Tracking"),
        Duration::from_secs(5),
    )
    .await?;
    Ok(())
}

async fn close_dev_menu_if_open(app: &App) -> Result<(), ExpectationFailure> {
    let open = app
        .wait_for(&text("Reboot"), Duration::from_millis(300))
        .await
        .is_ok();
    if open {
        println!("  dev menu visible, tapping dev-bubble");
        app.tap(&id("dev-bubble")).await?;
        tokio::time::sleep(Duration::from_millis(800)).await;
    } else {
        println!("  dev menu already dismissed");
    }
    Ok(())
}

/// Mirror of `subflows/open-menu.yaml`:
/// - extendedWaitUntil id="btn-open-menu" 30s
/// - repeat while notVisible "Dashboard": tap "Open menu" + 800ms settle
///   (cap 5 rounds — beyond that surface as failure)
async fn open_menu(app: &App) -> Result<(), ExpectationFailure> {
    app.wait_for(&id("btn-open-menu"), Duration::from_secs(30))
        .await?;
    for _ in 0..5 {
        if app
            .wait_for(&text("Dashboard"), Duration::from_millis(500))
            .await
            .is_ok()
        {
            return Ok(());
        }
        let _ = app.tap(&text("Open menu")).await;
        tokio::time::sleep(Duration::from_millis(800)).await;
    }
    app.wait_for(&text("Dashboard"), Duration::from_secs(5))
        .await?;
    Ok(())
}

/// Mirror of `subflows/go-to-dashboard.yaml`: open menu → tap "Dashboard"
/// → wait animation → assert "Dashboard" visible (header).
async fn go_to_dashboard(app: &App) -> Result<(), ExpectationFailure> {
    open_menu(app).await?;
    app.tap(&text("Dashboard")).await?;
    tokio::time::sleep(Duration::from_millis(800)).await;
    app.assert_visible(&text("Dashboard")).await?;
    Ok(())
}

/// scrollUntilVisible(target, direction) — hand-rolled loop matching
/// maestro's poll-then-swipe-then-poll semantics. `direction` here is
/// the **gesture direction** (Up = swipe screen up = reveal content
/// below; Down = swipe screen down = reveal content above), so callers
/// translate maestro `direction: DOWN` (page scrolls down) → ::Up.
async fn scroll_until_visible(
    app: &App,
    target: &Selector,
    direction: SwipeDirection,
) -> Result<(), ExpectationFailure> {
    for _ in 0..10 {
        if app.find(target).await? {
            return Ok(());
        }
        app.scroll(target, direction).await?;
        tokio::time::sleep(Duration::from_millis(400)).await;
    }
    app.wait_for(target, Duration::from_secs(5)).await?;
    Ok(())
}

// ---- dashboard flow (port of flows/main/dashboard.yaml) ------------------

async fn dashboard_flow(app: &App) -> Result<(), ExpectationFailure> {
    println!("step 1: open menu + tap Dashboard");
    go_to_dashboard(app).await?;

    println!("step 2: assert top cards Device Overview + Data Visualization");
    app.assert_visible(&text("Device Overview")).await?;
    app.assert_visible(&text("Data Visualization")).await?;

    println!("step 3: scrollUntilVisible Data Visualization (DOWN → swipe-up)");
    scroll_until_visible(app, &text("Data Visualization"), SwipeDirection::Up).await?;
    app.assert_visible(&text("Data Visualization")).await?;

    println!("step 4: scrollUntilVisible Recent events (DOWN → swipe-up)");
    scroll_until_visible(app, &text("Recent events"), SwipeDirection::Up).await?;
    app.assert_visible(&text("Recent events")).await?;

    println!("step 5: scrollUntilVisible Device Overview (UP → swipe-down) — round-trip back");
    scroll_until_visible(app, &text("Device Overview"), SwipeDirection::Down).await?;

    Ok(())
}
