// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef SERVICES_DATA_DECODER_PUBLIC_CPP_XML_DOM_H_
#define SERVICES_DATA_DECODER_PUBLIC_CPP_XML_DOM_H_

#include <stdint.h>

#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include <vector>

#include "base/containers/span.h"
#include "base/memory/raw_ptr.h"
#include "base/types/expected.h"
#include "base/types/pass_key.h"
#include "base/values.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_map.h"

namespace data_decoder::xml {

namespace ffi {
class DomBuilder;
}

class Node;

// A qualified name in XML, as defined in https://www.w3.org/TR/xml-names/.
// Used for both element and attribute names.
struct Name {
  std::string_view local_name;
  std::string_view prefix;
};

// Equivalent to `Name` but owns its fields.
struct OwnedName {
  std::string local_name;
  std::string prefix;

  bool operator==(const OwnedName& other) const = default;
  bool operator==(const Name& other) const {
    return local_name == other.local_name && prefix == other.prefix;
  }

  struct absl_container_hash {
    using is_transparent = void;

    size_t operator()(const OwnedName& name) const {
      return absl::HashOf(name.local_name, name.prefix);
    }

    size_t operator()(const Name& name) const {
      return absl::HashOf(name.local_name, name.prefix);
    }
  };
};

class Document {
 public:
  explicit Document(std::unique_ptr<Node> root);
  ~Document();

  Document(const Document&) = delete;
  Document& operator=(const Document&) = delete;
  Document(Document&&);
  Document& operator=(Document&&);

  static base::expected<Document, std::string> FromBytes(
      base::span<const uint8_t> bytes);
  static base::expected<Document, std::string> FromUtf8(std::string_view str);

  const Node* GetRoot() const;

  // Depth-first search for the first element with a matching `name`, or
  // `nullptr` if there is no such element.
  const Node* FindFirstElementByTagName(Name name) const;

  // Returns a base::Value representation of the document compatible with
  // the legacy safe_xml_parser.h.
  base::Value ToValueForTesting() const;

 private:
  std::unique_ptr<Node> root_;
};

// This node representation only supports element nodes, text nodes, and CDATA
// nodes. The address of a Node is guaranteed to be stable.
//
// Unlike XML/XSLT/XPath, namespaces and attributes are not represented as a
// distinct node type and are just extra bits of data stored on element nodes,
// i.e. nodes for which `GetType() == Type::kElement` is true.
//
// TODO(dcheng): Implement namespace support.
class Node {
 public:
  enum class Type { kElement, kText, kCdata };

  ~Node();

  Type GetType() const;
  const Node* parent() const { return parent_; }

  // Returns the name of the element node. Returns nullptr if called on
  // non-element nodes.
  const OwnedName* GetName() const;
  const std::string* GetLocalName() const;
  // The namespace prefix of the element name, or nullptr if the
  // node is not an element node. Shorthand for `GetName()->prefix`.
  //   <html:br /> -> returns "html"
  //   <element /> -> returns ""
  const std::string* GetNamespacePrefix() const;

  // Returns the attributes of the element node. Returns nullptr if called on
  // non-element nodes.
  const absl::flat_hash_map<OwnedName, std::string>* GetAttributes() const;
  // The value of the attribute with the given `name`, or `nullptr` if the
  // element does not specify an attribute with `name` or the node is not an
  // element node.
  const std::string* GetAttribute(Name name) const;

  // Returns the namespace definitions of the element node. Returns nullptr if
  // called on non-element nodes.
  const absl::flat_hash_map<std::string, std::string>* GetNamespaces() const;

  // Returns the children of the element node. Returns nullptr if called on
  // non-element nodes.
  const std::vector<std::unique_ptr<Node>>* GetChildren() const;
  std::vector<const Node*> GetChildrenByTagName(Name name) const;
  const Node* FindFirstChildByTagName(Name name) const;

  // These methods are only usable on text or cdata nodes and will crash if
  // called on non-text and non-cdata nodes.
  const std::string& GetTextContent() const;

  // Returns a base::Value representation of the document compatible with
  // the legacy safe_xml_parser.h.
  base::Value ToValueForTesting() const;

  // Rust FFI helpers:
  static std::unique_ptr<Node> CreateElement(base::PassKey<ffi::DomBuilder>,
                                             std::string local_name,
                                             std::string prefix);
  static std::unique_ptr<Node> CreateTextNode(base::PassKey<ffi::DomBuilder>,
                                              std::string text);
  static std::unique_ptr<Node> CreateCdataNode(base::PassKey<ffi::DomBuilder>,
                                               std::string text);
  void SetAttribute(base::PassKey<ffi::DomBuilder>,
                    std::string local_name,
                    std::string prefix,
                    std::string value);
  void SetNamespace(base::PassKey<ffi::DomBuilder>,
                    std::string prefix,
                    std::string uri);
  void AddChild(base::PassKey<ffi::DomBuilder>, std::unique_ptr<Node> child);

 private:
  template <typename T>
  explicit Node(T data) : data_(std::move(data)), parent_(nullptr) {}

  struct Element {
    Element();
    explicit Element(OwnedName name);
    Element(Element&&);
    Element& operator=(Element&&);
    ~Element();

    OwnedName name;
    absl::flat_hash_map<OwnedName, std::string> attributes;
    absl::flat_hash_map<std::string, std::string> namespaces;
    std::vector<std::unique_ptr<Node>> children;
  };
  struct Text {
    std::string text;
  };
  struct Cdata {
    std::string text;
  };
  std::variant<Element, Text, Cdata> data_;
  raw_ptr<Node> parent_;
};

}  // namespace data_decoder::xml

#endif  // SERVICES_DATA_DECODER_PUBLIC_CPP_XML_DOM_H_
