#include "character_manager.hpp"

#include "../../util/math.hpp"

#include <imgui.h>
#include <algorithm>
#include <optional>

using namespace game::resource::xml;
using namespace game::util;

namespace game::state::play
{
  void CharacterManager::update(Entity& character, Entity& cursor, Text& text, Canvas& canvas)
  {
    static constexpr auto SCALE_EFFECT_TIME_TICKS_DEFAULT = 20.0f;
    static constexpr auto SCALE_EFFECT_CYCLES_DEFAULT = 1.0f;
    static constexpr auto ZERO_FLOAT = 0.0f;

    auto interact_area_override_tick = [](Entity::Override& override_)
    {
      auto& scale = override_.frame.scale;
      auto& scaleBase = override_.frameBase.scale;
      auto isScaleValid =
          scale.x.has_value() && scale.y.has_value() && scaleBase.x.has_value() && scaleBase.y.has_value();

      if (isScaleValid && override_.time.has_value() && override_.timeStart.has_value())
      {
        auto percent = glm::clamp(*override_.time / *override_.timeStart, 0.0f, 1.0f);
        auto elapsed = 1.0f - percent;

        auto oscillation = cosf(elapsed * glm::tau<float>() * override_.cycles);
        auto envelope = percent;
        auto amplitude = glm::abs(glm::vec2(*scaleBase.x, *scaleBase.y));
        auto value = amplitude * (oscillation * envelope);

        scale.x = value.x;
        scale.y = value.y;
      }
    };

    auto& dialogue = character.data.dialogue;
    auto cursorWorldPosition =
        cursor.entityType == AUTOCURSOR ? cursor.position : canvas.screen_position_convert(cursor.position);
    auto isMouseLeftClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
    auto isMouseLeftDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
    auto isMouseLeftReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
    auto isImguiCaptureMouse = ImGui::GetIO().WantCaptureMouse;

    if (inputOverride)
    {
      isImguiCaptureMouse = inputOverride->isImguiCaptureMouse;
      isMouseLeftClick = inputOverride->isMouseLeftClick;
      isMouseLeftDown = inputOverride->isMouseLeftDown;
      isMouseLeftReleased = inputOverride->isMouseLeftReleased;
      inputOverride.reset();
    }

    if (isDisabled)
    {
      isInteracting = false;
      isHoldInteracting = false;
      isHovering = false;
      interactAreaID = -1;
      return;
    }

    isInteractingPrevious = isInteracting;
    isHoveringPrevious = isHovering;
    isHoldInteractingPrevious = isHoldInteracting;
    isHovering = false;
    particles.clear();
    if (!isInteracting) isHoldInteracting = false;

    if (isJustStoppedHoldInteracting)
    {
      cursor.play({cursor.defaultAnimation});
      if (!character.is_animation_request())
        character.play({.animation = character.idle_animation_get(), .appendID = character.animation_append_id_get()});
      isJustStoppedHoldInteracting = false;
    }
    else if (isJustStoppedInteracting)
    {
      cursor.play({cursor.defaultAnimation});
      isJustStoppedInteracting = false;
    }

    if (isJustStoppedHovering)
    {
      cursor.play({cursor.defaultAnimation});
      isJustStoppedHovering = false;
    }

    auto interactAreas = character.data.interact_areas_get();
    if (interactAreaDialogueTimers.size() != interactAreas.size())
      interactAreaDialogueTimers.assign(interactAreas.size(), 0);
    if (!text.entry) std::ranges::fill(interactAreaDialogueTimers, 0);
    for (auto& timer : interactAreaDialogueTimers)
      if (timer > 0) timer--;

    for (int i = 0; i < (int)interactAreas.size(); i++)
    {
      auto* interactArea = interactAreas.at(i);
      auto nullID = character.data.null_id_get(interactArea->null);
      auto layerID = character.data.layer_id_get(interactArea->layer);
      if (nullID == -1) continue;
      auto rect = character.null_frame_rect(nullID);

      if (cursor.cursorState == Entity::DEFAULT && math::is_point_in_rectf(rect, cursorWorldPosition) &&
          !isImguiCaptureMouse &&
          character.data.interact_type_id_get(interactArea->typeString) == cursor.interactTypeID)
      {
        cursor.cursorState = Entity::HOVER;
        cursor.play({.animation = interactArea->animationCursorHover, .transition = Entity::Transition::IF_IDLE});
        isHovering = true;
        interactAreaID = i;

        auto interact_area_proc = [&](bool isInteract)
        {
          if (isInteract)
          {
            isInteracting = true;
            isHoldInteracting = interactArea->isHold;
          }
          else
          {
            cursor.cursorState = Entity::ACTION;
            cursor.play({.animation = interactArea->animationCursorActive,
                         .mode = Entity::PLAY_FORCE,
                         .interrupt = Entity::Interrupt::ALWAYS});
          }
          interactArea->soundEntries.play();
          for (auto* particle : character.data.children_get(*interactArea, Schema::Element::PARTICLE))
            if (particle && !particle->typeString.empty())
              particles.push_back({.label = particle->typeString, .position = cursorWorldPosition});

          if (interactArea->digestionBonusOnClick > 0 && character.calories > 0 && !character.isDigesting)
            character.digestionProgress += interactArea->digestionBonusOnClick;

          if (layerID != -1)
          {
            auto scaleEffectTimeTicks = interactArea->timeTicks == ZERO_FLOAT ? SCALE_EFFECT_TIME_TICKS_DEFAULT
                                                                              : interactArea->timeTicks;
            auto scaleEffectCycles = interactArea->scaleEffectCycles == ZERO_FLOAT ? SCALE_EFFECT_CYCLES_DEFAULT
                                                                                  : interactArea->scaleEffectCycles;
            character.overrides.emplace_back(Entity::Override(
                layerID, Anm2::LAYER, Entity::Override::ADD, {.scale = glm::vec2(interactArea->scaleEffectAmplitude)},
                std::optional<float>(scaleEffectTimeTicks), interact_area_override_tick, scaleEffectCycles));
          }

          auto isDialogueReady = interactAreaDialogueTimers[i] <= 0;
          interactAreaDialogueTimers[i] = std::max(0, interactArea->timeToActivateNewDialogueTicks);

          auto poolFullID = character.data.dialogue_pool_id_get(interactArea->dialoguePoolIDFull);
          auto poolID = character.data.dialogue_pool_id_get(interactArea->dialoguePoolID);
          if (character.is_over_capacity() && poolFullID != -1) poolID = poolFullID;

          if (poolID != -1)
          {
            if (text.is_interruptible() && isDialogueReady)
            {
              auto* entry = dialogue.dialogue_pool_entry_get(poolID);
              if (entry) text.set(entry, character);
            }
          }
          else if (interactArea->dialoguePoolID.empty() && !interactArea->animation.empty())
            character.play({.animation = interactArea->animation,
                            .appendID = character.animation_append_id_get(),
                            .mode = Entity::PLAY_FORCE});
        };

        if (interactArea->isHold && (isMouseLeftClick || (isMouseLeftDown && !isInteracting)))
          interact_area_proc(true);
        else if (!interactArea->isHold && isMouseLeftClick)
          interact_area_proc(false);

        if (isInteracting)
        {
          cursor.cursorState = Entity::ACTION;
          cursor.play({interactArea->animationCursorActive});

          if (interactArea->digestionBonusOnHover > 0 && character.calories > 0 && !character.isDigesting)
          {
            auto mouseDelta = cursorWorldPosition - cursorWorldPositionPrevious;
            if (fabs(mouseDelta.x) > 0.0f || fabs(mouseDelta.y) > 0.0f)
              character.digestionProgress += interactArea->digestionBonusOnHover;
          }
        }
      }

      if ((i == interactAreaID && !math::is_point_in_rectf(rect, cursorWorldPosition)) || isMouseLeftReleased ||
          isImguiCaptureMouse)
      {
        isInteracting = false;
        isHoldInteracting = false;
        interactAreaID = -1;
      }
    }

    if (isInteracting != isInteractingPrevious && !isInteracting) isJustStoppedInteracting = true;
    if (isHoldInteracting != isHoldInteractingPrevious && !isHoldInteracting) isJustStoppedHoldInteracting = true;
    if (isHovering != isHoveringPrevious && !isHovering) isJustStoppedHovering = true;

    cursorWorldPositionPrevious = cursorWorldPosition;

    if (character.isJustDigested && text.is_interruptible())
      if (auto* digest = dialogue.get(Schema::Element::DIGEST))
        text.set(dialogue.dialogue_pool_entry_get(*digest), character);
    if (character.isJustStageUp)
      if (auto* stageUp = dialogue.get(Schema::Element::STAGE_UP))
        text.set(dialogue.dialogue_pool_entry_get(*stageUp), character);
  }
}
