//! Export and serialization methods for Params.

use super::super::errors::ParamsError;
use super::Params;
use sha2::{Digest, Sha256};

/// Configuration for exporting Params to HeroScript format.
#[derive(Debug, Clone)]
pub struct ExportArgs {
    /// Keys to sort first in output.
    pub presort: Vec<String>,
    /// Keys to sort last in output.
    pub postsort: Vec<String>,
    /// Use default sorting (id, cid, name first).
    pub sortdefault: bool,
    /// Allow positional args in output.
    pub args_allowed: bool,
    /// Remove args from output.
    pub args_remove: bool,
    /// Remove comments from output.
    pub comments_remove: bool,
    /// Max column size before wrapping.
    pub maxcolsize: usize,
    /// Put everything on one line.
    pub oneline: bool,
    /// Allow multiline string values.
    pub multiline: bool,
    /// Indentation string.
    pub indent: String,
    /// Prefix (e.g., "!!actor.action").
    pub pre: String,
    /// Skip empty values.
    pub skip_empty: bool,
}

impl Default for ExportArgs {
    fn default() -> Self {
        Self {
            presort: Vec::new(),
            postsort: Vec::new(),
            sortdefault: true,
            args_allowed: true,
            args_remove: false,
            comments_remove: false,
            maxcolsize: 80,
            oneline: false,
            multiline: true,
            indent: "    ".to_string(),
            pre: String::new(),
            skip_empty: false,
        }
    }
}

impl Params {
    /// Export to HeroScript format with custom settings.
    pub fn export(&self, args: &ExportArgs) -> String {
        let mut lines: Vec<String> = Vec::new();

        // Add prefix if present
        if !args.pre.is_empty() {
            if args.oneline {
                lines.push(args.pre.clone());
            } else {
                lines.push(args.pre.clone());
            }
        }

        // Collect params in order
        let ordered_params = self.order_params(args);

        // Format each param
        for param in &ordered_params {
            if args.skip_empty && param.value.is_empty() {
                continue;
            }

            let formatted = format_param(&param.key, &param.value, &param.comment, args);

            if args.oneline {
                if lines.is_empty() {
                    lines.push(formatted);
                } else {
                    lines[0].push(' ');
                    lines[0].push_str(&formatted);
                }
            } else {
                lines.push(format!("{}{}", args.indent, formatted));
            }
        }

        // Add args if allowed
        if args.args_allowed && !args.args_remove {
            for arg in &self.args {
                if args.oneline {
                    if lines.is_empty() {
                        lines.push(arg.clone());
                    } else {
                        lines[0].push(' ');
                        lines[0].push_str(arg);
                    }
                } else {
                    lines.push(format!("{}{}", args.indent, arg));
                }
            }
        }

        // Add comments if not removed
        if !args.comments_remove {
            for comment in &self.comments {
                let comment_line = format!("// {}", comment);
                if args.oneline {
                    if lines.is_empty() {
                        lines.push(comment_line);
                    } else {
                        lines[0].push(' ');
                        lines[0].push_str(&comment_line);
                    }
                } else {
                    lines.push(format!("{}{}", args.indent, comment_line));
                }
            }
        }

        lines.join("\n")
    }

    /// Export to default HeroScript format.
    pub fn heroscript(&self) -> String {
        self.export(&ExportArgs::default())
    }

    /// Export to compact one-line format.
    pub fn to_oneline(&self) -> String {
        self.export(&ExportArgs {
            oneline: true,
            indent: String::new(),
            ..Default::default()
        })
    }

    /// Export to JSON.
    pub fn to_json(&self) -> Result<String, ParamsError> {
        serde_json::to_string(self).map_err(|e| ParamsError::ParseError {
            position: 0,
            message: e.to_string(),
        })
    }

    /// Export to pretty JSON.
    pub fn to_json_pretty(&self) -> Result<String, ParamsError> {
        serde_json::to_string_pretty(self).map_err(|e| ParamsError::ParseError {
            position: 0,
            message: e.to_string(),
        })
    }

    /// Import from JSON.
    pub fn from_json(json: &str) -> Result<Self, ParamsError> {
        serde_json::from_str(json).map_err(|e| ParamsError::ParseError {
            position: 0,
            message: e.to_string(),
        })
    }

    /// Get SHA256 hash of params (normalized for consistent comparison).
    pub fn hexhash(&self) -> String {
        let mut hasher = Sha256::new();

        // Hash params in sorted order for consistency
        let mut sorted_params = self.params.clone();
        sorted_params.sort_by(|a, b| a.key.cmp(&b.key));

        for param in &sorted_params {
            hasher.update(param.key.as_bytes());
            hasher.update(b":");
            hasher.update(param.value.as_bytes());
            hasher.update(b";");
        }

        // Hash args in sorted order
        let mut sorted_args = self.args.clone();
        sorted_args.sort();

        for arg in &sorted_args {
            hasher.update(arg.as_bytes());
            hasher.update(b",");
        }

        let result = hasher.finalize();
        hex::encode(result)
    }

    /// Check equality (content-based, order-independent).
    pub fn equals(&self, other: &Params) -> bool {
        self.hexhash() == other.hexhash()
    }

    /// Order params according to export args.
    fn order_params(&self, args: &ExportArgs) -> Vec<super::Param> {
        let mut result = Vec::new();
        let mut remaining: Vec<_> = self.params.iter().cloned().collect();

        // Add presort keys first
        let presort = if args.sortdefault && args.presort.is_empty() {
            vec!["id".to_string(), "cid".to_string(), "name".to_string()]
        } else {
            args.presort.clone()
        };

        for key in &presort {
            if let Some(pos) = remaining
                .iter()
                .position(|p| p.key.eq_ignore_ascii_case(key))
            {
                result.push(remaining.remove(pos));
            }
        }

        // Add remaining (except postsort)
        let postsort_lower: Vec<_> = args.postsort.iter().map(|s| s.to_lowercase()).collect();
        let mut middle: Vec<_> = remaining
            .iter()
            .filter(|p| !postsort_lower.contains(&p.key))
            .cloned()
            .collect();

        // Sort middle alphabetically if sortdefault
        if args.sortdefault {
            middle.sort_by(|a, b| a.key.cmp(&b.key));
        }

        result.extend(middle);

        // Add postsort keys last
        for key in &args.postsort {
            if let Some(param) = remaining.iter().find(|p| p.key.eq_ignore_ascii_case(key)) {
                result.push(param.clone());
            }
        }

        result
    }
}

/// Format a single parameter for output.
fn format_param(key: &str, value: &str, comment: &str, args: &ExportArgs) -> String {
    let needs_quote = value.contains(' ') || value.contains('\n') || value.contains('\'');

    let formatted_value = if needs_quote {
        if args.multiline && value.contains('\n') {
            format!("'{}'", value.replace('\n', "\n"))
        } else {
            format!("'{}'", value.replace('\'', "''"))
        }
    } else {
        value.to_string()
    };

    let param_str = format!("{}:{}", key, formatted_value);

    if !comment.is_empty() && !args.comments_remove {
        format!("{} // {}", param_str, comment)
    } else {
        param_str
    }
}

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

    #[test]
    fn test_export_basic() {
        let params = parse("name:john age:30").unwrap();
        let output = params.heroscript();
        assert!(output.contains("name:john"));
        assert!(output.contains("age:30"));
    }

    #[test]
    fn test_export_oneline() {
        let params = parse("name:john age:30").unwrap();
        let output = params.to_oneline();
        assert!(!output.contains('\n'));
        assert!(output.contains("name:john"));
        assert!(output.contains("age:30"));
    }

    #[test]
    fn test_export_with_prefix() {
        let params = parse("name:john").unwrap();
        let output = params.export(&ExportArgs {
            pre: "!!person.define".to_string(),
            ..Default::default()
        });
        assert!(output.starts_with("!!person.define"));
    }

    #[test]
    fn test_to_json_and_back() {
        let params = parse("name:john age:30 debug").unwrap();
        let json = params.to_json().unwrap();
        let restored = Params::from_json(&json).unwrap();
        assert_eq!(params.get("name").unwrap(), restored.get("name").unwrap());
        assert_eq!(params.get("age").unwrap(), restored.get("age").unwrap());
    }

    #[test]
    fn test_hexhash() {
        let params1 = parse("name:john age:30").unwrap();
        let params2 = parse("age:30 name:john").unwrap(); // different order
        let params3 = parse("name:jane age:30").unwrap(); // different value

        assert_eq!(params1.hexhash(), params2.hexhash()); // order independent
        assert_ne!(params1.hexhash(), params3.hexhash()); // different content
    }

    #[test]
    fn test_equals() {
        let params1 = parse("name:john age:30").unwrap();
        let params2 = parse("age:30 name:john").unwrap();

        assert!(params1.equals(&params2));
    }

    #[test]
    fn test_export_quoted_value() {
        let params = parse("desc:'hello world'").unwrap();
        let output = params.heroscript();
        assert!(output.contains("desc:'hello world'"));
    }

    #[test]
    fn test_presort() {
        let params = parse("zebra:z name:john alpha:a").unwrap();
        let output = params.export(&ExportArgs {
            presort: vec!["name".to_string()],
            sortdefault: false,
            ..Default::default()
        });
        let lines: Vec<_> = output.lines().collect();
        assert!(lines[0].contains("name:"));
    }
}
