// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "fake_flatland.h"

#include <zircon/types.h>

#include <algorithm>  // For std::remove_if
#include <memory>

#include "flutter/fml/logging.h"

namespace flutter_runner::testing {

FakeFlatland::FakeFlatland()
    : allocator_binding_(this),
      flatland_binding_(this),
      present_handler_([](auto args) {}) {}

FakeFlatland::~FakeFlatland() = default;

fuchsia::ui::composition::AllocatorHandle FakeFlatland::ConnectAllocator(
    async_dispatcher_t* dispatcher) {
  FML_CHECK(!allocator_binding_.is_bound());

  fuchsia::ui::composition::AllocatorHandle allocator;
  allocator_binding_.Bind(allocator.NewRequest(), dispatcher);

  return allocator;
}

fuchsia::ui::composition::FlatlandHandle FakeFlatland::ConnectFlatland(
    async_dispatcher_t* dispatcher) {
  FML_CHECK(!flatland_binding_.is_bound());

  fuchsia::ui::composition::FlatlandHandle flatland;
  flatland_binding_.Bind(flatland.NewRequest(), dispatcher);

  return flatland;
}

void FakeFlatland::Disconnect(fuchsia::ui::composition::FlatlandError error) {
  flatland_binding_.events().OnError(error);
  flatland_binding_.Unbind();
  allocator_binding_
      .Unbind();  // TODO(fxb/85619): Does the real Scenic unbind this when
                  // Flatland has an error?  Or is it independent?
}

void FakeFlatland::SetPresentHandler(PresentHandler present_handler) {
  present_handler_ =
      present_handler ? std::move(present_handler) : [](auto args) {};
}

void FakeFlatland::FireOnNextFrameBeginEvent(
    fuchsia::ui::composition::OnNextFrameBeginValues
        on_next_frame_begin_values) {
  flatland_binding_.events().OnNextFrameBegin(
      std::move(on_next_frame_begin_values));
}

void FakeFlatland::FireOnFramePresentedEvent(
    fuchsia::scenic::scheduling::FramePresentedInfo frame_presented_info) {
  flatland_binding_.events().OnFramePresented(std::move(frame_presented_info));
}

void FakeFlatland::NotImplemented_(const std::string& name) {
  FML_LOG(FATAL) << "FakeFlatland does not implement " << name;
}

void FakeFlatland::RegisterBufferCollection(
    fuchsia::ui::composition::RegisterBufferCollectionArgs args,
    RegisterBufferCollectionCallback callback) {
  auto [export_token_koid, _] = GetKoids(args.export_token());

  // Callers must not set both.
  ZX_ASSERT(!args.has_buffer_collection_token2() ||
            !args.has_buffer_collection_token());
  fuchsia::sysmem2::BufferCollectionTokenHandle sysmem_token;
  if (args.has_buffer_collection_token2()) {
    sysmem_token = std::move(*args.mutable_buffer_collection_token2());
  } else {
    sysmem_token = fuchsia::sysmem2::BufferCollectionTokenHandle(
        args.mutable_buffer_collection_token()->TakeChannel());
  }

  auto [__, emplace_binding_success] =
      graph_bindings_.buffer_collections.emplace(
          export_token_koid,
          BufferCollectionBinding{
              .export_token = std::move(*args.mutable_export_token()),
              .sysmem_token = std::move(sysmem_token),
              .usage = args.usage(),
          });
  // TODO(fxb/85619): Disconnect the Allocator here
  FML_CHECK(emplace_binding_success)
      << "FakeFlatland::RegisterBufferCollection: BufferCollection already "
         "exists with koid "
      << export_token_koid;
}

void FakeFlatland::Present(fuchsia::ui::composition::PresentArgs args) {
  // Each FIDL call between this `Present()` and the last one mutated the
  // `pending_graph_` independently of the `current_graph_`.  Only the
  // `current_graph_` is visible externally in the test.
  //
  // `Present()` updates the current graph with a deep clone of the pending one.
  current_graph_ = pending_graph_.Clone();

  present_handler_(std::move(args));
}

void FakeFlatland::CreateView(
    fuchsia::ui::views::ViewCreationToken token,
    fidl::InterfaceRequest<fuchsia::ui::composition::ParentViewportWatcher>
        parent_viewport_watcher) {
  CreateView2(std::move(token), fuchsia::ui::views::ViewIdentityOnCreation{},
              fuchsia::ui::composition::ViewBoundProtocols{},
              std::move(parent_viewport_watcher));
}

void FakeFlatland::CreateView2(
    fuchsia::ui::views::ViewCreationToken token,
    fuchsia::ui::views::ViewIdentityOnCreation view_identity,
    fuchsia::ui::composition::ViewBoundProtocols view_protocols,
    fidl::InterfaceRequest<fuchsia::ui::composition::ParentViewportWatcher>
        parent_viewport_watcher) {
  // TODO(fxb/85619): Handle a 2nd CreateView call
  FML_CHECK(!pending_graph_.view.has_value());
  FML_CHECK(!graph_bindings_.viewport_watcher.has_value());

  auto view_token_koids = GetKoids(token);
  auto view_ref_koids = GetKoids(view_identity.view_ref);
  auto view_ref_control_koids = GetKoids(view_identity.view_ref_control);
  FML_CHECK(view_ref_koids.first == view_ref_control_koids.second);
  FML_CHECK(view_ref_koids.second == view_ref_control_koids.first);

  pending_graph_.view.emplace(FakeView{
      .view_token = view_token_koids.first,
      .view_ref = view_ref_koids.first,
      .view_ref_control = view_ref_control_koids.first,
      .view_ref_focused =
          view_protocols.has_view_ref_focused()
              ? GetKoids(view_protocols.view_ref_focused()).first
              : zx_koid_t{},
      .focuser = view_protocols.has_view_focuser()
                     ? GetKoids(view_protocols.view_focuser()).first
                     : zx_koid_t{},
      .touch_source = view_protocols.has_touch_source()
                          ? GetKoids(view_protocols.touch_source()).first
                          : zx_koid_t{},
      .mouse_source = view_protocols.has_mouse_source()
                          ? GetKoids(view_protocols.mouse_source()).first
                          : zx_koid_t{},
      .parent_viewport_watcher = GetKoids(parent_viewport_watcher).first,
  });
  graph_bindings_.viewport_watcher.emplace(
      view_token_koids.first,
      ParentViewportWatcher(
          std::move(token), std::move(view_identity), std::move(view_protocols),
          std::move(parent_viewport_watcher), flatland_binding_.dispatcher()));
}

void FakeFlatland::CreateTransform(
    fuchsia::ui::composition::TransformId transform_id) {
  if (transform_id.value == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false)
        << "FakeFlatland::CreateTransform: TransformId 0 is invalid.";
    return;
  }
  if (pending_graph_.transform_map.count(transform_id.value) != 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::CreateTransform: TransformId "
                     << transform_id.value << " is already in use.";
    return;
  }

  auto [emplaced_transform, emplace_success] =
      pending_graph_.transform_map.emplace(
          transform_id.value, std::make_shared<FakeTransform>(FakeTransform{
                                  .id = transform_id,
                              }));
  // TODO(fxb/85619): Raise a FlatlandError here
  FML_CHECK(emplace_success)
      << "FakeFlatland::CreateTransform: Internal error (transform_map) adding "
         "transform with id: "
      << transform_id.value;

  auto [_, emplace_parent_success] = parents_map_.emplace(
      transform_id.value,
      std::make_pair(std::weak_ptr<FakeTransform>(),
                     std::weak_ptr(emplaced_transform->second)));
  // TODO(fxb/85619): Raise a FlatlandError here
  FML_CHECK(emplace_parent_success)
      << "FakeFlatland::CreateTransform: Internal error (parent_map) adding "
         "transform with id: "
      << transform_id.value;
}

void FakeFlatland::SetTranslation(
    fuchsia::ui::composition::TransformId transform_id,
    fuchsia::math::Vec translation) {
  if (transform_id.value == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false)
        << "FakeFlatland::SetTranslation: TransformId 0 is invalid.";
    return;
  }

  auto found_transform = pending_graph_.transform_map.find(transform_id.value);
  if (found_transform == pending_graph_.transform_map.end()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::SetTranslation: TransformId "
                     << transform_id.value << " does not exist.";
    return;
  }

  auto& transform = found_transform->second;
  FML_CHECK(transform);
  transform->translation = translation;
}

void FakeFlatland::SetScale(fuchsia::ui::composition::TransformId transform_id,
                            fuchsia::math::VecF scale) {
  if (transform_id.value == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::SetScale: TransformId 0 is invalid.";
    return;
  }

  auto found_transform = pending_graph_.transform_map.find(transform_id.value);
  if (found_transform == pending_graph_.transform_map.end()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::SetScale: TransformId "
                     << transform_id.value << " does not exist.";
    return;
  }

  if (scale.x == 0.f || scale.y == 0.f) {
    FML_CHECK(false) << "SetScale failed, zero values not allowed (" << scale.x
                     << ", " << scale.y << " ).";
    return;
  }

  if (isinf(scale.x) || isinf(scale.y) || isnan(scale.x) || isnan(scale.y)) {
    FML_CHECK(false) << "SetScale failed, invalid scale values (" << scale.x
                     << ", " << scale.y << " ).";
    return;
  }

  auto& transform = found_transform->second;
  FML_CHECK(transform);
  transform->scale = scale;
}

void FakeFlatland::SetOrientation(
    fuchsia::ui::composition::TransformId transform_id,
    fuchsia::ui::composition::Orientation orientation) {
  if (transform_id.value == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false)
        << "FakeFlatland::SetOrientation: TransformId 0 is invalid.";
    return;
  }

  auto found_transform = pending_graph_.transform_map.find(transform_id.value);
  if (found_transform == pending_graph_.transform_map.end()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::SetOrientation: TransformId "
                     << transform_id.value << " does not exist.";
    return;
  }

  auto& transform = found_transform->second;
  FML_CHECK(transform);
  transform->orientation = orientation;
}

void FakeFlatland::SetOpacity(
    fuchsia::ui::composition::TransformId transform_id,
    float value) {
  if (transform_id.value == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::SetOpacity: TransformId 0 is invalid.";
    return;
  }

  auto found_transform = pending_graph_.transform_map.find(transform_id.value);
  if (found_transform == pending_graph_.transform_map.end()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::SetOpacity: TransformId "
                     << transform_id.value << " does not exist.";
    return;
  }

  if (value < 0.f || value > 1.f) {
    FML_CHECK(false) << "FakeFlatland::SetOpacity: Invalid opacity value.";
  }

  auto& transform = found_transform->second;
  FML_CHECK(transform);
  transform->opacity = value;
}

void FakeFlatland::SetClipBoundary(
    fuchsia::ui::composition::TransformId transform_id,
    std::unique_ptr<fuchsia::math::Rect> bounds) {
  if (transform_id.value == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false)
        << "FakeFlatland::SetClipBoundary: TransformId 0 is invalid.";
    return;
  }

  auto found_transform = pending_graph_.transform_map.find(transform_id.value);
  if (found_transform == pending_graph_.transform_map.end()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::SetClipBoundary: TransformId "
                     << transform_id.value << " does not exist.";
    return;
  }

  auto& transform = found_transform->second;
  FML_CHECK(transform);
  if (bounds == nullptr) {
    transform->clip_bounds = std::nullopt;
    return;
  }

  transform->clip_bounds = *bounds.get();
}

// TODO(fxbug.dev/89111): Re-enable once SDK rolls.
// void FakeFlatland::SetImageOpacity(
//     fuchsia::ui::composition::ContentId content_id,
//     float opacity) {
//   if (content_id.value == 0) {
//     // TODO(fxb/85619): Raise a FlatlandError here
//     FML_CHECK(false)
//         << "FakeFlatland::SetImageOpacity: ContentId 0 is invalid.";
//     return;
//   }

//   auto found_content = pending_graph_.content_map.find(image_id.value);
//   if (found_content == pending_graph_.content_map.end()) {
//     // TODO(fxb/85619): Raise a FlatlandError here
//     FML_CHECK(false) << "FakeFlatland::SetImageOpacity: ContentId "
//                      << image_id.value << " does not exist.";
//     return;
//   }

//   auto& content = found_content->second;
//   FML_CHECK(content);
//   FakeImage* image = std::get_if<FakeImage>(content.get());
//   if (image == nullptr) {
//     // TODO(fxb/85619): Raise a FlatlandError here
//     FML_CHECK(false) << "FakeFlatland::SetImageOpacity: ContentId "
//                      << image_id.value << " is not an Image.";
//     return;
//   }

//   image->opacity = opacity;
// }

void FakeFlatland::AddChild(
    fuchsia::ui::composition::TransformId parent_transform_id,
    fuchsia::ui::composition::TransformId child_transform_id) {
  if (parent_transform_id.value == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false)
        << "FakeFlatland::AddChild: Parent TransformId 0 is invalid.";
    return;
  }
  if (child_transform_id.value == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false)
        << "FakeFlatland::AddChild: Child TransformId 0 is invalid.";
    return;
  }

  auto found_parent =
      pending_graph_.transform_map.find(parent_transform_id.value);
  if (found_parent == pending_graph_.transform_map.end()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::AddChild: Parent TransformId "
                     << parent_transform_id.value << " does not exist.";
    return;
  }
  auto found_child =
      pending_graph_.transform_map.find(child_transform_id.value);
  if (found_child == pending_graph_.transform_map.end()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::AddChild: Child TransformId "
                     << child_transform_id.value << " does not exist.";
    return;
  }
  auto found_child_old_parent = parents_map_.find(child_transform_id.value);
  if (found_child_old_parent == parents_map_.end()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false)
        << "FakeFlatland::AddChild: Internal error - Child TransformId "
        << child_transform_id.value << " is not in parents_map.";
    return;
  }
  if (found_child_old_parent->second.second.expired()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false)
        << "FakeFlatland::AddChild: Internal error - Child TransformId "
        << child_transform_id.value << " is expired in parents_map.";
    return;
  }

  auto& child = found_child->second;
  auto& new_parent = found_parent->second;
  new_parent->children.push_back(child);
  if (auto old_parent = found_child_old_parent->second.first.lock()) {
    old_parent->children.erase(std::remove_if(
        old_parent->children.begin(), old_parent->children.end(),
        [&child](const auto& transform) { return transform == child; }));
  }
  found_child_old_parent->second.first = std::weak_ptr(new_parent);
}

void FakeFlatland::RemoveChild(
    fuchsia::ui::composition::TransformId parent_transform_id,
    fuchsia::ui::composition::TransformId child_transform_id) {
  if (parent_transform_id.value == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false)
        << "FakeFlatland::RemoveChild: Parent TransformId 0 is invalid.";
    return;
  }
  if (child_transform_id.value == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false)
        << "FakeFlatland::RemoveChild: Child TransformId 0 is invalid.";
    return;
  }

  auto found_child =
      pending_graph_.transform_map.find(child_transform_id.value);
  if (found_child == pending_graph_.transform_map.end()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::RemoveChild: Child TransformId "
                     << child_transform_id.value << " does not exist.";
    return;
  }

  auto found_parent =
      pending_graph_.transform_map.find(parent_transform_id.value);
  if (found_parent == pending_graph_.transform_map.end()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::RemoveChild: Parent TransformId "
                     << parent_transform_id.value << " does not exist.";
    return;
  }

  auto found_child_parent = parents_map_.find(child_transform_id.value);
  if (found_child_parent == parents_map_.end()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false)
        << "FakeFlatland::RemoveChild: Internal error - Child TransformId "
        << child_transform_id.value << " is not in parents_map.";
    return;
  }
  if (found_child_parent->second.second.expired()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false)
        << "FakeFlatland::RemoveChild: Internal error - Child TransformId "
        << child_transform_id.value << " is expired in parents_map.";
    return;
  }
  if (found_child_parent->second.first.lock() != found_parent->second) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false)
        << "FakeFlatland::RemoveChild: Internal error - Child TransformId "
        << child_transform_id.value << " is not a child of Parent TransformId "
        << parent_transform_id.value << ".";
    return;
  }

  found_child_parent->second.first = std::weak_ptr<FakeTransform>();
  found_parent->second->children.erase(std::remove_if(
      found_parent->second->children.begin(),
      found_parent->second->children.end(),
      [child_to_remove = found_child->second](const auto& child) {
        return child == child_to_remove;
      }));
}

void FakeFlatland::SetContent(
    fuchsia::ui::composition::TransformId transform_id,
    fuchsia::ui::composition::ContentId content_id) {
  if (transform_id.value == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::SetContent: TransformId 0 is invalid.";
    return;
  }

  auto found_transform = pending_graph_.transform_map.find(transform_id.value);
  if (found_transform == pending_graph_.transform_map.end()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::SetContent: TransformId "
                     << transform_id.value << " does not exist.";
    return;
  }

  auto& transform = found_transform->second;
  FML_CHECK(transform);
  if (content_id.value == 0) {
    transform->content.reset();
    return;
  }

  auto found_content = pending_graph_.content_map.find(content_id.value);
  if (found_content == pending_graph_.content_map.end()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::SetContent: ContentId "
                     << content_id.value << " does not exist.";
    return;
  }

  auto& content = found_content->second;
  FML_CHECK(content);
  transform->content = content;
}

void FakeFlatland::SetRootTransform(
    fuchsia::ui::composition::TransformId transform_id) {
  if (transform_id.value == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false)
        << "FakeFlatland::SetRootTransform: TransformId 0 is invalid.";
    return;
  }

  auto found_new_root = pending_graph_.transform_map.find(transform_id.value);
  if (found_new_root == pending_graph_.transform_map.end()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::SetRootTransform: TransformId "
                     << transform_id.value << " does not exist.";
    return;
  }

  pending_graph_.root_transform = found_new_root->second;
}

void FakeFlatland::CreateViewport(
    fuchsia::ui::composition::ContentId viewport_id,
    fuchsia::ui::views::ViewportCreationToken token,
    fuchsia::ui::composition::ViewportProperties properties,
    fidl::InterfaceRequest<fuchsia::ui::composition::ChildViewWatcher>
        child_view_watcher) {
  if (viewport_id.value == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::CreateViewport: ContentId 0 is invalid.";
    return;
  }
  if (pending_graph_.content_map.count(viewport_id.value) != 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::CreateViewport: ContentId "
                     << viewport_id.value << " is already in use.";
    return;
  }

  auto viewport_token_koids = GetKoids(token.value);
  auto [emplaced_viewport, emplace_success] =
      pending_graph_.content_map.emplace(
          viewport_id.value,
          std::make_shared<FakeContent>(FakeViewport{
              .id = viewport_id,
              .viewport_properties = std::move(properties),
              .viewport_token = viewport_token_koids.first,
              .child_view_watcher = GetKoids(child_view_watcher).first,
          }));
  // TODO(fxb/85619): Raise a FlatlandError here
  FML_CHECK(emplace_success)
      << "FakeFlatland::CreateViewport: Internal error (content_map) adding "
         "viewport with id: "
      << viewport_id.value;

  auto [_, emplace_binding_success] = graph_bindings_.view_watchers.emplace(
      viewport_token_koids.first,
      ChildViewWatcher(std::move(token), std::move(child_view_watcher),
                       flatland_binding_.dispatcher()));
  // TODO(fxb/85619): Raise a FlatlandError here
  FML_CHECK(emplace_binding_success)
      << "FakeFlatland::CreateViewport: Internal error (view_watcher) adding "
         "viewport with id: "
      << viewport_id.value;
}

void FakeFlatland::CreateImage(
    fuchsia::ui::composition::ContentId image_id,
    fuchsia::ui::composition::BufferCollectionImportToken import_token,
    uint32_t vmo_index,
    fuchsia::ui::composition::ImageProperties properties) {
  if (image_id.value == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::CreateImage: ContentId 0 is invalid.";
    return;
  }
  if (pending_graph_.content_map.count(image_id.value) != 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::CreateImage: ContentId "
                     << image_id.value << " is already in use.";
    return;
  }

  auto import_token_koids = GetKoids(import_token);
  auto [emplaced_image, emplace_success] = pending_graph_.content_map.emplace(
      image_id.value, std::make_shared<FakeContent>(FakeImage{
                          .id = image_id,
                          .image_properties = std::move(properties),
                          .import_token = import_token_koids.first,
                          .vmo_index = vmo_index,
                      }));
  // TODO(fxb/85619): Raise a FlatlandError here
  FML_CHECK(emplace_success)
      << "FakeFlatland::CreateImage: Internal error (content_map) adding "
         "image with id: "
      << image_id.value;

  auto [_, emplace_binding_success] = graph_bindings_.images.emplace(
      import_token_koids.first, ImageBinding{
                                    .import_token = std::move(import_token),
                                });
  // TODO(fxb/85619): Raise a FlatlandError here
  FML_CHECK(emplace_binding_success)
      << "FakeFlatland::CreateImage: Internal error (images_binding) adding "
         "viewport with id: "
      << image_id.value;
}

void FakeFlatland::SetImageSampleRegion(
    fuchsia::ui::composition::ContentId image_id,
    fuchsia::math::RectF rect) {
  if (image_id.value == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false)
        << "FakeFlatland::SetImageSampleRegion: ContentId 0 is invalid.";
    return;
  }

  auto found_content = pending_graph_.content_map.find(image_id.value);
  if (found_content == pending_graph_.content_map.end()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::SetImageSampleRegion: ContentId "
                     << image_id.value << " does not exist.";
    return;
  }

  auto& content = found_content->second;
  FML_CHECK(content);
  FakeImage* image = std::get_if<FakeImage>(content.get());
  if (image == nullptr) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::SetImageSampleRegion: ContentId "
                     << image_id.value << " is not an Image.";
    return;
  }

  image->sample_region = rect;
}

void FakeFlatland::SetImageDestinationSize(
    fuchsia::ui::composition::ContentId image_id,
    fuchsia::math::SizeU size) {
  if (image_id.value == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false)
        << "FakeFlatland::SetImageDestinationSize: ContentId 0 is invalid.";
    return;
  }

  auto found_content = pending_graph_.content_map.find(image_id.value);
  if (found_content == pending_graph_.content_map.end()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::SetImageDestinationSize: ContentId "
                     << image_id.value << " does not exist.";
    return;
  }

  auto& content = found_content->second;
  FML_CHECK(content);
  FakeImage* image = std::get_if<FakeImage>(content.get());
  if (image == nullptr) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::SetImageDestinationSize: ContentId "
                     << image_id.value << " is not an Image.";
    return;
  }

  image->destination_size = size;
}

void FakeFlatland::SetImageBlendingFunction(
    fuchsia::ui::composition::ContentId image_id,
    fuchsia::ui::composition::BlendMode blend_mode) {
  if (image_id.value == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false)
        << "FakeFlatland::SetImageDestinationSize: ContentId 0 is invalid.";
    return;
  }

  auto found_content = pending_graph_.content_map.find(image_id.value);
  if (found_content == pending_graph_.content_map.end()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::SetImageDestinationSize: ContentId "
                     << image_id.value << " does not exist.";
    return;
  }

  auto& content = found_content->second;
  FML_CHECK(content);
  FakeImage* image = std::get_if<FakeImage>(content.get());
  if (image == nullptr) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::SetImageDestinationSize: ContentId "
                     << image_id.value << " is not an Image.";
    return;
  }

  image->blend_mode = blend_mode;
}

void FakeFlatland::SetViewportProperties(
    fuchsia::ui::composition::ContentId viewport_id,
    fuchsia::ui::composition::ViewportProperties properties) {
  if (viewport_id.value == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false)
        << "FakeFlatland::SetViewportProperties: ContentId 0 is invalid.";
    return;
  }

  auto found_content = pending_graph_.content_map.find(viewport_id.value);
  if (found_content == pending_graph_.content_map.end()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::SetViewportProperties: ContentId "
                     << viewport_id.value << " does not exist.";
    return;
  }

  auto& content = found_content->second;
  FML_CHECK(content);
  FakeViewport* viewport = std::get_if<FakeViewport>(content.get());
  if (viewport == nullptr) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::SetViewportProperties: ContentId "
                     << viewport_id.value << " is not a Viewport.";
    return;
  }

  viewport->viewport_properties = std::move(properties);
}

void FakeFlatland::ReleaseTransform(
    fuchsia::ui::composition::TransformId transform_id) {
  if (transform_id.value == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false)
        << "FakeFlatland::ReleaseTransform: TransformId 0 is invalid.";
    return;
  }

  size_t erased = pending_graph_.transform_map.erase(transform_id.value);
  if (erased == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::ReleaseTransform: TransformId "
                     << transform_id.value << " does not exist.";
  }

  size_t parents_erased = parents_map_.erase(transform_id.value);
  if (parents_erased == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::ReleaseTransform: TransformId "
                     << transform_id.value << " does not exist in parents_map.";
  }
}

void FakeFlatland::ReleaseViewport(
    fuchsia::ui::composition::ContentId viewport_id,
    ReleaseViewportCallback callback) {
  if (viewport_id.value == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false)
        << "FakeFlatland::ReleaseViewport: ContentId 0 is invalid.";
    return;
  }

  auto found_content = pending_graph_.content_map.find(viewport_id.value);
  if (found_content == pending_graph_.content_map.end()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::ReleaseViewport: ContentId "
                     << viewport_id.value << " does not exist.";
    return;
  }

  auto& content = found_content->second;
  FML_CHECK(content);
  FakeViewport* viewport = std::get_if<FakeViewport>(content.get());
  if (viewport == nullptr) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::ReleaseViewport: ContentId "
                     << viewport_id.value << " is not a Viewport.";
    return;
  }

  pending_graph_.content_map.erase(found_content);
}

void FakeFlatland::ReleaseImage(fuchsia::ui::composition::ContentId image_id) {
  if (image_id.value == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::ReleaseImage: ContentId 0 is invalid.";
    return;
  }

  auto found_content = pending_graph_.content_map.find(image_id.value);
  if (found_content == pending_graph_.content_map.end()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::ReleaseImage: ContentId "
                     << image_id.value << " does not exist.";
    return;
  }

  auto& content = found_content->second;
  FML_CHECK(content);
  FakeImage* image = std::get_if<FakeImage>(content.get());
  if (image == nullptr) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::ReleaseImage: ContentId "
                     << image_id.value << " is not a Viewport.";
    return;
  }

  pending_graph_.content_map.erase(found_content);
}

void FakeFlatland::SetHitRegions(
    fuchsia::ui::composition::TransformId transform_id,
    std::vector<fuchsia::ui::composition::HitRegion> regions) {
  if (transform_id.value == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false)
        << "FakeFlatland::SetTranslation: TransformId 0 is invalid.";
    return;
  }

  auto found_transform = pending_graph_.transform_map.find(transform_id.value);
  if (found_transform == pending_graph_.transform_map.end()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::SetTranslation: TransformId "
                     << transform_id.value << " does not exist.";
    return;
  }

  auto& transform = found_transform->second;
  FML_CHECK(transform);
  transform->hit_regions = std::move(regions);
}

void FakeFlatland::SetInfiniteHitRegion(
    fuchsia::ui::composition::TransformId transform_id,
    fuchsia::ui::composition::HitTestInteraction hit_test) {
  if (transform_id.value == 0) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false)
        << "FakeFlatland::SetTranslation: TransformId 0 is invalid.";
    return;
  }

  auto found_transform = pending_graph_.transform_map.find(transform_id.value);
  if (found_transform == pending_graph_.transform_map.end()) {
    // TODO(fxb/85619): Raise a FlatlandError here
    FML_CHECK(false) << "FakeFlatland::SetTranslation: TransformId "
                     << transform_id.value << " does not exist.";
    return;
  }

  auto& transform = found_transform->second;
  ZX_ASSERT(transform);
  transform->hit_regions = {kInfiniteHitRegion};
}

void FakeFlatland::Clear() {
  parents_map_.clear();
  pending_graph_.Clear();
}

void FakeFlatland::SetDebugName(std::string debug_name) {
  debug_name_ = std::move(debug_name);
}

FakeFlatland::ParentViewportWatcher::ParentViewportWatcher(
    fuchsia::ui::views::ViewCreationToken view_token,
    fuchsia::ui::views::ViewIdentityOnCreation view_identity,
    fuchsia::ui::composition::ViewBoundProtocols view_protocols,
    fidl::InterfaceRequest<fuchsia::ui::composition::ParentViewportWatcher>
        parent_viewport_watcher,
    async_dispatcher_t* dispatcher)
    : view_token(std::move(view_token)),
      view_identity(std::move(view_identity)),
      view_protocols(std::move(view_protocols)),
      parent_viewport_watcher(this,
                              std::move(parent_viewport_watcher),
                              dispatcher) {}

FakeFlatland::ParentViewportWatcher::ParentViewportWatcher(
    ParentViewportWatcher&& other)
    : view_token(std::move(other.view_token)),
      view_identity(std::move(other.view_identity)),
      view_protocols(std::move(other.view_protocols)),
      parent_viewport_watcher(this,
                              other.parent_viewport_watcher.Unbind(),
                              other.parent_viewport_watcher.dispatcher()) {}

FakeFlatland::ParentViewportWatcher::~ParentViewportWatcher() = default;

void FakeFlatland::ParentViewportWatcher::NotImplemented_(
    const std::string& name) {
  FML_LOG(FATAL) << "FakeFlatland::ParentViewportWatcher does not implement "
                 << name;
}

void FakeFlatland::ParentViewportWatcher::GetLayout(
    GetLayoutCallback callback) {
  NotImplemented_("GetLayout");
}

void FakeFlatland::ParentViewportWatcher::GetStatus(
    GetStatusCallback callback) {
  NotImplemented_("GetStatus");
}

FakeFlatland::ChildViewWatcher::ChildViewWatcher(
    fuchsia::ui::views::ViewportCreationToken viewport_token,
    fidl::InterfaceRequest<fuchsia::ui::composition::ChildViewWatcher>
        child_view_watcher,
    async_dispatcher_t* dispatcher)
    : viewport_token(std::move(viewport_token)),
      child_view_watcher(this, std::move(child_view_watcher), dispatcher) {}

FakeFlatland::ChildViewWatcher::ChildViewWatcher(ChildViewWatcher&& other)
    : viewport_token(std::move(other.viewport_token)),
      child_view_watcher(this,
                         other.child_view_watcher.Unbind(),
                         other.child_view_watcher.dispatcher()) {}

FakeFlatland::ChildViewWatcher::~ChildViewWatcher() = default;

void FakeFlatland::ChildViewWatcher::NotImplemented_(const std::string& name) {
  FML_LOG(FATAL) << "FakeFlatland::ChildViewWatcher does not implement "
                 << name;
}

void FakeFlatland::ChildViewWatcher::GetStatus(GetStatusCallback callback) {
  NotImplemented_("GetStatus");
}

}  // namespace flutter_runner::testing
