use crate::{
    BlockId, DeprecationEntry, Example, FromValue, PipelineData, ShellError, SyntaxShape, Type,
    Value, VarId,
    engine::{Call, Command, CommandType, EngineState, Stack},
};
use nu_derive_value::FromValue as DeriveFromValue;
use serde::{Deserialize, Serialize};
use std::fmt::Write;

// Make nu_protocol available in this namespace, consumers of this crate will
// have this without such an export.
// The `FromValue` derive macro fully qualifies paths to "nu_protocol".
use crate as nu_protocol;

/// The signature definition of a named flag that either accepts a value or acts as a toggle flag
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Flag {
    pub long: String,
    pub short: Option<char>,
    pub arg: Option<SyntaxShape>,
    pub required: bool,
    pub desc: String,

    // For custom commands
    pub var_id: Option<VarId>,
    pub default_value: Option<Value>,
}

/// The signature definition for a positional argument
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PositionalArg {
    pub name: String,
    pub desc: String,
    pub shape: SyntaxShape,

    // For custom commands
    pub var_id: Option<VarId>,
    pub default_value: Option<Value>,
}

/// Command categories
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Category {
    Bits,
    Bytes,
    Chart,
    Conversions,
    Core,
    Custom(String),
    Database,
    Date,
    Debug,
    Default,
    Deprecated,
    Removed,
    Env,
    Experimental,
    FileSystem,
    Filters,
    Formats,
    Generators,
    Hash,
    History,
    Math,
    Misc,
    Network,
    Path,
    Platform,
    Plugin,
    Random,
    Shells,
    Strings,
    System,
    Viewers,
}

impl std::fmt::Display for Category {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let msg = match self {
            Category::Bits => "bits",
            Category::Bytes => "bytes",
            Category::Chart => "chart",
            Category::Conversions => "conversions",
            Category::Core => "core",
            Category::Custom(name) => name,
            Category::Database => "database",
            Category::Date => "date",
            Category::Debug => "debug",
            Category::Default => "default",
            Category::Deprecated => "deprecated",
            Category::Removed => "removed",
            Category::Env => "env",
            Category::Experimental => "experimental",
            Category::FileSystem => "filesystem",
            Category::Filters => "filters",
            Category::Formats => "formats",
            Category::Generators => "generators",
            Category::Hash => "hash",
            Category::History => "history",
            Category::Math => "math",
            Category::Misc => "misc",
            Category::Network => "network",
            Category::Path => "path",
            Category::Platform => "platform",
            Category::Plugin => "plugin",
            Category::Random => "random",
            Category::Shells => "shells",
            Category::Strings => "strings",
            Category::System => "system",
            Category::Viewers => "viewers",
        };

        write!(f, "{msg}")
    }
}

pub fn category_from_string(category: &str) -> Category {
    match category {
        "bits" => Category::Bits,
        "bytes" => Category::Bytes,
        "chart" => Category::Chart,
        "conversions" => Category::Conversions,
        // Let's protect our own "core" commands by preventing scripts from having this category.
        "core" => Category::Custom("custom_core".to_string()),
        "database" => Category::Database,
        "date" => Category::Date,
        "debug" => Category::Debug,
        "default" => Category::Default,
        "deprecated" => Category::Deprecated,
        "removed" => Category::Removed,
        "env" => Category::Env,
        "experimental" => Category::Experimental,
        "filesystem" => Category::FileSystem,
        "filter" => Category::Filters,
        "formats" => Category::Formats,
        "generators" => Category::Generators,
        "hash" => Category::Hash,
        "history" => Category::History,
        "math" => Category::Math,
        "misc" => Category::Misc,
        "network" => Category::Network,
        "path" => Category::Path,
        "platform" => Category::Platform,
        "plugin" => Category::Plugin,
        "random" => Category::Random,
        "shells" => Category::Shells,
        "strings" => Category::Strings,
        "system" => Category::System,
        "viewers" => Category::Viewers,
        _ => Category::Custom(category.to_string()),
    }
}

/// Signature information of a [`Command`]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Signature {
    pub name: String,
    pub description: String,
    pub extra_description: String,
    pub search_terms: Vec<String>,
    pub required_positional: Vec<PositionalArg>,
    pub optional_positional: Vec<PositionalArg>,
    pub rest_positional: Option<PositionalArg>,
    pub named: Vec<Flag>,
    pub input_output_types: Vec<(Type, Type)>,
    pub allow_variants_without_examples: bool,
    pub is_filter: bool,
    pub creates_scope: bool,
    pub allows_unknown_args: bool,
    // Signature category used to classify commands stored in the list of declarations
    pub category: Category,
}

impl PartialEq for Signature {
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name
            && self.description == other.description
            && self.required_positional == other.required_positional
            && self.optional_positional == other.optional_positional
            && self.rest_positional == other.rest_positional
            && self.is_filter == other.is_filter
    }
}

impl Eq for Signature {}

impl Signature {
    pub fn new(name: impl Into<String>) -> Signature {
        Signature {
            name: name.into(),
            description: String::new(),
            extra_description: String::new(),
            search_terms: vec![],
            required_positional: vec![],
            optional_positional: vec![],
            rest_positional: None,
            input_output_types: vec![],
            allow_variants_without_examples: false,
            named: vec![],
            is_filter: false,
            creates_scope: false,
            category: Category::Default,
            allows_unknown_args: false,
        }
    }

    // Gets the input type from the signature
    pub fn get_input_type(&self) -> Type {
        match self.input_output_types.len() {
            0 => Type::Any,
            1 => self.input_output_types[0].0.clone(),
            _ => {
                let first = &self.input_output_types[0].0;
                if self
                    .input_output_types
                    .iter()
                    .all(|(input, _)| input == first)
                {
                    first.clone()
                } else {
                    Type::Any
                }
            }
        }
    }

    // Gets the output type from the signature
    pub fn get_output_type(&self) -> Type {
        match self.input_output_types.len() {
            0 => Type::Any,
            1 => self.input_output_types[0].1.clone(),
            _ => {
                let first = &self.input_output_types[0].1;
                if self
                    .input_output_types
                    .iter()
                    .all(|(_, output)| output == first)
                {
                    first.clone()
                } else {
                    Type::Any
                }
            }
        }
    }

    // Add a default help option to a signature
    pub fn add_help(mut self) -> Signature {
        // default help flag
        let flag = Flag {
            long: "help".into(),
            short: Some('h'),
            arg: None,
            desc: "Display the help message for this command".into(),
            required: false,
            var_id: None,
            default_value: None,
        };
        self.named.push(flag);
        self
    }

    // Build an internal signature with default help option
    pub fn build(name: impl Into<String>) -> Signature {
        Signature::new(name.into()).add_help()
    }

    /// Add a description to the signature
    ///
    /// This should be a single sentence as it is the part shown for example in the completion
    /// menu.
    pub fn description(mut self, msg: impl Into<String>) -> Signature {
        self.description = msg.into();
        self
    }

    /// Add an extra description to the signature.
    ///
    /// Here additional documentation can be added
    pub fn extra_description(mut self, msg: impl Into<String>) -> Signature {
        self.extra_description = msg.into();
        self
    }

    /// Add search terms to the signature
    pub fn search_terms(mut self, terms: Vec<String>) -> Signature {
        self.search_terms = terms;
        self
    }

    /// Update signature's fields from a Command trait implementation
    pub fn update_from_command(mut self, command: &dyn Command) -> Signature {
        self.search_terms = command
            .search_terms()
            .into_iter()
            .map(|term| term.to_string())
            .collect();
        self.extra_description = command.extra_description().to_string();
        self.description = command.description().to_string();
        self
    }

    /// Allow unknown signature parameters
    pub fn allows_unknown_args(mut self) -> Signature {
        self.allows_unknown_args = true;
        self
    }

    /// Add a required positional argument to the signature
    pub fn required(
        mut self,
        name: impl Into<String>,
        shape: impl Into<SyntaxShape>,
        desc: impl Into<String>,
    ) -> Signature {
        self.required_positional.push(PositionalArg {
            name: name.into(),
            desc: desc.into(),
            shape: shape.into(),
            var_id: None,
            default_value: None,
        });

        self
    }

    /// Add an optional positional argument to the signature
    pub fn optional(
        mut self,
        name: impl Into<String>,
        shape: impl Into<SyntaxShape>,
        desc: impl Into<String>,
    ) -> Signature {
        self.optional_positional.push(PositionalArg {
            name: name.into(),
            desc: desc.into(),
            shape: shape.into(),
            var_id: None,
            default_value: None,
        });

        self
    }

    pub fn rest(
        mut self,
        name: &str,
        shape: impl Into<SyntaxShape>,
        desc: impl Into<String>,
    ) -> Signature {
        self.rest_positional = Some(PositionalArg {
            name: name.into(),
            desc: desc.into(),
            shape: shape.into(),
            var_id: None,
            default_value: None,
        });

        self
    }

    /// Is this command capable of operating on its input via cell paths?
    pub fn operates_on_cell_paths(&self) -> bool {
        self.required_positional
            .iter()
            .chain(self.rest_positional.iter())
            .any(|pos| {
                matches!(
                    pos,
                    PositionalArg {
                        shape: SyntaxShape::CellPath,
                        ..
                    }
                )
            })
    }

    /// Add an optional named flag argument to the signature
    pub fn named(
        mut self,
        name: impl Into<String>,
        shape: impl Into<SyntaxShape>,
        desc: impl Into<String>,
        short: Option<char>,
    ) -> Signature {
        let (name, s) = self.check_names(name, short);

        self.named.push(Flag {
            long: name,
            short: s,
            arg: Some(shape.into()),
            required: false,
            desc: desc.into(),
            var_id: None,
            default_value: None,
        });

        self
    }

    /// Add a required named flag argument to the signature
    pub fn required_named(
        mut self,
        name: impl Into<String>,
        shape: impl Into<SyntaxShape>,
        desc: impl Into<String>,
        short: Option<char>,
    ) -> Signature {
        let (name, s) = self.check_names(name, short);

        self.named.push(Flag {
            long: name,
            short: s,
            arg: Some(shape.into()),
            required: true,
            desc: desc.into(),
            var_id: None,
            default_value: None,
        });

        self
    }

    /// Add a switch to the signature
    pub fn switch(
        mut self,
        name: impl Into<String>,
        desc: impl Into<String>,
        short: Option<char>,
    ) -> Signature {
        let (name, s) = self.check_names(name, short);

        self.named.push(Flag {
            long: name,
            short: s,
            arg: None,
            required: false,
            desc: desc.into(),
            var_id: None,
            default_value: None,
        });

        self
    }

    /// Changes the input type of the command signature
    pub fn input_output_type(mut self, input_type: Type, output_type: Type) -> Signature {
        self.input_output_types.push((input_type, output_type));
        self
    }

    /// Set the input-output type signature variants of the command
    pub fn input_output_types(mut self, input_output_types: Vec<(Type, Type)>) -> Signature {
        self.input_output_types = input_output_types;
        self
    }

    /// Changes the signature category
    pub fn category(mut self, category: Category) -> Signature {
        self.category = category;

        self
    }

    /// Sets that signature will create a scope as it parses
    pub fn creates_scope(mut self) -> Signature {
        self.creates_scope = true;
        self
    }

    // Is it allowed for the type signature to feature a variant that has no corresponding example?
    pub fn allow_variants_without_examples(mut self, allow: bool) -> Signature {
        self.allow_variants_without_examples = allow;
        self
    }

    pub fn call_signature(&self) -> String {
        let mut one_liner = String::new();
        one_liner.push_str(&self.name);
        one_liner.push(' ');

        // Note: the call signature needs flags first because on the nu commandline,
        // flags will precede the script file name. Flags for internal commands can come
        // either before or after (or around) positional parameters, so there isn't a strong
        // preference, so we default to the more constrained example.
        if self.named.len() > 1 {
            one_liner.push_str("{flags} ");
        }

        for positional in &self.required_positional {
            one_liner.push_str(&get_positional_short_name(positional, true));
        }
        for positional in &self.optional_positional {
            one_liner.push_str(&get_positional_short_name(positional, false));
        }

        if let Some(rest) = &self.rest_positional {
            let _ = write!(one_liner, "...{}", get_positional_short_name(rest, false));
        }

        // if !self.subcommands.is_empty() {
        //     one_liner.push_str("<subcommand> ");
        // }

        one_liner
    }

    /// Get list of the short-hand flags
    pub fn get_shorts(&self) -> Vec<char> {
        self.named.iter().filter_map(|f| f.short).collect()
    }

    /// Get list of the long-hand flags
    pub fn get_names(&self) -> Vec<&str> {
        self.named.iter().map(|f| f.long.as_str()).collect()
    }

    /// Checks if short or long are already present
    /// Panics if one of them is found
    fn check_names(&self, name: impl Into<String>, short: Option<char>) -> (String, Option<char>) {
        let s = short.inspect(|c| {
            assert!(
                !self.get_shorts().contains(c),
                "There may be duplicate short flags for '-{}'",
                c
            );
        });

        let name = {
            let name: String = name.into();
            assert!(
                !self.get_names().contains(&name.as_str()),
                "There may be duplicate name flags for '--{}'",
                name
            );
            name
        };

        (name, s)
    }

    pub fn get_positional(&self, position: usize) -> Option<&PositionalArg> {
        if position < self.required_positional.len() {
            self.required_positional.get(position)
        } else if position < (self.required_positional.len() + self.optional_positional.len()) {
            self.optional_positional
                .get(position - self.required_positional.len())
        } else {
            self.rest_positional.as_ref()
        }
    }

    pub fn num_positionals(&self) -> usize {
        let mut total = self.required_positional.len() + self.optional_positional.len();

        for positional in &self.required_positional {
            if let SyntaxShape::Keyword(..) = positional.shape {
                // Keywords have a required argument, so account for that
                total += 1;
            }
        }
        for positional in &self.optional_positional {
            if let SyntaxShape::Keyword(..) = positional.shape {
                // Keywords have a required argument, so account for that
                total += 1;
            }
        }
        total
    }

    /// Find the matching long flag
    pub fn get_long_flag(&self, name: &str) -> Option<Flag> {
        for flag in &self.named {
            if flag.long == name {
                return Some(flag.clone());
            }
        }
        None
    }

    /// Find the matching long flag
    pub fn get_short_flag(&self, short: char) -> Option<Flag> {
        for flag in &self.named {
            if let Some(short_flag) = &flag.short {
                if *short_flag == short {
                    return Some(flag.clone());
                }
            }
        }
        None
    }

    /// Set the filter flag for the signature
    pub fn filter(mut self) -> Signature {
        self.is_filter = true;
        self
    }

    /// Create a placeholder implementation of Command as a way to predeclare a definition's
    /// signature so other definitions can see it. This placeholder is later replaced with the
    /// full definition in a second pass of the parser.
    pub fn predeclare(self) -> Box<dyn Command> {
        Box::new(Predeclaration { signature: self })
    }

    /// Combines a signature and a block into a runnable block
    pub fn into_block_command(
        self,
        block_id: BlockId,
        attributes: Vec<(String, Value)>,
        examples: Vec<CustomExample>,
    ) -> Box<dyn Command> {
        Box::new(BlockCommand {
            signature: self,
            block_id,
            attributes,
            examples,
        })
    }

    pub fn formatted_flags(self) -> String {
        if self.named.len() < 11 {
            let mut s = "Available flags:".to_string();
            for flag in self.named {
                if let Some(short) = flag.short {
                    let _ = write!(s, " --{}(-{}),", flag.long, short);
                } else {
                    let _ = write!(s, " --{},", flag.long);
                }
            }
            s.remove(s.len() - 1);
            let _ = write!(s, ". Use `--help` for more information.");
            s
        } else {
            let mut s = "Some available flags:".to_string();
            for flag in self.named {
                if let Some(short) = flag.short {
                    let _ = write!(s, " --{}(-{}),", flag.long, short);
                } else {
                    let _ = write!(s, " --{},", flag.long);
                }
            }
            s.remove(s.len() - 1);
            let _ = write!(
                s,
                "... Use `--help` for a full list of flags and more information."
            );
            s
        }
    }
}

#[derive(Clone)]
struct Predeclaration {
    signature: Signature,
}

impl Command for Predeclaration {
    fn name(&self) -> &str {
        &self.signature.name
    }

    fn signature(&self) -> Signature {
        self.signature.clone()
    }

    fn description(&self) -> &str {
        &self.signature.description
    }

    fn extra_description(&self) -> &str {
        &self.signature.extra_description
    }

    fn run(
        &self,
        _engine_state: &EngineState,
        _stack: &mut Stack,
        _call: &Call,
        _input: PipelineData,
    ) -> Result<PipelineData, crate::ShellError> {
        panic!("Internal error: can't run a predeclaration without a body")
    }
}

fn get_positional_short_name(arg: &PositionalArg, is_required: bool) -> String {
    match &arg.shape {
        SyntaxShape::Keyword(name, ..) => {
            if is_required {
                format!("{} <{}> ", String::from_utf8_lossy(name), arg.name)
            } else {
                format!("({} <{}>) ", String::from_utf8_lossy(name), arg.name)
            }
        }
        _ => {
            if is_required {
                format!("<{}> ", arg.name)
            } else {
                format!("({}) ", arg.name)
            }
        }
    }
}

#[derive(Clone, DeriveFromValue)]
pub struct CustomExample {
    pub example: String,
    pub description: String,
    pub result: Option<Value>,
}

impl CustomExample {
    pub fn to_example(&self) -> Example<'_> {
        Example {
            example: self.example.as_str(),
            description: self.description.as_str(),
            result: self.result.clone(),
        }
    }
}

#[derive(Clone)]
struct BlockCommand {
    signature: Signature,
    block_id: BlockId,
    attributes: Vec<(String, Value)>,
    examples: Vec<CustomExample>,
}

impl Command for BlockCommand {
    fn name(&self) -> &str {
        &self.signature.name
    }

    fn signature(&self) -> Signature {
        self.signature.clone()
    }

    fn description(&self) -> &str {
        &self.signature.description
    }

    fn extra_description(&self) -> &str {
        &self.signature.extra_description
    }

    fn run(
        &self,
        _engine_state: &EngineState,
        _stack: &mut Stack,
        _call: &Call,
        _input: PipelineData,
    ) -> Result<crate::PipelineData, crate::ShellError> {
        Err(ShellError::GenericError {
            error: "Internal error: can't run custom command with 'run', use block_id".into(),
            msg: "".into(),
            span: None,
            help: None,
            inner: vec![],
        })
    }

    fn command_type(&self) -> CommandType {
        CommandType::Custom
    }

    fn block_id(&self) -> Option<BlockId> {
        Some(self.block_id)
    }

    fn attributes(&self) -> Vec<(String, Value)> {
        self.attributes.clone()
    }

    fn examples(&self) -> Vec<Example> {
        self.examples
            .iter()
            .map(CustomExample::to_example)
            .collect()
    }

    fn search_terms(&self) -> Vec<&str> {
        self.signature
            .search_terms
            .iter()
            .map(String::as_str)
            .collect()
    }

    fn deprecation_info(&self) -> Vec<DeprecationEntry> {
        self.attributes
            .iter()
            .filter_map(|(key, value)| {
                (key == "deprecated")
                    .then_some(value.clone())
                    .map(DeprecationEntry::from_value)
                    .and_then(Result::ok)
            })
            .collect()
    }
}
