#include "overworld_map.h"

#include <cstddef>
#include <cstdint>
#include <unordered_map>
#include <vector>

#include "app/core/features.h"
#include "app/gfx/snes_color.h"
#include "app/gfx/snes_tile.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"

namespace yaze {
namespace zelda3 {

OverworldMap::OverworldMap(int index, Rom* rom)
    : index_(index), parent_(index), rom_(rom) {
  LoadAreaInfo();
  // Load parent ID from ROM data if available (for custom ASM versions)
  uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
  if (asm_version != 0xFF && asm_version >= 0x03) {
    // For v3+, parent ID is stored in expanded table
    parent_ = (*rom_)[kOverworldMapParentIdExpanded + index_];
  }

  if (asm_version != 0xFF) {
    if (asm_version == 0x03) {
      LoadCustomOverworldData();
    } else {
      SetupCustomTileset(asm_version);
    }
  } else if (core::FeatureFlags::get().overworld.kLoadCustomOverworld) {
    // Pure vanilla ROM but flag enabled - set up custom data manually
    LoadCustomOverworldData();
  }
  // For pure vanilla ROMs, LoadAreaInfo already handles everything
}

absl::Status OverworldMap::BuildMap(int count, int game_state, int world,
                                    std::vector<gfx::Tile16>& tiles16,
                                    OverworldBlockset& world_blockset) {
  game_state_ = game_state;
  world_ = world;
  uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];

  // For large maps in vanilla ROMs, we need to handle special world graphics
  // This ensures proper rendering of special overworld areas like Zora's Domain
  if (large_map_ && asm_version == 0xFF) {
    if (parent_ != index_ && !initialized_) {
      if (index_ >= kSpecialWorldMapIdStart && index_ <= 0x8A &&
          index_ != 0x88) {
        // Most special world areas use the special graphics group
        area_graphics_ = (*rom_)[kOverworldSpecialGfxGroup +
                                 (parent_ - kSpecialWorldMapIdStart)];
        area_palette_ = (*rom_)[kOverworldSpecialPalGroup + 1];
      } else if (index_ == 0x88) {
        // Triforce room has special hardcoded values
        area_graphics_ = 0x51;
        area_palette_ = 0x00;
      } else {
        // Fallback to standard area graphics
        area_graphics_ = (*rom_)[kAreaGfxIdPtr + parent_];
        area_palette_ = (*rom_)[kOverworldMapPaletteIds + parent_];
      }

      initialized_ = true;
    }
  }

  LoadAreaGraphics();
  RETURN_IF_ERROR(BuildTileset())
  RETURN_IF_ERROR(BuildTiles16Gfx(tiles16, count))
  RETURN_IF_ERROR(LoadPalette());
  RETURN_IF_ERROR(LoadOverlay());
  RETURN_IF_ERROR(BuildBitmap(world_blockset))
  built_ = true;
  return absl::OkStatus();
}

void OverworldMap::LoadAreaInfo() {
  uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];

  // ZSCustomOverworld ASM Version System:
  // 0x00-0x02: Legacy versions with limited features
  // 0x03: Current version with full area size expansion and custom data support
  // 0xFF: Pure vanilla ROM (no ASM applied)

  // Load message ID and area size based on ASM version
  if (asm_version < 3 || asm_version == 0xFF) {
    // v2 and vanilla: use original message table
    message_id_ = (*rom_)[kOverworldMessageIds + (parent_ * 2)] |
                  ((*rom_)[kOverworldMessageIds + (parent_ * 2) + 1] << 8);

    // Load area size for v2/vanilla
    if (index_ < 0x80) {
      // For LW and DW, check the screen size byte
      // Note: v2 had a bug where large/small values were swapped
      uint8_t size_byte = (*rom_)[kOverworldScreenSize + (index_ & 0x3F)];
      switch (size_byte) {
        case 0:
          area_size_ = AreaSizeEnum::LargeArea;
          break;
        case 1:
        default:
          area_size_ = AreaSizeEnum::SmallArea;
          break;
        case 2:
          area_size_ = AreaSizeEnum::WideArea;
          break;
        case 3:
          area_size_ = AreaSizeEnum::TallArea;
          break;
      }
    } else {
      // For SW, use hardcoded values for v2 compatibility
      // Zora's Domain areas (0x81, 0x82, 0x89, 0x8A) are large areas
      area_size_ =
          (index_ == 0x81 || index_ == 0x82 || index_ == 0x89 || index_ == 0x8A)
              ? AreaSizeEnum::LargeArea
              : AreaSizeEnum::SmallArea;
    }
  } else {
    // v3: use expanded message table and area size table
    // All area sizes are now stored in the expanded table, supporting all size types
    message_id_ =
        (*rom_)[kOverworldMessagesExpanded + (parent_ * 2)] |
        ((*rom_)[kOverworldMessagesExpanded + (parent_ * 2) + 1] << 8);
    area_size_ =
        static_cast<AreaSizeEnum>((*rom_)[kOverworldScreenSize + index_]);
  }

  // Update large_map_ based on area size
  large_map_ = (area_size_ == AreaSizeEnum::LargeArea);

  // Load area-specific data based on index range
  if (index_ < kDarkWorldMapIdStart) {
    // Light World (LW) areas
    sprite_graphics_[0] = (*rom_)[kOverworldSpriteset + parent_];
    sprite_graphics_[1] =
        (*rom_)[kOverworldSpriteset + parent_ + kDarkWorldMapIdStart];
    sprite_graphics_[2] =
        (*rom_)[kOverworldSpriteset + parent_ + kSpecialWorldMapIdStart];

    area_graphics_ = (*rom_)[kAreaGfxIdPtr + parent_];
    area_palette_ = (*rom_)[kOverworldPalettesScreenToSetNew + parent_];

    sprite_palette_[0] = (*rom_)[kOverworldSpritePaletteIds + parent_];
    sprite_palette_[1] =
        (*rom_)[kOverworldSpritePaletteIds + parent_ + kDarkWorldMapIdStart];
    sprite_palette_[2] =
        (*rom_)[kOverworldSpritePaletteIds + parent_ + kSpecialWorldMapIdStart];

    area_music_[0] = (*rom_)[kOverworldMusicBeginning + parent_];
    area_music_[1] = (*rom_)[kOverworldMusicZelda + parent_];
    area_music_[2] = (*rom_)[kOverworldMusicMasterSword + parent_];
    area_music_[3] = (*rom_)[kOverworldMusicAgahnim + parent_];

    // For v2/vanilla, use original palette table
    if (asm_version < 3 || asm_version == 0xFF) {
      area_palette_ = (*rom_)[kOverworldMapPaletteIds + parent_];
    }
  } else if (index_ < kSpecialWorldMapIdStart) {
    // Dark World (DW) areas
    sprite_graphics_[0] =
        (*rom_)[kOverworldSpriteset + parent_ + kSpecialWorldMapIdStart];
    sprite_graphics_[1] =
        (*rom_)[kOverworldSpriteset + parent_ + kSpecialWorldMapIdStart];
    sprite_graphics_[2] =
        (*rom_)[kOverworldSpriteset + parent_ + kSpecialWorldMapIdStart];

    area_graphics_ = (*rom_)[kAreaGfxIdPtr + parent_];
    area_palette_ = (*rom_)[kOverworldPalettesScreenToSetNew + parent_];

    sprite_palette_[0] =
        (*rom_)[kOverworldSpritePaletteIds + parent_ + kSpecialWorldMapIdStart];
    sprite_palette_[1] =
        (*rom_)[kOverworldSpritePaletteIds + parent_ + kSpecialWorldMapIdStart];
    sprite_palette_[2] =
        (*rom_)[kOverworldSpritePaletteIds + parent_ + kSpecialWorldMapIdStart];

    area_music_[0] =
        (*rom_)[kOverworldMusicDarkWorld + (parent_ - kDarkWorldMapIdStart)];

    // For v2/vanilla, use original palette table
    if (asm_version < 3 || asm_version == 0xFF) {
      area_palette_ = (*rom_)[kOverworldMapPaletteIds + parent_];
    }
  } else {
    // Special World (SW) areas (index >= 0x80)
    // Message ID already loaded above based on ASM version

    // For v3, use expanded sprite tables with full customization support
    if (asm_version >= 3 && asm_version != 0xFF) {
      sprite_graphics_[0] =
          (*rom_)[kOverworldSpecialSpriteGfxGroupExpandedTemp + parent_ -
                  kSpecialWorldMapIdStart];
      sprite_graphics_[1] =
          (*rom_)[kOverworldSpecialSpriteGfxGroupExpandedTemp + parent_ -
                  kSpecialWorldMapIdStart];
      sprite_graphics_[2] =
          (*rom_)[kOverworldSpecialSpriteGfxGroupExpandedTemp + parent_ -
                  kSpecialWorldMapIdStart];

      sprite_palette_[0] = (*rom_)[kOverworldSpecialSpritePaletteExpandedTemp +
                                   parent_ - kSpecialWorldMapIdStart];
      sprite_palette_[1] = (*rom_)[kOverworldSpecialSpritePaletteExpandedTemp +
                                   parent_ - kSpecialWorldMapIdStart];
      sprite_palette_[2] = (*rom_)[kOverworldSpecialSpritePaletteExpandedTemp +
                                   parent_ - kSpecialWorldMapIdStart];
    } else {
      // For v2/vanilla, use original sprite tables
      sprite_graphics_[0] = (*rom_)[kOverworldSpecialGfxGroup + parent_ -
                                    kSpecialWorldMapIdStart];
      sprite_graphics_[1] = (*rom_)[kOverworldSpecialGfxGroup + parent_ -
                                    kSpecialWorldMapIdStart];
      sprite_graphics_[2] = (*rom_)[kOverworldSpecialGfxGroup + parent_ -
                                    kSpecialWorldMapIdStart];

      sprite_palette_[0] = (*rom_)[kOverworldSpecialPalGroup + parent_ -
                                   kSpecialWorldMapIdStart];
      sprite_palette_[1] = (*rom_)[kOverworldSpecialPalGroup + parent_ -
                                   kSpecialWorldMapIdStart];
      sprite_palette_[2] = (*rom_)[kOverworldSpecialPalGroup + parent_ -
                                   kSpecialWorldMapIdStart];
    }

    area_graphics_ = (*rom_)[kAreaGfxIdPtr + parent_];
    area_palette_ = (*rom_)[kOverworldPalettesScreenToSetNew + parent_];

    // For v2/vanilla, use original palette table and handle special cases
    // These hardcoded cases are needed for vanilla compatibility
    if (asm_version < 3 || asm_version == 0xFF) {
      area_palette_ = (*rom_)[kOverworldMapPaletteIds + parent_];

      // Handle special world area cases based on ZScream documentation
      if (index_ == 0x88 || index_ == 0x93) {
        // Triforce room - special graphics and palette
        area_graphics_ = 0x51;
        area_palette_ = 0x00;
      } else if (index_ == 0x80) {
        // Master Sword area - use special graphics group
        area_graphics_ = (*rom_)[kOverworldSpecialGfxGroup +
                                 (parent_ - kSpecialWorldMapIdStart)];
        area_palette_ = (*rom_)[kOverworldSpecialPalGroup + 1];
      } else if (index_ == 0x81 || index_ == 0x82 || index_ == 0x89 ||
                 index_ == 0x8A) {
        // Zora's Domain areas - use special sprite graphics and area graphics
        // Note: These are the large area maps that were causing crashes
        sprite_graphics_[0] = 0x0E;
        sprite_graphics_[1] = 0x0E;
        sprite_graphics_[2] = 0x0E;

        area_graphics_ = (*rom_)[kOverworldSpecialGfxGroup +
                                 (parent_ - kSpecialWorldMapIdStart)];
        area_palette_ = (*rom_)[kOverworldSpecialPalGroup + 1];
      } else if (index_ == 0x94) {
        // Make this the same GFX as the true master sword area
        area_graphics_ = (*rom_)[kOverworldSpecialGfxGroup +
                                 (0x80 - kSpecialWorldMapIdStart)];
        area_palette_ = (*rom_)[kOverworldSpecialPalGroup + 1];
      } else if (index_ == 0x95) {
        // Make this the same GFX as the LW death mountain areas
        area_graphics_ = (*rom_)[kAreaGfxIdPtr + 0x03];
        area_palette_ = (*rom_)[kOverworldMapPaletteIds + 0x03];
      } else if (index_ == 0x96) {
        // Make this the same GFX as the pyramid areas
        area_graphics_ = (*rom_)[kAreaGfxIdPtr + 0x5B];
        area_palette_ = (*rom_)[kOverworldMapPaletteIds + 0x5B];
      } else if (index_ == 0x9C) {
        // Make this the same GFX as the DW death mountain areas
        area_graphics_ = (*rom_)[kAreaGfxIdPtr + 0x43];
        area_palette_ = (*rom_)[kOverworldMapPaletteIds + 0x43];
      } else {
        // Default case - use basic graphics
        area_graphics_ = (*rom_)[kAreaGfxIdPtr + 0x00];
        area_palette_ = (*rom_)[kOverworldMapPaletteIds + 0x00];
      }
    }
  }
}

void OverworldMap::LoadCustomOverworldData() {
  // Set the main palette values based on ZScream logic
  if (index_ < 0x40 || index_ == 0x95) {  // LW
    main_palette_ = 0;
  } else if ((index_ >= 0x40 && index_ < 0x80) || index_ == 0x96) {  // DW
    main_palette_ = 1;
  } else if (index_ >= 0x80 && index_ < 0xA0) {  // SW
    main_palette_ = 0;
  }

  if (index_ == 0x03 || index_ == 0x05 ||
      index_ == 0x07) {  // LW Death Mountain
    main_palette_ = 2;
  } else if (index_ == 0x43 || index_ == 0x45 ||
             index_ == 0x47) {  // DW Death Mountain
    main_palette_ = 3;
  } else if (index_ == 0x88 || index_ == 0x93) {  // Triforce room
    main_palette_ = 4;
  }

  // Set the mosaic values based on ZScream logic
  switch (index_) {
    case 0x00:  // Leaving Skull Woods / Lost Woods
    case 0x40:
      mosaic_expanded_ = {false, true, false, true};
      break;
    case 0x02:  // Going into Skull woods / Lost Woods west
    case 0x0A:
    case 0x42:
    case 0x4A:
      mosaic_expanded_ = {false, false, true, false};
      break;
    case 0x0F:  // Going into Zora's Domain North
    case 0x10:  // Going into Skull Woods / Lost Woods North
    case 0x11:
    case 0x50:
    case 0x51:
      mosaic_expanded_ = {true, false, false, false};
      break;
    case 0x80:  // Leaving Zora's Domain, the Master Sword area, and the
                // Triforce area
    case 0x81:
    case 0x88:
      mosaic_expanded_ = {false, true, false, false};
      break;
    default:
      mosaic_expanded_ = {false, false, false, false};
      break;
  }

  // Set up world index for GFX groups
  int index_world = 0x20;
  if (parent_ >= kDarkWorldMapIdStart &&
      parent_ < kSpecialWorldMapIdStart) {  // DW
    index_world = 0x21;
  } else if (parent_ == 0x88 || parent_ == 0x93) {  // Triforce room
    index_world = 0x24;
  }

  const auto overworld_gfx_groups2 =
      rom_->version_constants().kOverworldGfxGroups2;

  // Main Blocksets
  for (int i = 0; i < 8; i++) {
    custom_gfx_ids_[i] =
        (uint8_t)(*rom_)[overworld_gfx_groups2 + (index_world * 8) + i];
  }

  const auto overworldgfxGroups =
      rom_->version_constants().kOverworldGfxGroups1;

  // Replace the variable tiles with the variable ones
  uint8_t temp = (*rom_)[overworldgfxGroups + (area_graphics_ * 4)];
  if (temp != 0) {
    custom_gfx_ids_[3] = temp;
  } else {
    custom_gfx_ids_[3] = 0xFF;
  }

  temp = (*rom_)[overworldgfxGroups + (area_graphics_ * 4) + 1];
  if (temp != 0) {
    custom_gfx_ids_[4] = temp;
  } else {
    custom_gfx_ids_[4] = 0xFF;
  }

  temp = (*rom_)[overworldgfxGroups + (area_graphics_ * 4) + 2];
  if (temp != 0) {
    custom_gfx_ids_[5] = temp;
  } else {
    custom_gfx_ids_[5] = 0xFF;
  }

  temp = (*rom_)[overworldgfxGroups + (area_graphics_ * 4) + 3];
  if (temp != 0) {
    custom_gfx_ids_[6] = temp;
  } else {
    custom_gfx_ids_[6] = 0xFF;
  }

  // Set the animated GFX values
  if (index_ == 0x03 || index_ == 0x05 || index_ == 0x07 || index_ == 0x43 ||
      index_ == 0x45 || index_ == 0x47 || index_ == 0x95) {
    animated_gfx_ = 0x59;
  } else {
    animated_gfx_ = 0x5B;
  }

  // Set the subscreen overlay values
  subscreen_overlay_ = 0x00FF;

  if (index_ == 0x00 || index_ == 0x01 || index_ == 0x08 || index_ == 0x09 ||
      index_ == 0x40 || index_ == 0x41 || index_ == 0x48 ||
      index_ == 0x49) {  // Add fog 2 to the lost woods and skull woods
    subscreen_overlay_ = 0x009D;
  } else if (index_ == 0x03 || index_ == 0x04 || index_ == 0x0B ||
             index_ == 0x0C || index_ == 0x05 || index_ == 0x06 ||
             index_ == 0x0D || index_ == 0x0E ||
             index_ == 0x07) {  // Add the sky BG to LW death mountain
    subscreen_overlay_ = 0x0095;
  } else if (index_ == 0x43 || index_ == 0x44 || index_ == 0x4B ||
             index_ == 0x4C || index_ == 0x45 || index_ == 0x46 ||
             index_ == 0x4D || index_ == 0x4E ||
             index_ == 0x47) {  // Add the lava to DW death mountain
    subscreen_overlay_ = 0x009C;
  } else if (index_ == 0x5B || index_ == 0x5C || index_ == 0x63 ||
             index_ == 0x64) {  // TODO: Might need this one too "index == 0x1B"
                                // but for now I don't think so
    subscreen_overlay_ = 0x0096;
  } else if (index_ == 0x80) {  // Add fog 1 to the master sword area
    subscreen_overlay_ = 0x0097;
  } else if (index_ ==
             0x88) {  // Add the triforce room curtains to the triforce room
    subscreen_overlay_ = 0x0093;
  }
}

void OverworldMap::SetupCustomTileset(uint8_t asm_version) {
  // Load custom palette and mosaic settings
  main_palette_ = (*rom_)[OverworldCustomMainPaletteArray + index_];
  mosaic_ = (*rom_)[OverworldCustomMosaicArray + index_] != 0x00;

  uint8_t mosaicByte = (*rom_)[OverworldCustomMosaicArray + index_];
  mosaic_expanded_ = {(mosaicByte & 0x08) != 0x00, (mosaicByte & 0x04) != 0x00,
                      (mosaicByte & 0x02) != 0x00, (mosaicByte & 0x01) != 0x00};

  // Load area size for v3
  if (asm_version >= 3 && asm_version != 0xFF) {
    uint8_t size_byte = (*rom_)[kOverworldScreenSize + index_];
    area_size_ = static_cast<AreaSizeEnum>(size_byte);
    large_map_ = (area_size_ == AreaSizeEnum::LargeArea);
  }

  // Load custom GFX groups based on ASM version
  if (asm_version >= 0x01 && asm_version != 0xFF) {
    // Load from custom GFX group array
    for (int i = 0; i < 8; i++) {
      custom_gfx_ids_[i] =
          (*rom_)[OverworldCustomTileGFXGroupArray + (index_ * 8) + i];
    }
    animated_gfx_ = (*rom_)[OverworldCustomAnimatedGFXArray + index_];
  } else {
    // Fallback to vanilla logic for ROMs without custom ASM
    int index_world = 0x20;
    if (parent_ >= kDarkWorldMapIdStart &&
        parent_ < kSpecialWorldMapIdStart) {  // DW
      index_world = 0x21;
    } else if (parent_ == 0x88 || parent_ == 0x93) {  // Triforce room
      index_world = 0x24;
    }

    // Main Blocksets
    for (int i = 0; i < 8; i++) {
      custom_gfx_ids_[i] =
          (uint8_t)(*rom_)[rom_->version_constants().kOverworldGfxGroups2 +
                           (index_world * 8) + i];
    }

    const auto overworldgfxGroups =
        rom_->version_constants().kOverworldGfxGroups1;

    // Replace the variable tiles with the variable ones
    // If the variable is 00 set it to 0xFF which is the new "don't load
    // anything" value
    uint8_t temp = (*rom_)[overworldgfxGroups + (area_graphics_ * 4)];
    if (temp != 0x00) {
      custom_gfx_ids_[3] = temp;
    } else {
      custom_gfx_ids_[3] = 0xFF;
    }

    temp = (*rom_)[overworldgfxGroups + (area_graphics_ * 4) + 1];
    if (temp != 0x00) {
      custom_gfx_ids_[4] = temp;
    } else {
      custom_gfx_ids_[4] = 0xFF;
    }

    temp = (*rom_)[overworldgfxGroups + (area_graphics_ * 4) + 2];
    if (temp != 0x00) {
      custom_gfx_ids_[5] = temp;
    } else {
      custom_gfx_ids_[5] = 0xFF;
    }

    temp = (*rom_)[overworldgfxGroups + (area_graphics_ * 4) + 3];
    if (temp != 0x00) {
      custom_gfx_ids_[6] = temp;
    } else {
      custom_gfx_ids_[6] = 0xFF;
    }

    // Set the animated GFX values
    if (index_ == 0x03 || index_ == 0x05 || index_ == 0x07 || index_ == 0x43 ||
        index_ == 0x45 || index_ == 0x47) {
      animated_gfx_ = 0x59;
    } else {
      animated_gfx_ = 0x5B;
    }
  }

  // Load subscreen overlay
  subscreen_overlay_ =
      (*rom_)[OverworldCustomSubscreenOverlayArray + (index_ * 2)];
}

void OverworldMap::LoadMainBlocksetId() {
  if (parent_ < kDarkWorldMapIdStart) {
    main_gfx_id_ = 0x20;
  } else if (parent_ >= kDarkWorldMapIdStart &&
             parent_ < kSpecialWorldMapIdStart) {
    main_gfx_id_ = 0x21;
  } else if (parent_ >= kSpecialWorldMapIdStart) {
    // Special world maps - use appropriate graphics ID based on the specific map
    if (parent_ == 0x88) {
      main_gfx_id_ = 0x24;
    } else {
      // Default special world graphics ID
      main_gfx_id_ = 0x20;
    }
  }
}

void OverworldMap::LoadSpritesBlocksets() {
  int static_graphics_base = 0x73;
  static_graphics_[8] = static_graphics_base + 0x00;
  static_graphics_[9] = static_graphics_base + 0x01;
  static_graphics_[10] = static_graphics_base + 0x06;
  static_graphics_[11] = static_graphics_base + 0x07;

  for (int i = 0; i < 4; i++) {
    static_graphics_[12 + i] =
        ((*rom_)[rom_->version_constants().kSpriteBlocksetPointer +
                 (sprite_graphics_[game_state_] * 4) + i] +
         static_graphics_base);
  }
}

void OverworldMap::LoadMainBlocksets() {
  for (int i = 0; i < 8; i++) {
    static_graphics_[i] =
        (*rom_)[rom_->version_constants().kOverworldGfxGroups2 +
                (main_gfx_id_ * 8) + i];
  }
}

// For animating water tiles on the overworld map.
// We want to swap out static_graphics_[07] with the next sheet
// Usually it is 5A, so we make it 5B instead.
// There is a middle frame which contains tiles from the bottom half
// of the 5A sheet, so this will need some special manipulation to make work
// during the BuildBitmap step (or a new one specifically for animating).
void OverworldMap::DrawAnimatedTiles() {
  if (static_graphics_[7] == 0x5B) {
    static_graphics_[7] = 0x5A;
  } else {
    if (static_graphics_[7] == 0x59) {
      static_graphics_[7] = 0x58;
    }
    static_graphics_[7] = 0x5B;
  }
}

void OverworldMap::LoadAreaGraphicsBlocksets() {
  for (int i = 0; i < 4; i++) {
    uint8_t value = (*rom_)[rom_->version_constants().kOverworldGfxGroups1 +
                            (area_graphics_ * 4) + i];
    if (value != 0) {
      static_graphics_[3 + i] = value;
    }
  }
}

// TODO: Change the conditions for death mountain gfx
// JaredBrian: This is how ZS did it, but in 3.0.4 I changed it to just check
// for 03, 05, 07, and the DW ones as that's how it would appear in-game if
// you were to make area 03 not a large area anymore for example, so you might
// want to do the same.
void OverworldMap::LoadDeathMountainGFX() {
  static_graphics_[7] = (((parent_ >= 0x03 && parent_ <= 0x07) ||
                          (parent_ >= 0x0B && parent_ <= 0x0E)) ||
                         ((parent_ >= 0x43 && parent_ <= 0x47) ||
                          (parent_ >= 0x4B && parent_ <= 0x4E)))
                            ? 0x59
                            : 0x5B;
}

void OverworldMap::LoadAreaGraphics() {
  LoadMainBlocksetId();
  LoadSpritesBlocksets();
  LoadMainBlocksets();
  LoadAreaGraphicsBlocksets();
  LoadDeathMountainGFX();
}

namespace palette_internal {

absl::Status SetColorsPalette(Rom& rom, int index, gfx::SnesPalette& current,
                              gfx::SnesPalette main, gfx::SnesPalette animated,
                              gfx::SnesPalette aux1, gfx::SnesPalette aux2,
                              gfx::SnesPalette hud, gfx::SnesColor bgrcolor,
                              gfx::SnesPalette spr, gfx::SnesPalette spr2) {
  // Palettes infos, color 0 of a palette is always transparent (the arrays
  // contains 7 colors width wide) There is 16 color per line so 16*Y

  // Left side of the palette - Main, Animated
  std::array<gfx::SnesColor, 256> new_palette = {};

  // Main Palette, Location 0,2 : 35 colors [7x5]
  int k = 0;
  for (int y = 2; y < 7; y++) {
    for (int x = 1; x < 8; x++) {
      new_palette[x + (16 * y)] = main[k];
      k++;
    }
  }

  // Animated Palette, Location 0,7 : 7colors
  for (int x = 1; x < 8; x++) {
    new_palette[(16 * 7) + (x)] = animated[(x - 1)];
  }

  // Right side of the palette - Aux1, Aux2

  // Aux1 Palette, Location 8,2 : 21 colors [7x3]
  k = 0;
  for (int y = 2; y < 5; y++) {
    for (int x = 9; x < 16; x++) {
      new_palette[x + (16 * y)] = aux1[k];
      k++;
    }
  }

  // Aux2 Palette, Location 8,5 : 21 colors [7x3]
  k = 0;
  for (int y = 5; y < 8; y++) {
    for (int x = 9; x < 16; x++) {
      new_palette[x + (16 * y)] = aux2[k];
      k++;
    }
  }

  // Hud Palette, Location 0,0 : 32 colors [16x2]
  for (int i = 0; i < 32; i++) {
    new_palette[i] = hud[i];
  }

  // Hardcoded grass color (that might change to become invisible instead)
  for (int i = 0; i < 8; i++) {
    new_palette[(i * 16)] = bgrcolor;
    new_palette[(i * 16) + 8] = bgrcolor;
  }

  // Sprite Palettes
  k = 0;
  for (int y = 8; y < 9; y++) {
    for (int x = 1; x < 8; x++) {
      new_palette[x + (16 * y)] = rom.palette_group().sprites_aux1[1][k];
      k++;
    }
  }

  // Sprite Palettes
  k = 0;
  for (int y = 8; y < 9; y++) {
    for (int x = 9; x < 16; x++) {
      new_palette[x + (16 * y)] = rom.palette_group().sprites_aux3[0][k];
      k++;
    }
  }

  // Sprite Palettes
  k = 0;
  for (int y = 9; y < 13; y++) {
    for (int x = 1; x < 16; x++) {
      new_palette[x + (16 * y)] = rom.palette_group().global_sprites[0][k];
      k++;
    }
  }

  // Sprite Palettes
  k = 0;
  for (int y = 13; y < 14; y++) {
    for (int x = 1; x < 8; x++) {
      new_palette[x + (16 * y)] = spr[k];
      k++;
    }
  }

  // Sprite Palettes
  k = 0;
  for (int y = 14; y < 15; y++) {
    for (int x = 1; x < 8; x++) {
      new_palette[x + (16 * y)] = spr2[k];
      k++;
    }
  }

  // Sprite Palettes
  k = 0;
  for (int y = 15; y < 16; y++) {
    for (int x = 1; x < 16; x++) {
      new_palette[x + (16 * y)] = rom.palette_group().armors[0][k];
      k++;
    }
  }

  for (int i = 0; i < 256; i++) {
    current[i] = new_palette[i];
    current[(i / 16) * 16].set_transparent(true);
  }

  current.set_size(256);
  return absl::OkStatus();
}
}  // namespace palette_internal

absl::StatusOr<gfx::SnesPalette> OverworldMap::GetPalette(
    const gfx::PaletteGroup& palette_group, int index, int previous_index,
    int limit) {
  if (index == 255) {
    index = (*rom_)[rom_->version_constants().kOverworldMapPaletteGroup +
                    (previous_index * 4)];
  }
  if (index >= limit) {
    index = limit - 1;
  }
  return palette_group[index];
}

absl::Status OverworldMap::LoadPalette() {
  uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];

  int previous_pal_id = 0;
  int previous_spr_pal_id = 0;

  if (index_ > 0) {
    // Load previous palette ID based on ASM version
    if (asm_version < 3 || asm_version == 0xFF) {
      previous_pal_id = (*rom_)[kOverworldMapPaletteIds + parent_ - 1];
    } else {
      // v3 uses expanded palette table
      previous_pal_id = (*rom_)[kOverworldPalettesScreenToSetNew + parent_ - 1];
    }

    previous_spr_pal_id = (*rom_)[kOverworldSpritePaletteIds + parent_ - 1];
  }

  area_palette_ = std::min((int)area_palette_, 0xA3);

  uint8_t pal0 = 0;
  uint8_t pal1 = (*rom_)[rom_->version_constants().kOverworldMapPaletteGroup +
                         (area_palette_ * 4)];
  uint8_t pal2 = (*rom_)[rom_->version_constants().kOverworldMapPaletteGroup +
                         (area_palette_ * 4) + 1];
  uint8_t pal3 = (*rom_)[rom_->version_constants().kOverworldMapPaletteGroup +
                         (area_palette_ * 4) + 2];
  uint8_t pal4 = (*rom_)[kOverworldSpritePaletteGroup +
                         (sprite_palette_[game_state_] * 2)];
  uint8_t pal5 = (*rom_)[kOverworldSpritePaletteGroup +
                         (sprite_palette_[game_state_] * 2) + 1];

  auto grass_pal_group = rom_->palette_group().grass;
  auto bgr = grass_pal_group[0][0];

  // Handle 0xFF palette references (use previous palette)
  if (pal1 == 0xFF) {
    pal1 = (*rom_)[rom_->version_constants().kOverworldMapPaletteGroup +
                   (previous_pal_id * 4)];
  }

  if (pal2 == 0xFF) {
    pal2 = (*rom_)[rom_->version_constants().kOverworldMapPaletteGroup +
                   (previous_pal_id * 4) + 1];
  }

  if (pal3 == 0xFF) {
    pal3 = (*rom_)[rom_->version_constants().kOverworldMapPaletteGroup +
                   (previous_pal_id * 4) + 2];
  }

  auto ow_aux_pal_group = rom_->palette_group().overworld_aux;
  ASSIGN_OR_RETURN(gfx::SnesPalette aux1,
                   GetPalette(ow_aux_pal_group, pal1, previous_pal_id, 20));
  ASSIGN_OR_RETURN(gfx::SnesPalette aux2,
                   GetPalette(ow_aux_pal_group, pal2, previous_pal_id, 20));

  // Set background color based on world type and area-specific settings
  bool use_area_specific_bg =
      (*rom_)[OverworldCustomAreaSpecificBGEnabled] != 0x00;
  if (use_area_specific_bg) {
    // Use area-specific background color from custom array
    area_specific_bg_color_ =
        (*rom_)[OverworldCustomAreaSpecificBGPalette + (parent_ * 2)] |
        ((*rom_)[OverworldCustomAreaSpecificBGPalette + (parent_ * 2) + 1]
         << 8);
    // Convert 15-bit SNES color to palette color
    bgr = gfx::SnesColor(area_specific_bg_color_);
  } else {
    // Use default world-based background colors
    if (parent_ < kDarkWorldMapIdStart) {
      bgr = grass_pal_group[0][0];  // LW
    } else if (parent_ >= kDarkWorldMapIdStart &&
               parent_ < kSpecialWorldMapIdStart) {
      bgr = grass_pal_group[0][1];  // DW
    } else if (parent_ >= 128 && parent_ < kNumOverworldMaps) {
      bgr = grass_pal_group[0][2];  // SW
    }
  }

  // Use main palette from the overworld map data (matches ZScream logic)
  pal0 = main_palette_;

  auto ow_main_pal_group = rom_->palette_group().overworld_main;
  ASSIGN_OR_RETURN(gfx::SnesPalette main,
                   GetPalette(ow_main_pal_group, pal0, previous_pal_id, 255));
  auto ow_animated_pal_group = rom_->palette_group().overworld_animated;
  ASSIGN_OR_RETURN(gfx::SnesPalette animated,
                   GetPalette(ow_animated_pal_group, std::min((int)pal3, 13),
                              previous_pal_id, 14));

  auto hud_pal_group = rom_->palette_group().hud;
  gfx::SnesPalette hud = hud_pal_group[0];

  // Handle 0xFF sprite palette references (use previous sprite palette)
  if (pal4 == 0xFF) {
    pal4 = (*rom_)[kOverworldSpritePaletteGroup + (previous_spr_pal_id * 2)];
  }

  if (pal4 == 0xFF) {
    pal4 = 0;  // Fallback to 0 if still 0xFF
  }

  if (pal5 == 0xFF) {
    pal5 =
        (*rom_)[kOverworldSpritePaletteGroup + (previous_spr_pal_id * 2) + 1];
  }

  if (pal5 == 0xFF) {
    pal5 = 0;  // Fallback to 0 if still 0xFF
  }

  ASSIGN_OR_RETURN(gfx::SnesPalette spr,
                   GetPalette(rom_->palette_group().sprites_aux3, pal4,
                              previous_spr_pal_id, 24));
  ASSIGN_OR_RETURN(gfx::SnesPalette spr2,
                   GetPalette(rom_->palette_group().sprites_aux3, pal5,
                              previous_spr_pal_id, 24));

  RETURN_IF_ERROR(palette_internal::SetColorsPalette(
      *rom_, parent_, current_palette_, main, animated, aux1, aux2, hud, bgr,
      spr, spr2));

  if (palettesets_.count(area_palette_) == 0) {
    palettesets_[area_palette_] = gfx::Paletteset{
        main, animated, aux1, aux2, bgr, hud, spr, spr2, current_palette_};
  }

  return absl::OkStatus();
}

absl::Status OverworldMap::LoadOverlay() {
  uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];

  // Load overlays based on ROM version
  if (asm_version == 0xFF) {
    // Vanilla ROM - load overlay from overlay pointers
    return LoadVanillaOverlayData();
  } else {
    // Custom overworld ROM - use overlay from custom data
    overlay_id_ = subscreen_overlay_;
    has_overlay_ = (overlay_id_ != 0x00FF);
    overlay_data_.clear();
    return absl::OkStatus();
  }
}

absl::Status OverworldMap::LoadVanillaOverlayData() {

  // Load vanilla overlay for this map (interactive overlays for revealing holes/changing elements)
  int address = (kOverlayPointersBank << 16) +
                ((*rom_)[kOverlayPointers + (index_ * 2) + 1] << 8) +
                (*rom_)[kOverlayPointers + (index_ * 2)];

  // Convert SNES address to PC address
  address = ((address & 0x7F0000) >> 1) | (address & 0x7FFF);

  // Check if custom overlay code is present
  if ((*rom_)[kOverlayData1] == 0x6B) {
    // Use custom overlay data pointer
    address = ((*rom_)[kOverlayData2 + 2 + (index_ * 3)] << 16) +
              ((*rom_)[kOverlayData2 + 1 + (index_ * 3)] << 8) +
              (*rom_)[kOverlayData2 + (index_ * 3)];
    address = ((address & 0x7F0000) >> 1) | (address & 0x7FFF);
  }

  // Validate address
  if (address >= rom_->size()) {
    has_overlay_ = false;
    overlay_id_ = 0;
    overlay_data_.clear();
    return absl::OkStatus();
  }

  // Parse overlay data (interactive overlays)
  overlay_data_.clear();
  uint8_t b = (*rom_)[address];

  // Parse overlay commands until we hit END (0x60)
  while (b != 0x60 && address < rom_->size()) {
    overlay_data_.push_back(b);

    // Handle different overlay commands
    switch (b) {
      case 0xA9:  // LDA #$
        if (address + 2 < rom_->size()) {
          overlay_data_.push_back((*rom_)[address + 1]);
          overlay_data_.push_back((*rom_)[address + 2]);
          address += 3;
        } else {
          address++;
        }
        break;
      case 0xA2:  // LDX #$
        if (address + 2 < rom_->size()) {
          overlay_data_.push_back((*rom_)[address + 1]);
          overlay_data_.push_back((*rom_)[address + 2]);
          address += 3;
        } else {
          address++;
        }
        break;
      case 0x8D:  // STA $xxxx
        if (address + 3 < rom_->size()) {
          overlay_data_.push_back((*rom_)[address + 1]);
          overlay_data_.push_back((*rom_)[address + 2]);
          overlay_data_.push_back((*rom_)[address + 3]);
          address += 4;
        } else {
          address++;
        }
        break;
      case 0x9D:  // STA $xxxx,x
        if (address + 3 < rom_->size()) {
          overlay_data_.push_back((*rom_)[address + 1]);
          overlay_data_.push_back((*rom_)[address + 2]);
          overlay_data_.push_back((*rom_)[address + 3]);
          address += 4;
        } else {
          address++;
        }
        break;
      case 0x8F:  // STA $xxxxxx
        if (address + 4 < rom_->size()) {
          overlay_data_.push_back((*rom_)[address + 1]);
          overlay_data_.push_back((*rom_)[address + 2]);
          overlay_data_.push_back((*rom_)[address + 3]);
          overlay_data_.push_back((*rom_)[address + 4]);
          address += 5;
        } else {
          address++;
        }
        break;
      case 0x1A:  // INC A
        address++;
        break;
      case 0x4C:  // JMP
        if (address + 3 < rom_->size()) {
          overlay_data_.push_back((*rom_)[address + 1]);
          overlay_data_.push_back((*rom_)[address + 2]);
          overlay_data_.push_back((*rom_)[address + 3]);
          address += 4;
        } else {
          address++;
        }
        break;
      default:
        address++;
        break;
    }

    if (address < rom_->size()) {
      b = (*rom_)[address];
    } else {
      break;
    }
  }

  // Add the END command if we found it
  if (b == 0x60) {
    overlay_data_.push_back(0x60);
  }

  // Set overlay ID based on map index (simplified)
  overlay_id_ = index_;
  has_overlay_ = !overlay_data_.empty();

  return absl::OkStatus();
}

void OverworldMap::ProcessGraphicsBuffer(int index, int static_graphics_offset,
                                         int size, uint8_t* all_gfx) {
  // Ensure we don't go out of bounds
  int max_offset = static_graphics_offset * size + size;
  if (max_offset > rom_->graphics_buffer().size()) {
    // Fill with zeros if out of bounds
    for (int i = 0; i < size; i++) {
      current_gfx_[(index * size) + i] = 0x00;
    }
    return;
  }

  for (int i = 0; i < size; i++) {
    auto byte = all_gfx[i + (static_graphics_offset * size)];
    switch (index) {
      case 0:
      case 3:
      case 4:
      case 5:
        byte += 0x88;
        break;
    }
    current_gfx_[(index * size) + i] = byte;
  }
}

absl::Status OverworldMap::BuildTileset() {
  if (current_gfx_.size() == 0)
    current_gfx_.resize(0x10000, 0x00);

  // Process the 8 main graphics sheets (slots 0-7)
  for (int i = 0; i < 8; i++) {
    if (static_graphics_[i] != 0) {
      ProcessGraphicsBuffer(i, static_graphics_[i], 0x1000,
                            rom_->graphics_buffer().data());
    }
  }

  // Process sprite graphics (slots 8-15)
  for (int i = 8; i < 16; i++) {
    if (static_graphics_[i] != 0) {
      ProcessGraphicsBuffer(i, static_graphics_[i], 0x1000,
                            rom_->graphics_buffer().data());
    }
  }

  // Process animated graphics if available (slot 16)
  if (static_graphics_[16] != 0) {
    ProcessGraphicsBuffer(7, static_graphics_[16], 0x1000,
                          rom_->graphics_buffer().data());
  }

  return absl::OkStatus();
}

absl::Status OverworldMap::BuildTiles16Gfx(std::vector<gfx::Tile16>& tiles16,
                                           int count) {
  if (current_blockset_.size() == 0)
    current_blockset_.resize(0x100000, 0x00);

  const int offsets[] = {0x00, 0x08, 0x400, 0x408};
  auto yy = 0;
  auto xx = 0;

  for (auto i = 0; i < count; i++) {
    for (auto tile = 0; tile < 0x04; tile++) {
      gfx::TileInfo info = tiles16[i].tiles_info[tile];
      int offset = offsets[tile];
      for (auto y = 0; y < 0x08; ++y) {
        for (auto x = 0; x < 0x08; ++x) {
          int mx = x;
          int my = y;

          if (info.horizontal_mirror_ != 0) {
            mx = 0x07 - x;
          }

          if (info.vertical_mirror_ != 0) {
            my = 0x07 - y;
          }

          int xpos = ((info.id_ % 0x10) * 0x08);
          int ypos = (((info.id_ / 0x10)) * 0x400);
          int source = ypos + xpos + (x + (y * 0x80));

          auto destination = xx + yy + offset + (mx + (my * 0x80));
          current_blockset_[destination] =
              (current_gfx_[source] & 0x0F) + (info.palette_ * 0x10);
        }
      }
    }

    xx += 0x10;
    if (xx >= 0x80) {
      yy += 0x800;
      xx = 0;
    }
  }

  return absl::OkStatus();
}

absl::Status OverworldMap::BuildBitmap(OverworldBlockset& world_blockset) {
  if (bitmap_data_.size() != 0) {
    bitmap_data_.clear();
  }
  bitmap_data_.reserve(0x40000);
  for (int i = 0; i < 0x40000; i++) {
    bitmap_data_.push_back(0x00);
  }

  int superY = ((index_ - (world_ * 0x40)) / 0x08);
  int superX = index_ - (world_ * 0x40) - (superY * 0x08);

  for (int y = 0; y < 0x20; y++) {
    for (int x = 0; x < 0x20; x++) {
      auto xt = x + (superX * 0x20);
      auto yt = y + (superY * 0x20);
      gfx::CopyTile8bpp16((x * 0x10), (y * 0x10), world_blockset[xt][yt],
                          bitmap_data_, current_blockset_);
    }
  }
  return absl::OkStatus();
}

}  // namespace zelda3
}  // namespace yaze
