#include "character.hpp"

#include "../log.hpp"
#include "../util/preferences.hpp"

#include <format>

using namespace game::util;

namespace game::resource::xml
{
  const Schema::Element* interact_area_default_get(const Character& character, const Schema::Element& area)
  {
    if (area.type != Schema::Element::INTERACT_AREA || area.typeString.empty() || area.null.empty()) return nullptr;
    auto* rootElement = character.root();
    auto* areas = rootElement ? character.schema.child_get(*rootElement, Schema::Element::INTERACT_AREA_TYPES) : nullptr;
    if (!areas) return nullptr;
    for (auto* candidate : character.schema.children_get(*areas, Schema::Element::INTERACT_AREA))
      if (candidate && candidate->typeString == area.typeString) return candidate;
    return nullptr;
  }

  int map_id_get(const std::unordered_map<std::string, int>& map, const std::string& name)
  {
    auto it = map.find(name);
    return it == map.end() ? -1 : it->second;
  }

  Character::Character(const std::filesystem::path& path)
  {
    physfs::Archive archive(path, path.filename().string());

    if (!archive.is_valid())
    {
      logger.error(std::format("Failed to initialize character from PhysicsFS archive: {} ({})", path.string(),
                               physfs::error_get()));
      return;
    }

    physfs::Path characterPath(archive + "/" + "character.xml");

    if (!characterPath.is_valid()) return;

    schema = Schema(characterPath);
    auto* root = schema.root();
    if (root)
    {
      auto dialoguePath = physfs::Path(archive + "/" + "dialogue.xml");

      if (!dialoguePath.is_valid())
        logger.warning(std::format("No character dialogue.xml file found: {}", path.string()));
      else
        dialogue = Schema(dialoguePath);
    }

    if (auto itemSchemaPath = physfs::Path(archive + "/" + "items.xml"); itemSchemaPath.is_valid())
      itemSchema = Schema(itemSchemaPath);
    else
      logger.warning(std::format("No character items.xml file found: {}", path.string()));

    if (auto areaSchemaPath = physfs::Path(archive + "/" + "areas.xml"); areaSchemaPath.is_valid())
      areaSchema = Schema(areaSchemaPath);
    else
      logger.warning(std::format("No character areas.xml file found: {}", path.string()));

    if (auto menuSchemaPath = physfs::Path(archive + "/" + "menu.xml"); menuSchemaPath.is_valid())
      menuSchema = Schema(menuSchemaPath);
    else
      logger.warning(std::format("No character menu.xml file found: {}", path.string()));

    if (auto cursorSchemaPath = physfs::Path(archive + "/" + "cursor.xml"); cursorSchemaPath.is_valid())
      cursorSchema = Schema(cursorSchemaPath);
    else
      logger.warning(std::format("No character cursor.xml file found: {}", path.string()));

    if (auto particleSchemaPath = physfs::Path(archive + "/" + "particles.xml"); particleSchemaPath.is_valid())
      particleSchema = Schema(particleSchemaPath);
    else
      logger.warning(std::format("No character particles.xml file found: {}", path.string()));

    if (auto stringsPath = physfs::Path(archive + "/" + "strings.xml"); stringsPath.is_valid())
      strings = Strings(stringsPath);

    logger.info(std::format("Initialized character: {}", root ? root->name : ""));

    this->path = path;
    save = Schema(save_path_get());
  }

  Schema::Element* Character::root() { return schema.root(); }
  const Schema::Element* Character::root() const { return schema.root(); }
  Schema::Element* Character::alternate_spritesheet()
  {
    auto* rootElement = root();
    return rootElement ? schema.child_get(*rootElement, Schema::Element::ALTERNATE_SPRITESHEET) : nullptr;
  }
  const Schema::Element* Character::alternate_spritesheet() const
  {
    auto* rootElement = root();
    return rootElement ? schema.child_get(*rootElement, Schema::Element::ALTERNATE_SPRITESHEET) : nullptr;
  }

  const Schema::Element* Character::animations_root() const
  {
    auto* rootElement = root();
    return rootElement ? schema.child_get(*rootElement, Schema::Element::ANIMATIONS) : nullptr;
  }

  const Schema::Element* Character::overrides_root() const
  {
    auto* rootElement = root();
    return rootElement ? schema.child_get(*rootElement, Schema::Element::ANIMATION_OVERRIDES) : nullptr;
  }

  const Schema::Element* Character::child_get(const Schema::Element& element, Schema::Element::Type type, int index) const
  {
    return schema.child_get(element, type, index);
  }

  std::vector<const Schema::Element*> Character::children_get(const Schema::Element& element,
                                                              Schema::Element::Type type) const
  {
    auto result = schema.children_get(element, type);
    if (type != Schema::Element::PARTICLE || !result.empty()) return result;
    auto* defaults = interact_area_default_get(*this, element);
    if (!defaults) return result;
    return schema.children_get(*defaults, type);
  }

  std::vector<const Schema::Element*> Character::stages_get() const
  {
    auto* rootElement = root();
    auto* stages = rootElement ? schema.child_get(*rootElement, Schema::Element::STAGES) : nullptr;
    return stages ? schema.children_get(*stages, Schema::Element::STAGE) : std::vector<const Schema::Element*>{};
  }

  int Character::stage_count_get() const { return (int)stages_get().size() + 1; }

  std::vector<const Schema::Element*> Character::expand_areas_get() const
  {
    auto* rootElement = root();
    auto* areas = rootElement ? schema.child_get(*rootElement, Schema::Element::EXPAND_AREAS) : nullptr;
    return areas ? schema.children_get(*areas, Schema::Element::EXPAND_AREA) : std::vector<const Schema::Element*>{};
  }

  std::vector<const Schema::Element*> Character::eat_areas_get() const
  {
    auto* rootElement = root();
    auto* areas = rootElement ? schema.child_get(*rootElement, Schema::Element::EAT_AREAS) : nullptr;
    return areas ? schema.children_get(*areas, Schema::Element::EAT_AREA) : std::vector<const Schema::Element*>{};
  }

  std::vector<const Schema::Element*> Character::interact_area_types_get() const
  {
    auto* rootElement = root();
    auto* areas = rootElement ? schema.child_get(*rootElement, Schema::Element::INTERACT_AREA_TYPES) : nullptr;
    return areas ? schema.children_get(*areas, Schema::Element::INTERACT_AREA) : std::vector<const Schema::Element*>{};
  }

  std::vector<const Schema::Element*> Character::interact_areas_get() const
  {
    auto* rootElement = root();
    auto* areas = rootElement ? schema.child_get(*rootElement, Schema::Element::INTERACT_AREAS) : nullptr;
    return areas ? schema.children_get(*areas, Schema::Element::INTERACT_AREA) : std::vector<const Schema::Element*>{};
  }

  std::vector<std::string> Character::interact_type_names_get() const
  {
    std::vector<std::string> names{};
    for (auto* area : interact_area_types_get())
    {
      if (!area || area->typeString.empty()) continue;
      if (std::ranges::find(names, area->typeString) == names.end()) names.push_back(area->typeString);
    }
    return names;
  }

  std::string Character::animation_get(Schema::Element::Type type) const
  {
    auto* animations = animations_root();
    auto* animation = animations ? schema.child_get(*animations, type) : nullptr;
    return animation ? animation->animation : "";
  }

  int Character::dialogue_pool_id_get(const std::string& poolID) const
  {
    return poolID.empty() ? -1 : dialogue.dialogue_pool_id_get(poolID);
  }

  int Character::layer_id_get(const std::string& name) const
  {
    return root() ? map_id_get(root()->anm2Entry.anm2.layerMap, name) : -1;
  }
  int Character::null_id_get(const std::string& name) const
  {
    return root() ? map_id_get(root()->anm2Entry.anm2.nullMap, name) : -1;
  }
  int Character::event_id_get(const std::string& name) const
  {
    return root() ? map_id_get(root()->anm2Entry.anm2.eventMap, name) : -1;
  }

  int Character::interact_type_id_get(const std::string& name) const
  {
    auto names = interact_type_names_get();
    for (int i = 0; i < (int)names.size(); i++)
      if (names[i] == name) return i;
    return -1;
  }

  int Character::stage_pool_id_get(int stage) const
  {
    if (stage <= 0) return dialogue_pool_id_get(root() ? root()->dialoguePoolID : "");
    auto stages = stages_get();
    auto index = stage - 1;
    return index >= 0 && index < (int)stages.size() ? dialogue_pool_id_get(stages[index]->dialoguePoolID) : -1;
  }

  std::filesystem::path Character::save_path_get()
  {
    auto savePath = path.stem();
    savePath = preferences::path() / "saves" / savePath.replace_extension(".save");
    std::filesystem::create_directories(savePath.parent_path());
    return savePath;
  }
}
