//! Setter and modifier methods for Params.

use super::super::errors::ParamsError;
use super::{Param, Params};
use std::collections::HashMap;

impl Params {
    /// Set or update a key-value parameter.
    ///
    /// If the key exists, its value is updated. Otherwise, a new param is added.
    pub fn set(&mut self, key: &str, value: &str) {
        if let Some(param) = self.find_param_mut(key) {
            param.value = value.to_string();
        } else {
            self.params.push(Param::new(key, value));
        }
    }

    /// Set a parameter with an associated comment.
    pub fn set_with_comment(&mut self, key: &str, value: &str, comment: &str) {
        if let Some(param) = self.find_param_mut(key) {
            param.value = value.to_string();
            param.comment = comment.to_string();
        } else {
            self.params.push(Param::with_comment(key, value, comment));
        }
    }

    /// Delete a parameter by key.
    ///
    /// Returns true if the parameter was found and deleted.
    pub fn delete(&mut self, key: &str) -> bool {
        if let Some(index) = self.find_param_index(key) {
            self.params.remove(index);
            true
        } else {
            false
        }
    }

    /// Add a positional argument.
    ///
    /// Does nothing if the argument already exists.
    pub fn set_arg(&mut self, value: &str) {
        if !self.exists_arg(value) {
            self.args.push(value.to_string());
        }
    }

    /// Add a positional argument with a comment.
    ///
    /// The comment is added to the comments list.
    pub fn set_arg_with_comment(&mut self, value: &str, comment: &str) {
        if !self.exists_arg(value) {
            self.args.push(value.to_string());
            if !comment.is_empty() {
                self.comments.push(comment.to_string());
            }
        }
    }

    /// Delete a positional argument.
    ///
    /// Returns true if the argument was found and deleted.
    pub fn delete_arg(&mut self, arg: &str) -> bool {
        if let Some(pos) = self.args.iter().position(|a| a.eq_ignore_ascii_case(arg)) {
            self.args.remove(pos);
            true
        } else {
            false
        }
    }

    /// Merge another Params into this one.
    ///
    /// Values from `other` override existing values with the same key.
    /// Arguments and comments are appended.
    pub fn merge(&mut self, other: &Params) {
        // Merge params (other overrides)
        for param in &other.params {
            self.set(&param.key, &param.value);
            if !param.comment.is_empty() {
                if let Some(p) = self.find_param_mut(&param.key) {
                    p.comment = param.comment.clone();
                }
            }
        }

        // Append args (avoid duplicates)
        for arg in &other.args {
            self.set_arg(arg);
        }

        // Append comments
        for comment in &other.comments {
            if !self.comments.contains(comment) {
                self.comments.push(comment.clone());
            }
        }
    }

    /// Parse and add params from text.
    ///
    /// This parses the text and merges the resulting params.
    pub fn add(&mut self, text: &str) -> Result<(), ParamsError> {
        let other = super::parse(text)?;
        self.merge(&other);
        Ok(())
    }

    /// Convert params to a HashMap.
    ///
    /// Only includes key-value params, not args or comments.
    pub fn get_map(&self) -> HashMap<String, String> {
        self.params
            .iter()
            .map(|p| (p.key.clone(), p.value.clone()))
            .collect()
    }

    /// Clear all params, args, and comments.
    pub fn clear(&mut self) {
        self.params.clear();
        self.args.clear();
        self.comments.clear();
    }
}

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

    #[test]
    fn test_set_new_param() {
        let mut params = Params::new();
        params.set("name", "john");
        assert_eq!(params.get("name").unwrap(), "john");
    }

    #[test]
    fn test_set_update_param() {
        let mut params = parse("name:john").unwrap();
        params.set("name", "jane");
        assert_eq!(params.get("name").unwrap(), "jane");
    }

    #[test]
    fn test_set_with_comment() {
        let mut params = Params::new();
        params.set_with_comment("name", "john", "this is the name");
        assert_eq!(params.get("name").unwrap(), "john");
        assert_eq!(params.params[0].comment, "this is the name");
    }

    #[test]
    fn test_delete_param() {
        let mut params = parse("name:john age:30").unwrap();
        assert!(params.delete("name"));
        assert!(!params.exists("name"));
        assert!(params.exists("age"));
        assert!(!params.delete("missing"));
    }

    #[test]
    fn test_set_arg() {
        let mut params = Params::new();
        params.set_arg("debug");
        assert!(params.exists_arg("debug"));

        // Adding same arg again should not duplicate
        params.set_arg("debug");
        assert_eq!(params.args.len(), 1);
    }

    #[test]
    fn test_delete_arg() {
        let mut params = parse("debug verbose").unwrap();
        assert!(params.delete_arg("debug"));
        assert!(!params.exists_arg("debug"));
        assert!(params.exists_arg("verbose"));
    }

    #[test]
    fn test_merge() {
        let mut params1 = parse("name:john age:30").unwrap();
        let params2 = parse("name:jane city:nyc").unwrap();

        params1.merge(&params2);

        assert_eq!(params1.get("name").unwrap(), "jane"); // overridden
        assert_eq!(params1.get("age").unwrap(), "30"); // kept
        assert_eq!(params1.get("city").unwrap(), "nyc"); // added
    }

    #[test]
    fn test_add() {
        let mut params = parse("name:john").unwrap();
        params.add("age:30 debug").unwrap();

        assert_eq!(params.get("name").unwrap(), "john");
        assert_eq!(params.get_int("age").unwrap(), 30);
        assert!(params.exists_arg("debug"));
    }

    #[test]
    fn test_get_map() {
        let params = parse("name:john age:30 debug").unwrap();
        let map = params.get_map();

        assert_eq!(map.get("name").unwrap(), "john");
        assert_eq!(map.get("age").unwrap(), "30");
        assert!(!map.contains_key("debug")); // args not in map
    }

    #[test]
    fn test_clear() {
        let mut params = parse("name:john debug // comment").unwrap();
        params.clear();
        assert!(params.is_empty());
    }
}
