// Copyright (c) 2026, Salesforce, Inc.,
// All rights reserved.
// For full license text, see the LICENSE.txt file
use peg::parser;
use std::fmt::Display;

#[derive(Clone, PartialEq, Debug)]
pub struct Message {
    pub message: String,
    pub kind: MessageKind,
    pub location: Location,
}

impl Display for Message {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let str = format!(
            "{} at {}:{}",
            self.message, self.location.start, self.location.end
        );
        write!(f, "{str}")
    }
}

impl AstNode {
    pub fn location(&self) -> Location {
        match self {
            AstNode::Str { location, .. } => location.to_owned(),
            AstNode::Boolean { location, .. } => location.to_owned(),
            AstNode::Number { location, .. } => location.to_owned(),
            AstNode::Null { location, .. } => location.to_owned(),
            AstNode::DateTime { location, .. } => location.to_owned(),
            AstNode::LocalDateTime { location, .. } => location.to_owned(),
            AstNode::Period { location, .. } => location.to_owned(),
            AstNode::Time { location, .. } => location.to_owned(),
            AstNode::LocalTime { location, .. } => location.to_owned(),
            AstNode::Date { location, .. } => location.to_owned(),
            AstNode::NameIdentifier { location, .. } => location.to_owned(),
            AstNode::VariableReference { location, .. } => location.to_owned(),
            AstNode::Array { location, .. } => location.to_owned(),
            AstNode::Comparison { location, .. } => location.to_owned(),
            AstNode::Logical { location, .. } => location.to_owned(),
            AstNode::Not { location, .. } => location.to_owned(),
            AstNode::ValueSelector { location, .. } => location.to_owned(),
            AstNode::InfixFunctionCall { location, .. } => location.to_owned(),
            AstNode::FunctionCall { location, .. } => location.to_owned(),
            AstNode::IfElse { location, .. } => location.to_owned(),
            AstNode::Default { location, .. } => location.to_owned(),
            AstNode::Document { location, .. } => location.to_owned(),
            AstNode::VersionDirective { location, .. } => location.to_owned(),
            AstNode::InputDirective { location, .. } => location.to_owned(),
            AstNode::OutputDirective { location, .. } => location.to_owned(),
            AstNode::Object { location, .. } => location.to_owned(),
            AstNode::KeyValuePair { location, .. } => location.to_owned(),
            AstNode::Option { location, .. } => location.to_owned(),
            AstNode::ContentType { location, .. } => location.to_owned(),
            AstNode::ImportDirective { location, .. } => location.to_owned(),
            AstNode::ConditionalKeyValuePair { location, .. } => location.to_owned(),
            AstNode::StrInterpolation { location, .. } => location.to_owned(),
            AstNode::NullSafeSelector { location, .. } => location.to_owned(),
            AstNode::AttributeSelector { location, .. } => location.to_owned(),
            AstNode::MultiValueSelector { location, .. } => location.to_owned(),
            AstNode::Uri { location, .. } => location.to_owned(),
            AstNode::NamespaceDirective { location, .. } => location.to_owned(),
            AstNode::Name { location, .. } => location.to_owned(),
            AstNode::MetadataSelector { location, .. } => location.to_owned(),
        }
    }

    pub fn name(&self) -> &'static str {
        match self {
            AstNode::Str { .. } => "Str",
            AstNode::Boolean { .. } => "Boolean",
            AstNode::Number { .. } => "Number",
            AstNode::Null { .. } => "Null",
            AstNode::DateTime { .. } => "DateTime",
            AstNode::LocalDateTime { .. } => "LocalDateTime",
            AstNode::Period { .. } => "Period",
            AstNode::Time { .. } => "Time",
            AstNode::LocalTime { .. } => "LocalTime",
            AstNode::Date { .. } => "Date",
            AstNode::NameIdentifier { .. } => "NameIdentifier",
            AstNode::VariableReference { .. } => "VariableReference",
            AstNode::Array { .. } => "Array",
            AstNode::Comparison { .. } => "Comparison",
            AstNode::Logical { .. } => "Logical",
            AstNode::Not { .. } => "Not",
            AstNode::ValueSelector { .. } => "ValueSelector",
            AstNode::NullSafeSelector { .. } => "NullSafeSelector",
            AstNode::InfixFunctionCall { .. } => "InfixFunctionCall",
            AstNode::FunctionCall { .. } => "FunctionCall",
            AstNode::IfElse { .. } => "IfElse",
            AstNode::Default { .. } => "Default",
            AstNode::Document { .. } => "Document",
            AstNode::VersionDirective { .. } => "VersionDirective",
            AstNode::InputDirective { .. } => "InputDirective",
            AstNode::OutputDirective { .. } => "OutputDirective",
            AstNode::Object { .. } => "Object",
            AstNode::KeyValuePair { .. } => "KeyValuePair",
            AstNode::Option { .. } => "Option",
            AstNode::ContentType { .. } => "ContentType",
            AstNode::ImportDirective { .. } => "ImportDirective",
            AstNode::ConditionalKeyValuePair { .. } => "ConditionalKeyValuePair",
            AstNode::StrInterpolation { .. } => "StrInterpolation",
            AstNode::AttributeSelector { .. } => "AttributeSelector",
            AstNode::MultiValueSelector { .. } => "MultiValueSelector",
            AstNode::NamespaceDirective { .. } => "NamespaceDirective",
            AstNode::Uri { .. } => "Uri",
            AstNode::Name { .. } => "Name",
            AstNode::MetadataSelector { .. } => "MetadataSelector",
        }
    }

    pub fn get_value(&self) -> Option<String> {
        match self {
            AstNode::Str { value, .. } => Some(value.to_owned()),
            AstNode::Uri { value, .. } => Some(value.to_owned()),
            AstNode::Boolean { value, .. } => Some(value.to_string()),
            AstNode::Number { value, .. } => Some(value.to_string()),
            AstNode::Null { .. } => Some("null".to_string()),
            AstNode::DateTime { value, .. } => Some(value.to_string()),
            AstNode::LocalDateTime { value, .. } => Some(value.to_owned()),
            AstNode::Period { value, .. } => Some(value.to_owned()),
            AstNode::Time { value, .. } => Some(value.to_owned()),
            AstNode::LocalTime { value, .. } => Some(value.to_owned()),
            AstNode::Date { value, .. } => Some(value.to_owned()),
            AstNode::NameIdentifier { name, .. } => Some(name.join("::").to_owned()),
            _ => None,
        }
    }

    pub fn children(&self) -> Vec<AstNode> {
        match self {
            AstNode::VariableReference { reference, .. } => {
                vec![reference.as_ref().to_owned()]
            }
            AstNode::Array { items, .. } => items.to_vec(),
            AstNode::StrInterpolation { segments, .. } => segments.to_vec(),
            AstNode::Comparison { lhs, rhs, .. } => {
                vec![lhs.as_ref().to_owned(), rhs.as_ref().to_owned()]
            }
            AstNode::Logical { lhs, rhs, .. } => {
                vec![lhs.as_ref().to_owned(), rhs.as_ref().to_owned()]
            }
            AstNode::Not { rhs, .. } => {
                vec![rhs.as_ref().to_owned()]
            }
            AstNode::ValueSelector { lhs, rhs, .. } => {
                vec![lhs.as_ref().to_owned(), rhs.as_ref().to_owned()]
            }
            AstNode::MultiValueSelector { lhs, rhs, .. } => {
                vec![lhs.as_ref().to_owned(), rhs.as_ref().to_owned()]
            }
            AstNode::MetadataSelector { lhs, rhs, .. } => {
                let mut children = vec![lhs.as_ref().to_owned()];
                let x = rhs.as_ref().to_owned();
                if x.is_some() {
                    if let Some(ast_node) = x {
                        children.push(ast_node.to_owned());
                    }
                }
                children
            }
            AstNode::AttributeSelector { lhs, rhs, .. } => {
                vec![lhs.as_ref().to_owned(), rhs.as_ref().to_owned()]
            }
            AstNode::InfixFunctionCall { lhs, rhs, func, .. } => {
                vec![
                    lhs.as_ref().to_owned(),
                    rhs.as_ref().to_owned(),
                    func.as_ref().to_owned(),
                ]
            }
            AstNode::FunctionCall { lhs, args, .. } => {
                let mut vec1 = vec![lhs.as_ref().to_owned()];
                vec1.append(&mut args.to_vec());
                vec1
            }
            AstNode::IfElse {
                if_branch,
                condition,
                else_branch,
                ..
            } => {
                vec![
                    if_branch.as_ref().to_owned(),
                    condition.as_ref().to_owned(),
                    else_branch.as_ref().to_owned(),
                ]
            }
            AstNode::Default { lhs, rhs, .. } => {
                vec![lhs.as_ref().to_owned(), rhs.as_ref().to_owned()]
            }
            AstNode::Document { body, header, .. } => {
                let mut result = vec![body.as_ref().to_owned()];
                match header {
                    None => {}
                    Some(some_header) => {
                        result.append(&mut some_header.to_vec());
                    }
                }

                result
            }
            AstNode::InputDirective {
                name,
                data_format,
                options,
                ..
            } => {
                let mut result = vec![name.as_ref().to_owned(), data_format.as_ref().to_owned()];
                result.append(&mut options.to_vec());
                result
            }
            AstNode::OutputDirective {
                data_format,
                options,
                ..
            } => {
                let mut result = vec![data_format.as_ref().to_owned()];
                result.append(&mut options.to_vec());
                result
            }
            AstNode::Object { fields, .. } => fields.to_vec(),
            AstNode::KeyValuePair { key, value, .. } => {
                vec![key.as_ref().to_owned(), value.as_ref().to_owned()]
            }
            AstNode::Option { name, value, .. } => {
                vec![name.as_ref().to_owned(), value.as_ref().to_owned()]
            }
            AstNode::ImportDirective { module, .. } => {
                let result = vec![module.name.as_ref().to_owned()];
                result
            }
            AstNode::NullSafeSelector { lhs, .. } => {
                let result = vec![lhs.as_ref().to_owned()];
                result
            }
            AstNode::ConditionalKeyValuePair {
                key,
                value,
                condition,
                ..
            } => {
                vec![
                    key.as_ref().to_owned(),
                    value.as_ref().to_owned(),
                    condition.as_ref().to_owned(),
                ]
            }
            AstNode::NamespaceDirective { prefix, uri, .. } => {
                vec![prefix.as_ref().to_owned(), uri.as_ref().to_owned()]
            }
            AstNode::Name { prefix, value, .. } => {
                let mut result = vec![];
                if prefix.is_some() {
                    result.push(prefix.to_owned().unwrap().to_owned());
                }
                result.push(value.as_ref().to_owned());
                result
            }
            _ => {
                vec![]
            }
        }
    }
}

impl Display for AstNode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        fn to_ast_string(node: &AstNode, indent: usize) -> String {
            let mut result = String::new();
            let indentation_str = "  ".repeat(indent);
            result.push_str(indentation_str.as_str());
            result.push_str("|-");
            result.push_str(node.name());
            result.push(' ');
            result.push_str(node.location().to_string().as_str());
            if node.get_value().is_some() {
                result.push_str(" Value=");
                result.push('\'');
                result.push_str(node.get_value().unwrap().as_str());
                result.push('\'');
            }
            result.push('\n');
            for child in node.children() {
                result.push_str(to_ast_string(&child, indent + 1).as_str())
            }
            result
        }
        write!(f, "{}", to_ast_string(self, 0))
    }
}

#[derive(Clone, PartialEq, Debug)]
pub struct DWParseResult {
    pub ast: AstNode,
    pub messages: Vec<Message>,
}

#[derive(Clone, PartialEq, Debug)]
pub struct DWError {
    pub messages: Vec<Message>,
}

#[derive(Clone, PartialEq, Debug)]
pub struct Location {
    pub start: usize,
    pub end: usize,
}

impl Display for Location {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // write!(f, "{}", format!("Start='{}' End='{}'", self.start, self.end))
        write!(f, "Start='{}' End='{}'", self.start, self.end)
    }
}

#[derive(Clone, PartialEq, Debug)]
pub enum AstNode {
    Str {
        value: String,
        quote: Option<char>,
        location: Location,
    },
    Boolean {
        value: bool,
        location: Location,
    },
    Number {
        value: String,
        location: Location,
    },
    Null {
        location: Location,
    },
    DateTime {
        value: String,
        location: Location,
    },
    LocalDateTime {
        value: String,
        location: Location,
    },
    Period {
        value: String,
        location: Location,
    },
    Time {
        value: String,
        location: Location,
    },
    LocalTime {
        value: String,
        location: Location,
    },
    Date {
        value: String,
        location: Location,
    },
    NameIdentifier {
        name: Vec<String>,
        location: Location,
    },
    VariableReference {
        reference: Box<AstNode>,
        location: Location,
    },
    Array {
        items: Vec<AstNode>,
        location: Location,
    },
    Comparison {
        lhs: Box<AstNode>,
        rhs: Box<AstNode>,
        comp: ComparisonType,
        location: Location,
    },
    Logical {
        lhs: Box<AstNode>,
        rhs: Box<AstNode>,
        comp: LogicalType,
        location: Location,
    },
    Not {
        rhs: Box<AstNode>,
        location: Location,
    },
    Name {
        prefix: Box<Option<AstNode>>,
        value: Box<AstNode>,
        location: Location,
    },
    ValueSelector {
        lhs: Box<AstNode>,
        rhs: Box<AstNode>,
        location: Location,
    },
    MetadataSelector {
        lhs: Box<AstNode>,
        rhs: Box<Option<AstNode>>,
        location: Location,
    },
    AttributeSelector {
        lhs: Box<AstNode>,
        rhs: Box<AstNode>,
        location: Location,
    },
    MultiValueSelector {
        lhs: Box<AstNode>,
        rhs: Box<AstNode>,
        location: Location,
    },
    InfixFunctionCall {
        lhs: Box<AstNode>,
        func: Box<AstNode>,
        rhs: Box<AstNode>,
        location: Location,
    },
    FunctionCall {
        lhs: Box<AstNode>,
        args: Vec<AstNode>,
        location: Location,
    },
    IfElse {
        condition: Box<AstNode>,
        if_branch: Box<AstNode>,
        else_branch: Box<AstNode>,
        location: Location,
    },
    Default {
        lhs: Box<AstNode>,
        rhs: Box<AstNode>,
        location: Location,
    },
    Document {
        header: Option<Vec<AstNode>>,
        body: Box<AstNode>,
        location: Location,
    },
    VersionDirective {
        version: String,
        location: Location,
    },
    InputDirective {
        name: Box<AstNode>,
        data_format: Box<AstNode>,
        options: Vec<AstNode>,
        location: Location,
    },
    OutputDirective {
        data_format: Box<AstNode>,
        options: Vec<AstNode>,
        location: Location,
    },
    Object {
        fields: Vec<AstNode>,
        location: Location,
    },
    KeyValuePair {
        key: Box<AstNode>,
        value: Box<AstNode>,
        location: Location,
    },
    ConditionalKeyValuePair {
        key: Box<AstNode>,
        value: Box<AstNode>,
        condition: Box<AstNode>,
        location: Location,
    },
    StrInterpolation {
        segments: Vec<AstNode>,
        quote: Option<char>,
        location: Location,
    },
    Option {
        name: Box<AstNode>,
        value: Box<AstNode>,
        location: Location,
    },
    ContentType {
        value: String,
        location: Location,
    },
    ImportDirective {
        module: Box<ImportedElement>,
        elements: Vec<ImportedElement>,
        location: Location,
    },
    NamespaceDirective {
        prefix: Box<AstNode>,
        uri: Box<AstNode>,
        location: Location,
    },
    Uri {
        value: String,
        location: Location,
    },
    NullSafeSelector {
        lhs: Box<AstNode>,
        location: Location,
    },
}

#[derive(Clone, PartialEq, Debug)]
pub struct ImportedElement {
    pub name: Box<AstNode>,
    pub alias: Option<AstNode>,
    pub location: Location,
}

#[derive(Clone, PartialEq, Debug)]
pub enum ComparisonType {
    Greater,
    Less,
    Equal,
    NotEqual,
    Similar,
    GreaterEqual,
    LessEqual,
}

#[derive(Clone, PartialEq, Debug)]
pub enum LogicalType {
    And,
    Or,
}

pub fn unescape_string(s: String, quotedby: &Option<char>) -> String {
    if quotedby.is_none() {
        s
    } else {
        let mut chars = s.chars();
        let mut result = String::new();
        while let Some(c) = chars.next() {
            if c == '\\' {
                match chars.next().unwrap() {
                    '\\' => result.push('\\'),
                    '/' => result.push('/'),
                    'b' => result.push('\x08'),
                    'f' => result.push('\x0C'),
                    'n' => result.push('\n'),
                    'r' => result.push('\r'),
                    't' => result.push('\t'),
                    '$' => result.push('$'),
                    'u' => {
                        let mut codepoint = 0u32;
                        for _ in 0..4 {
                            let c = chars.next().unwrap();
                            codepoint *= 16;
                            codepoint += match c {
                                '0'..='9' => c as u32 - '0' as u32,
                                'a'..='f' => c as u32 - 'a' as u32 + 10,
                                'A'..='F' => c as u32 - 'A' as u32 + 10,
                                _ => panic!("Invalid unicode escape"),
                            };
                        }
                        result.push(char::from_u32(codepoint).unwrap());
                    }
                    a => {
                        // Same behaviour as org.mule.weave.v2.utils.StringEscapeHelper#unescapeString from the DataWeave repository
                        if a == quotedby.unwrap() {
                            result.push(a)
                        } else {
                            result.push(c);
                            result.push(a)
                        }
                    }
                }
            } else {
                result.push(c);
            }
        }
        result
    }
}

static KEYWORDS: &[&str; 28] = &[
    "if", "else", "unless", "using", "---", "as", "is", "null", "true", "false", "default", "case",
    "fun", "input", "output", "ns", "type", "import", "var", "and", "or",
    //    Add once we have syntax directive
    //    "annotation",
    //    "from",
    // added to avoid future conflicts and breaking changes, we can remove them later.
    // but we wanna ensure that we are no breaking something in the near future
    "throw", "do", "for", "yield", "enum", "private", "async",
];

parser! {
/// Doc comment
    pub grammar dw_parser() for str {
        /// Top level parser rule
        /// This doc comment has multiple lines to test support for that as well

        rule valid_start_name_ch() = quiet!{[ 'a'..='z' | 'A'..='Z']}

        rule valid_name_ch() = quiet!{['_' | 'a'..='z' | 'A'..='Z' | '0'..='9']}


        //Date Rules
        rule digit() = quiet!{['0' ..= '9']}
        rule digit01() = quiet!{['0' ..= '1']}
        rule digit02() = quiet!{['0' ..= '2']}
        rule digit04() = quiet!{['0' ..= '4']}
        rule digit12() = quiet!{['1' ..= '2']}
        rule digit13() = quiet!{['1' ..= '3']}
        rule digit15() = quiet!{['1' ..= '5']}
        rule digit09() = quiet!{['0' ..= '9']}
        rule digit19() = quiet!{['1' ..= '9']}
        rule digit14() = quiet!{['1' ..= '4']}
        rule digit03() = quiet!{['0' ..= '3']}
        rule digit17() = quiet!{['1' ..= '7']}

        rule any_date_literal_expr() -> AstNode =
             e:any_date_expr()  {e}

        rule any_date_expr() -> AstNode  =
            period_literal_expr()
            / date_time_literal_expr()
            / time_literal_expr()

        rule date_time_literal_expr() -> AstNode =
            start:position!() "|" d:$(date()) t:$(time())? tz:$(time_zone())? "|" end:position!() {?
                match (tz, t) {
                    (None, None) =>
                        Ok(AstNode::Date {
                            value:d.to_string(),
                            location: Location {
                                start,
                                end,
                            },
                        }),
                    (None, Some(t)) => {
                        let mut result = String::new();
                        result.push_str(d);
                        result.push_str(t);
                        Ok(AstNode::LocalDateTime {
                            value: result,
                            location: Location {
                                start,
                                end,
                            },
                        })
                    }
                    (Some(tz), Some(t)) => {
                        let mut result = String::new();
                        result.push_str(d);
                        result.push_str(t);
                        result.push_str(tz);
                        Ok(AstNode::DateTime {
                            value: result,
                            location: Location {
                                start,
                                end,
                            },
                        })
                    }
                    _ => Err("Invalid datetime expression. If timezone is present it needs a time.")
                }
            }

        rule period_literal_expr() -> AstNode =
            start:position!() "|" d:$(duration()) "|" end:position!()  {
                AstNode::Period {
                    value: d.to_string(),
                    location: Location {
                        start,
                        end,
                    }
                }
            }

        rule time_literal_expr() -> AstNode =
            start:position!() "|" te:$(local_time()) tz:$(time_zone())? "|" end:position!() {
                if tz.is_none() {
                    AstNode::LocalTime {
                        value:te.to_string(),
                        location: Location {
                            start,
                            end,
                        },
                    }
                } else{
                    let mut result = String::new();
                    result.push_str(te);
                    result.push_str(tz.unwrap());
                    AstNode::Time {
                        value: result,
                        location: Location {
                            start,
                            end,
                        },
                    }
                }
            }

        rule time_zone() = ['Z'] / (['+'|'-'] time_zone_offset())

        rule duration() = ['P'] (timeDuration() / dateDuration())

        rule dateDuration() = (years())? (months())? (days())?

        rule years() = (['-'])? (digit())+ ['Y']

        rule months() = (['-'])? (digit())+ ['M']

        rule days() = (['-']?) digit()+ ['D']

        rule hours() = (['-'])? digit()+ ['H']

        rule minutes() = (['-'])? digit()+ ['M']

        rule timeDuration() = (days())? ['T'] (hours())? (minutes())? (seconds())?

        rule seconds() = (['-'])? digit()+ (['.'] digit()+)? ['S']

        rule time_zone_offset() = hh() [':'] mm()

        rule time() = ['T'] local_time()

        rule local_time() = hh() [':'] mm()  ([':'] ss())?

        rule date() = year() ['-']? (ordinal_date() / week_date() / calendar_date())

        rule calendar_date() = mm() (['-']  day())?

        rule week_date() = www() (['-']  dayOfWeek())?

        rule ordinal_date() = ddd()

        rule year() =
            ("-+"?  digit()  digit()  digit()  digit()  digit()) / (digit()  digit()  digit()  digit())

        rule ddd() =
            ("00"  digit19()) / (['0']  digit19()  digit09()) / (digit13()  digit09()  digit09()) / (digit09()  digit09()  digit09())

        rule hh() =
            zt9() / (['1'] digit09()) / (['2']  digit04()) / expected!("Hours from 0 to 24")

        rule mm() =
            zt9() / (digit15() digit09()) / (digit09()  digit09())

        rule ss() =
            (zt9() / (digit15()  digit09() ) / "60") (['.'] digit09()+)?

        rule zt9() =
            "00" / ot9()

        rule ot9() =
            ['0']  digit19()

        rule day() =
          ot9() / (digit12()  digit09()) / (['3'] digit01()) / (digit09() digit09())

        rule www() =
            ['W']  ((ot9() / (digit14()  digit09()) / (['5']  digit03())) / (digit09()  digit09()))

        rule dayOfWeek() =
            digit17()
            / digit09()


        rule number_str() =
            quiet!{['-']?['0'..='9']+ ("." ['0'..='9']+)?}
            / expected!("Number")

        rule ws() =
            quiet!{ [' ' | '\t' | '\n' | '\r']* comments()*}

        rule fws() =
            quiet!{ [' ' | '\t' | '\n' | '\r']+ comments()*}

        rule comments() =
            "//" (!['\n'][_])* ([' ' | '\t' | '\n' | '\r']*)?
           / "/*" (!"*/"[_])* "*/" ([' ' | '\t' | '\n' | '\r']*)?

        rule name_identifier_str()  =
           quiet!{ valid_start_name_ch()valid_name_ch()* }
           / "++"
           / "--"


        rule valid_name_identifier_str() -> String =
            input:$(name_identifier_str()) {? if(KEYWORDS.contains(&input)) {Err("Identifier is a reserved word.")} else {Ok(String::from(input))} }
            / expected!("Identifier")

        rule simple_name_identifier_expr() -> AstNode =
            start:position!() s:valid_name_identifier_str()  end:position!()  {
                AstNode::NameIdentifier{
                    name: vec![s],
                    location: Location {
                        start,
                        end,
                    }
                }
            }

        rule name_identifier_expr() -> AstNode =
           quiet!{ start:position!() s:valid_name_identifier_str() ++ ("::") end:position!()  {
                    AstNode::NameIdentifier{
                        name: s,
                        location: Location {
                            start,
                            end,
                        },
                    }
                }
            }
          / expected!("Identifier")

        ///Values
        rule variable_expr() -> AstNode =
            start:position!() ident:name_identifier_expr()  end:position!() {
                AstNode::VariableReference {
                    reference: Box::new(ident),
                    location: Location {
                        start,
                        end,
                    }
                }
            }

        rule double_quoted_str_expr() -> AstNode =
            start:position!() "\"" segs:(double_quote_string_segment() / interpolation_segment() / interpolation_variable_ref_expr())* "\"" end:position!() {
                   if segs.is_empty() {
                        AstNode::Str {
                            value: "".to_string(),
                            quote: Some('"'),
                            location: Location {
                                start,
                                end,
                            }
                        }
                   } else if segs.len() == 1 {
                     match &segs[0] {
                        AstNode::Str {value, location, quote} => {
                            AstNode::Str {
                                value: value.to_string(),
                                quote: Some('\"'),
                                location: Location {
                                    start,
                                    end,
                                }
                            }
                        }
                         _ => {
                            AstNode::StrInterpolation {
                                segments: segs,
                                quote: Some('\"'),
                                location: Location {
                                    start,
                                    end,
                                }
                            }
                        }
                     }
                   } else {
                    AstNode::StrInterpolation {
                        segments: segs,
                        quote: Some('\"'),
                        location: Location {
                            start,
                            end,
                        }
                    }
                }
            }

        rule dollar_expr() -> AstNode =
          start:position!() name:$(['$']+) end:position!() {
             AstNode::VariableReference {
                reference: Box::new(
                    AstNode::NameIdentifier {
                        name: vec![name.to_string()],
                        location: Location {
                            start,
                            end,
                        }
                    }
                ),
                location: Location {
                    start,
                    end,
                }
            }
          }

        rule dollar_named_variable_expr() -> AstNode =
            "$" vr:variable_expr() {vr}

        rule interpolation_variable_ref_expr() -> AstNode =
            dollar_named_variable_expr() / dollar_expr()

        rule interpolation_segment() -> AstNode = "$(" ws() e:expr() ws() ")" {e}

        rule single_quote_string_segment() -> AstNode =
           start:position!() s:$((!['\'' | '$' | '\\'][_] / "\\" [_] / "\\'" / "\\\\" / "\\$" / "\\b" / "\\f" / "\\n" / "\\r" / "\\t")+) end:position!() {
                    AstNode::Str {
                        value: s.to_string(),
                        quote: Some('\''),
                        location: Location {
                            start,
                            end,
                        }
                    }
            }

         rule double_quote_string_segment() -> AstNode =
           start:position!() s:$((!['"' | '$' | '\\'][_] / "\\" [_] / "\\'" / "\\\\" / "\\$" / "\\b" / "\\f" / "\\n" / "\\r" / "\\t")+) end:position!() {
                    AstNode::Str {
                        value: s.to_string(),
                        quote: Some('\"'),
                        location: Location {
                            start,
                            end,
                        }
                    }
            }

        rule single_quoted_str_expr() -> AstNode =
            start:position!() "'" segs:(single_quote_string_segment() / interpolation_segment() / interpolation_variable_ref_expr())* "'" end:position!() {
                   if segs.is_empty() {
                        AstNode::Str {
                            value: "".to_string(),
                            quote: Some('\''),
                            location: Location {
                                start,
                                end,
                            }
                        }
                   } else if segs.len() == 1 {
                     match &segs[0] {
                        AstNode::Str {value, location, quote} => {
                            AstNode::Str {
                                value: value.to_string(),
                                quote: Some('\''),
                                location: Location {
                                    start,
                                    end,
                                }
                            }
                        }
                         _ => {
                            AstNode::StrInterpolation {
                                segments: segs,
                                quote: Some('\''),
                                location: Location {
                                    start,
                                    end,
                                }
                            }
                        }
                     }
                   } else {
                    AstNode::StrInterpolation {
                        segments: segs,
                        quote: Some('\''),
                        location: Location {
                            start,
                            end,
                        }
                    }
                }
            }

        rule string_expr() -> AstNode =
            double_quoted_str_expr()
            / single_quoted_str_expr()
            / expected!("String")

        rule number_expr() -> AstNode =
            start:position!() n:$(number_str()) end:position!() {
                AstNode::Number { value: n.to_string(), location: Location {start, end} }
            }

        rule null_expr() -> AstNode =
            start:position!() "null" end:position!() {
                AstNode::Null {
                    location: Location {
                        start,
                        end,
                    }
                }
            }

        rule boolean_expr() -> AstNode =
            start:position!() "true" end:position!() {
                AstNode::Boolean {
                    value: true,
                    location: Location {
                        start,
                        end,
                    },
                }
            }
            / start:position!() "false" end:position!() {
                AstNode::Boolean {
                    value: false,
                    location: Location {
                        start,
                        end,
                    },
                }
            }
            / expected!("Boolean")

        rule array_expr() -> AstNode =
            start:position!() "[" ws() v:value() ** ("," ws())   "]" end:position!() {
                AstNode::Array {
                    items: v,
                    location: Location {
                        start,
                        end,
                    }
                }
            }

        rule key_value_pair_expr() -> AstNode =
            start:position!() key:(simple_name_identifier_expr() / string_expr()) ws() ":" ws() v:expr() end:position!()  {
                AstNode::KeyValuePair {
                    key: Box::new(key),
                    value: Box::new(v),
                    location: Location {
                        start,
                        end,
                    }
                }
            }

        rule conditional_key_value_pair_expr() -> AstNode =
            start:position!() "(" ws() key:(simple_name_identifier_expr() / string_expr() / enclosed_expr()) ws() ":" ws() v:expr() ws() ")" ws() "if" ws() condition:expr() end:position!()  {
                AstNode::ConditionalKeyValuePair {
                    key: Box::new(key),
                    value: Box::new(v),
                    condition: Box::new(condition),
                    location: Location {
                        start,
                        end,
                    }
                }
            }

        rule object_expr() -> AstNode =
            start:position!() "{" ws() kvp:(conditional_key_value_pair_expr() / key_value_pair_expr()) ** (ws() "," ws())  (",")? ws() "}"  end:position!() {
                AstNode::Object {
                    fields: kvp,
                    location: Location {
                        start,
                        end,
                    }
                }
            }

        rule enclosed_expr() -> AstNode =
            quiet!{"(" e:expr() ")" {e}}

        rule literal_expr() -> AstNode =
            quiet! {string_expr() / number_expr() / null_expr() / boolean_expr() / any_date_literal_expr()}
            / expected!("Literal Expression")


        rule value() -> AstNode =
            ws() v:(literal_expr() / variable_expr() / object_expr() / array_expr() / enclosed_expr()) ws() {v}
       ///Values

        rule if_else_expr() -> AstNode =
            start:position!() "if" ws() "(" cond:expr() ")" if_e:expr() "else" else_e:expr() end:position!() {
                AstNode::IfElse {
                    condition: Box::new(cond),
                    if_branch: Box::new(if_e),
                    else_branch:Box::new(else_e),
                    location: Location {
                        start,
                        end,
                    },
                }
            }

        rule not_exclamation_expr() -> AstNode =
            start:position!() "!" v:unary_expr() end:position!() {
                AstNode::Not {
                    rhs: Box::new(v),
                    location: Location {
                        start,
                        end,
                    },
                }
            }

        rule not_word_expr() -> AstNode =
            quiet!{
                start:position!() "not" v:boolean_or_expr() end:position!()  {
                    AstNode::Not {
                        rhs:Box::new(v),
                        location: Location {
                            start,
                            end,
                        }
                    }
                }
            }

        rule name_string_expr() -> AstNode =
            start:position!() s:valid_name_identifier_str() end:position!() {
                AstNode::Str{
                    value: s,
                    quote: None,
                    location: Location {
                        start,
                        end,
                    }
                }
            }

        rule prefix_expression() -> AstNode =
            prefix:name_identifier_expr() "#" {prefix}

        rule name_expr() -> AstNode =
            start:position!()
                prefix:prefix_expression()?
                name:(name_string_expr()/ string_expr())
                end:position!() {
                AstNode::Name {
                    prefix: Box::new(prefix),
                    value: Box::new(name),
                    location: Location {
                            start,
                            end,
                        }
                }
            }

        #[cache_left_rec]
        rule basic_expr() -> AstNode =
            start:position!() v:basic_expr() "(" args:(expr() ** ",") ")" end:position!() {
                AstNode::FunctionCall {
                    lhs: Box::new(v),
                    args,
                    location: Location {
                        start,
                        end,
                    }
                }
            }
          / start:position!()  v:basic_expr() ws() "." name:(name_expr()) end:position!() {
            AstNode::ValueSelector {
                lhs: Box::new(v),
                rhs: Box::new(name),
                location: Location {
                    start,
                    end,
                },
            }
          }
        / start:position!()  v:basic_expr() ws() ".@" name:(name_expr()) end:position!() {
            AstNode::AttributeSelector {
                lhs: Box::new(v),
                rhs: Box::new(name),
                location: Location {
                    start,
                    end,
                },
            }
          }
        / start:position!()  v:basic_expr() ws() ".*" name:(name_expr()) end:position!() {
            AstNode::MultiValueSelector {
                lhs: Box::new(v),
                rhs: Box::new(name),
                location: Location {
                    start,
                    end,
                },
            }
          }
        / start:position!()  v:basic_expr() ws() ".^" name:(name_expr()?) end:position!() {
            AstNode::MetadataSelector {
                lhs: Box::new(v),
                rhs: Box::new(name),
                location: Location {
                    start,
                    end,
                },
            }
          }
          / start:position!()  v:basic_expr() ws() "[" name:expr()  "]" end:position!() {
            AstNode::ValueSelector {
                lhs:Box::new(v),
                rhs: Box::new(name),
                location: Location {
                    start,
                    end,
                }
            }
          }
          / value()

        rule unary_expr() -> AstNode =
            not_exclamation_expr()
           / not_word_expr()
           / if_else_expr()
           / basic_expr()

        // No support for number manipulation
        rule additive_expr() -> AstNode = unary_expr()


        #[cache_left_rec]
        rule relational_expr() -> AstNode =
           start:position!() l:additive_expr() ws() "<=" ws() r:relational_expr()  end:position!() {AstNode::Comparison {lhs: Box::new(l), rhs: Box::new(r), comp: ComparisonType::LessEqual, location: Location {start, end}}}
            / start:position!() l:additive_expr() ws() ">=" ws() r:relational_expr() end:position!() {AstNode::Comparison {lhs: Box::new(l), rhs: Box::new(r), comp: ComparisonType::GreaterEqual, location: Location {start, end}}}
            / start:position!() l:additive_expr() ws() ">" ws() r:relational_expr() end:position!() {AstNode::Comparison {lhs: Box::new(l), rhs: Box::new(r), comp: ComparisonType::Greater, location: Location {start, end}}}
            / start:position!() l:additive_expr() ws() "<" ws() r:relational_expr() end:position!() {AstNode::Comparison {lhs: Box::new(l), rhs: Box::new(r), comp: ComparisonType::Less, location: Location {start, end}}}
            / additive_expr()


        ///Equality
        #[cache_left_rec]
        rule equality_expr() -> AstNode =
            start:position!() l:equality_expr() ws() "==" ws() r:relational_expr() end:position!() {AstNode::Comparison{lhs: Box::new(l), rhs: Box::new(r), comp: ComparisonType::Equal, location: Location {start, end}}}
            / start:position!() l:equality_expr() ws() "!=" ws() r:relational_expr() end:position!() {AstNode::Comparison{lhs: Box::new(l), rhs: Box::new(r), comp: ComparisonType::NotEqual, location: Location {start, end}}}
            / start:position!() l:equality_expr() ws() "=" ws() r:relational_expr() end:position!() {AstNode::Comparison{lhs: Box::new(l), rhs: Box::new(r), comp:  ComparisonType::Similar, location: Location {start, end}}}
            / relational_expr()
        ///Equality

        ///Logical

        #[cache_left_rec]
        rule boolean_and_expr() -> AstNode =
             start:position!() l:boolean_and_expr() ws() "and" ws() r:equality_expr() end:position!() {
                AstNode::Logical {
                    lhs: Box::new(l),
                    rhs: Box::new(r),
                    comp: LogicalType::And,
                    location: Location {
                        start,
                        end,
                    },
                }
            }
            / equality_expr()


        #[cache_left_rec]
        rule boolean_or_expr() -> AstNode =
             start:position!() l:boolean_or_expr() ws() "or" ws() r:boolean_and_expr() end:position!() {
                AstNode::Logical {
                    lhs: Box::new(l),
                    rhs: Box::new(r),
                    comp:  LogicalType::Or,
                    location: Location {
                        start,
                        end,
                    }
                }
            }
            / boolean_and_expr()

        ///Logical

        ///high_level_expr
        #[cache_left_rec]
        rule high_level_expr() -> AstNode =
            start:position!() l:high_level_expr() ws() "default" ws() r:boolean_or_expr() end:position!() {
                AstNode::Default {
                    lhs: Box::new(l),
                    rhs: Box::new(r),
                    location: Location {
                        start,
                        end
                    }
                }
            }
            / start:position!() l:high_level_expr() ws() vr:variable_expr() ws() r:boolean_or_expr() end:position!() {
                AstNode::InfixFunctionCall {
                    lhs: Box::new(l),
                    func: Box::new(vr),
                    rhs:Box::new(r),
                    location: Location {
                        start,
                        end,
                    }
                }
            }
            / boolean_or_expr()

        ///high_level_expr
        ///
        rule expr() -> AstNode = precedence! {
             x:@ ws() "!" end:position!() {
                   let start = x.location().start;
                    AstNode::NullSafeSelector {
                        lhs: Box::new(x),
                        location: Location {
                            start,
                            end,
                        },
                    }
            }
            --
             ws() hle:high_level_expr() ws() {hle}
        }

        rule uri() -> AstNode =
            start:position!() s:$(([^ ' ' | '\n' | '\r' | '\t']+)) end:position!() {
                AstNode::Uri {
                    value: s.to_string(),
                    location: Location {
                        start,
                        end,
                    }
                }
            }


        rule namespace_directive_expr() -> AstNode =
            start:position!() "ns" ws() prefix:name_identifier_expr() ws() ns:uri() end:position!() {
                AstNode::NamespaceDirective {
                    prefix: Box::new(prefix),
                    uri: Box::new(ns),
                    location: Location {
                        start,
                        end,
                    }
                }
            }

        rule import_directive_expr() -> AstNode =
            import_from_expr()
           / import_module_expr()

        rule import_from_expr() -> AstNode =
            start:position!() "import" ws() ie: imported_elements() ws() "from" module:imported_element_module() end:position!() {
                AstNode::ImportDirective {
                    module: Box::new(module),
                    elements: ie,
                    location: Location {
                        start,
                        end,
                    }
                }
            }

        rule import_module_expr() -> AstNode =
            start:position!() "import" ws() module:imported_element_module() end:position!() {
                AstNode::ImportDirective {
                    module: Box::new(module),
                    elements: vec![],
                    location: Location {
                        start,
                        end,
                    }
                }
            }

        rule import_alias() -> AstNode =
            ws() "as" ws() ni:simple_name_identifier_expr() {ni}

        rule imported_element_module() -> ImportedElement =
           start:position!() ws() module:name_identifier_expr() ni:(import_alias())? end:position!()  {
                ImportedElement {
                    name: Box::new(module),
                    alias: ni ,
                    location: Location {
                        start,
                        end,
                    }
                }
        }

        rule imported_elements() -> Vec<ImportedElement> =
            imported_element() ++ ","

        rule imported_element() -> ImportedElement =
            star_imported_element()
            / element_imported_element()

        rule element_imported_element() -> ImportedElement =
            start:position!() module:simple_name_identifier_expr() ni:(import_alias())? end:position!()  {
                ImportedElement {
                    name: Box::new(module),
                    alias: ni ,
                    location: Location {
                        start,
                        end,
                    }
                }
        }

        rule star_imported_element() -> ImportedElement =
            start:position!() s:star_name_identifier_expr() end:position!(){
               ImportedElement {
                    name: Box::new(s),
                    alias: None ,
                    location: Location {
                        start,
                        end,
                    }
               }
            }

        rule star_name_identifier_expr() -> AstNode =
          start:position!() n:$(['*']) end:position!() {
            AstNode::NameIdentifier {
                name: vec![n.to_string()],
                location: Location {
                    start,
                    end,
                }
            }
        }

        rule version_directive_expr() -> AstNode =
            start:position!() "%dw" ws() mayor:$(digit()) ['.'] minor:$(digit()) end:position!() {
                let mut result = String::new();
                result.push_str(mayor);
                result.push('.');
                result.push_str(minor);
                AstNode::VersionDirective {
                    version:  result,
                    location: Location {
                        start,
                        end,
                    }
                }
            }

        rule option_expr() -> AstNode =
            start:position!() name:simple_name_identifier_expr() ws() "=" ws() value:literal_expr() end:position!() {
                AstNode::Option {
                    name: Box::new(name),
                    value: Box::new(value),
                    location: Location {
                        start,
                        end,
                    },
                }
            }

        rule input_directive_expr() -> AstNode =
           start:position!() "input" ws() name:(simple_name_identifier_expr()) ws() df:(content_type_expr() / simple_name_identifier_expr()) ws() options:(option_expr() ** ",")  end:position!() {
             AstNode::InputDirective {
                name: Box::new(name),
                data_format: Box::new(df),
                options,
                location: Location {
                    start,
                    end,
                }
            }
        }

        rule output_directive_expr() -> AstNode =
           start:position!() "output" ws() df:(content_type_expr()/ simple_name_identifier_expr()) ws() options:(option_expr() ** ",")  end:position!() {
             AstNode::OutputDirective {
                data_format: Box::new(df),
                options,
                location: Location {start, end}
            }
        }

        rule content_type() =
            quiet!{
                ['a'..='z' | 'A'..='Z' | '0'..='9']+ "/" ['a'..='z' | 'A'..='Z' | '0'..='9' | '.' | '_' | '+' | '-']+
            }

         rule content_type_expr() -> AstNode =
           start:position!() ct:$(content_type()) end:position!() {
             AstNode::ContentType {
                value: ct.to_string(),
                location: Location {start, end}
            }
        }

        rule directive() -> AstNode =
           ws() vd:version_directive_expr() {vd}
            / ws() id:import_directive_expr() {id}
            / ws() nd:namespace_directive_expr() {nd}
            / ws() id: input_directive_expr() {id}
            / ws() od: output_directive_expr() {od}

        rule document() -> AstNode =
            start:position!() h:(directive()++ fws())? ws() "---"?  b:expr() end:position!() {
                AstNode::Document {
                    header: h,
                    body: Box::new(b),
                    location: Location {
                        start,
                        end,
                    }
                }
            }

        pub rule parse() -> AstNode = e:document() { e }
    }
}

#[derive(Clone, PartialEq, Debug)]
pub enum MessageKind {
    Warning,
    Error,
}

pub fn parse_dw(_name: &str, input: &str) -> Result<DWParseResult, DWError> {
    let result = dw_parser::parse(input);
    match result {
        Ok(node) => Ok(DWParseResult {
            ast: node,
            messages: vec![],
        }),
        Err(err) => {
            let message = Message {
                message: format!("Expecting {}", err.expected),
                kind: MessageKind::Error,
                location: Location {
                    start: err.location.offset,
                    end: err.location.offset,
                },
            };

            Err(DWError {
                messages: vec![message],
            })
        }
    }
}
