use crate::ir::value_type::ValueType;

#[derive(Debug, Clone)]
pub struct Tag {
    pub value_type: ValueType,

    pub name: bool,
    pub is_null: bool,
    pub example: bool,
    pub nullable: bool,
    pub raw: bool,
    pub allow_empty: bool,
    pub unique: bool,
    pub is_inherit: bool,

    pub desc: Option<String>,
    pub default_val: Option<String>,
    pub min: Option<String>,
    pub max: Option<String>,
    pub size: Option<u64>,
    pub enums: Option<String>,
    pub pattern: Option<String>,
    pub location: Option<i32>,
    pub version: Option<i32>,
    pub mime: Option<String>,

    pub child_desc: Option<String>,
    pub child_type: ValueType,
    pub child_nullable: bool,
    pub child_raw: bool,
    pub child_allow_empty: bool,
    pub child_unique: bool,
    pub child_default_val: Option<String>,
    pub child_min: Option<String>,
    pub child_max: Option<String>,
    pub child_size: Option<u64>,
    pub child_enums: Option<String>,
    pub child_pattern: Option<String>,
    pub child_location: Option<i32>,
    pub child_version: Option<i32>,
    pub child_mime: Option<String>,
}

impl Tag {
    pub fn new() -> Self {
        Self {
            value_type: ValueType::Unknown,
            name: false,
            is_null: false,
            example: false,
            nullable: false,
            raw: false,
            allow_empty: false,
            unique: false,
            is_inherit: false,
            desc: None,
            default_val: None,
            min: None,
            max: None,
            size: None,
            enums: None,
            pattern: None,
            location: None,
            version: None,
            mime: None,
            child_desc: None,
            child_type: ValueType::Unknown,
            child_nullable: false,
            child_raw: false,
            child_allow_empty: false,
            child_unique: false,
            child_default_val: None,
            child_min: None,
            child_max: None,
            child_size: None,
            child_enums: None,
            child_pattern: None,
            child_location: None,
            child_version: None,
            child_mime: None,
        }
    }

    pub fn inherit(&mut self, parent: &Tag) {
        self.is_inherit = true;

        if parent.child_type != ValueType::Unknown {
            self.value_type = parent.child_type;
        }

        if let Some(ref v) = parent.child_desc {
            self.desc = Some(v.clone());
        }

        if parent.child_nullable {
            self.nullable = true;
        }

        if parent.child_raw {
            self.raw = true;
        }

        if parent.child_allow_empty {
            self.allow_empty = true;
        }

        if parent.child_unique {
            self.unique = true;
        }

        if let Some(ref v) = parent.child_default_val {
            self.default_val = Some(v.clone());
        }

        if let Some(ref v) = parent.child_min {
            self.min = Some(v.clone());
        }

        if let Some(ref v) = parent.child_max {
            self.max = Some(v.clone());
        }

        if let Some(v) = parent.child_size {
            self.size = Some(v);
        }

        if let Some(ref v) = parent.child_enums {
            self.enums = Some(v.clone());
            self.value_type = ValueType::Enum;
        }

        if let Some(ref v) = parent.child_pattern {
            self.pattern = Some(v.clone());
        }

        if let Some(v) = parent.child_location {
            self.location = Some(v);
        }

        if let Some(v) = parent.child_version {
            self.version = Some(v);
        }

        if let Some(ref v) = parent.child_mime {
            self.mime = Some(v.clone());
        }
    }

    pub fn merge(dst: Option<Tag>, src: Tag) -> Tag {
        let mut dst = dst.unwrap_or_default();

        if src.is_null {
            dst.is_null = true;
        }

        if src.example {
            dst.example = true;
        }

        if let Some(ref v) = src.desc {
            dst.desc = Some(v.clone());
        }

        if src.nullable {
            dst.nullable = true;
        }

        if src.raw {
            dst.raw = true;
        }

        if src.allow_empty {
            dst.allow_empty = true;
        }

        if src.unique {
            dst.unique = true;
        }

        if src.value_type != ValueType::Unknown {
            dst.value_type = src.value_type;
        }

        if let Some(ref v) = src.default_val {
            dst.default_val = Some(v.clone());
        }

        if let Some(ref v) = src.min {
            dst.min = Some(v.clone());
        }

        if let Some(ref v) = src.max {
            dst.max = Some(v.clone());
        }

        if let Some(v) = src.size {
            dst.size = Some(v);
        }

        if let Some(ref v) = src.enums {
            dst.enums = Some(v.clone());
        }

        if let Some(ref v) = src.pattern {
            dst.pattern = Some(v.clone());
        }

        if let Some(v) = src.location {
            dst.location = Some(v);
        }

        if let Some(v) = src.version {
            dst.version = Some(v);
        }

        if let Some(ref v) = src.mime {
            dst.mime = Some(v.clone());
        }

        dst
    }

    pub fn parse(s: &str) -> Option<Tag> {
        let s = s.trim();

        let s = if let Some(rest) = s.strip_prefix("//") {
            rest
        } else if let Some(rest) = s.strip_prefix("/*") {
            rest.strip_suffix("*/")?
        } else {
            return None;
        };

        let s = s.trim();

        let s = s.strip_prefix("mm:")?;

        let s = s.trim();
        if s.is_empty() {
            return Some(Tag::new());
        }

        let mut tag = Tag::new();

        for part in s.split(';') {
            let part = part.trim();
            if part.is_empty() {
                continue;
            }

            let (key, value) = if let Some(idx) = part.find('=') {
                (part[..idx].trim(), Some(part[idx + 1..].trim().to_string()))
            } else {
                (part.trim(), None)
            };

            match key {
                "name" => {
                    tag.name = true;
                    let _v = value;
                }
                "type" => {
                    if let Some(ref v) = value {
                        tag.value_type = ValueType::from_str(v);
                    }
                }
                "desc" => {
                    if let Some(ref v) = value {
                        tag.desc = Some(v.trim_matches('"').to_string());
                    }
                }
                "default_val" => {
                    if let Some(ref v) = value {
                        tag.default_val = Some(v.trim_matches('"').to_string());
                    }
                }
                "min" => {
                    if let Some(ref v) = value {
                        tag.min = Some(v.trim_matches('"').to_string());
                    }
                }
                "max" => {
                    if let Some(ref v) = value {
                        tag.max = Some(v.trim_matches('"').to_string());
                    }
                }
                "size" => {
                    if let Some(ref v) = value {
                        tag.size = v.parse::<u64>().ok();
                    }
                }
                "enums" => {
                    if let Some(ref v) = value {
                        tag.value_type = ValueType::Enum;
                        tag.enums = Some(v.trim_matches('"').to_string());
                    }
                }
                "location" => {
                    if let Some(ref v) = value {
                        if let Ok(offset) = v.parse::<i32>() {
                            if (-12..=14).contains(&offset) {
                                tag.location = Some(offset);
                            }
                        }
                    }
                }
                "version" => {
                    if let Some(ref v) = value {
                        if let Ok(ver) = v.parse::<i32>() {
                            if (1..=10).contains(&ver) {
                                tag.version = Some(ver);
                            }
                        }
                    }
                }
                "mime" => {
                    if let Some(ref v) = value {
                        tag.mime = Some(v.clone());
                    }
                }
                "nullable" => {
                    if let Some(ref v) = value {
                        tag.nullable = v == "true";
                    } else {
                        tag.nullable = true;
                    }
                }
                "allow_empty" => {
                    if let Some(ref v) = value {
                        tag.allow_empty = v == "true";
                    } else {
                        tag.allow_empty = true;
                    }
                }
                "raw" => {
                    if let Some(ref v) = value {
                        tag.raw = v == "true";
                    } else {
                        tag.raw = true;
                    }
                }
                "unique" => {
                    if let Some(ref v) = value {
                        tag.unique = v == "true";
                    } else {
                        tag.unique = true;
                    }
                }
                "pattern" => {
                    if let Some(ref v) = value {
                        tag.pattern = Some(v.trim_matches('"').to_string());
                    }
                }
                "child_desc" => {
                    if let Some(ref v) = value {
                        tag.child_desc = Some(v.trim_matches('"').to_string());
                    }
                }
                "child_type" => {
                    if let Some(ref v) = value {
                        tag.child_type = ValueType::from_str(v);
                    }
                }
                "child_nullable" => {
                    if let Some(ref v) = value {
                        tag.child_nullable = v == "true";
                    } else {
                        tag.child_nullable = true;
                    }
                }
                "child_raw" => {
                    if let Some(ref v) = value {
                        tag.child_raw = v == "true";
                    } else {
                        tag.child_raw = true;
                    }
                }
                "child_allow_empty" => {
                    if let Some(ref v) = value {
                        tag.child_allow_empty = v == "true";
                    } else {
                        tag.child_allow_empty = true;
                    }
                }
                "child_unique" => {
                    if let Some(ref v) = value {
                        tag.child_unique = v == "true";
                    } else {
                        tag.child_unique = true;
                    }
                }
                "child_default_val" => {
                    if let Some(ref v) = value {
                        tag.child_default_val = Some(v.trim_matches('"').to_string());
                    }
                }
                "child_min" => {
                    if let Some(ref v) = value {
                        tag.child_min = Some(v.trim_matches('"').to_string());
                    }
                }
                "child_max" => {
                    if let Some(ref v) = value {
                        tag.child_max = Some(v.trim_matches('"').to_string());
                    }
                }
                "child_size" => {
                    if let Some(ref v) = value {
                        tag.child_size = v.parse::<u64>().ok();
                    }
                }
                "child_enums" => {
                    if let Some(ref v) = value {
                        tag.child_enums = Some(v.trim_matches('"').to_string());
                        tag.child_type = ValueType::Enum;
                    }
                }
                "child_location" => {
                    if let Some(ref v) = value {
                        if let Ok(offset) = v.parse::<i32>() {
                            if (-12..=14).contains(&offset) {
                                tag.child_location = Some(offset);
                            }
                        }
                    }
                }
                "child_version" => {
                    if let Some(ref v) = value {
                        if let Ok(ver) = v.parse::<i32>() {
                            if (1..=10).contains(&ver) {
                                tag.child_version = Some(ver);
                            }
                        }
                    }
                }
                "child_mime" => {
                    if let Some(ref v) = value {
                        tag.child_mime = Some(v.clone());
                    }
                }
                _ => {}
            }
        }

        Some(tag)
    }

    #[allow(clippy::inherent_to_string)]
    pub fn to_string(&self) -> String {
        let mut parts = Vec::new();

        if self.value_type != ValueType::Unknown && !self.is_inherit {
            match self.value_type {
                ValueType::Str
                | ValueType::I
                | ValueType::F64
                | ValueType::Bool
                | ValueType::Obj
                | ValueType::Vec => {}
                ValueType::Arr => if self.size.is_none() || self.size.unwrap_or(0) == 0 {},
                ValueType::Enum => if self.enums.is_some() {},
                _ => {
                    parts.push(format!("type={}", self.value_type.to_str()));
                }
            }
        }

        if self.example {
            parts.push("example".to_string());
        }

        if self.is_null {
            parts.push("is_null".to_string());
        }

        if self.nullable && !self.is_inherit && !self.is_null {
            parts.push("nullable".to_string());
        }

        if let Some(ref desc) = self.desc {
            if !self.is_inherit {
                parts.push(format!("desc=\"{}\"", desc));
            }
        }

        if self.raw && !self.is_inherit {
            parts.push("raw".to_string());
        }

        if self.allow_empty && !self.is_inherit {
            parts.push("allow_empty".to_string());
        }

        if self.unique && !self.is_inherit {
            parts.push("unique".to_string());
        }

        if let Some(ref v) = self.default_val {
            if !self.is_inherit {
                parts.push(format!("default_val={}", v));
            }
        }

        if let Some(ref v) = self.min {
            if !self.is_inherit {
                parts.push(format!("min={}", v));
            }
        }

        if let Some(ref v) = self.max {
            if !self.is_inherit {
                parts.push(format!("max={}", v));
            }
        }

        if let Some(v) = self.size {
            if !self.is_inherit {
                parts.push(format!("size={}", v));
            }
        }

        if let Some(ref v) = self.enums {
            if !self.is_inherit {
                parts.push(format!("enums={}", v));
            }
        }

        if let Some(ref v) = self.pattern {
            if !self.is_inherit {
                parts.push(format!("pattern={}", v));
            }
        }

        if let Some(v) = self.location {
            if !self.is_inherit {
                parts.push(format!("location={}", v));
            }
        }

        if let Some(v) = self.version {
            if !self.is_inherit {
                parts.push(format!("version={}", v));
            }
        }

        if let Some(ref v) = self.mime {
            if !self.is_inherit {
                parts.push(format!("mime={}", v));
            }
        }

        if let Some(ref v) = self.child_desc {
            parts.push(format!("child_desc=\"{}\"", v));
        }

        if self.child_type != ValueType::Unknown {
            match self.child_type {
                ValueType::Str
                | ValueType::I
                | ValueType::F64
                | ValueType::Bool
                | ValueType::Obj
                | ValueType::Vec => {}
                ValueType::Arr => {
                    if self.child_size.is_none() || self.child_size.unwrap_or(0) == 0 {}
                }
                ValueType::Enum => if self.child_enums.is_some() {},
                _ => {
                    parts.push(format!("child_type={}", self.child_type.to_str()));
                }
            }
        }

        if self.child_raw {
            parts.push("child_raw".to_string());
        }

        if self.child_nullable {
            parts.push("child_nullable".to_string());
        }

        if self.child_allow_empty {
            parts.push("child_allow_empty".to_string());
        }

        if self.child_unique {
            parts.push("child_unique".to_string());
        }

        if let Some(ref v) = self.child_default_val {
            parts.push(format!("child_default_val={}", v));
        }

        if let Some(ref v) = self.child_min {
            parts.push(format!("child_min={}", v));
        }

        if let Some(ref v) = self.child_max {
            parts.push(format!("child_max={}", v));
        }

        if let Some(v) = self.child_size {
            parts.push(format!("child_size={}", v));
        }

        if let Some(ref v) = self.child_enums {
            parts.push(format!("child_enums={}", v));
        }

        if let Some(ref v) = self.child_pattern {
            parts.push(format!("child_pattern={}", v));
        }

        if let Some(v) = self.child_location {
            parts.push(format!("child_location={}", v));
        }

        if let Some(v) = self.child_version {
            parts.push(format!("child_version={}", v));
        }

        if let Some(ref v) = self.child_mime {
            parts.push(format!("child_mime={}", v));
        }

        if parts.is_empty() {
            String::new()
        } else {
            parts.join("; ")
        }
    }

    pub fn to_bytes(&self) -> Vec<u8> {
        let mut parts = Vec::new();

        parts.push(format!("type={}", self.value_type.to_str()));

        if let Some(ref desc) = self.desc {
            parts.push(format!("desc={}", desc));
        }

        if self.nullable {
            parts.push("nullable=true".to_string());
        }

        if let Some(ref v) = self.default_val {
            parts.push(format!("default_val={}", v));
        }

        if let Some(ref v) = self.min {
            parts.push(format!("min={}", v));
        }

        if let Some(ref v) = self.max {
            parts.push(format!("max={}", v));
        }

        if let Some(v) = self.size {
            parts.push(format!("size={}", v));
        }

        if let Some(ref v) = self.enums {
            parts.push(format!("enums={}", v));
        }

        if let Some(ref v) = self.pattern {
            parts.push(format!("pattern={}", v));
        }

        if let Some(v) = self.location {
            parts.push(format!("location={}", v));
        }

        if let Some(v) = self.version {
            parts.push(format!("version={}", v));
        }

        if let Some(ref v) = self.mime {
            parts.push(format!("mime={}", v));
        }

        if let Some(ref v) = self.child_desc {
            parts.push(format!("child_desc={}", v));
        }

        if self.child_type != ValueType::Unknown {
            parts.push(format!("child_type={}", self.child_type.to_str()));
        }

        if self.child_raw {
            parts.push("child_raw=true".to_string());
        }

        if self.child_nullable {
            parts.push("child_nullable=true".to_string());
        }

        if let Some(ref v) = self.child_default_val {
            parts.push(format!("child_default_val={}", v));
        }

        if let Some(ref v) = self.child_min {
            parts.push(format!("child_min={}", v));
        }

        if let Some(ref v) = self.child_max {
            parts.push(format!("child_max={}", v));
        }

        if let Some(v) = self.child_size {
            parts.push(format!("child_size={}", v));
        }

        if let Some(ref v) = self.child_enums {
            parts.push(format!("child_enums={}", v));
        }

        if let Some(ref v) = self.child_pattern {
            parts.push(format!("child_pattern={}", v));
        }

        if let Some(v) = self.child_location {
            parts.push(format!("child_location={}", v));
        }

        if let Some(v) = self.child_version {
            parts.push(format!("child_version={}", v));
        }

        if let Some(ref v) = self.child_mime {
            parts.push(format!("child_mime={}", v));
        }

        parts.join(";").as_bytes().to_vec()
    }
}

impl Default for Tag {
    fn default() -> Self {
        Self::new()
    }
}
