// Copyright (c) 2026, Salesforce, Inc.,
// All rights reserved.
// For full license text, see the LICENSE.txt file

use std::fmt::{Display, Formatter};
use std::ops::Range;
use std::rc::Rc;

use crate::runtime::value::{InternalValue, Value};

#[derive(Debug)]
struct InnerDocument {
    origin: Rc<String>,
    nodes: Vec<InnerDocNode>,
}

impl InnerDocument {
    fn new(origin: Rc<String>) -> Self {
        InnerDocument {
            origin,
            nodes: vec![],
        }
    }

    fn get(&self, pos: usize) -> Option<&InnerDocNode> {
        self.nodes.get(pos)
    }

    fn len(&self) -> usize {
        self.nodes.len()
    }
}

type Attributes = Vec<(QualifiedName, String)>;

#[derive(Debug, Clone)]
pub struct DocNode {
    doc: Rc<InnerDocument>,
    pos: usize,
}

impl PartialEq for DocNode {
    fn eq(&self, other: &Self) -> bool {
        self.inner()
            .eq(other.inner(), self.doc.as_ref(), other.doc.as_ref())
    }
}

impl Display for DocNode {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        self.do_fmt(self.pos, 0, f, &mut vec![])
    }
}

fn add_prefix<'a>(
    f: &mut Formatter<'_>,
    name: &'a QualifiedName,
    namespaces: &mut Vec<(&'a str, &'a str)>,
    pushed: &mut Vec<usize>,
) -> std::fmt::Result {
    if name.namespace.is_empty() {
        return Ok(());
    }

    let mut iter = namespaces.iter();
    while let Some((prefix, namespace)) = iter.next_back() {
        if prefix.eq(&name.prefix) && namespace.eq(&name.namespace) {
            return Ok(());
        } else if prefix.eq(&name.prefix) {
            break;
        }
    }

    pushed.push(namespaces.len());
    namespaces.push((&name.prefix, &name.namespace));
    match name.prefix.is_empty() {
        true => f.write_fmt(format_args!(" xmlns=\"{}\"", name.namespace)),
        false => f.write_fmt(format_args!(
            " xmlns:{}=\"{}\"",
            name.prefix, name.namespace
        )),
    }
}

const PADDING: &str = "    ";
fn pad(f: &mut Formatter<'_>, level: usize) -> std::fmt::Result {
    for _ in 0..level {
        f.write_str(PADDING)?
    }
    Ok(())
}

impl DocNode {
    fn do_fmt<'a>(
        &'a self,
        node: usize,
        level: usize,
        f: &mut Formatter<'_>,
        namespaces: &mut Vec<(&'a str, &'a str)>,
    ) -> std::fmt::Result {
        match self.node(node) {
            InnerDocNode::InnerElement(e) => {
                let mut pushed = vec![];

                pad(f, level)?;
                f.write_fmt(format_args!("<{}", e.name))?;

                add_prefix(f, &e.name, namespaces, &mut pushed)?;

                for (name, value) in &e.attributes {
                    add_prefix(f, name, namespaces, &mut pushed)?;
                    f.write_fmt(format_args!(" {name}=\"{value}\""))?;
                }

                if e.children.is_empty() {
                    f.write_str("/>")?;
                } else {
                    f.write_str(">")?;
                    for child in &e.children {
                        f.write_str("\n")?;
                        self.do_fmt(*child, level + 1, f, namespaces)?;
                    }
                    f.write_str("\n")?;
                    pad(f, level)?;
                    f.write_fmt(format_args!("</{}>", e.name))?;
                }

                while let Some(push) = pushed.pop() {
                    namespaces.remove(push);
                }
                Ok(())
            }
            InnerDocNode::Leaf(e) => {
                pad(f, level)?;
                f.write_str(&e.value)
            }
        }
    }

    fn inner(&self) -> &InnerDocNode {
        self.doc.get(self.pos).unwrap()
    }

    fn node(&self, pos: usize) -> &InnerDocNode {
        self.doc.get(pos).unwrap()
    }

    pub fn child_by_name(&self, key: &str) -> Option<DocNode> {
        if let InnerDocNode::InnerElement(inner) = self.inner() {
            for child_pos in &inner.children {
                if let InnerDocNode::InnerElement(e) = self.node(*child_pos) {
                    if e.name.local_name.eq(key) {
                        return Some(DocNode {
                            doc: Rc::clone(&self.doc),
                            pos: *child_pos,
                        });
                    }
                }
            }
        }
        None
    }

    pub fn child_by_qname(&self, qname: &QualifiedName) -> Option<DocNode> {
        if let InnerDocNode::InnerElement(inner) = self.inner() {
            for child_pos in &inner.children {
                if let InnerDocNode::InnerElement(e) = self.node(*child_pos) {
                    if e.name.eq(qname) {
                        return Some(DocNode {
                            doc: Rc::clone(&self.doc),
                            pos: *child_pos,
                        });
                    }
                }
            }
        }
        None
    }

    pub fn attr_by_name(&self, key: &str) -> Option<&str> {
        match self.inner() {
            InnerDocNode::InnerElement(inner) => inner
                .attributes
                .iter()
                .filter(|(name, _)| name.local_name.eq(key))
                .map(|(_, val)| val.as_str())
                .next(),
            _ => None,
        }
    }

    pub fn attr_by_qname(&self, qname: &QualifiedName) -> Option<&str> {
        match self.inner() {
            InnerDocNode::InnerElement(inner) => inner
                .attributes
                .iter()
                .filter(|(name, _)| name.eq(qname))
                .map(|(_, val)| val.as_str())
                .next(),
            _ => None,
        }
    }

    pub fn child_by_index(&self, index: usize) -> Option<DocNode> {
        if let InnerDocNode::InnerElement(inner) = self.inner() {
            if index < inner.children.len() {
                return Some(DocNode {
                    doc: Rc::clone(&self.doc),
                    pos: *inner.children.get(index).unwrap(),
                });
            }
        }
        None
    }

    pub fn childs_by_name(&self, key: &str) -> Vec<DocNode> {
        let mut childs = Vec::new();
        if let InnerDocNode::InnerElement(inner) = self.inner() {
            for child_pos in &inner.children {
                if let InnerDocNode::InnerElement(e) = self.node(*child_pos) {
                    if e.name.local_name.eq(key) {
                        childs.push(DocNode {
                            doc: Rc::clone(&self.doc),
                            pos: *child_pos,
                        });
                    }
                }
            }
        }
        childs
    }

    pub fn childs_by_qname(&self, qname: &QualifiedName) -> Vec<DocNode> {
        let mut childs = Vec::new();
        if let InnerDocNode::InnerElement(inner) = self.inner() {
            for child_pos in &inner.children {
                if let InnerDocNode::InnerElement(e) = self.node(*child_pos) {
                    if e.name.eq(qname) {
                        childs.push(DocNode {
                            doc: Rc::clone(&self.doc),
                            pos: *child_pos,
                        });
                    }
                }
            }
        }
        childs
    }

    pub(super) fn compare(&self, other: &InternalValue) -> bool {
        match other {
            InternalValue::Null => self.is_null(),
            InternalValue::String(s) => self.as_str().map(|inner| inner.eq(s)).unwrap_or_default(),
            InternalValue::Node(other) => self.compare_children(other), // DW eq compares everything but the attributes and name for the current level
            InternalValue::Array(_) => false, // We need a valid DW example to give support to this comparison.
            InternalValue::Object(_) => false, // We need a valid DW example to give support to this comparison.
            _ => false,
        }
    }

    fn compare_children(&self, other: &DocNode) -> bool {
        match (self.inner(), other.inner()) {
            (InnerDocNode::InnerElement(first), InnerDocNode::InnerElement(second)) => {
                first.compare_children(second, self.doc.as_ref(), other.doc.as_ref())
            }
            _ => false,
        }
    }

    pub fn is_null(&self) -> bool {
        match self.inner() {
            InnerDocNode::InnerElement(i) => i.children.is_empty(),
            InnerDocNode::Leaf(_) => false,
        }
    }

    pub fn as_str(&self) -> Option<&str> {
        match self.inner() {
            InnerDocNode::Leaf(l) => Some(&l.value),
            InnerDocNode::InnerElement(e) => {
                if e.children.len() == 1 {
                    match self.node(*e.children.first().unwrap()) {
                        InnerDocNode::InnerElement(_) => None,
                        InnerDocNode::Leaf(leaf) => Some(&leaf.value),
                    }
                } else {
                    None
                }
            }
        }
    }

    pub fn content(&self) -> Option<String> {
        match self.inner() {
            InnerDocNode::InnerElement(e) => {
                if e.parent.is_none() {
                    self.doc.origin.get(e.range.clone()).map(|e| e.to_string())
                } else if e.children.len() == 1 {
                    let node = DocNode {
                        doc: Rc::clone(&self.doc),
                        pos: *e.children.first().unwrap(),
                    };
                    Some(format!("<?xml version='1.0' encoding='UTF-8'?>\n{node}"))
                } else {
                    None
                }
            }
            InnerDocNode::Leaf(l) => Some(l.value.clone()),
        }
    }

    pub fn is_empty(&self) -> bool {
        match self.inner() {
            InnerDocNode::Leaf(l) => l.value.is_empty(),
            InnerDocNode::InnerElement(e) => {
                if e.children.len() == 1 {
                    match self.node(*e.children.first().unwrap()) {
                        InnerDocNode::InnerElement(_) => false,
                        InnerDocNode::Leaf(leaf) => leaf.value.is_empty(),
                    }
                } else {
                    e.children.len() != 0
                }
            }
        }
    }

    pub fn size(&self) -> Option<usize> {
        match self.inner() {
            InnerDocNode::Leaf(l) => Some(l.value.len()),
            InnerDocNode::InnerElement(e) => {
                if e.children.len() == 1 {
                    match self.node(*e.children.first().unwrap()) {
                        InnerDocNode::InnerElement(_) => Some(e.children.len()),
                        InnerDocNode::Leaf(l) => Some(l.value.len()),
                    }
                } else {
                    Some(e.children.len())
                }
            }
        }
    }
}

#[derive(Debug, Clone)]
/// Represents a name with a namespace.
pub struct QualifiedName {
    pub(crate) prefix: String,
    pub(crate) namespace: String,
    local_name: String,
}

impl PartialEq for QualifiedName {
    fn eq(&self, other: &Self) -> bool {
        self.namespace.eq(&other.namespace) && self.local_name.eq(&other.local_name)
    }
}

impl Display for QualifiedName {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self.prefix.is_empty() {
            true => f.write_fmt(format_args!("{}", self.local_name)),
            false => f.write_fmt(format_args!("{}:{}", self.prefix, self.local_name)),
        }
    }
}

impl QualifiedName {
    /// Create a new qualified name for the given name, namespace. The namespace is represented by the prefix.
    pub fn prefixed<P: Into<String>, N: Into<String>, L: Into<String>>(
        prefix: P,
        namespace: N,
        local_name: L,
    ) -> Self {
        Self {
            prefix: prefix.into(),
            namespace: namespace.into(),
            local_name: local_name.into(),
        }
    }

    /// Create a new qualified name for the given name, namespace.
    pub fn new<T: Into<String>, K: Into<String>>(namespace: T, local_name: K) -> Self {
        Self {
            prefix: Default::default(),
            namespace: namespace.into(),
            local_name: local_name.into(),
        }
    }
}

#[derive(Debug)]
enum InnerDocNode {
    InnerElement(InnerElement),
    Leaf(Leaf),
}

impl InnerDocNode {
    fn eq(&self, other: &Self, self_array: &InnerDocument, other_array: &InnerDocument) -> bool {
        match (self, other) {
            (InnerDocNode::InnerElement(first), InnerDocNode::InnerElement(second)) => {
                first.eq(second, self_array, other_array)
            }
            (InnerDocNode::Leaf(first), InnerDocNode::Leaf(second)) => first.eq(second),
            _ => false,
        }
    }
}

#[derive(Debug)]
struct InnerElement {
    name: QualifiedName,
    parent: Option<usize>,
    range: Range<usize>,
    attributes: Attributes,
    children: Vec<usize>,
}

impl InnerElement {
    fn eq(&self, other: &Self, self_array: &InnerDocument, other_array: &InnerDocument) -> bool {
        self.name.eq(&other.name)
            && self.attributes.eq(&other.attributes)
            && self.compare_children(other, self_array, other_array)
    }

    fn compare_children(
        &self,
        other: &Self,
        self_array: &InnerDocument,
        other_array: &InnerDocument,
    ) -> bool {
        self.children.len() == other.children.len()
            && self
                .children
                .iter()
                .zip(other.children.iter())
                .all(|(first, second)| {
                    self_array.get(*first).unwrap().eq(
                        other_array.get(*second).unwrap(),
                        self_array,
                        other_array,
                    )
                })
    }
}

#[derive(Debug)]
struct Leaf {
    _parent: usize,
    value: String,
}

impl Leaf {
    fn eq(&self, other: &Self) -> bool {
        self.value.eq(&other.value)
    }
}

/// A helper to create a new `Value` from a DOM.
pub struct DocumentBuilder {
    doc: InnerDocument,
    parent_builder: Vec<usize>,
}

impl DocumentBuilder {
    pub(crate) fn new(origin: Rc<String>) -> Self {
        DocumentBuilder {
            doc: InnerDocument::new(origin),
            parent_builder: vec![],
        }
    }

    /// Create a new [`DocumentBuilder`] with a root element. The element is contained in the range of the provided origin.
    pub fn root(name: QualifiedName, origin: Rc<String>, range: Range<usize>) -> DocumentBuilder {
        let builder = DocumentBuilder::new(origin);
        builder
            .add(QualifiedName::new("", ""), range.clone()) // the root node that contains the first tag
            .add(name, range)
    }

    fn add(mut self, name: QualifiedName, range: Range<usize>) -> Self {
        let pos = self.doc.len();
        let parent_pos = self.parent_builder.last().cloned();

        if let Some(parent_pos) = parent_pos {
            if let Some(InnerDocNode::InnerElement(e)) = self.doc.nodes.get_mut(parent_pos) {
                e.children.push(pos)
            }
        }

        let element = InnerElement {
            name,
            parent: parent_pos,
            range,
            attributes: vec![],
            children: vec![],
        };

        self.doc.nodes.push(InnerDocNode::InnerElement(element));

        self.parent_builder.push(pos);

        self
    }

    /// Add a new attribute to the current element of the document.
    pub fn with_attribute<K: Into<String>>(mut self, name: QualifiedName, value: K) -> Self {
        if let Some(InnerDocNode::InnerElement(e)) = self.doc.nodes.get_mut(
            self.parent_builder
                .last()
                .cloned()
                .expect("Adding attribute to non existing element"),
        ) {
            e.attributes.push((name, value.into()))
        }
        self
    }

    /// Add a new element to the current level of the document. The element is contained in the range of the provided origin.
    pub fn with_element(self, name: QualifiedName, range: Range<usize>) -> Self {
        self.add(name, range)
    }

    /// Add a new text node to the current element of the document.
    pub fn with_text<T: Into<String>>(mut self, value: T) -> Self {
        let pos = self.doc.len();
        let parent_pos = self
            .parent_builder
            .last()
            .cloned()
            .expect("Adding text to the root element");

        if let Some(InnerDocNode::InnerElement(e)) = self.doc.nodes.get_mut(parent_pos) {
            e.children.push(pos)
        }

        let leaf = Leaf {
            _parent: parent_pos,
            value: value.into(),
        };

        self.doc.nodes.push(InnerDocNode::Leaf(leaf));

        self
    }

    /// Finish the current element of the document. And return to the previous level.
    pub fn finish_element(mut self) -> DocumentBuilder {
        if self.parent_builder.len() <= 1 {
            panic!("First element must be finished with finish_doc.")
        }

        self.parent_builder.pop();
        self
    }

    /// Indicate that the document is finished. And create the `Value`.
    pub fn build(self) -> Value {
        if self.parent_builder.len() != 1 {
            panic!("Finishing document before closing all elements.")
        }

        #[cfg(feature = "experimental_coerced_type")]
        let origin = Rc::clone(&self.doc.origin);

        #[cfg(feature = "experimental_coerced_type")]
        return Value::coerced_node(
            DocNode {
                doc: Rc::new(self.doc),
                pos: 0,
            },
            origin,
        );

        #[cfg(not(feature = "experimental_coerced_type"))]
        return Value::node(DocNode {
            doc: Rc::new(self.doc),
            pos: 0,
        });
    }
}
