/* SPDX-FileCopyrightText: 2023 Blender Foundation
 *
 * SPDX-License-Identifier: GPL-2.0-or-later */

/** \file
 * \ingroup edinterface
 */

#include "UI_interface.h"

#include <cstdio>
#include <memory>

#include "BLT_translation.h"

#include "DNA_collection_types.h"
#include "DNA_object_types.h"

#include "BKE_context.h"
#include "BKE_light_linking.h"

#include "RNA_access.h"
#include "RNA_prototypes.h"

#include "UI_interface.h"
#include "UI_interface.hh"
#include "UI_resources.h"
#include "UI_tree_view.hh"

#include "WM_api.h"

#include "ED_undo.h"

namespace blender::ui::light_linking {

namespace {

class CollectionDropTarget : public DropTargetInterface {
 public:
  CollectionDropTarget(Collection &collection) : collection_(collection) {}

  bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override
  {
    if (drag.type != WM_DRAG_ID) {
      return false;
    }

    const wmDragID *drag_id = static_cast<wmDragID *>(drag.ids.first);
    if (!drag_id) {
      return false;
    }

    /* The dragged IDs are guaranteed to be the same type, so only check the type of the first one.
     */
    const ID_Type id_type = GS(drag_id->id->name);
    if (!ELEM(id_type, ID_OB, ID_GR)) {
      *r_disabled_hint = "Can only add objects and collections to the light linking collection";
      return false;
    }

    return true;
  }

  std::string drop_tooltip(const DragInfo & /*drag*/) const override
  {
    return TIP_("Add to light linking collection");
  }

  bool on_drop(struct bContext *C, const DragInfo &drag) const override
  {
    Main *bmain = CTX_data_main(C);
    Scene *scene = CTX_data_scene(C);

    LISTBASE_FOREACH (wmDragID *, drag_id, &drag.drag_data.ids) {
      BKE_light_linking_add_receiver_to_collection(
          bmain, &collection_, drag_id->id, COLLECTION_LIGHT_LINKING_STATE_INCLUDE);
    }

    /* It is possible that the light linking collection is also used by the view layer.
     * For this case send a notifier so that the UI is updated for the changes in the collection
     * content. */
    WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);

    ED_undo_push(C, "Add to light linking collection");

    return true;
  }

 private:
  Collection &collection_;
};

class CollectionViewItem : public BasicTreeViewItem {
 public:
  CollectionViewItem(Collection &collection,
                     ID &id,
                     CollectionLightLinking &collection_light_linking,
                     const BIFIconID icon)
      : BasicTreeViewItem(id.name + 2, icon),
        collection_(collection),
        id_(&id),
        collection_light_linking_(collection_light_linking)
  {
  }

  void build_row(uiLayout &row) override
  {
    add_label(row);

    uiLayout *sub = uiLayoutRow(&row, true);
    uiLayoutSetPropDecorate(sub, false);

    build_state_button(*sub);
    build_remove_button(*sub);
  }

 private:
  int get_state_icon() const
  {
    /* TODO(sergey): Use proper icons. */
    switch (collection_light_linking_.link_state) {
      case COLLECTION_LIGHT_LINKING_STATE_INCLUDE:
        return ICON_OUTLINER_OB_LIGHT;
      case COLLECTION_LIGHT_LINKING_STATE_EXCLUDE:
        return ICON_LIGHT;
    }
    BLI_assert_unreachable();
    return ICON_NONE;
  }

  static void link_state_toggle(CollectionLightLinking &collection_light_linking)
  {
    switch (collection_light_linking.link_state) {
      case COLLECTION_LIGHT_LINKING_STATE_INCLUDE:
        collection_light_linking.link_state = COLLECTION_LIGHT_LINKING_STATE_EXCLUDE;
        return;
      case COLLECTION_LIGHT_LINKING_STATE_EXCLUDE:
        collection_light_linking.link_state = COLLECTION_LIGHT_LINKING_STATE_INCLUDE;
        return;
    }

    BLI_assert_unreachable();
  }

  void build_state_button(uiLayout &row)
  {
    uiBlock *block = uiLayoutGetBlock(&row);
    const int icon = get_state_icon();

    PointerRNA collection_light_linking_ptr;
    RNA_pointer_create(&collection_.id,
                       &RNA_CollectionLightLinking,
                       &collection_light_linking_,
                       &collection_light_linking_ptr);

    uiBut *button = uiDefIconButR(block,
                                  UI_BTYPE_BUT,
                                  0,
                                  icon,
                                  0,
                                  0,
                                  UI_UNIT_X,
                                  UI_UNIT_Y,
                                  &collection_light_linking_ptr,
                                  "link_state",
                                  0,
                                  0.0f,
                                  0.0f,
                                  0.0f,
                                  0.0f,
                                  nullptr);

    UI_but_func_set(button, [this](bContext &) { link_state_toggle(collection_light_linking_); });
  }

  void build_remove_button(uiLayout &row)
  {
    PointerRNA id_ptr;
    RNA_id_pointer_create(id_, &id_ptr);

    PointerRNA collection_ptr;
    RNA_id_pointer_create(&collection_.id, &collection_ptr);

    uiLayoutSetContextPointer(&row, "id", &id_ptr);
    uiLayoutSetContextPointer(&row, "collection", &collection_ptr);

    uiItemO(&row, "", ICON_X, "OBJECT_OT_light_linking_unlink_from_collection");
  }

  Collection &collection_;

  ID *id_{nullptr};
  CollectionLightLinking &collection_light_linking_;
};

class CollectionView : public AbstractTreeView {
 public:
  explicit CollectionView(Collection &collection) : collection_(collection) {}

  void build_tree() override
  {
    LISTBASE_FOREACH (CollectionChild *, collection_child, &collection_.children) {
      Collection *child_collection = collection_child->collection;
      add_tree_item<CollectionViewItem>(collection_,
                                        child_collection->id,
                                        collection_child->light_linking,
                                        ICON_OUTLINER_COLLECTION);
    }

    LISTBASE_FOREACH (CollectionObject *, collection_object, &collection_.gobject) {
      Object *child_object = collection_object->ob;
      add_tree_item<CollectionViewItem>(
          collection_, child_object->id, collection_object->light_linking, ICON_OBJECT_DATA);
    }
  }

  std::unique_ptr<DropTargetInterface> create_drop_target() override
  {
    return std::make_unique<CollectionDropTarget>(collection_);
  }

 private:
  Collection &collection_;
};

}  // namespace

}  // namespace blender::ui::light_linking

namespace ui = blender::ui;

void uiTemplateLightLinkingCollection(struct uiLayout *layout,
                                      struct PointerRNA *ptr,
                                      const char *propname)
{
  if (!ptr->data) {
    return;
  }

  PropertyRNA *prop = RNA_struct_find_property(ptr, propname);
  if (!prop) {
    printf(
        "%s: property not found: %s.%s\n", __func__, RNA_struct_identifier(ptr->type), propname);
    return;
  }

  if (RNA_property_type(prop) != PROP_POINTER) {
    printf("%s: expected pointer property for %s.%s\n",
           __func__,
           RNA_struct_identifier(ptr->type),
           propname);
    return;
  }

  const PointerRNA collection_ptr = RNA_property_pointer_get(ptr, prop);
  if (!collection_ptr.data) {
    return;
  }
  if (collection_ptr.type != &RNA_Collection) {
    printf("%s: expected collection pointer property for %s.%s\n",
           __func__,
           RNA_struct_identifier(ptr->type),
           propname);
    return;
  }

  Collection *collection = static_cast<Collection *>(collection_ptr.data);

  uiBlock *block = uiLayoutGetBlock(layout);

  ui::AbstractTreeView *tree_view = UI_block_add_view(
      *block,
      "Light Linking Collection Tree View",
      std::make_unique<blender::ui::light_linking::CollectionView>(*collection));
  tree_view->set_min_rows(3);

  ui::TreeViewBuilder::build_tree_view(*tree_view, *layout);
}
