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

#import "ui/accessibility/platform/browser_accessibility_ios.h"

#import "base/apple/foundation_util.h"
#import "base/memory/ptr_util.h"
#import "ui/accessibility/platform/browser_accessibility_manager_ios.h"
#import "ui/accessibility/platform/ax_platform_node_ios.h"
#import "ui/accessibility/platform/ax_platform_node_ui_kit_element.h"

namespace ui {

// static
std::unique_ptr<BrowserAccessibility> BrowserAccessibility::Create(
    BrowserAccessibilityManager* manager,
    AXNode* node) {
  return base::WrapUnique(new BrowserAccessibilityIOS(manager, node));
}

BrowserAccessibilityIOS::BrowserAccessibilityIOS(
    BrowserAccessibilityManager* manager,
    AXNode* node)
    : BrowserAccessibility(manager, node) {}

BrowserAccessibilityIOS::~BrowserAccessibilityIOS() = default;

void BrowserAccessibilityIOS::OnDataChanged() {
  // BrowserAccessibility::OnDataChanged is responsible for creating the
  // AXPlatformNode (through UpdatePlatformNode), and this event should be fired
  // before the node gets created. This is the wrong location for this event to
  // be fired from, though, and the TODO mentioned below should fix this by
  // moving this to BrowserAccessibilityIOS::FireGeneratedEvent.
  if (platform_node_) {
    // TODO(crbug.com/336611337): Investigate why this needs to be called
    // unconditionally rather than just for children changes.
    [base::apple::ObjCCastStrict<AXPlatformNodeUIKitElement>(
        platform_node_->GetNativeViewAccessible().Get()) childrenChanged];
  }

  BrowserAccessibility::OnDataChanged();
}

void BrowserAccessibilityIOS::UpdatePlatformNode() {
  if (!ShouldHavePlatformNode()) {
    platform_node_.reset();
    return;
  }
  if (!platform_node_) {
    CreatePlatformNode();
  }
}

size_t BrowserAccessibilityIOS::PlatformChildCount() const {
  size_t child_count = BrowserAccessibility::PlatformChildCount();

  // If this is a table, include the extra fake nodes generated by
  // AXTableInfo, for the column nodes and the table header container, all of
  // which are important only on macOS and iOS.
  // TODO(crbug.com/336611337): Find a way to share all the extra table nodes
  // logic with BrowserAccessibilityMac.
  const std::vector<raw_ptr<AXNode, VectorExperimental>>* extra_mac_nodes =
      node()->GetExtraMacNodes();
  if (!extra_mac_nodes) {
    return child_count;
  }

  return child_count + extra_mac_nodes->size();
}

BrowserAccessibility* BrowserAccessibilityIOS::PlatformGetChild(
    size_t child_index) const {
  if (child_index < BrowserAccessibility::PlatformChildCount()) {
    return BrowserAccessibility::PlatformGetChild(child_index);
  }

  if (child_index >= PlatformChildCount()) {
    return nullptr;
  }

  // If this is a table, include the extra fake nodes generated by
  // AXTableInfo, for the column nodes and the table header container, all of
  // which are only important on macOS and iOS.
  const std::vector<raw_ptr<AXNode, VectorExperimental>>* extra_mac_nodes =
      node()->GetExtraMacNodes();
  if (!extra_mac_nodes || extra_mac_nodes->empty()) {
    return nullptr;
  }

  child_index -= BrowserAccessibility::PlatformChildCount();
  if (child_index < extra_mac_nodes->size()) {
    return manager_->GetFromAXNode((*extra_mac_nodes)[child_index]);
  }

  return nullptr;
}

BrowserAccessibility* BrowserAccessibilityIOS::PlatformGetFirstChild() const {
  return PlatformGetChild(0);
}

BrowserAccessibility* BrowserAccessibilityIOS::PlatformGetLastChild() const {
  const std::vector<raw_ptr<AXNode, VectorExperimental>>* extra_mac_nodes =
      node()->GetExtraMacNodes();
  if (extra_mac_nodes && !extra_mac_nodes->empty()) {
    return manager_->GetFromAXNode(extra_mac_nodes->back());
  }
  return BrowserAccessibility::PlatformGetLastChild();
}

BrowserAccessibility* BrowserAccessibilityIOS::PlatformGetNextSibling() const {
  BrowserAccessibility* parent = PlatformGetParent();
  if (parent) {
    size_t next_child_index =
        node()->GetUnignoredIndexInParentCrossingTreeBoundary() + 1;
    if (next_child_index >= parent->InternalChildCount() &&
        next_child_index < parent->PlatformChildCount()) {
      // Get the extra_mac_node.
      return parent->PlatformGetChild(next_child_index);
    } else if (next_child_index >= parent->PlatformChildCount()) {
      return nullptr;
    }
  }
  return BrowserAccessibility::PlatformGetNextSibling();
}

BrowserAccessibility* BrowserAccessibilityIOS::PlatformGetPreviousSibling()
    const {
  BrowserAccessibility* parent = PlatformGetParent();
  if (parent) {
    size_t child_index =
        node()->GetUnignoredIndexInParentCrossingTreeBoundary();
    if (child_index > parent->InternalChildCount() &&
        child_index <= parent->PlatformChildCount()) {
      // Get the extra_mac_node.
      return parent->PlatformGetChild(child_index - 1);
    } else if (child_index == 0) {
      return nullptr;
    }
  }
  return BrowserAccessibility::PlatformGetPreviousSibling();
}

gfx::NativeViewAccessible BrowserAccessibilityIOS::GetNativeViewAccessible() {
  return platform_node_ ? platform_node_->GetNativeViewAccessible()
                        : gfx::NativeViewAccessible();
}

AXPlatformNode* BrowserAccessibilityIOS::GetAXPlatformNode() const {
  return platform_node_.get();
}

float BrowserAccessibilityIOS::GetDeviceScaleFactor() const {
  return manager_->device_scale_factor();
}

void BrowserAccessibilityIOS::CreatePlatformNode() {
  CHECK(!platform_node_);
  platform_node_ = AXPlatformNode::Create(*this);
  platform_node()->SetIOSDelegate(this);
}

}  // namespace ui
