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

use cagire_ratatui::{fuzzy_match, TreeLine, TreeLineKind};

const AUDIO_EXTENSIONS: &[&str] = &["wav", "flac", "ogg", "aiff", "aif", "mp3"];

pub enum SampleNode {
    Root {
        label: String,
        children: Vec<SampleNode>,
        expanded: bool,
    },
    Folder {
        name: String,
        children: Vec<SampleNode>,
        expanded: bool,
    },
    File {
        name: String,
    },
}

impl SampleNode {
    fn expanded(&self) -> bool {
        match self {
            SampleNode::Root { expanded, .. } | SampleNode::Folder { expanded, .. } => *expanded,
            SampleNode::File { .. } => false,
        }
    }

    fn set_expanded(&mut self, val: bool) {
        match self {
            SampleNode::Root { expanded, .. } | SampleNode::Folder { expanded, .. } => {
                *expanded = val;
            }
            SampleNode::File { .. } => {}
        }
    }

    fn is_expandable(&self) -> bool {
        !matches!(self, SampleNode::File { .. })
    }

    fn children(&self) -> &[SampleNode] {
        match self {
            SampleNode::Root { children, .. } | SampleNode::Folder { children, .. } => children,
            SampleNode::File { .. } => &[],
        }
    }

    fn label(&self) -> &str {
        match self {
            SampleNode::Root { label, .. } => label,
            SampleNode::Folder { name, .. } | SampleNode::File { name, .. } => name,
        }
    }

    fn flatten(&self, depth: u8, parent_folder: &str, file_index: usize, out: &mut Vec<TreeLine>) {
        let kind = match self {
            SampleNode::Root { expanded, .. } => TreeLineKind::Root { expanded: *expanded },
            SampleNode::Folder { expanded, .. } => TreeLineKind::Folder { expanded: *expanded },
            SampleNode::File { .. } => TreeLineKind::File,
        };
        out.push(TreeLine {
            depth,
            kind,
            label: self.label().to_string(),
            folder: parent_folder.to_string(),
            index: file_index,
        });
        if self.expanded() {
            let folder_name = self.label();
            let mut idx = 0;
            for child in self.children() {
                let child_idx = if matches!(child, SampleNode::File { .. }) {
                    let i = idx;
                    idx += 1;
                    i
                } else {
                    0
                };
                child.flatten(depth + 1, folder_name, child_idx, out);
            }
        }
    }
}

pub struct SampleTree {
    roots: Vec<SampleNode>,
}

impl SampleTree {
    pub fn from_paths(paths: &[PathBuf]) -> Self {
        if paths.len() == 1 {
            let roots = Self::scan_children(&paths[0]);
            return Self { roots };
        }
        let mut roots = Vec::new();
        for path in paths {
            if let Some(root) = Self::scan_root(path) {
                roots.push(root);
            }
        }
        Self { roots }
    }

    fn scan_children(path: &Path) -> Vec<SampleNode> {
        let entries = match fs::read_dir(path) {
            Ok(e) => e,
            Err(_) => return Vec::new(),
        };

        let mut files: Vec<String> = Vec::new();
        let mut folders: Vec<(String, PathBuf)> = Vec::new();

        for entry in entries.flatten() {
            let ft = entry.file_type().ok();
            let name = entry.file_name().to_string_lossy().into_owned();
            if ft.is_some_and(|t| t.is_dir()) {
                folders.push((name, entry.path()));
            } else if is_audio_file(&name) {
                files.push(name);
            }
        }

        folders.sort_by_key(|a| a.0.to_lowercase());
        files.sort_by_key(|a| a.to_lowercase());

        let mut nodes = Vec::new();
        for (name, folder_path) in folders {
            if let Some(folder) = Self::scan_folder(&name, &folder_path) {
                nodes.push(folder);
            }
        }
        for name in files {
            nodes.push(SampleNode::File { name });
        }
        nodes
    }

    fn scan_root(path: &Path) -> Option<SampleNode> {
        let entries = fs::read_dir(path).ok()?;
        let label = path
            .file_name()
            .map(|n| n.to_string_lossy().into_owned())
            .unwrap_or_else(|| path.display().to_string());

        let mut children = Vec::new();
        let mut files: Vec<String> = Vec::new();
        let mut folders: Vec<(String, PathBuf)> = Vec::new();

        for entry in entries.flatten() {
            let ft = entry.file_type().ok();
            let name = entry.file_name().to_string_lossy().into_owned();
            if ft.is_some_and(|t| t.is_dir()) {
                folders.push((name, entry.path()));
            } else if is_audio_file(&name) {
                files.push(name);
            }
        }

        folders.sort_by_key(|a| a.0.to_lowercase());
        files.sort_by_key(|a| a.to_lowercase());

        for (name, folder_path) in folders {
            if let Some(folder) = Self::scan_folder(&name, &folder_path) {
                children.push(folder);
            }
        }
        for name in files {
            children.push(SampleNode::File { name });
        }

        Some(SampleNode::Root {
            label,
            children,
            expanded: false,
        })
    }

    fn scan_folder(name: &str, path: &Path) -> Option<SampleNode> {
        let entries = fs::read_dir(path).ok()?;
        let mut children: Vec<SampleNode> = Vec::new();

        let mut files: Vec<String> = Vec::new();
        for entry in entries.flatten() {
            let ft = entry.file_type().ok();
            let entry_name = entry.file_name().to_string_lossy().into_owned();
            if ft.is_some_and(|t| t.is_file()) && is_audio_file(&entry_name) {
                files.push(entry_name);
            }
        }
        files.sort_by_key(|a| a.to_lowercase());

        for file in files {
            children.push(SampleNode::File { name: file });
        }

        if children.is_empty() {
            return None;
        }

        Some(SampleNode::Folder {
            name: name.to_string(),
            children,
            expanded: false,
        })
    }

    pub fn all_folder_names(&self) -> Vec<String> {
        let mut names = Vec::new();
        for root in &self.roots {
            Self::collect_folder_names(root, &mut names);
        }
        names.sort_by_key(|n| n.to_lowercase());
        names
    }

    fn collect_folder_names(node: &SampleNode, out: &mut Vec<String>) {
        match node {
            SampleNode::Root { children, .. } => {
                for child in children {
                    Self::collect_folder_names(child, out);
                }
            }
            SampleNode::Folder { name, .. } => {
                out.push(name.clone());
            }
            SampleNode::File { .. } => {}
        }
    }

    pub fn visible_entries(&self) -> Vec<TreeLine> {
        let mut out = Vec::new();
        for root in &self.roots {
            root.flatten(0, "", 0, &mut out);
        }
        out
    }

    fn node_at_mut(&mut self, visible_index: usize) -> Option<&mut SampleNode> {
        let mut count = 0;
        for root in &mut self.roots {
            if let Some(node) = Self::walk_mut(root, visible_index, &mut count) {
                return Some(node);
            }
        }
        None
    }

    fn walk_mut<'a>(
        node: &'a mut SampleNode,
        target: usize,
        count: &mut usize,
    ) -> Option<&'a mut SampleNode> {
        if *count == target {
            return Some(node);
        }
        *count += 1;
        if node.expanded() {
            let children = match node {
                SampleNode::Root { children, .. } | SampleNode::Folder { children, .. } => {
                    children
                }
                SampleNode::File { .. } => return None,
            };
            for child in children.iter_mut() {
                if let Some(found) = Self::walk_mut(child, target, count) {
                    return Some(found);
                }
            }
        }
        None
    }

    fn find_folder_mut(&mut self, name: &str) -> Option<&mut SampleNode> {
        for root in &mut self.roots {
            if let Some(node) = Self::find_folder_in(root, name) {
                return Some(node);
            }
        }
        None
    }

    fn find_folder_in<'a>(node: &'a mut SampleNode, name: &str) -> Option<&'a mut SampleNode> {
        match node {
            SampleNode::Folder { name: n, .. } if n == name => Some(node),
            SampleNode::Root { children, .. } | SampleNode::Folder { children, .. } => {
                for child in children.iter_mut() {
                    if let Some(found) = Self::find_folder_in(child, name) {
                        return Some(found);
                    }
                }
                None
            }
            SampleNode::File { .. } => None,
        }
    }

    fn filtered_entries(&self, names: &[String], collapsed: bool) -> Vec<TreeLine> {
        let mut out = Vec::new();
        for name in names {
            for root in &self.roots {
                Self::emit_filtered(root, name, collapsed, &mut out);
            }
        }
        out
    }

    fn emit_filtered(
        node: &SampleNode,
        target_name: &str,
        collapsed: bool,
        out: &mut Vec<TreeLine>,
    ) {
        match node {
            SampleNode::Folder {
                name,
                children,
                expanded,
            } if name == target_name => {
                let show_children = !collapsed && *expanded;
                out.push(TreeLine {
                    depth: 0,
                    kind: TreeLineKind::Folder {
                        expanded: show_children,
                    },
                    label: name.clone(),
                    folder: String::new(),
                    index: 0,
                });
                if show_children {
                    let mut idx = 0;
                    for child in children {
                        if let SampleNode::File { name: fname } = child {
                            out.push(TreeLine {
                                depth: 1,
                                kind: TreeLineKind::File,
                                label: fname.clone(),
                                folder: name.clone(),
                                index: idx,
                            });
                            idx += 1;
                        }
                    }
                }
            }
            SampleNode::Root { children, .. } => {
                for child in children {
                    Self::emit_filtered(child, target_name, collapsed, out);
                }
            }
            _ => {}
        }
    }
}

pub struct SampleBrowserState {
    pub tree: SampleTree,
    pub cursor: usize,
    pub scroll_offset: usize,
    pub search_query: String,
    pub search_active: bool,
    filter: Option<Vec<String>>,
}

impl SampleBrowserState {
    pub fn new(paths: &[PathBuf]) -> Self {
        Self {
            tree: SampleTree::from_paths(paths),
            cursor: 0,
            scroll_offset: 0,
            search_query: String::new(),
            search_active: false,
            filter: None,
        }
    }

    pub fn entries(&self) -> Vec<TreeLine> {
        match &self.filter {
            Some(names) => self.tree.filtered_entries(names, self.search_active),
            None => self.tree.visible_entries(),
        }
    }

    pub fn current_entry(&self) -> Option<TreeLine> {
        let entries = self.entries();
        entries.into_iter().nth(self.cursor)
    }

    pub fn sample_key(&self) -> Option<String> {
        let entry = self.current_entry()?;
        if !matches!(entry.kind, TreeLineKind::File) {
            return None;
        }
        if entry.folder.is_empty() {
            Some(
                entry
                    .label
                    .rsplit_once('.')
                    .map_or(entry.label.clone(), |(stem, _)| stem.to_string()),
            )
        } else {
            Some(format!("{}/{}", entry.folder, entry.index))
        }
    }

    pub fn visible_count(&self) -> usize {
        self.entries().len()
    }

    pub fn has_filter(&self) -> bool {
        self.filter.is_some()
    }

    fn clamp_view(&mut self) {
        let count = self.entries().len();
        if count == 0 {
            self.cursor = 0;
            self.scroll_offset = 0;
            return;
        }
        if self.cursor >= count {
            self.cursor = count - 1;
        }
        if self.scroll_offset > self.cursor {
            self.scroll_offset = self.cursor;
        }
    }

    pub fn move_up(&mut self) {
        if self.cursor > 0 {
            self.cursor -= 1;
            if self.cursor < self.scroll_offset {
                self.scroll_offset = self.cursor;
            }
        }
    }

    pub fn move_down(&mut self, visible_height: usize) {
        let count = self.visible_count();
        if count == 0 {
            return;
        }
        if self.cursor + 1 < count {
            self.cursor += 1;
            if self.cursor >= self.scroll_offset + visible_height {
                self.scroll_offset = self.cursor - visible_height + 1;
            }
        }
    }

    pub fn toggle_expand(&mut self) {
        if self.search_active {
            return;
        }
        if let Some(ref names) = self.filter {
            let entries = self.tree.filtered_entries(names, false);
            if let Some(entry) = entries.get(self.cursor) {
                if matches!(entry.kind, TreeLineKind::Folder { .. }) {
                    let label = entry.label.clone();
                    if let Some(node) = self.tree.find_folder_mut(&label) {
                        let new_val = !node.expanded();
                        node.set_expanded(new_val);
                    }
                }
            }
        } else if let Some(node) = self.tree.node_at_mut(self.cursor) {
            if node.is_expandable() {
                let new_val = !node.expanded();
                node.set_expanded(new_val);
            }
        }
        self.clamp_view();
    }

    pub fn collapse_at_cursor(&mut self) {
        if self.search_active {
            return;
        }
        let entries = self.entries();
        let entry = match entries.get(self.cursor) {
            Some(e) => e,
            None => return,
        };
        let is_file = matches!(entry.kind, TreeLineKind::File);
        if is_file {
            // Scan backward to find parent folder
            for i in (0..self.cursor).rev() {
                if matches!(
                    entries[i].kind,
                    TreeLineKind::Folder { .. } | TreeLineKind::Root { .. }
                ) {
                    let label = entries[i].label.clone();
                    if self.filter.is_some() {
                        if let Some(node) = self.tree.find_folder_mut(&label) {
                            node.set_expanded(false);
                        }
                    } else if let Some(node) = self.tree.node_at_mut(i) {
                        node.set_expanded(false);
                    }
                    self.cursor = i;
                    if self.cursor < self.scroll_offset {
                        self.scroll_offset = self.cursor;
                    }
                    return;
                }
            }
        } else {
            let label = entry.label.clone();
            if self.filter.is_some() {
                if let Some(node) = self.tree.find_folder_mut(&label) {
                    if node.expanded() {
                        node.set_expanded(false);
                    }
                }
            } else if let Some(node) = self.tree.node_at_mut(self.cursor) {
                if node.expanded() {
                    node.set_expanded(false);
                }
            }
        }
        self.clamp_view();
    }

    pub fn activate_search(&mut self) {
        self.search_active = true;
    }

    pub fn update_search(&mut self) {
        if self.search_query.is_empty() {
            self.filter = None;
        } else {
            let query = &self.search_query;
            let mut scored: Vec<(usize, String)> = Vec::new();
            for root in &self.tree.roots {
                Self::collect_matching_folder_names(root, query, &mut scored);
            }
            scored.sort_by_key(|(score, _)| *score);
            let names = scored.into_iter().map(|(_, name)| name).collect();
            self.filter = Some(names);
        }
        self.cursor = 0;
        self.scroll_offset = 0;
    }

    pub fn clear_search(&mut self) {
        self.search_query.clear();
        self.search_active = false;
        self.filter = None;
        self.cursor = 0;
        self.scroll_offset = 0;
    }

    pub fn clear_filter(&mut self) {
        self.filter = None;
        self.search_query.clear();
        self.cursor = 0;
        self.scroll_offset = 0;
    }

    fn collect_matching_folder_names(
        node: &SampleNode,
        query: &str,
        out: &mut Vec<(usize, String)>,
    ) {
        match node {
            SampleNode::Root { children, .. } => {
                for child in children {
                    Self::collect_matching_folder_names(child, query, out);
                }
            }
            SampleNode::Folder { name, .. } => {
                if let Some(score) = fuzzy_match(query, name) {
                    out.push((score, name.clone()));
                }
            }
            SampleNode::File { .. } => {}
        }
    }
}

fn is_audio_file(name: &str) -> bool {
    let lower = name.to_lowercase();
    AUDIO_EXTENSIONS.iter().any(|ext| lower.ends_with(ext))
}
