#include "editor/overworld/overworld_editor.h"

#include <algorithm>
#include <cmath>
#include <filesystem>
#include <future>
#include <set>
#include <unordered_map>
#include <vector>

#include "absl/status/status.h"
#include "absl/strings/str_format.h"
#include "app/core/asar_wrapper.h"
#include "app/core/features.h"
#include "app/gfx/performance_profiler.h"
#include "app/core/window.h"
#include "app/editor/overworld/entity.h"
#include "app/editor/overworld/map_properties.h"
#include "app/editor/overworld/tile16_editor.h"
#include "app/gfx/arena.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/performance_profiler.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/tilemap.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/gui/style.h"
#include "app/rom.h"
#include "app/zelda3/common.h"
#include "app/zelda3/overworld/overworld.h"
#include "app/zelda3/overworld/overworld_map.h"
#include "imgui/imgui.h"
#include "imgui_memory_editor.h"
#include "util/hex.h"
#include "util/log.h"
#include "util/macro.h"

namespace yaze::editor {

using namespace ImGui;

// Scratch space canvas methods
absl::Status OverworldEditor::DrawScratchSpace() {
  // Slot selector
  Text("Scratch Space Slot:");
  for (int i = 0; i < 4; i++) {
    if (i > 0)
      SameLine();
    bool is_current = (current_scratch_slot_ == i);
    if (is_current)
      PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.7f, 0.4f, 1.0f));
    if (Button(std::to_string(i + 1).c_str(), ImVec2(25, 25))) {
      current_scratch_slot_ = i;
    }
    if (is_current)
      PopStyleColor();
  }

  SameLine();
  if (Button("Save Selection")) {
    RETURN_IF_ERROR(SaveCurrentSelectionToScratch(current_scratch_slot_));
  }
  SameLine();
  if (Button("Load")) {
    RETURN_IF_ERROR(LoadScratchToSelection(current_scratch_slot_));
  }
  SameLine();
  if (Button("Clear")) {
    RETURN_IF_ERROR(ClearScratchSpace(current_scratch_slot_));
  }

  // Selection transfer buttons
  Separator();
  Text("Selection Transfer:");
  if (Button(ICON_MD_DOWNLOAD " From Overworld")) {
    // Transfer current overworld selection to scratch space
    if (ow_map_canvas_.select_rect_active() &&
        !ow_map_canvas_.selected_tiles().empty()) {
      RETURN_IF_ERROR(SaveCurrentSelectionToScratch(current_scratch_slot_));
    }
  }
  HOVER_HINT("Copy current overworld selection to this scratch slot");

  SameLine();
  if (Button(ICON_MD_UPLOAD " To Clipboard")) {
    // Copy scratch selection to clipboard for pasting in overworld
    if (scratch_canvas_.select_rect_active() &&
        !scratch_canvas_.selected_tiles().empty()) {
      // Copy scratch selection to clipboard
      std::vector<int> scratch_tile_ids;
      for (const auto& tile_pos : scratch_canvas_.selected_tiles()) {
        int tile_x = static_cast<int>(tile_pos.x) / 32;
        int tile_y = static_cast<int>(tile_pos.y) / 32;
        if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
          scratch_tile_ids.push_back(
              scratch_spaces_[current_scratch_slot_].tile_data[tile_x][tile_y]);
        }
      }
      if (!scratch_tile_ids.empty() && context_) {
        const auto& points = scratch_canvas_.selected_points();
        int width =
            std::abs(static_cast<int>((points[1].x - points[0].x) / 32)) + 1;
        int height =
            std::abs(static_cast<int>((points[1].y - points[0].y) / 32)) + 1;
        context_->shared_clipboard.overworld_tile16_ids =
            std::move(scratch_tile_ids);
        context_->shared_clipboard.overworld_width = width;
        context_->shared_clipboard.overworld_height = height;
        context_->shared_clipboard.has_overworld_tile16 = true;
      }
    }
  }
  HOVER_HINT("Copy scratch selection to clipboard for pasting in overworld");

  if (context_ && context_->shared_clipboard.has_overworld_tile16) {
    Text(ICON_MD_CONTENT_PASTE
         " Pattern ready! Use Shift+Click to stamp, or paste in overworld");
  }

  Text("Slot %d: %s (%dx%d)", current_scratch_slot_ + 1,
       scratch_spaces_[current_scratch_slot_].name.c_str(),
       scratch_spaces_[current_scratch_slot_].width,
       scratch_spaces_[current_scratch_slot_].height);
  Text(
      "Select tiles from Tile16 tab or make selections in overworld, then draw "
      "here!");

  // Initialize scratch bitmap with proper size based on scratch space dimensions
  auto& current_slot = scratch_spaces_[current_scratch_slot_];
  if (!current_slot.scratch_bitmap.is_active()) {
    // Create bitmap based on scratch space dimensions (each tile is 16x16)
    int bitmap_width = current_slot.width * 16;
    int bitmap_height = current_slot.height * 16;
    std::vector<uint8_t> empty_data(bitmap_width * bitmap_height, 0);
    current_slot.scratch_bitmap.Create(bitmap_width, bitmap_height, 8,
                                       empty_data);
    if (all_gfx_loaded_) {
      palette_ = overworld_.current_area_palette();
      current_slot.scratch_bitmap.SetPalette(palette_);
      // Queue texture creation via Arena's deferred system
      gfx::Arena::Get().QueueTextureCommand(
          gfx::Arena::TextureCommandType::CREATE, &current_slot.scratch_bitmap);
    }
  }

  // Draw the scratch space canvas with dynamic sizing
  gui::BeginPadding(3);
  ImGui::BeginGroup();

  // Set proper content size for scrolling based on scratch space dimensions
  ImVec2 scratch_content_size(current_slot.width * 16 + 4,
                              current_slot.height * 16 + 4);
  gui::BeginChildWithScrollbar("##ScratchSpaceScrollRegion",
                               scratch_content_size);
  scratch_canvas_.DrawBackground();
  gui::EndPadding();

  // Disable context menu for scratch space to allow right-click selection
  scratch_canvas_.SetContextMenuEnabled(false);

  // Draw the scratch bitmap with proper scaling
  if (current_slot.scratch_bitmap.is_active()) {
    scratch_canvas_.DrawBitmap(current_slot.scratch_bitmap, 2, 2, 1.0f);
  }

  // Simplified scratch space - just basic tile drawing like the original
  if (map_blockset_loaded_) {
    scratch_canvas_.DrawTileSelector(32.0f);
  }

  scratch_canvas_.DrawGrid();
  scratch_canvas_.DrawOverlay();

  EndChild();
  ImGui::EndGroup();

  return absl::OkStatus();
}

void OverworldEditor::DrawScratchSpaceEdits() {
  // Handle painting like the main overworld - continuous drawing
  auto mouse_position = scratch_canvas_.drawn_tile_position();

  // Use the scratch canvas scale and grid settings
  float canvas_scale = scratch_canvas_.global_scale();
  int grid_size =
      32;  // 32x32 grid for scratch space (matches kOverworldCanvasSize)

  // Calculate tile position using proper canvas scaling
  int tile_x = static_cast<int>(mouse_position.x) / grid_size;
  int tile_y = static_cast<int>(mouse_position.y) / grid_size;

  // Get current scratch slot dimensions
  auto& current_slot = scratch_spaces_[current_scratch_slot_];
  int max_width = current_slot.width > 0 ? current_slot.width : 20;
  int max_height = current_slot.height > 0 ? current_slot.height : 30;

  // Bounds check for current scratch space dimensions
  if (tile_x >= 0 && tile_x < max_width && tile_y >= 0 && tile_y < max_height) {
    // Bounds check for our tile_data array (always 32x32 max)
    if (tile_x < 32 && tile_y < 32) {
      current_slot.tile_data[tile_x][tile_y] = current_tile16_;
    }

    // Update the bitmap immediately for visual feedback
    UpdateScratchBitmapTile(tile_x, tile_y, current_tile16_);

    // Mark this scratch space as in use
    if (!current_slot.in_use) {
      current_slot.in_use = true;
      current_slot.name =
          absl::StrFormat("Layout %d", current_scratch_slot_ + 1);
    }
  }
}

void OverworldEditor::DrawScratchSpacePattern() {
  // Handle drawing patterns from overworld selections
  auto mouse_position = scratch_canvas_.drawn_tile_position();

  // Use 32x32 grid size (same as scratch canvas grid)
  int start_tile_x = static_cast<int>(mouse_position.x) / 32;
  int start_tile_y = static_cast<int>(mouse_position.y) / 32;

  // Get the selected tiles from overworld via clipboard
  if (!context_ || !context_->shared_clipboard.has_overworld_tile16) {
    return;
  }

  const auto& tile_ids = context_->shared_clipboard.overworld_tile16_ids;
  int pattern_width = context_->shared_clipboard.overworld_width;
  int pattern_height = context_->shared_clipboard.overworld_height;

  if (tile_ids.empty())
    return;

  auto& current_slot = scratch_spaces_[current_scratch_slot_];
  int max_width = current_slot.width > 0 ? current_slot.width : 20;
  int max_height = current_slot.height > 0 ? current_slot.height : 30;

  // Draw the pattern to scratch space
  int idx = 0;
  for (int py = 0; py < pattern_height && (start_tile_y + py) < max_height;
       ++py) {
    for (int px = 0; px < pattern_width && (start_tile_x + px) < max_width;
         ++px) {
      if (idx < static_cast<int>(tile_ids.size())) {
        int tile_id = tile_ids[idx];
        int scratch_x = start_tile_x + px;
        int scratch_y = start_tile_y + py;

        // Bounds check for tile_data array
        if (scratch_x >= 0 && scratch_x < 32 && scratch_y >= 0 &&
            scratch_y < 32) {
          current_slot.tile_data[scratch_x][scratch_y] = tile_id;
          UpdateScratchBitmapTile(scratch_x, scratch_y, tile_id);
        }
        idx++;
      }
    }
  }

  // Mark scratch space as modified
  current_slot.in_use = true;
  if (current_slot.name == "Empty") {
    current_slot.name =
        absl::StrFormat("Pattern %dx%d", pattern_width, pattern_height);
  }
}

void OverworldEditor::UpdateScratchBitmapTile(int tile_x, int tile_y,
                                              int tile_id, int slot) {
  gfx::ScopedTimer timer("overworld_update_scratch_tile");

  // Use current slot if not specified
  if (slot == -1)
    slot = current_scratch_slot_;

  // Get the tile data from the tile16 blockset
  auto tile_data = gfx::GetTilemapData(tile16_blockset_, tile_id);
  if (tile_data.empty())
    return;

  auto& scratch_slot = scratch_spaces_[slot];

  // Use canvas grid size (32x32) for consistent scaling
  const int grid_size = 32;
  int scratch_bitmap_width = scratch_slot.scratch_bitmap.width();
  int scratch_bitmap_height = scratch_slot.scratch_bitmap.height();

  // Calculate pixel position in scratch bitmap
  for (int y = 0; y < 16; ++y) {
    for (int x = 0; x < 16; ++x) {
      int src_index = y * 16 + x;

      // Scale to grid size - each tile takes up grid_size x grid_size pixels
      int dst_x = tile_x * grid_size + x + x;  // Double scaling for 32x32 grid
      int dst_y = tile_y * grid_size + y + y;

      // Bounds check for scratch bitmap
      if (dst_x >= 0 && dst_x < scratch_bitmap_width && dst_y >= 0 &&
          dst_y < scratch_bitmap_height &&
          src_index < static_cast<int>(tile_data.size())) {

        // Write 2x2 pixel blocks to fill the 32x32 grid space
        for (int py = 0; py < 2 && (dst_y + py) < scratch_bitmap_height; ++py) {
          for (int px = 0; px < 2 && (dst_x + px) < scratch_bitmap_width;
               ++px) {
            int dst_index = (dst_y + py) * scratch_bitmap_width + (dst_x + px);
            scratch_slot.scratch_bitmap.WriteToPixel(dst_index,
                                                     tile_data[src_index]);
          }
        }
      }
    }
  }

  scratch_slot.scratch_bitmap.set_modified(true);
  // Queue texture update via Arena's deferred system
  gfx::Arena::Get().QueueTextureCommand(
      gfx::Arena::TextureCommandType::UPDATE, &scratch_slot.scratch_bitmap);
  scratch_slot.in_use = true;
}

absl::Status OverworldEditor::SaveCurrentSelectionToScratch(int slot) {
  gfx::ScopedTimer timer("overworld_save_selection_to_scratch");

  if (slot < 0 || slot >= 4) {
    return absl::InvalidArgumentError("Invalid scratch slot");
  }

  if (ow_map_canvas_.select_rect_active() &&
      !ow_map_canvas_.selected_tiles().empty()) {
    // Calculate actual selection dimensions from overworld rectangle
    const auto& selected_points = ow_map_canvas_.selected_points();
    if (selected_points.size() >= 2) {
      const auto start = selected_points[0];
      const auto end = selected_points[1];

      // Calculate width and height in tiles
      int selection_width =
          std::abs(static_cast<int>((end.x - start.x) / 16)) + 1;
      int selection_height =
          std::abs(static_cast<int>((end.y - start.y) / 16)) + 1;

      // Update scratch space dimensions to match selection
      scratch_spaces_[slot].width = std::max(1, std::min(selection_width, 32));
      scratch_spaces_[slot].height =
          std::max(1, std::min(selection_height, 32));
      scratch_spaces_[slot].in_use = true;
      scratch_spaces_[slot].name =
          absl::StrFormat("Selection %dx%d", scratch_spaces_[slot].width,
                          scratch_spaces_[slot].height);

      // Recreate bitmap with new dimensions
      int bitmap_width = scratch_spaces_[slot].width * 16;
      int bitmap_height = scratch_spaces_[slot].height * 16;
      std::vector<uint8_t> empty_data(bitmap_width * bitmap_height, 0);
      scratch_spaces_[slot].scratch_bitmap.Create(bitmap_width, bitmap_height,
                                                  8, empty_data);
      if (all_gfx_loaded_) {
        palette_ = overworld_.current_area_palette();
        scratch_spaces_[slot].scratch_bitmap.SetPalette(palette_);
        // Queue texture creation via Arena's deferred system
        gfx::Arena::Get().QueueTextureCommand(
            gfx::Arena::TextureCommandType::CREATE, 
            &scratch_spaces_[slot].scratch_bitmap);
      }

      // Save selected tiles to scratch data with proper layout
      overworld_.set_current_world(current_world_);
      overworld_.set_current_map(current_map_);

      int idx = 0;
      for (int y = 0;
           y < scratch_spaces_[slot].height &&
           idx < static_cast<int>(ow_map_canvas_.selected_tiles().size());
           ++y) {
        for (int x = 0;
             x < scratch_spaces_[slot].width &&
             idx < static_cast<int>(ow_map_canvas_.selected_tiles().size());
             ++x) {
          if (idx < static_cast<int>(ow_map_canvas_.selected_tiles().size())) {
            int tile_id = overworld_.GetTileFromPosition(
                ow_map_canvas_.selected_tiles()[idx]);
            if (x < 32 && y < 32) {
              scratch_spaces_[slot].tile_data[x][y] = tile_id;
            }
            // Update the bitmap immediately
            UpdateScratchBitmapTile(x, y, tile_id, slot);
            idx++;
          }
        }
      }
    }
  } else {
    // Default single-tile scratch space
    scratch_spaces_[slot].width = 16;  // Default size
    scratch_spaces_[slot].height = 16;
    scratch_spaces_[slot].name = absl::StrFormat("Map %d Area", current_map_);
    scratch_spaces_[slot].in_use = true;
  }


  return absl::OkStatus();
}

absl::Status OverworldEditor::LoadScratchToSelection(int slot) {
  if (slot < 0 || slot >= 4) {
    return absl::InvalidArgumentError("Invalid scratch slot");
  }

  if (!scratch_spaces_[slot].in_use) {
    return absl::FailedPreconditionError("Scratch slot is empty");
  }

  // Placeholder - could restore tiles to current map position
  util::logf("Loading scratch slot %d: %s", slot,
             scratch_spaces_[slot].name.c_str());

  return absl::OkStatus();
}

absl::Status OverworldEditor::ClearScratchSpace(int slot) {
  if (slot < 0 || slot >= 4) {
    return absl::InvalidArgumentError("Invalid scratch slot");
  }

  scratch_spaces_[slot].in_use = false;
  scratch_spaces_[slot].name = "Empty";

  // Clear the bitmap
  if (scratch_spaces_[slot].scratch_bitmap.is_active()) {
    auto& data = scratch_spaces_[slot].scratch_bitmap.mutable_data();
    std::fill(data.begin(), data.end(), 0);
    scratch_spaces_[slot].scratch_bitmap.set_modified(true);
    // Queue texture update via Arena's deferred system
    gfx::Arena::Get().QueueTextureCommand(
        gfx::Arena::TextureCommandType::UPDATE, &scratch_spaces_[slot].scratch_bitmap);
  }

  return absl::OkStatus();
}

}