use super::*;

/// Returns true if the prompt_id was generated by the shell for a
/// server-initiated auto-wake turn (background task or subagent completion).
pub(crate) fn is_server_initiated_prompt(prompt_id: &str) -> bool {
    kigi_shell::session::PromptOrigin::from_prompt_id(prompt_id).is_synthetic()
}

/// Returns true if the prompt_id is a scheduled-task (`/loop`) fire.
///
/// Cron turns are synthetic (so [`is_server_initiated_prompt`] is also true for
/// them), but UNLIKE auto-wake / subagent-completion turns they are
/// CLIENT-driven via `MvpAgent::prompt()` and therefore DO emit a matching
/// `kigi/session/prompt_complete` turn-end signal. A viewer can thus safely
/// enter `TurnRunning` for them (the exit exists, so it won't strand) — which is
/// what lets the dashboard show a running `/loop` session as Working.
pub(crate) fn is_scheduler_fired_prompt(prompt_id: &str) -> bool {
    matches!(
        kigi_shell::session::PromptOrigin::from_prompt_id(prompt_id),
        kigi_shell::session::PromptOrigin::SchedulerFired
    )
}

/// Returns true for the auto-wake turn families (`task-completed-…`,
/// `subagent-completed-…`, `notifications-…`). These run non-adopted — no
/// `PromptResponse`, no viewer finalize — so their durable `TurnCompleted` is
/// the only signal marking the back-to-idle point, and it pushes the turn-end
/// marker directly. Deliberately narrower than "non-adopted synthetic": goal
/// turns render through the goal chip/loop chrome and `plan-resume-…` keeps
/// its current markerless shape.
pub(crate) fn is_wake_prompt(prompt_id: &str) -> bool {
    matches!(
        kigi_shell::session::PromptOrigin::from_prompt_id(prompt_id),
        kigi_shell::session::PromptOrigin::TaskCompleted { .. }
            | kigi_shell::session::PromptOrigin::SubagentCompleted { .. }
            | kigi_shell::session::PromptOrigin::NotificationDrain
    )
}

/// Whether a running `prompt_id` is adoptable — i.e. safe to bind as the
/// viewer's `current_prompt_id` and show as a live `TurnRunning`. The invariant:
/// adoptable iff the turn emits a terminal `kigi/session/prompt_complete`, the
/// only non-interactive way a viewer leaves `TurnRunning`. That holds for
/// user-driven turns and `/loop` (`scheduler-fired-…`) fires — both run via
/// `MvpAgent::prompt()` — and is false for actor-run synthetic turns
/// (task-completed / subagent-completion / notification-drain / goal-*), which
/// have no such exit, so adopting one strands the viewer in `TurnRunning`.
///
/// This is the pure pid-only synthetic-turn guard. It serves the live-delta
/// viewer gate and the `queue/changed` turn-start shim (the latter negated, as a
/// skip) directly. The two adoption-on-load paths (`SessionLoaded` in
/// `dispatch.rs`, the reconnect-reload `finalize_reload_and_maybe_adopt`) instead
/// call the agent-aware [`AgentView::should_adopt_running_prompt`], which ANDs
/// this guard with "not terminal-in-replay" so an already-ended turn replayed on
/// reattach is not re-adopted.
///
/// [`AgentView::should_adopt_running_prompt`]: crate::app::agent_view::AgentView::should_adopt_running_prompt
pub(crate) fn should_adopt_running_prompt(prompt_id: &str) -> bool {
    !is_server_initiated_prompt(prompt_id) || is_scheduler_fired_prompt(prompt_id)
}

/// Compute the monotonic anchor a VIEWER should use as its turn-start time.
///
/// A viewer adopts the driver's turn mid-stream, so stamping `Instant::now()`
/// would make its elapsed counter — and the final "Worked for X" marker
/// (both read off `turn_started_at` via [`AgentView::turn_elapsed`]) — read
/// LESS than the driver's by the time-to-first-delta. The shell records the
/// authoritative wall-clock turn start in `meta.turnStartMs` (UTC ms, stamped
/// at the top of the turn), so back-date the anchor from it; the viewer's
/// elapsed then matches the driver's, both live and on completion. Falls back
/// to `now` when `turnStartMs` is absent (older shell) or the wall clock is
/// skewed forward.
pub(super) fn viewer_turn_anchor(turn_start_ms: Option<i64>) -> std::time::Instant {
    let now = std::time::Instant::now();
    let Some(start_ms) = turn_start_ms else {
        return now;
    };
    let elapsed_ms = chrono::Utc::now()
        .timestamp_millis()
        .saturating_sub(start_ms);
    if elapsed_ms <= 0 {
        return now;
    }
    now.checked_sub(std::time::Duration::from_millis(elapsed_ms as u64))
        .unwrap_or(now)
}

/// Elapsed for a wake turn's end marker: its delta-borne `turnStartMs`
/// ([`AgentView::wake_turn_start`], consumed here on a pid match) to the
/// terminal's `agentTimestampMs` — both stamped by the shell clock, so client
/// skew cancels (fall back to client now when the stamp is missing). `None`
/// — no tracked start (old shells / no deltas seen) or a nonsensical negative
/// span — renders the marker without a duration rather than lying with
/// "0.0s".
pub(super) fn wake_turn_elapsed(
    agent: &mut AgentView,
    prompt_id: &str,
    end_ms: Option<i64>,
) -> Option<std::time::Duration> {
    let (_, start_ms) = agent.wake_turn_start.take_if(|(pid, _)| pid == prompt_id)?;
    let end_ms = end_ms.unwrap_or_else(|| chrono::Utc::now().timestamp_millis());
    u64::try_from(end_ms - start_ms)
        .ok()
        .map(std::time::Duration::from_millis)
}

/// Push a wake turn's end marker via the shared terminal-marker helper so the
/// wake turn's OWN stop hooks (pid-matched stash) render inline on the marker
/// instead of as a stray block. A REAL turn's leftover stash (pid mismatch)
/// must stay pending for its own marker rail — never fold into, nor flush
/// standalone on, an unrelated wake — hence `preserve_mismatched_stash`.
pub(super) fn push_wake_end_marker(
    agent: &mut AgentView,
    prompt_id: &str,
    elapsed: Option<std::time::Duration>,
) {
    // Wake turns skip PromptResponse; finish streaming so a trailing ` is flushed.
    agent.session.tracker.finish_turn(&mut agent.scrollback);

    crate::app::turn_completion::push_turn_terminal_marker(
        agent,
        Some(SessionEvent::TurnCompleted { elapsed }),
        Some(prompt_id),
        /* preserve_mismatched_stash */ true,
    );
}
