//! Application-level events used to coordinate UI actions.
//!
//! `AppEvent` is the internal message bus between UI components and the top-level `App` loop.
//! Widgets emit events to request actions that must be handled at the app layer (like opening
//! pickers, persisting configuration, or shutting down the agent), without needing direct access to
//! `App` internals.
//!
//! Exit is modelled explicitly via `AppEvent::Exit(ExitMode)` so callers can request shutdown-first
//! quits without reaching into the app loop or coupling to shutdown/exit sequencing.

use std::path::PathBuf;

use crate::product::agent::connectors::AppInfo;
use crate::product::agent::protocol::Event;
use crate::product::common::approval_presets::ApprovalPreset;
use crate::product::file_search::FileMatch;
use crate::product::protocol::ThreadId;
use crate::product::protocol::openai_models::ModelPreset;
use crate::product::protocol::openai_models::ReasoningEffort;

use crate::product::tui_app::bottom_pane::ApprovalRequest;
use crate::product::tui_app::changelog::ChangelogOutput;
use crate::product::tui_app::history_cell::HistoryCell;
use crate::product::tui_app::provider_config::CustomProviderConfig;

use crate::product::agent::features::Feature;
use crate::product::agent::protocol::AskForApproval;
use crate::product::agent::protocol::ReviewRequest;
use crate::product::agent::protocol::SandboxPolicy;
use crate::product::protocol::config_types::IdentityMask;
use crate::product::protocol::config_types::Personality;

#[derive(Debug, Clone)]
pub(crate) enum BuddyConfigEdit {
    Enabled(bool),
    Muted(bool),
    ObserverEnabled(bool),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
pub(crate) enum WindowsSandboxEnableMode {
    Elevated,
    Legacy,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
pub(crate) enum WindowsSandboxFallbackReason {
    ElevationFailed,
}

#[derive(Debug, Clone)]
pub(crate) struct ConnectorsSnapshot {
    pub(crate) connectors: Vec<AppInfo>,
}

#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
pub(crate) enum AppEvent {
    CodexEvent(Event),
    /// Event emitted by a thread listener and routed back through the app loop.
    ThreadEventReceived {
        thread_id: ThreadId,
        event: Event,
    },
    /// Start a new session.
    NewSession,

    /// Open the resume picker inside the running TUI session.
    OpenResumePicker,

    /// Fork the current session into a new thread.
    ForkCurrentSession,

    /// Request to exit the application.
    ///
    /// Use `ShutdownFirst` for user-initiated quits so core cleanup runs and the
    /// UI exits only after `ShutdownComplete`. `Immediate` is a last-resort
    /// escape hatch that skips shutdown and may drop in-flight work (e.g.,
    /// background tasks, rollout flush, or child process cleanup).
    Exit(ExitMode),

    /// Request to exit the application due to a fatal error.
    FatalExitRequest(String),

    /// Forward an `Op` to the Agent. Using an `AppEvent` for this avoids
    /// bubbling channels through layers of widgets.
    CodexOp(crate::product::agent::protocol::Op),

    /// Start a review using the current slash/TUI behavior.
    StartReview {
        review_request: ReviewRequest,
    },

    /// Kick off an asynchronous file search for the given query (text after
    /// the `@`). Previous searches may be cancelled by the app layer so there
    /// is at most one in-flight search.
    StartFileSearch(String),

    /// Result of a completed asynchronous file search. The `query` echoes the
    /// original search term so the UI can decide whether the results are
    /// still relevant.
    FileSearchResult {
        query: String,
        matches: Vec<FileMatch>,
    },

    /// Result of prefetching connectors.
    ConnectorsLoaded(Result<ConnectorsSnapshot, String>),

    /// Result of computing a `/diff` command.
    DiffResult(String),

    /// Request computation of a `/changelog` command.
    RequestChangelog,

    /// Result of computing a `/changelog` command.
    ChangelogResult(Result<ChangelogOutput, String>),

    #[allow(dead_code)]
    /// Open the app link view in the bottom pane.
    OpenAppLink {
        title: String,
        description: Option<String>,
        instructions: String,
        url: String,
        is_installed: bool,
    },

    InsertHistoryCell(Box<dyn HistoryCell>),
    InsertThreadHistoryCell {
        thread_id: ThreadId,
        cell: Box<dyn HistoryCell>,
    },

    StartCommitAnimation,
    StopCommitAnimation,
    CommitTick,

    /// Open the centered identity selection modal.
    OpenIdentityModal,

    /// Open the centered provider configuration modal.
    OpenProviderConfigModal,

    /// Open the centered review preset modal.
    OpenReviewModal,

    /// Open the centered model selection modal.
    OpenModelSelectionModal {
        presets: Vec<ModelPreset>,
    },

    /// Open the centered experimental features modal.
    OpenExperimentalFeaturesModal,

    /// Open memories settings in the bottom pane.
    OpenMemoriesSettingsView,

    /// Open the centered personality selection modal.
    OpenPersonalitySelectionModal {
        current_personality: Personality,
    },

    /// Open the centered MCP tools modal.
    OpenMcpToolsModal,

    /// Persist the selected model and reasoning effort to the appropriate config.
    PersistModelSelection {
        model: String,
        provider_id: Option<String>,
        effort: Option<ReasoningEffort>,
    },

    /// Persist the selected personality to the appropriate config.
    PersistPersonalitySelection {
        personality: Personality,
    },

    /// Persist the TUI buddy companion configuration.
    PersistBuddyConfig {
        edit: BuddyConfigEdit,
    },

    /// Update in-memory config after saving a custom provider and model.
    CustomProviderConfigured(CustomProviderConfig),

    /// Open the reasoning selection popup after picking a model.
    OpenReasoningPopup {
        model: ModelPreset,
    },

    /// Open the full model picker (non-auto models).
    #[allow(dead_code)]
    OpenAllModelsPopup {
        models: Vec<ModelPreset>,
    },

    /// Open the confirmation prompt before enabling full access mode.
    #[allow(dead_code)]
    OpenFullAccessConfirmation {
        preset: ApprovalPreset,
        return_to_permissions: bool,
    },

    /// Open the Windows world-writable directories warning.
    /// If `preset` is `Some`, the confirmation will apply the provided
    /// approval/sandbox configuration on Continue; if `None`, it performs no
    /// policy change and only acknowledges/dismisses the warning.
    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
    OpenWorldWritableWarningConfirmation {
        preset: Option<ApprovalPreset>,
        /// Up to 3 sample world-writable directories to display in the warning.
        sample_paths: Vec<String>,
        /// If there are more than `sample_paths`, this carries the remaining count.
        extra_count: usize,
        /// True when the scan failed (e.g. ACL query error) and protections could not be verified.
        failed_scan: bool,
    },

    /// Prompt to enable the Windows sandbox feature before using Agent mode.
    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
    OpenWindowsSandboxEnablePrompt {
        preset: ApprovalPreset,
    },

    /// Open the Windows sandbox fallback prompt after declining or failing elevation.
    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
    OpenWindowsSandboxFallbackPrompt {
        preset: ApprovalPreset,
        reason: WindowsSandboxFallbackReason,
    },

    /// Begin the elevated Windows sandbox setup flow.
    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
    BeginWindowsSandboxElevatedSetup {
        preset: ApprovalPreset,
    },

    /// Enable the Windows sandbox feature and switch to Agent mode.
    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
    EnableWindowsSandboxForAgentMode {
        preset: ApprovalPreset,
        mode: WindowsSandboxEnableMode,
    },

    /// Update the Windows sandbox feature mode without changing approval presets.
    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]

    /// Update the current approval policy in the running app and widget.
    UpdateAskForApprovalPolicy(AskForApproval),

    /// Update the current sandbox policy in the running app and widget.
    UpdateSandboxPolicy(SandboxPolicy),

    /// Update feature flags and persist them to the top-level config.
    UpdateFeatureFlags {
        updates: Vec<(Feature, bool)>,
    },

    /// Persist memories settings and update in-memory config.
    UpdateMemorySettings {
        feature_enabled: bool,
        use_memories: bool,
        generate_memories: bool,
        dedicated_tools: bool,
    },

    /// Update whether the full access warning prompt has been acknowledged.
    UpdateFullAccessWarningAcknowledged(bool),

    /// Update whether the world-writable directories warning has been acknowledged.
    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
    UpdateWorldWritableWarningAcknowledged(bool),

    /// Persist the acknowledgement flag for the full access warning prompt.
    PersistFullAccessWarningAcknowledged,

    /// Persist the acknowledgement flag for the world-writable directories warning.
    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
    PersistWorldWritableWarningAcknowledged,

    /// Persist the acknowledgement flag for the model migration prompt.
    PersistModelMigrationPromptAcknowledged {
        from_model: String,
        to_model: String,
    },

    /// Skip the next world-writable scan (one-shot) after a user-confirmed continue.
    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
    SkipNextWorldWritableScan,

    /// Re-open the approval presets popup.
    OpenApprovalsPopup,

    /// Open the centered skills management modal.
    OpenSkillsModal,

    /// Re-open the permissions presets popup.
    OpenPermissionsPopup,

    /// Open the branch picker option from the review popup.
    OpenReviewBranchPicker(PathBuf),

    /// Open the commit picker option from the review popup.
    OpenReviewCommitPicker(PathBuf),

    /// Open the custom prompt option from the review popup.
    OpenReviewCustomPrompt,

    /// Submit a user message with an explicit identity mask.
    SubmitUserMessageWithMode {
        text: String,
        identity: IdentityMask,
    },

    /// Start a planner-proposed plan as a /goal with an explicit programmer identity mask.
    StartGoalFromProposedPlan {
        plan_text: String,
        identity: IdentityMask,
    },

    /// Open the approval popup.
    FullScreenApprovalRequest(ApprovalRequest),

    /// Open the feedback note entry overlay after the user selects a category.
    OpenFeedbackNote {
        category: FeedbackCategory,
        include_logs: bool,
    },

    /// Open the upload consent popup for feedback after selecting a category.
    OpenFeedbackConsent {
        category: FeedbackCategory,
    },

    /// Launch the external editor after a normal draw has completed.
    LaunchExternalEditor,
}

/// The exit strategy requested by the UI layer.
///
/// Most user-initiated exits should use `ShutdownFirst` so core cleanup runs and the UI exits only
/// after core acknowledges completion. `Immediate` is an escape hatch for cases where shutdown has
/// already completed (or is being bypassed) and the UI loop should terminate right away.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ExitMode {
    /// Shutdown core and exit after completion.
    ShutdownFirst,
    /// Exit the UI loop immediately without waiting for shutdown.
    ///
    /// This skips `Op::Shutdown`, so any in-flight work may be dropped and
    /// cleanup that normally runs before `ShutdownComplete` can be missed.
    Immediate,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum FeedbackCategory {
    BadResult,
    GoodResult,
    Bug,
    Other,
}
