#include "ui/TilesTab.h"

#include <SDL3/SDL.h>
#include <imgui.h>

#include <bit>
#include <utility>

#include "App.h"
#include "core/TileSet.h"
#include "ui/CompressionFormatCombo.h"

namespace bmp2tile::ui
{

namespace
{

std::string DefaultTilesSavePath(const std::string& imagePath, CompressionFormat format)
{
    const size_t dot = imagePath.find_last_of('.');
    const std::string base = (dot == std::string::npos) ? imagePath : imagePath.substr(0, dot);
    return base + ".tiles." + CompressionFormatFileExtension(format);
}

// See the matching callback in PaletteTab.cpp for the main-thread note.
void OnTilesSaveDialogResult(void* userdata, const char* const* filelist, int /*filter*/)
{
    App& app = *static_cast<App*>(userdata);
    if (!filelist)
    {
        app.LastError = std::string("Save dialog error: ") + SDL_GetError();
        return;
    }
    if (!filelist[0])
    {
        return;  // user cancelled -- nothing to do
    }
    app.SaveTiles(filelist[0]);
}

}  // namespace

void DrawTilesTab(App& app)
{
    if (!app.bHasImage)
    {
        ImGui::TextDisabled("Load an image on the Source tab first.");
        return;
    }

    bool optionsChanged = false;
    optionsChanged |= ImGui::Checkbox("Remove duplicates", &app.bRemoveDuplicates);
    ImGui::SameLine();
    ImGui::BeginDisabled(!app.bRemoveDuplicates);
    optionsChanged |= ImGui::Checkbox("Use tile mirroring", &app.bUseMirroring);
    ImGui::EndDisabled();
    ImGui::SameLine();
    optionsChanged |= ImGui::Checkbox("8x16 sprite mode", &app.bSprite8x16);

    if (optionsChanged)
    {
        app.RebuildTileSet();
    }

    const int totalCells = app.CurrentTileSet.TilesWide * app.CurrentTileSet.TilesHigh;
    ImGui::Text("%d unique tile%s from %d cell%s (%dx%d grid)", static_cast<int>(app.CurrentTileSet.Tiles.size()),
                app.CurrentTileSet.Tiles.size() == 1 ? "" : "s", totalCells, totalCells == 1 ? "" : "s",
                app.CurrentTileSet.TilesWide, app.CurrentTileSet.TilesHigh);

    DrawCompressionFormatCombo("Compression", app.TilesCompressionFormat, SupportsTiles);

    if (ImGui::Button("Save tiles..."))
    {
        const std::string ext = CompressionFormatFileExtension(app.TilesCompressionFormat);
        const SDL_DialogFileFilter filters[] = {
            {"Tile data", ext.c_str()},
        };
        const std::string defaultPath = DefaultTilesSavePath(app.LoadedPath, app.TilesCompressionFormat);
        SDL_ShowSaveFileDialog(OnTilesSaveDialogResult, &app, app.Window, filters, 1, defaultPath.c_str());
    }
    if (!app.LastSaveMessage.empty())
    {
        ImGui::SameLine();
        ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "%s", app.LastSaveMessage.c_str());
    }

    if (ImGui::CollapsingHeader("Include text (.db format)"))
    {
        ImGui::TextDisabled("Uncompressed planar bytes only -- doesn't reflect the compression format selected above.");
        const std::string hex = FormatTilesAsSmsHex(app.CurrentTileSet.Tiles);
        ImGui::InputTextMultiline("##tileshex", const_cast<char*>(hex.c_str()), hex.size() + 1, ImVec2(-1, 150),
                                   ImGuiInputTextFlags_ReadOnly);
    }

    ImGui::Separator();

    static int ThumbSize = 32;
    ImGui::SliderInt("Thumbnail size", &ThumbSize, 16, 96);

    ImGui::BeginChild("TileGrid", ImVec2(0, 0), true);
    const float availWidth = ImGui::GetContentRegionAvail().x;
    const float stride = static_cast<float>(ThumbSize) + ImGui::GetStyle().ItemSpacing.x;
    const int fits = static_cast<int>(availWidth / stride);
    // Round the column count down to a power of two so each row starts at a
    // multiple of $10, $20, etc. Tile indices are read in hex when writing
    // VRAM-loading code, so a ragged 13-per-row grid makes "which tile is
    // this?" much harder to answer by eye than a narrower but aligned one.
    const int columns = (fits > 1) ? static_cast<int>(std::bit_floor(static_cast<unsigned int>(fits))) : 1;

    ImGui::GetWindowDrawList()->AddCallback(ImGui::GetPlatformIO().DrawCallback_SetSamplerNearest);
    for (size_t i = 0; i < app.TileTextures.size(); ++i)
    {
        ImGui::PushID(static_cast<int>(i));
        if (app.TileTextures[i])
        {
            // See the note in SourceTab.cpp about this cast.
            ImGui::Image(reinterpret_cast<ImTextureID>(app.TileTextures[i]),
                         ImVec2(static_cast<float>(ThumbSize), static_cast<float>(ThumbSize)));
        }
        else
        {
            ImGui::Dummy(ImVec2(static_cast<float>(ThumbSize), static_cast<float>(ThumbSize)));
        }
        if (ImGui::IsItemHovered())
        {
            ImGui::SetTooltip("Tile #%zu", i);
        }
        if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID))
        {
            int idx = static_cast<int>(i);
            ImGui::SetDragDropPayload("TILE_IDX", &idx, sizeof(int));
            if (app.TileTextures[i])
            {
                ImGui::Image(reinterpret_cast<ImTextureID>(app.TileTextures[i]),
                             ImVec2(static_cast<float>(ThumbSize), static_cast<float>(ThumbSize)));
                ImGui::SameLine();
            }
            ImGui::Text("Tile #%zu", i);
            ImGui::EndDragDropSource();
        }
        if (ImGui::BeginDragDropTarget())
        {
            if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("TILE_IDX"))
            {
                const int src = *static_cast<const int*>(payload->Data);
                const int dst = static_cast<int>(i);
                if (src != dst)
                {
                    std::swap(app.CurrentTileSet.Tiles[src], app.CurrentTileSet.Tiles[dst]);
                    std::swap(app.TileTextures[src], app.TileTextures[dst]);
                    // Fix up tilemap references so each cell still points at
                    // the same tile data after the swap.
                    for (TilemapEntry& entry : app.CurrentTileSet.Tilemap)
                    {
                        if (entry.TileIndex == src)
                            entry.TileIndex = dst;
                        else if (entry.TileIndex == dst)
                            entry.TileIndex = src;
                    }
                }
            }
            ImGui::EndDragDropTarget();
        }
        ImGui::PopID();

        if (static_cast<int>((i + 1) % columns) != 0 && i + 1 < app.TileTextures.size())
        {
            ImGui::SameLine();
        }
    }
    ImGui::GetWindowDrawList()->AddCallback(ImGui::GetPlatformIO().DrawCallback_SetSamplerLinear);
    ImGui::EndChild();
}

}  // namespace bmp2tile::ui
