use crate::commands::tree::{CommandTree, Node, NodeType};
use std::collections::VecDeque;
use std::fmt::{Display, Formatter, Write};

trait IsVisible {
    /// whether node should be printed in help command/usage hint
    fn is_visible(&self) -> bool;
}

impl<'a> IsVisible for Node<'a> {
    fn is_visible(&self) -> bool {
        match self.node_type {
            NodeType::ExecuteLeaf { .. } => false,
            NodeType::Literal { .. } => true,
            NodeType::Argument { .. } => true,
            NodeType::Require { .. } => false,
        }
    }
}

impl<'a> Display for Node<'a> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self.node_type {
            NodeType::Literal { string } => {
                f.write_str(string)?;
            }
            NodeType::Argument { name, .. } => {
                f.write_char('<')?;
                f.write_str(name)?;
                f.write_char('>')?;
            }
            _ => {}
        };

        Ok(())
    }
}

fn flatten_require_nodes(nodes: &[Node], children: &[usize]) -> Vec<usize> {
    let mut new_children = Vec::with_capacity(children.len());

    for &i in children {
        let node = &nodes[i];
        match &node.node_type {
            NodeType::Require { .. } => {
                new_children.extend(flatten_require_nodes(nodes, node.children.as_slice()))
            }
            _ => new_children.push(i),
        }
    }

    new_children
}

impl<'a> Display for CommandTree<'a> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.write_char('/')?;
        f.write_str(self.names[0])?;

        let mut todo = VecDeque::<&[usize]>::with_capacity(self.children.len());
        todo.push_back(&self.children);

        loop {
            let Some(children) = todo.pop_front() else {
                break;
            };

            let flattened_children = flatten_require_nodes(&self.nodes, children);
            let visible_children = flattened_children
                .iter()
                .copied()
                .filter(|&i| self.nodes[i].is_visible())
                .collect::<Vec<_>>();

            if visible_children.is_empty() {
                break;
            };

            f.write_char(' ')?;

            let is_optional = flattened_children
                .iter()
                .map(|&i| &self.nodes[i].node_type)
                .any(|node| matches!(node, NodeType::ExecuteLeaf { .. }));

            if is_optional {
                f.write_char('[')?;
            }

            match visible_children.as_slice() {
                [] => unreachable!(),
                [i] => {
                    let node = &self.nodes[*i];

                    node.fmt(f)?;

                    todo.push_back(&node.children);
                }
                _ => {
                    // todo: handle cases where one of these nodes has visible children
                    f.write_char('(')?;

                    let mut iter = visible_children.iter().map(|&i| &self.nodes[i]);

                    if let Some(node) = iter.next() {
                        node.fmt(f)?;
                    }

                    for node in iter {
                        f.write_char('|')?;
                        node.fmt(f)?;
                    }

                    f.write_char(')')?;
                }
            }

            if is_optional {
                f.write_char(']')?;
            }
        }

        Ok(())
    }
}
