//! v3.16 c2 — dogfood usecase: Merlin input (yaml port adaptive).
//!
//! Port reference: `insight/tools/maestro/flows/main/merlin-input.yaml`
//! & `insight/tools/maestro/subflows/go-to-merlin.yaml`. Drives Merlin
//! tab → assert send button → focus input → fill → clear → re-assert
//! send button.
//!
//! # Why this dogfood
//!
//! 38 yaml 全集 port 覆盖率提升 (input-bar flow 业务首证). yaml step 4
//! `tapOn: point: "50%,90%"` is maestro author's choice (point tap) but
//! insight's Merlin input bar has an a11y-addressable placeholder text
//! `"Ask Merlin..."`. smix port adaptive: use `text("Ask Merlin...")`
//! selector to focus input — equivalent business outcome, selector path
//! preferred over coord path when both reach the same target (CLAUDE.md
//! §9 #3 escape hatch is reserved for cases without a11y addressability;
//! see counting_fullscreen example for the picker backdrop coord case
//! that genuinely requires escape hatch).
//!
//! Also exercises:
//! - chunked fill via placeholder text selector (v3.6 c1 G1 chunked
//!   fill cap on a Merlin-tab path distinct from merlin_chat v3.5 c6)
//! - clear via same placeholder selector
//! - send button id persists across fill/clear (no overlay regression)
//!
//! # Reversibility
//!
//! Pure read — input is typed then cleared before send. Send button is
//! never tapped. Merlin chat history untouched.
//!
//! # Port note
//!
//! yaml step 4 (`tapOn: point: "50%,90%"`) was tried as
//! `app.tap_at_coord(0.5, 0.90)` in initial port (would have exercised
//! §9 #3 lift escape hatch on input-bar surface). Real-sim run showed
//! that on RN TextInput, raw Apple-coord tap does NOT reliably trigger
//! keyboard focus / RN focus state on the simulator (the wire event
//! reaches the runner but the React Native focus chain is not engaged
//! — likely a sim-only render limit, parallel to merlin_chat v3.5 c6
//! `"Hello" → "He"` keyboard race observation). selector-based tap
//! works reliably because RN's tap handler chain runs on the JS thread
//! synchronously and triggers focus directly. Adopting selector 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 Merlin input (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 merlin_input_flow(&app).await {
        Ok(()) => {
            println!("\n✓ Merlin input flow passed (1-round dogfood)");
            println!("\n🎉 v3.16 c2 dogfood — Merlin input ✅");
            Ok(())
        }
        Err(e) => {
            println!("\n✗ Merlin input flow failed");
            for line in e.to_prompt().lines() {
                println!("    {line}");
            }
            Err("dogfood merlin input FAIL".into())
        }
    }
}

// ---- inline helpers (each example self-contained, v3.14 c2 robust) -------

async fn ensure_login(app: &App) -> Result<(), ExpectationFailure> {
    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;
    }
    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.
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-input flow (port of flows/main/merlin-input.yaml) -----------

async fn merlin_input_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 send button (btn-merlin-send) present");
    app.assert_visible(&id("btn-merlin-send")).await?;

    println!("step 3: tap text(\"Ask Merlin...\") → focus input bar (port adaptive)");
    // yaml step 4 `tapOn: point: "50%,90%"` substituted with
    // selector-based tap on the visible placeholder. See module docs
    // §"Port note" for rationale.
    app.tap(&text("Ask Merlin...")).await?;
    tokio::time::sleep(Duration::from_millis(500)).await;

    println!("step 4: fill \"test message\" via placeholder selector (v3.6 c1 chunked-fill)");
    app.fill(&text("Ask Merlin..."), "test message").await?;
    tokio::time::sleep(Duration::from_millis(500)).await;

    println!("step 5: clear input via placeholder selector (yaml eraseText: 20)");
    // yaml `eraseText: 20` deletes 20 chars from cursor; "test message"
    // is 12 chars so 20 deletions empties the field. smix `clear`
    // empties the entire field — same end state, idempotent. After
    // clear, the placeholder text returns so the selector still matches.
    app.clear(&text("Ask Merlin...")).await?;
    tokio::time::sleep(Duration::from_millis(500)).await;

    println!("step 6: assert send button (btn-merlin-send) still present after clear");
    app.assert_visible(&id("btn-merlin-send")).await?;

    Ok(())
}
