//! UI type definitions for Cosmos
//!
//! Contains enums, structs, and their implementations for UI state management.

use crate::suggest::llm::FixPreview;
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use std::time::Instant;

use super::theme::Theme;

// ═══════════════════════════════════════════════════════════════════════════
//  PANEL AND VIEW STATE
// ═══════════════════════════════════════════════════════════════════════════

/// Active panel
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ActivePanel {
    #[default]
    Project,
    Suggestions,
}

/// View mode for file explorer
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ViewMode {
    #[default]
    Flat, // Traditional flat file list
    Grouped, // Grouped by layer and feature
}

impl ViewMode {
    pub fn label(&self) -> &'static str {
        match self {
            ViewMode::Flat => Theme::VIEW_FLAT,
            ViewMode::Grouped => Theme::VIEW_GROUPED,
        }
    }

    pub fn toggle(&self) -> Self {
        match self {
            ViewMode::Flat => ViewMode::Grouped,
            ViewMode::Grouped => ViewMode::Flat,
        }
    }
}

/// Input mode
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum InputMode {
    #[default]
    Normal,
    Search,
    Question, // Asking cosmos a question
}

// ═══════════════════════════════════════════════════════════════════════════
//  LOADING AND ANIMATION
// ═══════════════════════════════════════════════════════════════════════════

/// Loading state for background tasks
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LoadingState {
    #[default]
    None,
    GeneratingSuggestions,
    GeneratingSummaries,
    GeneratingPreview,   // Fast preview generation (<1s)
    GeneratingFix,       // Full fix generation (slower)
    ReviewingChanges,    // Adversarial review or PR review
    ApplyingReviewFixes, // Applying fixes from review
    Resetting,           // Clearing cache/data
    Answering,           // For question answering
}

impl LoadingState {
    pub fn is_loading(&self) -> bool {
        !matches!(self, LoadingState::None)
    }
}

/// Spinner animation frames (braille pattern)
pub const SPINNER_FRAMES: &[&str] = &["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];

// ═══════════════════════════════════════════════════════════════════════════
//  OVERLAY STATE
// ═══════════════════════════════════════════════════════════════════════════

/// Overlay state
#[derive(Debug, Clone, PartialEq, Default)]
pub enum Overlay {
    #[default]
    None,
    Help {
        scroll: usize,
    },
    FileDetail {
        path: PathBuf,
        scroll: usize,
    },
    /// Reset cosmos - selective cache/data reset
    Reset {
        /// List of (option, is_selected) pairs
        options: Vec<(crate::cache::ResetOption, bool)>,
        /// Currently focused option index
        selected: usize,
    },
    /// Startup check - shown when cosmos starts with unsaved work
    StartupCheck {
        /// Number of files with uncommitted changes
        changed_count: usize,
        /// Current git branch name (or "detached")
        current_branch: String,
        /// Default/main branch name
        main_branch: String,
        /// Scroll position within the overlay
        scroll: usize,
        /// True when showing "are you sure?" confirmation for discard
        confirming_discard: bool,
    },
}

// ═══════════════════════════════════════════════════════════════════════════
//  WORKFLOW NAVIGATION
// ═══════════════════════════════════════════════════════════════════════════

/// Steps in the Ship workflow
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ShipStep {
    #[default]
    Confirm, // Show what will happen
    Committing, // Committing changes
    Pushing,    // Pushing to remote
    CreatingPR, // Creating pull request
    Done,       // PR created successfully
}

/// Main workflow steps for the right panel: Suggestions → Verify → Review → Ship
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum WorkflowStep {
    #[default]
    Suggestions, // Browse and select suggestions
    Verify, // Verify the fix, apply it
    Review, // Review applied changes, fix issues
    Ship,   // Commit, push, create PR
}

impl WorkflowStep {
    pub fn index(&self) -> usize {
        match self {
            WorkflowStep::Suggestions => 0,
            WorkflowStep::Verify => 1,
            WorkflowStep::Review => 2,
            WorkflowStep::Ship => 3,
        }
    }
}

// ═══════════════════════════════════════════════════════════════════════════
//  WORKFLOW STATE STRUCTS
// ═══════════════════════════════════════════════════════════════════════════

/// State for the Verify step
#[derive(Debug, Clone, Default)]
pub struct VerifyState {
    pub suggestion_id: Option<uuid::Uuid>,
    pub file_path: Option<PathBuf>,
    /// Additional files for multi-file suggestions
    pub additional_files: Vec<PathBuf>,
    pub summary: String,
    pub preview: Option<FixPreview>,
    pub loading: bool,
    pub scroll: usize,
    /// Whether to show technical details (code evidence, affected areas, etc.)
    pub show_technical_details: bool,
    /// File content hashes captured during preview (for change detection)
    pub preview_hashes: HashMap<PathBuf, String>,
}

impl VerifyState {
    /// Check if this is a multi-file suggestion
    pub fn is_multi_file(&self) -> bool {
        !self.additional_files.is_empty()
    }

    /// Get total file count
    pub fn file_count(&self) -> usize {
        if self.file_path.is_some() {
            1 + self.additional_files.len()
        } else {
            self.additional_files.len()
        }
    }
}

/// State for the Review step
#[derive(Debug, Clone, Default)]
pub struct ReviewState {
    pub file_path: Option<PathBuf>,
    pub original_content: String,
    pub new_content: String,
    pub findings: Vec<crate::suggest::llm::ReviewFinding>,
    pub selected: HashSet<usize>,
    pub cursor: usize,
    pub summary: String,
    pub scroll: usize,
    pub reviewing: bool,
    pub fixing: bool,
    pub confirm_ship: bool,
    pub review_iteration: u32,
    pub fixed_titles: Vec<String>,
}

/// State for the Ship step
#[derive(Debug, Clone, Default)]
pub struct ShipState {
    pub branch_name: String,
    pub commit_message: String,
    pub files: Vec<PathBuf>,
    pub step: ShipStep,
    pub scroll: usize,
    pub pr_url: Option<String>,
}

/// State for the Ask Cosmos panel mode
#[derive(Debug, Clone, Default)]
pub struct AskCosmosState {
    pub response: String,
    pub scroll: usize,
}

// ═══════════════════════════════════════════════════════════════════════════
//  TOAST NOTIFICATIONS
// ═══════════════════════════════════════════════════════════════════════════

/// Toast notification kind - affects duration and styling
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ToastKind {
    #[default]
    Info,
    Success,
    Error,
    RateLimit,
}

impl ToastKind {
    /// Duration in seconds before toast expires
    pub fn duration_secs(&self) -> u64 {
        match self {
            ToastKind::Info => 3,
            ToastKind::Success => 3,
            ToastKind::Error => 10,     // Errors stay longer
            ToastKind::RateLimit => 15, // Rate limits stay even longer
        }
    }
}

/// Toast notification
pub struct Toast {
    pub message: String,
    pub created_at: Instant,
    pub kind: ToastKind,
}

impl Toast {
    pub fn new(message: &str) -> Self {
        // Auto-detect toast type - check success indicators BEFORE error keywords
        let kind = if message.contains("Rate limit") || message.contains("rate limited") {
            ToastKind::RateLimit
        } else if message.starts_with("Fixed:") || message.starts_with('+') {
            ToastKind::Success
        } else if message.contains("failed")
            || message.contains("error")
            || message.contains("Error")
        {
            ToastKind::Error
        } else {
            ToastKind::Info
        };

        Self {
            message: message.to_string(),
            created_at: Instant::now(),
            kind,
        }
    }

    pub fn is_expired(&self) -> bool {
        self.created_at.elapsed().as_secs() >= self.kind.duration_secs()
    }

    pub fn is_error(&self) -> bool {
        matches!(self.kind, ToastKind::Error | ToastKind::RateLimit)
    }
}

// ═══════════════════════════════════════════════════════════════════════════
//  PENDING CHANGES
// ═══════════════════════════════════════════════════════════════════════════

/// A single file change within a pending change (for multi-file support)
#[derive(Debug, Clone)]
pub struct FileChange {
    pub path: PathBuf,
    pub diff: String,
}

impl FileChange {
    pub fn new(path: PathBuf, diff: String) -> Self {
        Self { path, diff }
    }
}

/// A pending change that has been applied but not yet committed
#[derive(Debug, Clone)]
pub struct PendingChange {
    pub suggestion_id: uuid::Uuid,
    /// All file changes in this pending change (supports multi-file refactors)
    pub files: Vec<FileChange>,
    pub description: String,
    /// Human-friendly title (e.g., "Batch Processing", "Error Handling")
    pub friendly_title: Option<String>,
    /// Behavior-focused problem description for non-technical readers
    pub problem_summary: Option<String>,
    /// What will be different after the fix (outcome-focused)
    pub outcome: Option<String>,
}

impl PendingChange {
    /// Create a multi-file pending change with human-friendly context
    pub fn with_preview_context_multi(
        suggestion_id: uuid::Uuid,
        files: Vec<FileChange>,
        description: String,
        friendly_title: String,
        problem_summary: String,
        outcome: String,
    ) -> Self {
        Self {
            suggestion_id,
            files,
            description,
            friendly_title: Some(friendly_title),
            problem_summary: Some(problem_summary),
            outcome: Some(outcome),
        }
    }

    /// Get the primary file path (first file, for backward compatibility)
    pub fn file_path(&self) -> &PathBuf {
        &self.files[0].path
    }

    /// Get the primary diff (first file, for backward compatibility)
    pub fn diff(&self) -> &str {
        &self.files[0].diff
    }

    /// Check if this is a multi-file change
    pub fn is_multi_file(&self) -> bool {
        self.files.len() > 1
    }
}
