#include "ui/PaletteTab.h"

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

#include <utility>

#include "App.h"
#include "core/Palette.h"

namespace bmp2tile::ui
{

namespace
{

// Suggests "<image name>.palette.bin" next to the source image, as a
// starting point for the save dialog rather than an empty/blank name.
std::string DefaultPaletteSavePath(const std::string& imagePath)
{
    const size_t dot = imagePath.find_last_of('.');
    const std::string base = (dot == std::string::npos) ? imagePath : imagePath.substr(0, dot);
    return base + ".palette.bin";
}

// Invoked by SDL once the native save dialog closes. Per SDL3's dialog
// docs, and confirmed in practice, this fires on the main thread, so
// it's safe to touch App directly here.
void OnPaletteSaveDialogResult(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.SavePalette(filelist[0]);
}

}  // namespace

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

    ImGui::Text("%zu colours", app.CurrentImage.Palette.size());
    ImGui::SameLine();
    ImGui::Checkbox("Always emit 16 colours", &app.bAlwaysEmit16Colours);
    if (app.bAlwaysEmit16Colours && app.CurrentImage.Palette.size() < 16)
    {
        ImGui::SameLine();
        ImGui::TextDisabled("(padded with black below)");
    }
    ImGui::Separator();

    // Reflects exactly what -- and how many entries -- will actually be
    // written out, so the swatches/preview/save are always consistent
    // with each other.
    const std::vector<Color> displayPalette =
        app.bAlwaysEmit16Colours ? PadPaletteTo16(app.CurrentImage.Palette) : app.CurrentImage.Palette;

    for (size_t i = 0; i < displayPalette.size(); ++i)
    {
        const Color& c = displayPalette[i];
        const ImVec4 col(c.R / 255.0f, c.G / 255.0f, c.B / 255.0f, 1.0f);
        // Padding entries added by "Always emit 16 colours" are not part of
        // the real palette and cannot be reordered.
        const bool bActualEntry = (i < app.CurrentImage.Palette.size());
        ImGui::PushID(static_cast<int>(i));
        ImGui::ColorButton("##swatch", col, ImGuiColorEditFlags_NoTooltip, ImVec2(28, 28));
        if (ImGui::IsItemHovered())
        {
            ImGui::SetTooltip("Index %zu: #%02X%02X%02X%s", i, c.R, c.G, c.B,
                               bActualEntry ? "" : " (padding)");
        }
        if (bActualEntry && ImGui::BeginDragDropSource())
        {
            int idx = static_cast<int>(i);
            ImGui::SetDragDropPayload("PALETTE_IDX", &idx, sizeof(int));
            ImGui::ColorButton("##drag", col, ImGuiColorEditFlags_NoTooltip, ImVec2(28, 28));
            ImGui::SameLine();
            ImGui::Text("Index %zu", i);
            ImGui::EndDragDropSource();
        }
        if (bActualEntry && ImGui::BeginDragDropTarget())
        {
            if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("PALETTE_IDX"))
            {
                const int src = *static_cast<const int*>(payload->Data);
                const int dst = static_cast<int>(i);
                if (src != dst)
                {
                    std::swap(app.CurrentImage.Palette[src], app.CurrentImage.Palette[dst]);
                    // Remap pixel indices so the image still maps to the same
                    // colours after the palette swap.
                    for (uint8_t& px : app.CurrentImage.Pixels)
                    {
                        if (px == static_cast<uint8_t>(src))
                            px = static_cast<uint8_t>(dst);
                        else if (px == static_cast<uint8_t>(dst))
                            px = static_cast<uint8_t>(src);
                    }
                    app.RebuildTileSet();
                }
            }
            ImGui::EndDragDropTarget();
        }
        ImGui::PopID();
        if ((i + 1) % 8 != 0 && i + 1 < displayPalette.size())
        {
            ImGui::SameLine();
        }
    }

    ImGui::Separator();
    const char* formatLabels[] = {"SMS hex", "GG hex", "cl123 constants"};
    int formatIndex = static_cast<int>(app.CurrentPaletteFormat);
    if (ImGui::Combo("Output format", &formatIndex, formatLabels, 3))
    {
        app.CurrentPaletteFormat = static_cast<PaletteFormat>(formatIndex);
    }

    std::string preview;
    switch (app.CurrentPaletteFormat)
    {
        case PaletteFormat::Sms:
            preview = FormatPaletteAsSmsHex(displayPalette);
            break;
        case PaletteFormat::Gg:
            ImGui::TextDisabled("12-bit GG format is a straightforward channel>>4 shift, per the original tool's own documented behaviour -- no smarter palette matching.");
            preview = FormatPaletteAsGgHex(displayPalette);
            break;
        case PaletteFormat::Cl123:
            ImGui::TextDisabled("Pair with a project colours.inc that #defines each clRGB name to its SMS byte value.");
            preview = FormatPaletteAsCl123Constants(displayPalette);
            break;
    }
    ImGui::InputTextMultiline("##palettehex", const_cast<char*>(preview.c_str()), preview.size() + 1, ImVec2(-1, 80),
                               ImGuiInputTextFlags_ReadOnly);

    ImGui::Separator();
    if (app.CurrentPaletteFormat == PaletteFormat::Cl123)
    {
        ImGui::TextDisabled("cl123 is a text/source format -- there's no binary file to save. Switch to SMS or GG to save one.");
    }
    else
    {
        ImGui::TextDisabled("Save as raw bytes (one colour per %s), e.g. for SMS_loadBGPalette:",
                             app.CurrentPaletteFormat == PaletteFormat::Gg ? "2 bytes, %ggggrrrr %0000bbbb" : "byte, %00BBGGRR");
        if (ImGui::Button("Save##palette"))
        {
            const SDL_DialogFileFilter filters[] = {
                {"Binary palette", "bin"},
            };
            const std::string defaultPath = DefaultPaletteSavePath(app.LoadedPath);
            SDL_ShowSaveFileDialog(OnPaletteSaveDialogResult, &app, app.Window, filters, 1, defaultPath.c_str());
        }
    }
    if (!app.LastSaveMessage.empty())
    {
        ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "%s", app.LastSaveMessage.c_str());
    }
}

}  // namespace bmp2tile::ui
