use std::cmp::Ordering;
use std::fs;
use std::path::{Path, PathBuf};

#[derive(Clone, PartialEq, Eq)]
pub enum FileBrowserMode {
    Save,
    Load,
}

#[derive(Clone, PartialEq, Eq)]
pub struct DirEntry {
    pub name: String,
    pub is_dir: bool,
}

impl DirEntry {
    pub fn is_cagire(&self) -> bool {
        !self.is_dir && self.name.ends_with(".cagire")
    }
}

#[derive(Clone, PartialEq, Eq)]
pub struct FileBrowserState {
    pub mode: FileBrowserMode,
    pub input: String,
    pub entries: Vec<DirEntry>,
    pub selected: usize,
    pub scroll_offset: usize,
}

impl FileBrowserState {
    pub fn new_save(initial_path: String) -> Self {
        let mut state = Self {
            mode: FileBrowserMode::Save,
            input: initial_path,
            entries: Vec::new(),
            selected: 0,
            scroll_offset: 0,
        };
        state.refresh_entries();
        state
    }

    pub fn new_load(initial_path: String) -> Self {
        let mut state = Self {
            mode: FileBrowserMode::Load,
            input: initial_path,
            entries: Vec::new(),
            selected: 0,
            scroll_offset: 0,
        };
        state.refresh_entries();
        state
    }

    pub fn current_dir(&self) -> PathBuf {
        if self.input.is_empty() {
            return std::env::current_dir().unwrap_or_else(|_| PathBuf::from("/"));
        }
        let path = Path::new(&self.input);
        if self.input.ends_with('/') {
            path.to_path_buf()
        } else {
            match path.parent() {
                Some(p) if !p.as_os_str().is_empty() => p.to_path_buf(),
                _ => std::env::current_dir().unwrap_or_else(|_| PathBuf::from("/")),
            }
        }
    }

    fn partial_name(&self) -> &str {
        if self.input.is_empty() || self.input.ends_with('/') {
            ""
        } else if !self.input.contains('/') {
            &self.input
        } else {
            Path::new(&self.input)
                .file_name()
                .and_then(|n| n.to_str())
                .unwrap_or("")
        }
    }

    pub fn refresh_entries(&mut self) {
        let dir = self.current_dir();
        let prefix = self.partial_name().to_lowercase();

        let mut entries = Vec::new();

        if prefix.is_empty() && dir.parent().is_some() {
            entries.push(DirEntry {
                name: "..".to_string(),
                is_dir: true,
            });
        }

        if let Ok(read_dir) = fs::read_dir(&dir) {
            for entry in read_dir.flatten() {
                let name = entry.file_name().to_string_lossy().into_owned();
                if name.starts_with('.') {
                    continue;
                }
                let is_dir = entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false);
                if prefix.is_empty() || name.to_lowercase().starts_with(&prefix) {
                    entries.push(DirEntry { name, is_dir });
                }
            }
        }

        entries.sort_by(|a, b| match (a.name.as_str(), b.name.as_str()) {
            ("..", _) => Ordering::Less,
            (_, "..") => Ordering::Greater,
            _ => match (a.is_dir, b.is_dir) {
                (true, false) => Ordering::Less,
                (false, true) => Ordering::Greater,
                _ => a.name.to_lowercase().cmp(&b.name.to_lowercase()),
            },
        });

        self.entries = entries;
        self.selected = 0;
        self.scroll_offset = 0;
    }

    pub fn autocomplete(&mut self) {
        let real_entries: Vec<&DirEntry> =
            self.entries.iter().filter(|e| e.name != "..").collect();
        if real_entries.is_empty() {
            return;
        }

        let lcp = longest_common_prefix(&real_entries);
        if lcp.is_empty() {
            return;
        }

        let dir = self.current_dir();
        let mut new_input = dir.join(&lcp).display().to_string();

        if real_entries.len() == 1 && real_entries[0].is_dir && !new_input.ends_with('/') {
            new_input.push('/');
        }

        if new_input != self.input {
            self.input = new_input;
            self.refresh_entries();
        }
    }

    pub fn select_next(&mut self, visible_height: usize) {
        if self.selected + 1 < self.entries.len() {
            self.selected += 1;
            if self.selected >= self.scroll_offset + visible_height {
                self.scroll_offset = self.selected + 1 - visible_height;
            }
        }
    }

    pub fn select_prev(&mut self, _visible_height: usize) {
        if self.selected > 0 {
            self.selected -= 1;
            if self.selected < self.scroll_offset {
                self.scroll_offset = self.selected;
            }
        }
    }

    pub fn confirm(&mut self) -> Option<PathBuf> {
        if !self.entries.is_empty() {
            let entry = &self.entries[self.selected];
            if entry.is_dir {
                self.navigate_to_selected();
                return None;
            }
            let path = self.current_dir().join(&entry.name);
            if self.mode == FileBrowserMode::Save {
                ensure_parent_dirs(&path);
            }
            return Some(path);
        }

        if self.mode == FileBrowserMode::Save && !self.input.is_empty() {
            let path = PathBuf::from(&self.input);
            ensure_parent_dirs(&path);
            return Some(path);
        }

        None
    }

    pub fn enter_selected(&mut self) {
        if let Some(entry) = self.entries.get(self.selected) {
            if entry.is_dir {
                self.navigate_to_selected();
            }
        }
    }

    pub fn go_up(&mut self) {
        let dir = self.current_dir();
        if let Some(parent) = dir.parent() {
            self.input = format_dir_path(parent);
            self.refresh_entries();
        }
    }

    pub fn backspace(&mut self) {
        if self.input.is_empty() {
            return;
        }
        if self.input.ends_with('/') && self.input.len() > 1 {
            let trimmed = &self.input[..self.input.len() - 1];
            match trimmed.rfind('/') {
                Some(pos) => self.input = trimmed[..=pos].to_string(),
                None => self.input.clear(),
            }
        } else {
            self.input.pop();
        }
        self.refresh_entries();
    }

    fn navigate_to_selected(&mut self) {
        let entry = &self.entries[self.selected];
        if entry.name == ".." {
            self.go_up();
        } else {
            let dir = self.current_dir();
            self.input = format_dir_path(&dir.join(&entry.name));
            self.refresh_entries();
        }
    }
}

fn format_dir_path(path: &Path) -> String {
    let mut s = path.display().to_string();
    if !s.ends_with('/') {
        s.push('/');
    }
    s
}

fn ensure_parent_dirs(path: &Path) {
    if let Some(parent) = path.parent() {
        if !parent.exists() {
            let _ = fs::create_dir_all(parent);
        }
    }
}

fn longest_common_prefix(entries: &[&DirEntry]) -> String {
    if entries.is_empty() {
        return String::new();
    }
    let first = &entries[0].name;
    let mut len = first.len();
    for entry in &entries[1..] {
        len = first
            .chars()
            .zip(entry.name.chars())
            .take_while(|(a, b)| a.to_lowercase().eq(b.to_lowercase()))
            .count()
            .min(len);
    }
    first[..first
        .char_indices()
        .nth(len)
        .map(|(i, _)| i)
        .unwrap_or(first.len())]
    .to_string()
}
