use std::sync::Arc;

use crate::domain::connection::{ConnectionNameError, ConnectionProfile, ServiceEntry};
use crate::model::connection::error::ConnectionErrorInfo;
use crate::model::shared::focused_pane::FocusedPane;
use crate::model::shared::key_sequence::Prefix;
use crate::model::sql_editor::completion::CompletionCandidate;
use crate::policy::write::write_guardrails::WritePreview;
use crate::ports::outbound::clipboard::ClipboardError;
use crate::ports::outbound::connection_store::ConnectionStoreError;
use crate::ports::outbound::folder_opener::FolderOpenError;
use crate::ports::outbound::query_history::QueryHistoryError;
use crate::ports::outbound::settings_store::SettingsStoreError;
use crate::ports::outbound::{AppSettings, DbOperationError};
use std::collections::HashMap;

use crate::domain::{ConnectionId, DatabaseMetadata, QueryResult, QuerySource, Table};

#[derive(Debug, Clone, thiserror::Error)]
pub enum ConnectionSaveError {
    #[error("{0}")]
    Validation(#[from] ConnectionNameError),
    #[error("{0}")]
    Store(#[from] ConnectionStoreError),
    #[error("{0}")]
    Metadata(#[from] DbOperationError),
}

#[derive(Debug, Clone, thiserror::Error)]
pub enum ErDiagramError {
    #[error("{0}")]
    NoData(String),
    #[error("{0}")]
    ExportFailed(String),
    #[error("Task panicked: {0}")]
    TaskPanicked(String),
}

#[derive(Debug, Clone, thiserror::Error)]
pub enum ErLogError {
    #[error("{0}")]
    Io(String),
    #[error("{0}")]
    Config(String),
}

pub use crate::model::shared::cursor::CursorMove;

// ---------------------------------------------------------------------------
// Parametric Action types
// ---------------------------------------------------------------------------

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScrollTarget {
    Result,
    Inspector,
    Help,
    ConnectionError,
    ConfirmDialog,
    ExplainPlan,
    ExplainCompare,
    ExplainConfirm,
    Explorer,
    JsonbDetail,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScrollDirection {
    Up,
    Down,
    Left,
    Right,
}

impl ScrollDirection {
    pub fn clamp_vertical_offset(self, current: usize, max: usize, delta: usize) -> usize {
        match self {
            Self::Down => (current + delta).min(max),
            Self::Up => current.saturating_sub(delta),
            Self::Left | Self::Right => current,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScrollAmount {
    Line,
    ToStart,
    ToEnd,
    ViewportTop,
    ViewportMiddle,
    ViewportBottom,
    HalfPage,
    FullPage,
}

impl ScrollAmount {
    pub fn page_delta(self, visible: usize) -> Option<usize> {
        if visible == 0 {
            return None;
        }

        Some(match self {
            Self::HalfPage => (visible / 2).max(1),
            Self::FullPage => visible,
            _ => return None,
        })
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScrollToCursorTarget {
    Explorer,
    Result,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CursorPosition {
    Center,
    Top,
    Bottom,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InputTarget {
    SqlModal,
    SqlModalHighRisk,
    SqlModalAnalyzeHighRisk,
    ResultCellEdit,
    ConnectionSetup,
    CommandLine,
    Filter,
    ErFilter,
    SettingsErBrowser,
    QueryHistoryFilter,
    JsonbEdit,
    JsonbSearch,
    HelpFilter,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SelectMotion {
    Next,
    Previous,
    First,
    Last,
    ViewportTop,
    ViewportMiddle,
    ViewportBottom,
    HalfPageDown,
    HalfPageUp,
    FullPageDown,
    FullPageUp,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ListTarget {
    ConnectionList,
    QueryHistory,
    TablePicker,
    ErTablePicker,
    CommandPalette,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ListMotion {
    Next,
    Previous,
}

/// Payload-free modal lifecycle kinds.
///
/// Modal lifecycle actions stay generic only while opening does not need a
/// payload. Payload-bearing modals keep explicit actions until there is a
/// repeated payload pattern worth abstracting.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModalKind {
    TablePicker,
    CommandPalette,
    Settings,
    Help,
    SqlModal,
    ErTablePicker,
    QueryHistoryPicker,
    JsonbDetail,
    ConnectionSetup,
    ConnectionSelector,
}

#[derive(Debug, Clone)]
pub struct SmartErRefreshResult {
    pub dsn: String,
    pub run_id: u64,
    pub new_metadata: Arc<DatabaseMetadata>,
    pub stale_tables: Vec<String>,
    pub added_tables: Vec<String>,
    pub removed_tables: Vec<String>,
    pub missing_in_cache: Vec<String>,
    pub new_signatures: HashMap<String, String>,
}

#[derive(Debug, Clone)]
pub struct SmartErRefreshError {
    pub dsn: String,
    pub run_id: u64,
    pub error: DbOperationError,
    pub new_metadata: Option<Arc<DatabaseMetadata>>,
}

#[derive(Debug, Clone)]
pub struct ErDiagramInfo {
    pub path: String,
    pub table_count: usize,
    pub total_tables: usize,
}

#[derive(Debug, Clone)]
pub struct ConnectionsLoadedPayload {
    pub profiles: Vec<ConnectionProfile>,
    pub services: Vec<ServiceEntry>,
    pub service_file_path: Option<std::path::PathBuf>,
    pub profile_load_warning: Option<String>,
    pub service_load_warning: Option<String>,
}

#[derive(Debug, Clone)]
pub struct TableTarget {
    pub schema: String,
    pub table: String,
    pub generation: u64,
}

#[derive(Debug, Clone)]
pub struct ConnectionTarget {
    pub id: ConnectionId,
    pub dsn: String,
    pub name: String,
}

// Full Action equality is intentionally unavailable: some payloads carry
// snapshots or errors that are not value-comparable.
//
// Classification rule:
// Order shared controls first, then product objects by dependency:
// setup -> DB structure -> SQL -> query results -> result derivatives.
// Product groups follow the object in the action sentence, not UI/reducer names
// (e.g., MetadataLoaded -> Database structure, QueryCompleted -> Query results).
#[derive(Debug, Clone)]
pub enum Action {
    // App shell
    None,
    Quit,
    Render,
    Resize(u16, u16),
    SetFocusedPane(FocusedPane),

    // Input primitives
    Scroll {
        target: ScrollTarget,
        direction: ScrollDirection,
        amount: ScrollAmount,
    },
    ScrollToCursor {
        target: ScrollToCursorTarget,
        position: CursorPosition,
    },
    TextInput {
        target: InputTarget,
        ch: char,
    },
    TextBackspace {
        target: InputTarget,
    },
    TextDelete {
        target: InputTarget,
    },
    TextMoveCursor {
        target: InputTarget,
        direction: CursorMove,
    },
    Select(SelectMotion),
    ListSelect {
        target: ListTarget,
        motion: ListMotion,
    },
    Paste(String),
    BeginKeySequence(Prefix),
    CancelKeySequence,

    // Modal shell
    OpenModal(ModalKind),
    CloseModal(ModalKind),
    ToggleModal(ModalKind),
    Escape,
    ConfirmSelection,
    ConfirmDialogConfirm,
    ConfirmDialogCancel,

    // Command line
    EnterCommandLine,
    ExitCommandLine,
    CommandLineSubmit,

    // Connections
    TryConnect,
    SwitchConnection(ConnectionTarget),
    ConnectionsLoaded(ConnectionsLoadedPayload),
    ConfirmConnectionSelection,
    StartEditConnection(ConnectionId),
    ConnectionSetupNextField,
    ConnectionSetupPrevField,
    ConnectionSetupToggleDropdown,
    ConnectionSetupDropdownNext,
    ConnectionSetupDropdownPrev,
    ConnectionSetupDropdownConfirm,
    ConnectionSetupDropdownCancel,
    ConnectionSetupSave,
    ConnectionSetupCancel,
    ConnectionSaveCompleted(ConnectionTarget),
    ConnectionSaveFailed(ConnectionSaveError),
    ConnectionEditLoaded(Box<ConnectionProfile>),
    ConnectionEditLoadFailed(ConnectionStoreError),
    ShowConnectionError(ConnectionErrorInfo),
    CloseConnectionError,
    ToggleConnectionErrorDetails,
    CopyConnectionError,
    ConnectionErrorCopied,
    ReenterConnectionSetup,
    RetryServiceConnection,
    RequestDeleteSelectedConnection,
    DeleteConnection(ConnectionId),
    ConnectionDeleted(ConnectionId),
    ConnectionDeleteFailed(ConnectionStoreError),
    RequestEditSelectedConnection,

    // Settings
    SettingsSelectNext,
    SettingsSelectPrevious,
    SettingsNextSection,
    SettingsPreviousSection,
    SettingsStartCustomBrowserEdit,
    SettingsStopCustomBrowserEdit,
    SettingsApply,
    SettingsCancel,
    SettingsSaved(AppSettings),
    SettingsSaveFailed(SettingsStoreError),

    // Database structure
    LoadMetadata,
    ReloadMetadata,
    MetadataLoaded {
        dsn: String,
        run_id: u64,
        metadata: Arc<DatabaseMetadata>,
    },
    MetadataFailed {
        dsn: String,
        run_id: u64,
        error: DbOperationError,
    },
    LoadTableDetail(TableTarget),
    TableDetailLoaded {
        dsn: String,
        run_id: u64,
        detail: Box<Table>,
        generation: u64,
    },
    TableDetailFailed {
        dsn: String,
        run_id: u64,
        error: DbOperationError,
        generation: u64,
    },
    PrefetchTableDetail {
        run_id: u64,
        schema: String,
        table: String,
    },
    TableDetailCached {
        dsn: String,
        run_id: u64,
        schema: String,
        table: String,
        detail: Box<Table>,
    },
    TableDetailCacheFailed {
        dsn: String,
        run_id: u64,
        schema: String,
        table: String,
        error: DbOperationError,
    },
    TableDetailAlreadyCached {
        dsn: String,
        run_id: u64,
        schema: String,
        table: String,
    },
    StartPrefetchAll,
    StartPrefetchScoped {
        tables: Vec<String>,
    },
    ExpandPrefetchWithFkNeighbors,
    FkNeighborsDiscovered {
        tables: Vec<String>,
    },
    ProcessPrefetchQueue {
        run_id: u64,
    },
    InspectorNextTab,
    InspectorPrevTab,

    // SQL editing
    SqlModalAppendInsert,
    SqlModalEnterInsert,
    SqlModalEnterNormal,
    SqlModalYank,
    SqlModalYankSuccess,
    SqlModalNewLine,
    SqlModalTab,
    SqlModalSubmit,
    SqlModalClear,
    SqlModalCancelConfirm,
    SqlModalConfirmExecute,
    SqlModalNextTab,
    SqlModalPrevTab,
    CompletionRequest,
    CompletionUpdated {
        candidates: Vec<CompletionCandidate>,
        trigger_position: usize,
        visible: bool,
    },
    CompletionAccept,
    CompletionDismiss,
    CompletionNext,
    CompletionPrev,

    // Explain plans
    ExplainRequest,
    ExplainAnalyzeRequest,
    ExplainAnalyzeConfirm,
    ExplainAnalyzeCancel,
    ExplainCompleted {
        dsn: String,
        run_id: u64,
        query: String,
        plan_text: String,
        is_analyze: bool,
        execution_time_ms: u64,
    },
    ExplainFailed {
        dsn: String,
        run_id: u64,
        error: DbOperationError,
    },
    CompareEditQuery,

    // Query results
    ExecutePreview(TableTarget),
    ExecuteAdhoc(String),
    ExecuteWrite(String),
    QueryCompleted {
        dsn: String,
        run_id: u64,
        result: Arc<QueryResult>,
        generation: u64,
        target_page: Option<usize>,
    },
    QueryFailed {
        dsn: String,
        run_id: u64,
        error: DbOperationError,
        generation: u64,
        source: QuerySource,
    },
    ExecuteWriteSucceeded {
        dsn: String,
        run_id: u64,
        affected_rows: usize,
    },
    ExecuteWriteFailed {
        dsn: String,
        run_id: u64,
        error: DbOperationError,
    },
    ResultNextPage,
    ResultPrevPage,
    ResultActivateCell,
    ResultExitToScroll,
    ResultCellLeft,
    ResultCellRight,
    ResultCellYank,
    ResultCellYankSuccess {
        row: usize,
        col: usize,
    },
    ResultRowYankOperatorPending,
    ResultRowYank,
    ResultRowYankSuccess {
        row: usize,
    },
    DdlYank,
    DdlYankSuccess,
    ResultDeleteOperatorPending,
    StageRowForDelete,
    UnstageLastStagedRow,
    ClearStagedDeletes,
    RequestDeleteActiveRow,
    ResultEnterCellEdit,
    ResultCancelCellEdit,
    ResultDiscardCellEdit,
    SubmitCellEditWrite,
    OpenWritePreviewConfirm(Box<WritePreview>),
    CopyFailed(ClipboardError),
    OpenFolderFailed(FolderOpenError),
    ToggleFocus,
    ToggleReadOnly,

    // Query history
    QueryHistoryLoaded(
        crate::domain::ConnectionId,
        Vec<crate::domain::query_history::QueryHistoryEntry>,
    ),
    QueryHistoryLoadFailed(crate::domain::ConnectionId, QueryHistoryError),
    QueryHistoryAppendFailed(QueryHistoryError),
    QueryHistoryConfirmSelection,

    // CSV export
    RequestCsvExport,
    CsvExportRowsCounted {
        dsn: String,
        run_id: u64,
        row_count: Option<usize>,
        export_query: String,
        file_name: String,
    },
    ExecuteCsvExport {
        dsn: String,
        run_id: u64,
        export_query: String,
        file_name: String,
        row_count: Option<usize>,
    },
    CsvExportSucceeded {
        dsn: String,
        run_id: u64,
        path: String,
        row_count: Option<usize>,
    },
    CsvExportFailed {
        dsn: String,
        run_id: u64,
        error: DbOperationError,
    },

    // JSONB detail
    JsonbYankAll,
    JsonbYankSuccess,
    JsonbEnterEdit,
    JsonbAppendInsert,
    JsonbExitEdit,
    JsonbEnterSearch,
    JsonbExitSearch,
    JsonbSearchNext,
    JsonbSearchPrev,
    JsonbSearchSubmit,

    // ER diagrams
    ErToggleSelection,
    ErSelectAll,
    ErConfirmSelection,
    ErOpenDiagram,
    ErGenerateFromCache,
    SmartErRefreshCompleted(SmartErRefreshResult),
    SmartErRefreshFailed(SmartErRefreshError),
    ErDiagramOpened(ErDiagramInfo),
    ErDiagramFailed(ErDiagramError),
    ErLogWriteFailed(ErLogError),
}

impl Action {
    pub fn is_none(&self) -> bool {
        matches!(self, Self::None)
    }

    pub fn is_scroll(&self) -> bool {
        matches!(self, Self::Scroll { .. })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use rstest::rstest;

    #[test]
    fn scroll_action_returns_true() {
        let action = Action::Scroll {
            target: ScrollTarget::Result,
            direction: ScrollDirection::Down,
            amount: ScrollAmount::Line,
        };
        assert!(action.is_scroll());
    }

    #[rstest]
    #[case(Action::None)]
    #[case(Action::Quit)]
    #[case(Action::Render)]
    #[case(Action::ScrollToCursor {
        target: ScrollToCursorTarget::Result,
        position: CursorPosition::Center,
    })]
    fn non_scroll_action_returns_false(#[case] action: Action) {
        assert!(!action.is_scroll());
    }

    mod shared_scroll_helpers {
        use super::*;

        #[rstest]
        #[case(ScrollDirection::Down, 3, 10, 4, 7)]
        #[case(ScrollDirection::Down, 3, 5, 10, 5)]
        #[case(ScrollDirection::Down, 3, 0, 10, 0)]
        #[case(ScrollDirection::Up, 8, 10, 3, 5)]
        #[case(ScrollDirection::Up, 3, 10, 5, 0)]
        #[case(ScrollDirection::Up, 3, 0, 5, 0)]
        #[case(ScrollDirection::Left, 4, 10, 6, 4)]
        #[case(ScrollDirection::Right, 4, 10, 6, 4)]
        fn clamp_vertical_offset_handles_boundaries(
            #[case] direction: ScrollDirection,
            #[case] current: usize,
            #[case] max: usize,
            #[case] delta: usize,
            #[case] expected: usize,
        ) {
            assert_eq!(
                direction.clamp_vertical_offset(current, max, delta),
                expected
            );
        }

        #[rstest]
        #[case(ScrollAmount::HalfPage, 0, None)]
        #[case(ScrollAmount::HalfPage, 1, Some(1))]
        #[case(ScrollAmount::HalfPage, 17, Some(8))]
        #[case(ScrollAmount::FullPage, 0, None)]
        #[case(ScrollAmount::FullPage, 1, Some(1))]
        #[case(ScrollAmount::FullPage, 17, Some(17))]
        #[case(ScrollAmount::Line, 17, None)]
        fn page_delta_respects_visible_rows(
            #[case] amount: ScrollAmount,
            #[case] visible: usize,
            #[case] expected: Option<usize>,
        ) {
            assert_eq!(amount.page_delta(visible), expected);
        }
    }
}
