// 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 "ui/views/accessibility/tree/view_accessibility_ax_tree_source.h"

#include <string>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/ax_tree_data.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/views/accessibility/ax_virtual_view.h"
#include "ui/views/accessibility/tree/widget_view_ax_cache.h"

namespace views {

ViewAccessibilityAXTreeSource::ViewAccessibilityAXTreeSource(
    ui::AXNodeID root_id,
    const ui::AXTreeID& tree_id,
    WidgetViewAXCache* cache)
    : root_id_(root_id), tree_id_(tree_id), cache_(cache) {
  CHECK_NE(root_id_, ui::kInvalidAXNodeID);
  CHECK_NE(tree_id_, ui::AXTreeIDUnknown());
}

ViewAccessibilityAXTreeSource::~ViewAccessibilityAXTreeSource() = default;

void ViewAccessibilityAXTreeSource::HandleAccessibleAction(
    const ui::AXActionData& action) {
  int id = action.target_node_id;

  // In Views, we only support setting the selection within a single node,
  // not across multiple nodes like on the web.
  if (action.action == ax::mojom::Action::kSetSelection) {
    CHECK_EQ(action.anchor_node_id, action.focus_node_id);
    id = action.anchor_node_id;
  }

  // TODO(crbug.com/40672441): Add a convenience virtual function
  // HandleAccessibleAction on ViewAccessibility once AXVirtualView, a subclass
  // of ViewAccessibility, doesn't need to extend the AXPlatformNodeDelegate
  // anymore -- there's currently a function with a conflicting name.
  if (ViewAccessibility* node = GetFromId(id)) {
    if (View* view = node->view()) {
      view->HandleAccessibleAction(action);
    } else if (AXVirtualView* virtual_view =
                   static_cast<AXVirtualView*>(node)) {
      virtual_view->HandleAccessibleAction(action);
    }
  }
}

bool ViewAccessibilityAXTreeSource::GetTreeData(
    ui::AXTreeData* tree_data) const {
  tree_data->tree_id = tree_id_;
  tree_data->parent_tree_id = parent_tree_id_;
  tree_data->loaded = true;
  tree_data->loading_progress = 1.0;
  const ui::AXNodeID focus_id =
      transient_focus_id_for_serialization_.value_or(focused_node_id_);
  tree_data->focus_id = focus_id;

  // Populate text selection fields from the focused node's attributes.
  if (focus_id != ui::kInvalidAXNodeID) {
    if (ViewAccessibility* focused_node = cache_->Get(focus_id)) {
      ui::AXNodeData node_data;
      focused_node->GetAccessibleNodeData(&node_data);
      if (node_data.HasIntAttribute(ax::mojom::IntAttribute::kTextSelStart) &&
          node_data.HasIntAttribute(ax::mojom::IntAttribute::kTextSelEnd)) {
        tree_data->sel_anchor_object_id = focus_id;
        tree_data->sel_focus_object_id = focus_id;
        tree_data->sel_anchor_offset =
            node_data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart);
        tree_data->sel_focus_offset =
            node_data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd);
        tree_data->sel_is_backward =
            tree_data->sel_anchor_offset > tree_data->sel_focus_offset;
      }
    }
  }

  return true;
}

ViewAccessibility* ViewAccessibilityAXTreeSource::GetRoot() const {
  return cache_->Get(root_id_);
}

ViewAccessibility* ViewAccessibilityAXTreeSource::GetFromId(int32_t id) const {
  return cache_->Get(id);
}

int32_t ViewAccessibilityAXTreeSource::GetId(ViewAccessibility* node) const {
  if (!node) {
    return ui::kInvalidAXNodeID;
  }
  return node->GetUniqueId();
}

void ViewAccessibilityAXTreeSource::CacheChildrenIfNeeded(
    ViewAccessibility* node) {
  if (!node) {
    return;
  }
  cache_->CacheChildrenIfNeeded(node);
}

size_t ViewAccessibilityAXTreeSource::GetChildCount(
    ViewAccessibility* node) const {
  if (!node) {
    return 0;
  }
  return cache_->CachedChildCount(node);
}

ViewAccessibility* ViewAccessibilityAXTreeSource::ChildAt(
    ViewAccessibility* node,
    size_t index) const {
  if (!node) {
    return nullptr;
  }

  return cache_->CachedChildAt(node, index);
}

void ViewAccessibilityAXTreeSource::ClearChildCache(ViewAccessibility* node) {
  if (!node) {
    return;
  }
  cache_->RemoveFromChildCache(node);
}

ViewAccessibility* ViewAccessibilityAXTreeSource::GetParent(
    ViewAccessibility* node) const {
  if (!node || node->GetUniqueId() == root_id_) {
    return nullptr;
  }
  return node->GetViewAccessibilityParent();
}

bool ViewAccessibilityAXTreeSource::IsIgnored(ViewAccessibility* node) const {
  if (!node) {
    return false;
  }
  return node->GetIsIgnored();
}

bool ViewAccessibilityAXTreeSource::IsEqual(ViewAccessibility* node1,
                                            ViewAccessibility* node2) const {
  if (!node1 || !node2) {
    return false;
  }
  return node1->GetUniqueId() == node2->GetUniqueId();
}

ViewAccessibility* ViewAccessibilityAXTreeSource::GetNull() const {
  return nullptr;
}

std::string ViewAccessibilityAXTreeSource::GetDebugString(
    ViewAccessibility* node) const {
  if (!node) {
    return "null";
  }
  return node->GetDebugString();
}

void ViewAccessibilityAXTreeSource::SerializeNode(
    ViewAccessibility* node,
    ui::AXNodeData* out_data) const {
  if (!node || !out_data) {
    return;
  }
  node->GetAccessibleNodeData(out_data);
  node->ClearPendingAriaNotifications();
}

std::string ViewAccessibilityAXTreeSource::ToString(ViewAccessibility* root,
                                                    std::string prefix) {
  if (!root) {
    return prefix + "null\n";
  }

  ui::AXNodeData data;
  SerializeNode(root, &data);
  std::string output = prefix + data.ToString() + '\n';

  auto children = root->GetChildren();

  prefix += prefix[0];
  for (auto child : children) {
    output += ToString(child, prefix);
  }

  return output;
}

}  // namespace views
