//! Project selector widget for switching between projects
//!
//! Provides UI components for selecting and managing projects in a multi-tool
//! dashboard environment.

use crate::config::Theme;
use ratatui::prelude::*;
use ratatui::widgets::{Block, Borders, Paragraph};

/// Project information
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Project {
    /// Project identifier (kebab-case path or name)
    pub id: String,
    /// Project display name
    pub name: String,
    /// Project path or location
    pub path: String,
    /// Project description
    pub description: String,
}

impl Project {
    /// Create a new project
    #[must_use]
    pub const fn new(id: String, name: String, path: String) -> Self {
        Self {
            id,
            name,
            path,
            description: String::new(),
        }
    }

    /// Set project description
    #[must_use]
    pub fn description(mut self, desc: String) -> Self {
        self.description = desc;
        self
    }
}

/// Project selector widget
///
/// Allows users to select from available projects in a dashboard.
///
/// # Example
///
/// ```ignore
/// let mut selector = ProjectSelector::new(vec![
///     Project::new("proj1".to_string(), "Project 1".to_string(), "/path/1".to_string()),
///     Project::new("proj2".to_string(), "Project 2".to_string(), "/path/2".to_string()),
/// ]);
///
/// selector.select_next();
/// selector.render(&theme, frame, area);
/// ```
#[derive(Clone, Debug)]
pub struct ProjectSelector {
    /// Available projects
    projects: Vec<Project>,
    /// Selected project index
    selected: usize,
    /// Dirty flag for efficient rendering
    dirty: bool,
}

impl ProjectSelector {
    /// Create a new project selector
    #[must_use]
    pub const fn new(projects: Vec<Project>) -> Self {
        Self {
            projects,
            selected: 0,
            dirty: true,
        }
    }

    /// Get the number of projects
    #[must_use]
    pub const fn project_count(&self) -> usize {
        self.projects.len()
    }

    /// Get all projects
    #[must_use]
    pub fn projects(&self) -> &[Project] {
        &self.projects
    }

    /// Get currently selected project
    #[must_use]
    pub fn selected(&self) -> Option<&Project> {
        if self.selected < self.projects.len() {
            Some(&self.projects[self.selected])
        } else {
            None
        }
    }

    /// Get selected project index
    #[must_use]
    pub const fn selected_index(&self) -> usize {
        self.selected
    }

    /// Move selection to next project
    pub const fn select_next(&mut self) {
        if !self.projects.is_empty() {
            self.selected = (self.selected + 1) % self.projects.len();
            self.dirty = true;
        }
    }

    /// Move selection to previous project
    pub const fn select_previous(&mut self) {
        if !self.projects.is_empty() {
            self.selected = if self.selected == 0 {
                self.projects.len() - 1
            } else {
                self.selected - 1
            };
            self.dirty = true;
        }
    }

    /// Select project by index
    pub const fn select(&mut self, index: usize) {
        if index < self.projects.len() {
            self.selected = index;
            self.dirty = true;
        }
    }

    /// Select project by ID
    ///
    /// # Errors
    ///
    /// Returns an error if the operation fails
    pub fn select_by_id(&mut self, id: &str) -> Result<(), String> {
        self.projects.iter().position(|p| p.id == id).map_or_else(
            || Err(format!("Project '{id}' not found")),
            |pos| {
                self.select(pos);
                Ok(())
            },
        )
    }

    /// Add a project
    pub fn add_project(&mut self, project: Project) {
        self.projects.push(project);
        self.dirty = true;
    }

    /// Remove project by index
    pub fn remove_project(&mut self, index: usize) {
        if index < self.projects.len() {
            self.projects.remove(index);
            if self.selected >= self.projects.len() && !self.projects.is_empty() {
                self.selected = self.projects.len() - 1;
            }
            self.dirty = true;
        }
    }

    /// Check if state has changed since last render
    #[must_use]
    pub const fn is_dirty(&self) -> bool {
        self.dirty
    }

    /// Reset dirty flag after rendering
    pub const fn reset_dirty(&mut self) {
        self.dirty = false;
    }

    /// Render the project selector
    pub fn render(&mut self, theme: &Theme, frame: &mut Frame, area: Rect) {
        if area.height < 1 || self.projects.is_empty() {
            return;
        }

        let mut lines = Vec::new();

        for (idx, project) in self.projects.iter().enumerate() {
            let is_selected = idx == self.selected;

            let style = if is_selected {
                theme.selected_style()
            } else {
                theme.normal_style()
            };

            let text = format!("📁 {} ({})", project.name, project.id);
            let line = Line::from(Span::styled(text, style));
            lines.push(line);
        }

        let paragraph = Paragraph::new(lines)
            .block(
                Block::default()
                    .title("Projects")
                    .borders(Borders::ALL)
                    .border_style(theme.focused_style()),
            )
            .style(theme.normal_style());

        frame.render_widget(paragraph, area);
        self.reset_dirty();
    }
}

impl Default for ProjectSelector {
    fn default() -> Self {
        Self::new(vec![])
    }
}

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

    #[test]
    fn test_project_new() {
        let proj = Project::new(
            "proj1".to_string(),
            "Project 1".to_string(),
            "/path/1".to_string(),
        );
        assert_eq!(proj.id, "proj1");
        assert_eq!(proj.name, "Project 1");
        assert_eq!(proj.path, "/path/1");
    }

    #[test]
    fn test_project_description() {
        let proj = Project::new(
            "proj1".to_string(),
            "Project 1".to_string(),
            "/path/1".to_string(),
        )
        .description("Test project".to_string());

        assert_eq!(proj.description, "Test project");
    }

    #[test]
    fn test_project_clone() {
        let proj1 = Project::new(
            "proj1".to_string(),
            "Project 1".to_string(),
            "/path/1".to_string(),
        );
        let proj2 = proj1.clone();
        assert_eq!(proj1, proj2);
    }

    #[test]
    fn test_project_selector_new() {
        let projects = vec![
            Project::new("p1".to_string(), "P1".to_string(), "/p1".to_string()),
            Project::new("p2".to_string(), "P2".to_string(), "/p2".to_string()),
        ];

        let selector = ProjectSelector::new(projects);
        assert_eq!(selector.project_count(), 2);
        assert_eq!(selector.selected_index(), 0);
    }

    #[test]
    fn test_project_selector_selected() {
        let projects = vec![Project::new(
            "p1".to_string(),
            "P1".to_string(),
            "/p1".to_string(),
        )];

        let selector = ProjectSelector::new(projects);
        let selected = selector.selected();
        assert!(selected.is_some());
        assert_eq!(selected.unwrap().id, "p1");
    }

    #[test]
    fn test_project_selector_select_next() {
        let projects = vec![
            Project::new("p1".to_string(), "P1".to_string(), "/p1".to_string()),
            Project::new("p2".to_string(), "P2".to_string(), "/p2".to_string()),
            Project::new("p3".to_string(), "P3".to_string(), "/p3".to_string()),
        ];

        let mut selector = ProjectSelector::new(projects);
        assert_eq!(selector.selected_index(), 0);

        selector.select_next();
        assert_eq!(selector.selected_index(), 1);

        selector.select_next();
        assert_eq!(selector.selected_index(), 2);

        selector.select_next();
        assert_eq!(selector.selected_index(), 0); // Wraps
    }

    #[test]
    fn test_project_selector_select_previous() {
        let projects = vec![
            Project::new("p1".to_string(), "P1".to_string(), "/p1".to_string()),
            Project::new("p2".to_string(), "P2".to_string(), "/p2".to_string()),
        ];

        let mut selector = ProjectSelector::new(projects);
        selector.select(1);
        selector.select_previous();
        assert_eq!(selector.selected_index(), 0);
    }

    #[test]
    fn test_project_selector_select() {
        let projects = vec![
            Project::new("p1".to_string(), "P1".to_string(), "/p1".to_string()),
            Project::new("p2".to_string(), "P2".to_string(), "/p2".to_string()),
        ];

        let mut selector = ProjectSelector::new(projects);
        selector.select(1);
        assert_eq!(selector.selected_index(), 1);
    }

    #[test]
    fn test_project_selector_select_by_id() {
        let projects = vec![
            Project::new("p1".to_string(), "P1".to_string(), "/p1".to_string()),
            Project::new("p2".to_string(), "P2".to_string(), "/p2".to_string()),
        ];

        let mut selector = ProjectSelector::new(projects);
        let result = selector.select_by_id("p2");
        assert!(result.is_ok());
        assert_eq!(selector.selected_index(), 1);
    }

    #[test]
    fn test_project_selector_select_by_id_not_found() {
        let projects = vec![Project::new(
            "p1".to_string(),
            "P1".to_string(),
            "/p1".to_string(),
        )];

        let mut selector = ProjectSelector::new(projects);
        let result = selector.select_by_id("p999");
        assert!(result.is_err());
    }

    #[test]
    fn test_project_selector_add_project() {
        let projects = vec![Project::new(
            "p1".to_string(),
            "P1".to_string(),
            "/p1".to_string(),
        )];

        let mut selector = ProjectSelector::new(projects);
        selector.add_project(Project::new(
            "p2".to_string(),
            "P2".to_string(),
            "/p2".to_string(),
        ));

        assert_eq!(selector.project_count(), 2);
    }

    #[test]
    fn test_project_selector_remove_project() {
        let projects = vec![
            Project::new("p1".to_string(), "P1".to_string(), "/p1".to_string()),
            Project::new("p2".to_string(), "P2".to_string(), "/p2".to_string()),
        ];

        let mut selector = ProjectSelector::new(projects);
        selector.remove_project(0);
        assert_eq!(selector.project_count(), 1);
    }

    #[test]
    fn test_project_selector_remove_selected() {
        let projects = vec![
            Project::new("p1".to_string(), "P1".to_string(), "/p1".to_string()),
            Project::new("p2".to_string(), "P2".to_string(), "/p2".to_string()),
        ];

        let mut selector = ProjectSelector::new(projects);
        selector.select(1);
        selector.remove_project(1);
        assert_eq!(selector.selected_index(), 0); // Adjusted
    }

    #[test]
    fn test_project_selector_dirty_flag() {
        let projects = vec![Project::new(
            "p1".to_string(),
            "P1".to_string(),
            "/p1".to_string(),
        )];

        let mut selector = ProjectSelector::new(projects);
        assert!(selector.is_dirty());

        selector.reset_dirty();
        assert!(!selector.is_dirty());

        selector.select_next();
        assert!(selector.is_dirty());
    }

    #[test]
    fn test_project_selector_default() {
        let selector = ProjectSelector::default();
        assert_eq!(selector.project_count(), 0);
    }

    #[test]
    fn test_project_selector_clone() {
        let projects = vec![Project::new(
            "p1".to_string(),
            "P1".to_string(),
            "/p1".to_string(),
        )];

        let selector1 = ProjectSelector::new(projects);
        let selector2 = selector1.clone();
        assert_eq!(selector1.project_count(), selector2.project_count());
    }
}
