//! v3.16 c2 — dogfood usecase: counting fullscreen (§9 #3 lift escape hatch).
//!
//! Port reference: `insight/tools/maestro/flows/main/counting-fullscreen.yaml`
//! & `insight/tools/maestro/subflows/go-to-alerts.yaml`. Drives the
//! Alerts → Counting sub-tab → first counting item → fullscreen chart →
//! date picker → **picker dismiss via `App::tap_at_coord(0.5, 0.95)`** →
//! exit fullscreen → back to portrait chart controls.
//!
//! # Why this dogfood
//!
//! v3.16 §9 #3 不变量 lift business first-証. yaml step 12 (`tapOn: point:
//! "50%,95%"`) dismisses the date picker by tapping the backdrop at a
//! coord without a11y semantic. Pre-lift, this case had no smix path
//! (Selector surface forbids coords). Post-lift, `App::tap_at_coord(nx,
//! ny)` is the canonical escape hatch — this example exercises it
//! end-to-end through SDK → SimctlDriver::tap_at_norm_coord → runner →
//! Apple native event chain.
//!
//! Also exercises (graceful-skip if no counting data):
//! - id-selector tap on chart fullscreen enter/exit/date buttons
//! - extendedWaitUntil pattern via wait_for + text-regex
//! - fullscreen state retention across modal dismissal
//!
//! # Reversibility
//!
//! Pure read — counting list is server-state, view-only. Fullscreen/exit
//! toggle is local view scope. Date picker dismiss leaves chart untouched.
//! Bottom-tab nav back to home is single tap.
//!
//! # Capability gap surfaced
//!
//! None planned. If counting list is empty (server-state), flow gracefully
//! stops after step 4. fullscreen path is RN-rendered chart so v1.7
//! skeleton-loader risk (capstone §"已知风险" #3) is monitored — first
//! cycle real-sim run will confirm.

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 counting fullscreen (v3.16 c2)");
    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 counting_fullscreen_flow(&app).await {
        Ok(()) => {
            println!("\n✓ counting fullscreen flow passed (1-round dogfood)");
            println!("\n🎉 v3.16 c2 dogfood — counting fullscreen ✅");
            Ok(())
        }
        Err(e) => {
            println!("\n✗ counting fullscreen flow failed");
            for line in e.to_prompt().lines() {
                println!("    {line}");
            }
            Err("dogfood counting fullscreen 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/go-to-alerts.yaml`: repeat tap "Alerts" until
/// `screen-alerts` id is visible.
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(())
}

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

async fn counting_fullscreen_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 Estimate"),
        Duration::from_secs(30),
    )
    .await?;

    println!("step 4: probe first counting item via regex+nth=0 (graceful skip if empty)");
    let first_counting_item = Selector::Text {
        text: Pattern::regex("Area Counting|Line Crossing|Crowd Estimate"),
        modifiers: Modifiers {
            nth: Some(0),
            ..Modifiers::default()
        },
    };
    if !app.find(&first_counting_item).await? {
        println!("  no counting configurations — fullscreen path not exercisable, graceful stop");
        return Ok(());
    }
    app.tap(&first_counting_item).await?;
    tokio::time::sleep(Duration::from_millis(800)).await;

    println!("step 5: tap counting-chart-enter-fullscreen");
    app.tap(&id("counting-chart-enter-fullscreen")).await?;
    tokio::time::sleep(Duration::from_millis(800)).await;

    println!("step 6: wait counting-chart-exit-fullscreen visible (≤5s — fullscreen confirmed)");
    app.wait_for(
        &id("counting-chart-exit-fullscreen"),
        Duration::from_secs(5),
    )
    .await?;

    println!("step 7: tap counting-chart-fullscreen-date → date picker opens");
    app.tap(&id("counting-chart-fullscreen-date")).await?;
    tokio::time::sleep(Duration::from_millis(800)).await;

    println!("step 8: tap_at_coord(0.5, 0.95) → dismiss picker via backdrop (§9 #3 lift)");
    // This is the v3.16 escape hatch use — yaml step 12 `tapOn: point:
    // "50%,95%"`. The picker has no a11y label for its backdrop region,
    // so Selector-based tap is impossible here. Per CLAUDE.md §9 #3
    // 2026-05-27 lift, App::tap_at_coord(nx, ny) is the authorized
    // escape hatch.
    app.tap_at_coord(0.5, 0.95).await?;
    tokio::time::sleep(Duration::from_millis(800)).await;

    println!("step 9: picker dismissed but still in fullscreen (exit-fullscreen id still visible)");
    app.assert_visible(&id("counting-chart-exit-fullscreen"))
        .await?;

    println!("step 10: tap counting-chart-exit-fullscreen → back to portrait");
    app.tap(&id("counting-chart-exit-fullscreen")).await?;
    tokio::time::sleep(Duration::from_millis(800)).await;

    println!("step 11: portrait chart controls visible (Current Count/Day/Week/Month, ≤5s)");
    app.wait_for(
        &text_regex("Current Count|Day|Week|Month"),
        Duration::from_secs(5),
    )
    .await?;

    Ok(())
}
