#include "path_widget.hpp"

#include <imgui.h>

namespace game::util::imgui
{
  namespace
  {
    bool relative_path_is_local(const std::filesystem::path& path)
    {
      if (path.empty() || path.is_absolute()) return false;
      for (auto& part : path)
        if (part == "..") return false;
      return true;
    }

    std::filesystem::path schema_relative_path_get(const std::filesystem::path& schemaPath,
                                                   const std::filesystem::path& path)
    {
      auto base = schemaPath.parent_path();
      std::error_code error{};
      auto relative = std::filesystem::relative(path, base, error);
      if (error || relative.empty()) relative = path.lexically_relative(base);
      if (relative.empty()) relative = path;
      return relative;
    }

    std::filesystem::path root_prefix_strip(const std::filesystem::path& rootPath, const std::filesystem::path& path)
    {
      if (rootPath.empty()) return path;

      auto root = rootPath.lexically_normal();
      auto value = path.lexically_normal();
      auto rootIt = root.begin();
      auto valueIt = value.begin();
      while (rootIt != root.end() && valueIt != value.end() && *rootIt == *valueIt)
      {
        ++rootIt;
        ++valueIt;
      }
      if (rootIt != root.end()) return path;

      std::filesystem::path result{};
      for (; valueIt != value.end(); ++valueIt)
        result /= *valueIt;
      return result.empty() ? path : result;
    }

    bool path_button_draw(const char* id, const std::string& path, const std::string& displayPath, const ImVec2& size)
    {
      ImGui::PushID(id);
      auto label = std::string{id ? id : ""};
      auto hasVisibleLabel = !label.empty() && !label.starts_with("##");
      auto buttonSize = size;
      if (hasVisibleLabel && buttonSize.x <= 0.0f) buttonSize.x = ImGui::CalcItemWidth();
      auto clicked = ImGui::Button(displayPath.empty() ? "<empty>" : displayPath.c_str(), buttonSize);
      if (!path.empty() && ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) ImGui::SetTooltip("%s", path.c_str());
      if (hasVisibleLabel)
      {
        ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
        ImGui::TextUnformatted(label.c_str());
      }
      ImGui::PopID();
      return clicked;
    }
  }

  std::filesystem::path path_base_get(const std::filesystem::path& schemaPath, const std::string& rootPath)
  {
    return rootPath.empty() ? schemaPath.parent_path() : schemaPath.parent_path() / rootPath;
  }

  std::filesystem::path path_absolute_get(const std::filesystem::path& schemaPath, const std::string& rootPath,
                                          const std::string& path)
  {
    auto value = std::filesystem::path(path);
    if (value.is_absolute()) return value;
    auto root = std::filesystem::path(rootPath);
    auto rootIt = root.begin();
    auto valueIt = value.begin();
    while (rootIt != root.end() && valueIt != value.end() && *rootIt == *valueIt)
    {
      ++rootIt;
      ++valueIt;
    }
    if (rootIt == root.end()) return schemaPath.parent_path() / value;
    return path_base_get(schemaPath, rootPath) / value;
  }

  std::string path_display_get(const std::filesystem::path& schemaPath, const std::string& rootPath,
                               const std::string& path)
  {
    auto root = std::filesystem::path(rootPath);
    auto value = std::filesystem::path(path).lexically_normal();
    if (!value.is_absolute()) return root_prefix_strip(root, value).generic_string();

    auto base = path_base_get(schemaPath, rootPath);
    std::error_code error{};
    auto relative = std::filesystem::relative(value, base, error);
    if (!error && relative_path_is_local(relative)) return relative.generic_string();

    error.clear();
    relative = std::filesystem::relative(value, schemaPath.parent_path(), error);
    if (!error && relative_path_is_local(relative)) return root_prefix_strip(root, relative).generic_string();

    return value.generic_string();
  }

  std::string path_store_get(const std::filesystem::path& schemaPath, const std::string& rootPath,
                             const std::filesystem::path& path)
  {
    auto normalizedPath = path.lexically_normal();
    auto base = path_base_get(schemaPath, rootPath);
    std::error_code error{};
    auto relative = std::filesystem::relative(normalizedPath, base, error);
    if (error || relative.empty()) relative = normalizedPath.lexically_relative(base);
    if (relative.empty() || relative.is_absolute())
    {
      auto schemaRelative = schema_relative_path_get(schemaPath, normalizedPath);
      auto root = std::filesystem::path(rootPath);
      auto rootIt = root.begin();
      auto schemaIt = schemaRelative.begin();
      while (rootIt != root.end() && schemaIt != schemaRelative.end() && *rootIt == *schemaIt)
      {
        ++rootIt;
        ++schemaIt;
      }

      if (rootIt == root.end())
      {
        relative.clear();
        for (; schemaIt != schemaRelative.end(); ++schemaIt) relative /= *schemaIt;
      }
      else
        relative = schemaRelative;
    }
    return relative.generic_string();
  }

  bool path_widget(const char* id, const std::string& path, const ImVec2& size)
  {
    return path_widget(id, path, path, size);
  }

  bool path_widget(const char* id, const std::string& path, const std::string& displayPath, const ImVec2& size)
  {
    return path_button_draw(id, path, displayPath, size);
  }

  bool folder_widget(const char* id, const std::string& path, const ImVec2& size)
  {
    return folder_widget(id, path, path, size);
  }

  bool folder_widget(const char* id, const std::string& path, const std::string& displayPath, const ImVec2& size)
  {
    return path_button_draw(id, path, displayPath, size);
  }
}
