//! v3.17 c3 — dogfood usecase: setup AP WiFi list (mock SN → AP mode → wifi list).
//!
//! Port reference: `insight/tools/maestro/flows/setup/ap-wifi-list.yaml`
//! & subflows `launch-warm.yaml`, `ensure-login.yaml`, `go-to-add-device.yaml`,
//! `open-menu.yaml`. Drives:
//!
//! 1. launch-warm (terminate + openurl deep link + wait landing/home)
//! 2. ensure-login (double-check pattern, dev-bubble → Login if visible)
//! 3. go-to-add-device (open drawer → tap "Add Device" → wait `screen-add-device`)
//! 4. tap "Enter code" → tap `input-sn` → fill "MOCK00010001"
//! 5. tap "Add Device" → wait `Select Network|device not found` (≤30s)
//! 6. tap "WiFi" optional → tap "Continue" optional (skip-if-missing)
//! 7. wait `SSID|Wi-Fi Setup|device not found` (≤30s)
//! 8. assertVisible "Select a Wi-Fi network" optional
//! 9. assertVisible "Enter SSID manually" optional
//!
//! # Why this dogfood
//!
//! Setup write-path E2E first-証 — exercises the mock-SN happy path through
//! AP mode handshake into the WiFi-list page. Mock backend (`MOCK00010001`)
//! auto-advances connecting → wifi-list without real device, so flow is
//! deterministic in mock mode. yaml uses heavy `optional: true` pattern
//! because real-device branches (network select / WiFi / Continue button)
//! may or may not render depending on backend response — mock path skips
//! most. smix SDK has no native `optional` flag on `tap` / `assert_visible`,
//! so this example uses `find` (boolean probe) gating to mirror the
//! skip-if-missing semantics.
//!
//! Also covers:
//! - `fill` on `input-sn` id (text-input fill via XCUITest typeText path)
//! - regex pipe pattern `Select Network|device not found` (text_regex)
//! - extendedWaitUntil timeout=30s mapped to `wait_for` Duration::from_secs(30)
//!
//! # Reversibility
//!
//! Mock SN — mock backend `MOCK00010001` is the canonical test serial,
//! does not commit a real device add to production. Flow terminates at
//! WiFi-list page (or earlier on optional-skip), leaving app in
//! mid-setup view state. Next dogfood run starts with `launch-warm`
//! (terminate + openurl), which discards the in-flight setup wizard
//! state — no cross-test leakage.
//!
//! # Capability gap surfaced
//!
//! - **G-cap-gap candidate** (not confirmed pre-real-sim): smix SDK lacks
//!   first-class `optional: true` flag on `tap` / `assert_visible`. This
//!   example uses `find` (visible boolean probe) + `if` gating to
//!   approximate maestro semantics. If real-sim runs show false-negatives
//!   (element appears AFTER `find` probe but before the next step would
//!   need it), the workaround pattern is insufficient and a proper
//!   `tap_optional` / `assert_visible_optional` API may be needed —
//!   立案 at-discovery, not preemptive.
//! - Mock-backend dependency: flow assumes mock SN `MOCK00010001` is wired
//!   in the running app build. Real backend would reject (different SN
//!   format / no auto-advance). Not a smix core gap, but a dogfood-env
//!   precondition documented here.

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 setup AP WiFi list (v3.17 c3)");
    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!("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 setup_ap_wifi_list_flow(&app).await {
        Ok(()) => {
            println!("\n✓ setup AP WiFi list flow passed (1-round dogfood)");
            println!("\n🎉 v3.17 c3 dogfood — setup_ap_wifi_list ✅");
            Ok(())
        }
        Err(e) => {
            println!("\n✗ setup AP WiFi list flow failed");
            for line in e.to_prompt().lines() {
                println!("    {line}");
            }
            Err("dogfood setup AP WiFi list 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`: wait for `btn-open-menu`, then
/// repeat-until-Dashboard-visible pattern bounded to 5 iterations.
async fn open_menu(app: &App) -> Result<(), ExpectationFailure> {
    tokio::time::sleep(Duration::from_millis(800)).await;
    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-add-device.yaml`: open drawer, tap "Add
/// Device", wait `screen-add-device` id visible (≤30s).
async fn go_to_add_device(app: &App) -> Result<(), ExpectationFailure> {
    open_menu(app).await?;
    app.tap(&text("Add Device")).await?;
    tokio::time::sleep(Duration::from_millis(800)).await;
    app.wait_for(&id("screen-add-device"), Duration::from_secs(30))
        .await?;
    Ok(())
}

// ---- setup-ap-wifi-list flow (port of flows/setup/ap-wifi-list.yaml) ----

async fn setup_ap_wifi_list_flow(app: &App) -> Result<(), ExpectationFailure> {
    println!("step 1: go-to-add-device (drawer → Add Device → screen-add-device)");
    go_to_add_device(app).await?;

    println!("step 2: tap 'Enter code' (Animated.Text — switch to manual SN mode)");
    app.tap(&text("Enter code")).await?;
    tokio::time::sleep(Duration::from_millis(800)).await;

    println!("step 3: tap input-sn → fill 'MOCK00010001'");
    app.tap(&id("input-sn")).await?;
    tokio::time::sleep(Duration::from_millis(400)).await;
    app.fill(&id("input-sn"), "MOCK00010001").await?;
    tokio::time::sleep(Duration::from_millis(400)).await;

    println!("step 4: tap 'Add Device' → wait 'Select Network|device not found' (≤30s)");
    app.tap(&text("Add Device")).await?;
    tokio::time::sleep(Duration::from_millis(800)).await;
    app.wait_for(
        &text_regex("Select Network|device not found"),
        Duration::from_secs(30),
    )
    .await?;

    println!("step 5: optional tap 'WiFi' (skip-if-missing — mock path may skip)");
    if app.find(&text("WiFi")).await? {
        println!("  WiFi label visible, tapping");
        app.tap(&text("WiFi")).await?;
        tokio::time::sleep(Duration::from_millis(800)).await;
    } else {
        println!("  WiFi label not visible, skipping (mock auto-advance)");
    }

    println!("step 6: optional tap 'Continue' (skip-if-missing)");
    if app.find(&text("Continue")).await? {
        println!("  Continue button visible, tapping");
        app.tap(&text("Continue")).await?;
        tokio::time::sleep(Duration::from_millis(800)).await;
    } else {
        println!("  Continue button not visible, skipping");
    }

    println!("step 7: wait 'SSID|Wi-Fi Setup|device not found' (≤30s — AP mode landed)");
    app.wait_for(
        &text_regex("SSID|Wi-Fi Setup|device not found"),
        Duration::from_secs(30),
    )
    .await?;

    println!("step 8: optional assert 'Select a Wi-Fi network'");
    if app.find(&text("Select a Wi-Fi network")).await? {
        println!("  'Select a Wi-Fi network' visible (AP mode reached)");
    } else {
        println!("  'Select a Wi-Fi network' not visible, skipping (device-not-found path)");
    }

    println!("step 9: optional assert 'Enter SSID manually'");
    if app.find(&text("Enter SSID manually")).await? {
        println!("  'Enter SSID manually' visible");
    } else {
        println!("  'Enter SSID manually' not visible, skipping");
    }

    Ok(())
}
