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

#include "chrome/browser/actor/ui/dom_node_geometry.h"

#include "base/trace_event/trace_event.h"
#include "base/types/pass_key.h"
#include "chrome/browser/actor/ui/actor_ui_metrics.h"
#include "components/optimization_guide/proto/features/common_quality_data.pb.h"
#include "ui/gfx/geometry/rect.h"
namespace actor::ui {
namespace {
using optimization_guide::proto::AnnotatedPageContent;
using optimization_guide::proto::ContentAttributes;
using optimization_guide::proto::ContentNode;
using optimization_guide::proto::DocumentIdentifier;
using NodeGeomMap = DomNodeGeometry::NodeGeomMap;

void BuildNodeMapInternal(const DocumentIdentifier& doc_id,
                          const ContentNode& root,
                          NodeGeomMap& map) {
  const auto& content_attributes = root.content_attributes();
  if (content_attributes.has_common_ancestor_dom_node_id()) {
    DomNode key{.node_id = content_attributes.common_ancestor_dom_node_id(),
                .document_identifier = doc_id.serialized_token()};
    map.emplace(std::move(key), root.content_attributes());
  }

  // If the node is an iframe, then use the iframe's doc_id for its children.
  // Otherwise use the same doc_id.
  const DocumentIdentifier& child_doc_id =
      (content_attributes.has_iframe_data() &&
       content_attributes.iframe_data().has_frame_data())
          ? content_attributes.iframe_data().frame_data().document_identifier()
          : doc_id;
  for (const auto& child : root.children_nodes()) {
    BuildNodeMapInternal(child_doc_id, child, map);
  }
}

NodeGeomMap BuildNodeMap(const DocumentIdentifier& doc_id,
                         const ContentNode& root) {
  NodeGeomMap map;
  BuildNodeMapInternal(doc_id, root, map);
  return map;
}
}  // namespace

std::unique_ptr<DomNodeGeometry> DomNodeGeometry::InitFromApc(
    const AnnotatedPageContent& apc) {
  TRACE_EVENT("actor", "DomNodeGeometry::InitFromApc");
  if (!apc.has_main_frame_data() ||
      !apc.main_frame_data().has_document_identifier()) {
    return std::make_unique<DomNodeGeometry>(
        base::PassKey<DomNodeGeometry>(),
        GetDomNodeResult::kNoApcMainFrameData);
  }
  auto map = BuildNodeMap(apc.main_frame_data().document_identifier(),
                          apc.root_node());
  return std::make_unique<DomNodeGeometry>(base::PassKey<DomNodeGeometry>(),
                                           std::move(map));
}

base::expected<gfx::Point, GetDomNodeResult> DomNodeGeometry::GetDomNode(
    const DomNode& node) const {
  TRACE_EVENT("actor", "DomNodeGeometry::GetDomNode");
  auto result = InternalGetDomNode(node);
  RecordGetDomNodeResult(result.error_or(GetDomNodeResult::kSuccess));
  return result;
}

base::expected<gfx::Point, GetDomNodeResult>
DomNodeGeometry::InternalGetDomNode(const DomNode& node) const {
  if (init_error_.has_value()) {
    return base::unexpected(init_error_.value());
  }
  auto it = node_map_.find(node);
  if (it == node_map_.end()) {
    return base::unexpected(GetDomNodeResult::kNodeNotFoundInApc);
  }
  const ContentAttributes& attr = it->second;
  if (!attr.has_geometry()) {
    return base::unexpected(GetDomNodeResult::kNoGeometry);
  }
  const auto& geom = attr.geometry();
  if (!geom.has_visible_bounding_box()) {
    return base::unexpected(GetDomNodeResult::kOffScreen);
  }
  const auto& bounding_box = geom.visible_bounding_box();
  gfx::Rect rect(bounding_box.x(), bounding_box.y(), bounding_box.width(),
                 bounding_box.height());
  if (rect.IsEmpty()) {
    return base::unexpected(GetDomNodeResult::kEmptyBoundingBox);
  }
  return rect.CenterPoint();
}

DomNodeGeometry::DomNodeGeometry(base::PassKey<DomNodeGeometry>,
                                 GetDomNodeResult init_error)
    : init_error_(init_error), node_map_() {}

DomNodeGeometry::DomNodeGeometry(base::PassKey<DomNodeGeometry>,
                                 NodeGeomMap node_map)
    : init_error_(std::nullopt), node_map_(std::move(node_map)) {}

DomNodeGeometry::~DomNodeGeometry() = default;

}  // namespace actor::ui
