//! v3.5 c6 — dogfood usecase #3: Merlin AI chat nav + UI verify.
//!
//! Port reference: `insight/tools/maestro/flows/main/merlin.yaml` (nav
//! portion only). The full YAML also exercises a `inputText "Hello"`
//! + `tapOn btn-merlin-send` + `wait visible "Hello"` round-trip; that
//!   path is **deferred** — see "Capability gap surfaced" below.
//!
//! # Why this dogfood
//!
//! Distinct surface from c4 (logged-out picker) and c5 (logged-in id
//! nav):
//!
//! - **bottom-tab navigation** — Merlin is a tab, not a drawer item,
//!   so this exercises a path that does NOT need `open_menu` (one of
//!   the v3.4 c4 drawer-vs-tab disambiguation cases).
//! - **multi-assert id ladder** — `btn-merlin-new-chat` + `btn-merlin-send`
//!   on the same screen — three consecutive host-resolve assertions
//!   on the same /tree snapshot. Tests c2 dispatch under load and c3
//!   raw_type→Role derive across a chat-screen subtree.
//! - **AI Assistant header text** — `wait_for(text("AI Assistant"))`
//!   tests the bottom-tab transition timing (the tap-and-retry inside
//!   `go_to_merlin` mirrors maestro's repeat-while-not-visible).
//!
//! # Capability gap surfaced (deferred to v3.6+ backlog)
//!
//! Attempted the full multi-turn flow first; real-sim 2026-05-26 dig:
//!
//!   app.fill(&text("Ask merlin..."), "Hello").await?;
//!   sleep(800ms);
//!   app.tap(&id("btn-merlin-send")).await?;
//!   app.wait_for(&text("Hello"), 30s).await?;  // <— TIMEOUT
//!
//! The user message that appears in chat is consistently "He", not
//! "Hello" — i.e. the fill path commits only the first two characters
//! before the send button fires. Adding latency between fill and tap
//! did not help. Root cause likely sits at one of:
//!   - smix-runner-client `/fill` route → swift UITest `typeText`
//!     timing vs RN TextInput debounce on a 5-char string,
//!   - RN keyboard state machine clearing partway through the typed
//!     sequence (the dump shows the input bar reverting to placeholder
//!     after send rather than holding "Hello"),
//!   - Apple `XCUIElement.typeText` race between the synthesised
//!     keystrokes and the focused TextInput's onChangeText callback.
//!
//! Not patched here — per CLAUDE.md §13 + smix-capability-gap-first,
//! input-commit reliability is its own capability and needs a deliberate
//! cycle (probably a new `App.fill_and_wait_committed(...)` API or a
//! swift-side `typeText` -> `setText` swap with verification poll).
//! Recorded in plan-cold/v3.5.md as the next cap-gap candidate.
//!
//! # Reversibility
//!
//! Pure read — no message sent, no chat history mutated, no backend
//! call. Closing or relaunching returns the state untouched.

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 Merlin chat (v3.5 c6)");
    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: tap Device tab + drawer-close");
    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 merlin_chat_flow(&app).await {
        Ok(()) => {
            println!("\n✓ Merlin chat flow passed (1-round dogfood)");
            println!("\n🎉 v3.5 c6 dogfood #3 — insight Merlin chat ✅");
            Ok(())
        }
        Err(e) => {
            println!("\n✗ Merlin chat flow failed");
            for line in e.to_prompt().lines() {
                println!("    {line}");
            }
            Err("dogfood merlin chat 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-merlin.yaml` — repeat tap "Merlin" until the
/// AI Assistant header is visible. Merlin is a **bottom tab**, not a
/// drawer item — `open_menu` would put the drawer overlay over the tab
/// bar and route the tap to the drawer's "Log out" instead.
async fn go_to_merlin(app: &App) -> Result<(), ExpectationFailure> {
    for _ in 0..5 {
        if app
            .wait_for(&text("AI Assistant"), Duration::from_millis(500))
            .await
            .is_ok()
        {
            return Ok(());
        }
        let _ = app.tap(&text("Merlin")).await;
        tokio::time::sleep(Duration::from_secs(2)).await;
    }
    app.wait_for(&text("AI Assistant"), Duration::from_secs(5))
        .await?;
    Ok(())
}

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

async fn merlin_chat_flow(app: &App) -> Result<(), ExpectationFailure> {
    println!("step 1: tap Merlin bottom tab until AI Assistant header is up");
    go_to_merlin(app).await?;

    println!("step 2: assert chat controls present");
    app.assert_visible(&id("btn-merlin-new-chat")).await?;
    app.assert_visible(&id("btn-merlin-send")).await?;
    app.assert_visible(&text("Ask merlin...")).await?;

    println!("step 3: tap input placeholder \"Ask merlin...\"");
    app.tap(&text("Ask merlin...")).await?;
    tokio::time::sleep(Duration::from_millis(500)).await;

    println!("step 4: fill \"Hello\" (v3.6 c1 chunked fill — every char a separate POST)");
    app.fill(&text("Ask merlin..."), "Hello").await?;
    tokio::time::sleep(Duration::from_millis(300)).await;

    println!("step 5: tap id btn-merlin-send");
    app.tap(&id("btn-merlin-send")).await?;

    println!("step 6: wait for user message bubble \"Hello\" (≤30s)");
    app.wait_for(&text("Hello"), Duration::from_secs(30))
        .await?;

    println!("step 7: tap id btn-merlin-new-chat → fresh chat");
    app.tap(&id("btn-merlin-new-chat")).await?;
    tokio::time::sleep(Duration::from_millis(500)).await;

    println!("step 8: AI Assistant header + send button still present after new-chat");
    app.assert_visible(&text("AI Assistant")).await?;
    app.assert_visible(&id("btn-merlin-send")).await?;

    Ok(())
}
