//! v3.9 c1 — dogfood usecase: alerts counting (text-regex + nth=0 modifier).
//!
//! Port reference: `insight/tools/maestro/flows/main/alerts-counting.yaml`
//! & `insight/tools/maestro/subflows/go-to-alerts.yaml`. Drives the
//! Alerts → Counting sub-tab, taps the first counting item by **regex
//! pattern + nth index**, then exercises chart time-range toggles.
//!
//! # Why this dogfood
//!
//! v3.4 baseline `insight_parity::flow_alerts_counting` covers this
//! yaml only up to step 4 ("list/empty visible"). Steps 5+ — the
//! `tapOn text:"Area Counting|Line Crossing|Crowd Estimate" index:0`
//! call — was never exercised end-to-end. That call mixes a regex
//! pattern with `nth=0` modifier; v3.7 c2 + v3.8 c2 used
//! `Selector::Text { text: Pattern::regex(".+") + below + nth }` —
//! a different composition variant (regex+spatial+index). v3.9 c1
//! exercises **regex+nth without spatial**, surfacing whether the
//! resolver's nth-after-regex pipeline behaves the same on plain
//! list candidates.
//!
//! Also exercises:
//!
//! - Alerts bottom-tab nav + Counting sub-tab text-selector tap
//! - chart time-range text-selector tap (Week / Month, optional)
//!
//! # Reversibility
//!
//! Pure read — Alerts list is server-state, view-only. Time toggle
//! just changes the chart's local view scope, no mutation. Bottom-tab
//! nav back to home is a single tap.

use smix_sdk::{App, ExpectationFailure, Modifiers, Pattern, Selector, 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 alerts counting (v3.9 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!("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 alerts_counting_flow(&app).await {
        Ok(()) => {
            println!("\n✓ alerts counting flow passed (1-round dogfood)");
            println!("\n🎉 v3.9 c1 dogfood — alerts counting ✅");
            Ok(())
        }
        Err(e) => {
            println!("\n✗ alerts counting flow failed");
            for line in e.to_prompt().lines() {
                println!("    {line}");
            }
            Err("dogfood alerts counting FAIL".into())
        }
    }
}

// ---- inline helpers (each example 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(())
}

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(())
}

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/go-to-alerts.yaml`: repeat tap "Alerts" until
/// `screen-alerts` id is visible. We probe via wait_for short-timeout
/// rather than find to honour the visibility filter.
async fn go_to_alerts(app: &App) -> Result<(), ExpectationFailure> {
    for _ in 0..5 {
        if app
            .wait_for(&id("screen-alerts"), Duration::from_millis(500))
            .await
            .is_ok()
        {
            return Ok(());
        }
        let _ = app.tap(&text("Alerts")).await;
        tokio::time::sleep(Duration::from_secs(2)).await;
    }
    app.wait_for(&id("screen-alerts"), Duration::from_secs(5))
        .await?;
    Ok(())
}

// ---- alerts-counting flow (port of flows/main/alerts-counting.yaml) -----

async fn alerts_counting_flow(app: &App) -> Result<(), ExpectationFailure> {
    println!("step 1: tap Alerts bottom tab → screen-alerts visible");
    go_to_alerts(app).await?;

    println!("step 2: switch to Counting sub-tab");
    app.tap(&text("Counting")).await?;
    tokio::time::sleep(Duration::from_millis(800)).await;

    println!("step 3: wait Counting list or empty state (≤30s)");
    app.wait_for(
        &text_regex("No counting configurations|Area Counting|Line Crossing|Crowd"),
        Duration::from_secs(30),
    )
    .await?;

    println!("step 4: tap first counting item via text-regex + nth=0 modifier (optional)");
    // Composition variant: regex match + nth index, no spatial anchor.
    // v3.7 c2 used regex + below + nth; v3.8 c2 used regex + below + nth;
    // v3.9 c1 strips the spatial anchor — pure regex match then nth pick.
    let first_counting_item = Selector::Text {
        text: Pattern::regex("Area Counting|Line Crossing|Crowd Estimate"),
        modifiers: Modifiers {
            nth: Some(0),
            ..Modifiers::default()
        },
    };
    match app.tap(&first_counting_item).await {
        Ok(()) => {
            println!("  tapped first counting item via regex+nth (list has data)");
            tokio::time::sleep(Duration::from_millis(800)).await;
        }
        Err(_) => {
            println!("  no counting item to tap (empty configurations)");
        }
    }

    println!(
        "step 5: verify chart page or stayed on list (regex Day|Week|Month|No counting|Area Counting, ≤30s)"
    );
    app.wait_for(
        &text_regex("Day|Week|Month|No counting configurations|Area Counting"),
        Duration::from_secs(30),
    )
    .await?;

    println!("step 6: try Week time-range toggle (optional)");
    if app
        .wait_for(&text("Week"), Duration::from_millis(500))
        .await
        .is_ok()
    {
        let _ = app.tap(&text("Week")).await;
        tokio::time::sleep(Duration::from_millis(500)).await;
    } else {
        println!("  Week toggle not visible (stayed on list / empty state)");
    }

    println!("step 7: try Month time-range toggle (optional)");
    if app
        .wait_for(&text("Month"), Duration::from_millis(500))
        .await
        .is_ok()
    {
        let _ = app.tap(&text("Month")).await;
        tokio::time::sleep(Duration::from_millis(500)).await;
    } else {
        println!("  Month toggle not visible");
    }

    Ok(())
}
