// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <algorithm>
#include <vector>
#include "ime_ui.h"
#include "imgui/imgui_std.h"

namespace Libraries::Ime {

using namespace ImGui;

static constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f};

ImeState::ImeState(const OrbisImeParam* param, const OrbisImeParamExtended* extended) {
    if (!param) {
        LOG_ERROR(Lib_Ime, "Invalid IME parameters");
        return;
    }
    if (!param->work) {
        LOG_ERROR(Lib_Ime, "Invalid work buffer pointer");
        return;
    }
    if (!param->inputTextBuffer) {
        LOG_ERROR(Lib_Ime, "Invalid text buffer pointer");
        return;
    }
    work_buffer = param->work;
    text_buffer = param->inputTextBuffer;
    // Respect both the absolute IME limit and the caller-provided limit
    max_text_length = std::min(param->maxTextLength, ORBIS_IME_MAX_TEXT_LENGTH);

    if (extended) {
        LOG_INFO(Lib_Ime, "Extended IME parameters provided");
    }

    if (text_buffer) {
        const std::size_t text_len = std::char_traits<char16_t>::length(text_buffer);
        if (!ConvertOrbisToUTF8(text_buffer, text_len, current_text.begin(),
                                ORBIS_IME_MAX_TEXT_LENGTH * 4 + 1)) {
            LOG_ERROR(Lib_Ime, "Failed to convert text to utf8 encoding");
        }
    }
}

ImeState::ImeState(ImeState&& other) noexcept
    : work_buffer(other.work_buffer), text_buffer(other.text_buffer),
      max_text_length(other.max_text_length), current_text(std::move(other.current_text)),
      event_queue(std::move(other.event_queue)) {
    other.text_buffer = nullptr;
    other.max_text_length = 0;
}

ImeState& ImeState::operator=(ImeState&& other) noexcept {
    if (this != &other) {
        work_buffer = other.work_buffer;
        text_buffer = other.text_buffer;
        max_text_length = other.max_text_length;
        current_text = std::move(other.current_text);
        event_queue = std::move(other.event_queue);

        other.text_buffer = nullptr;
        other.max_text_length = 0;
    }
    return *this;
}

void ImeState::SendEvent(OrbisImeEvent* event) {
    std::unique_lock<std::mutex> lock{queue_mutex};
    event_queue.push(*event);
}

void ImeState::SendEnterEvent() {
    OrbisImeEvent enterEvent{};
    enterEvent.id = OrbisImeEventId::PressEnter;

    // Include current text payload for consumers expecting text with Enter
    OrbisImeEditText text{};
    text.str = reinterpret_cast<char16_t*>(work_buffer);
    // Sync work and input buffers with the latest UTF-8 text
    if (current_text.begin()) {
        ConvertUTF8ToOrbis(current_text.begin(), current_text.size(),
                           reinterpret_cast<char16_t*>(work_buffer), max_text_length + 1);
        if (text_buffer) {
            ConvertUTF8ToOrbis(current_text.begin(), current_text.size(), text_buffer,
                               max_text_length + 1);
        }
    }
    if (text.str) {
        const u32 len = static_cast<u32>(std::char_traits<char16_t>::length(text.str));
        // 0-based caret at end
        text.caret_index = len;
        text.area_num = 1;
        text.text_area[0].mode = OrbisImeTextAreaMode::Edit;
        // No edit happening on Enter: length=0; index can be caret
        text.text_area[0].index = len;
        text.text_area[0].length = 0;
        enterEvent.param.text = text;
    }

    LOG_DEBUG(Lib_Ime,
              "IME Event queued: PressEnter caret={} area_num={} edit.index={} edit.length={}",
              text.caret_index, text.area_num, text.text_area[0].index, text.text_area[0].length);
    SendEvent(&enterEvent);
}

void ImeState::SendCloseEvent() {
    OrbisImeEvent closeEvent{};
    closeEvent.id = OrbisImeEventId::PressClose;

    // Populate text payload with current buffer snapshot
    OrbisImeEditText text{};
    text.str = reinterpret_cast<char16_t*>(work_buffer);
    // Sync work and input buffers with the latest UTF-8 text
    if (current_text.begin()) {
        ConvertUTF8ToOrbis(current_text.begin(), current_text.size(),
                           reinterpret_cast<char16_t*>(work_buffer), max_text_length + 1);
        if (text_buffer) {
            ConvertUTF8ToOrbis(current_text.begin(), current_text.size(), text_buffer,
                               max_text_length + 1);
        }
    }
    if (text.str) {
        const u32 len = static_cast<u32>(std::char_traits<char16_t>::length(text.str));
        // 0-based caret at end
        text.caret_index = len;
        text.area_num = 1;
        text.text_area[0].mode = OrbisImeTextAreaMode::Edit;
        // No edit happening on Close: length=0; index can be caret
        text.text_area[0].index = len;
        text.text_area[0].length = 0;
        closeEvent.param.text = text;
    }

    LOG_DEBUG(Lib_Ime,
              "IME Event queued: PressClose caret={} area_num={} edit.index={} edit.length={}",
              text.caret_index, text.area_num, text.text_area[0].index, text.text_area[0].length);
    SendEvent(&closeEvent);
}

void ImeState::SetText(const char16_t* text, u32 length) {
    if (!text) {
        LOG_WARNING(Lib_Ime, "ImeState::SetText received null text pointer");
        return;
    }

    // Clamp to the effective maximum number of characters
    const u32 clamped_len = std::min(length, max_text_length) + 1;
    if (!ConvertOrbisToUTF8(text, clamped_len, current_text.begin(), current_text.capacity())) {
        LOG_ERROR(Lib_Ime, "ImeState::SetText failed to convert updated text to UTF-8");
        return;
    }
}
void ImeState::SetCaret(u32 position) {}

bool ImeState::ConvertOrbisToUTF8(const char16_t* orbis_text, std::size_t orbis_text_len,
                                  char* utf8_text, std::size_t utf8_text_len) {
    std::fill(utf8_text, utf8_text + utf8_text_len, '\0');
    const ImWchar* orbis_text_ptr = reinterpret_cast<const ImWchar*>(orbis_text);
    ImTextStrToUtf8(utf8_text, utf8_text_len, orbis_text_ptr, orbis_text_ptr + orbis_text_len);

    return true;
}

bool ImeState::ConvertUTF8ToOrbis(const char* utf8_text, std::size_t utf8_text_len,
                                  char16_t* orbis_text, std::size_t orbis_text_len) {
    std::fill(orbis_text, orbis_text + orbis_text_len, u'\0');
    const char* end = utf8_text ? (utf8_text + utf8_text_len) : nullptr;
    ImTextStrFromUtf8(reinterpret_cast<ImWchar*>(orbis_text), orbis_text_len, utf8_text, end);

    return true;
}

ImeUi::ImeUi(ImeState* state, const OrbisImeParam* param, const OrbisImeParamExtended* extended)
    : state(state), ime_param(param), extended_param(extended) {
    if (param) {
        AddLayer(this);
    }
}

ImeUi::~ImeUi() {
    std::scoped_lock lock(draw_mutex);
    Free();
}

ImeUi& ImeUi::operator=(ImeUi&& other) {
    std::scoped_lock lock(draw_mutex, other.draw_mutex);
    Free();

    state = other.state;
    ime_param = other.ime_param;
    first_render = other.first_render;
    other.state = nullptr;
    other.ime_param = nullptr;

    AddLayer(this);
    return *this;
}

void ImeUi::Draw() {
    std::unique_lock<std::mutex> lock{draw_mutex};

    if (!state) {
        return;
    }

    const auto& ctx = *GetCurrentContext();
    const auto& io = ctx.IO;

    // TODO: Figure out how to properly translate the positions -
    //       for example, if a game wants to center the IME panel,
    //       we have to translate the panel position in a way that it
    //       still becomes centered, as the game normally calculates
    //       the position assuming a it's running on a 1920x1080 screen,
    //       whereas we are running on a 1280x720 window size (by default).
    //
    //       e.g. Panel position calculation from a game:
    //       param.posx = (1920 / 2) - (panelWidth  / 2);
    //       param.posy = (1080 / 2) - (panelHeight / 2);
    const auto size = GetIO().DisplaySize;
    f32 pos_x = (ime_param->posx / 1920.0f * (float)size.x);
    f32 pos_y = (ime_param->posy / 1080.0f * (float)size.y);

    ImVec2 window_pos = {pos_x, pos_y};
    ImVec2 window_size = {500.0f, 100.0f};

    // SetNextWindowPos(window_pos);
    SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f),
                     ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f));
    SetNextWindowSize(window_size);
    SetNextWindowCollapsed(false);

    if (first_render || !io.NavActive) {
        SetNextWindowFocus();
    }

    if (Begin("IME##Ime", nullptr,
              ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
                  ImGuiWindowFlags_NoSavedSettings)) {
        DrawPrettyBackground();

        DrawInputText();
        SetCursorPosY(GetCursorPosY() + 10.0f);

        const char* button_text;
        button_text = "Done##ImeDone";

        float button_spacing = 10.0f;
        float total_button_width = BUTTON_SIZE.x * 2 + button_spacing;
        float button_start_pos = (window_size.x - total_button_width) / 2.0f;

        SetCursorPosX(button_start_pos);

        if (Button(button_text, BUTTON_SIZE) || (IsKeyPressed(ImGuiKey_Enter))) {
            state->SendEnterEvent();
        }

        SameLine(0.0f, button_spacing);

        if (Button("Close##ImeClose", BUTTON_SIZE)) {
            state->SendCloseEvent();
        }
    }
    End();

    first_render = false;
}

void ImeUi::DrawInputText() {
    ImVec2 input_size = {GetWindowWidth() - 40.0f, 0.0f};
    SetCursorPosX(20.0f);
    if (first_render) {
        SetKeyboardFocusHere();
    }
    if (InputTextExLimited("##ImeInput", nullptr, state->current_text.begin(),
                           ime_param->maxTextLength * 4 + 1, input_size,
                           ImGuiInputTextFlags_CallbackAlways, ime_param->maxTextLength,
                           InputTextCallback, this)) {
    }
}

int ImeUi::InputTextCallback(ImGuiInputTextCallbackData* data) {
    ImeUi* ui = static_cast<ImeUi*>(data->UserData);
    ASSERT(ui);

    static std::string lastText;
    static int lastCaretPos = -1;
    std::string currentText(data->Buf, data->BufTextLen);
    if (currentText != lastText) {
        OrbisImeEditText eventParam{};
        eventParam.str = reinterpret_cast<char16_t*>(ui->ime_param->work);
        eventParam.area_num = 1;
        eventParam.text_area[0].mode = OrbisImeTextAreaMode::Edit;

        if (!ui->state->ConvertUTF8ToOrbis(data->Buf, data->BufTextLen, eventParam.str,
                                           ui->state->max_text_length + 1)) {
            LOG_ERROR(Lib_Ime, "Failed to convert UTF-8 to Orbis for eventParam.str");
            return 0;
        }

        if (!ui->state->ConvertUTF8ToOrbis(data->Buf, data->BufTextLen,
                                           ui->ime_param->inputTextBuffer,
                                           ui->state->max_text_length + 1)) {
            LOG_ERROR(Lib_Ime, "Failed to convert UTF-8 to Orbis for inputTextBuffer");
            return 0;
        }

        eventParam.caret_index = data->CursorPos;
        eventParam.text_area[0].index = data->CursorPos;
        eventParam.text_area[0].length =
            (data->CursorPos > lastCaretPos) ? 1 : -1; // data->CursorPos;

        OrbisImeEvent event{};
        event.id = OrbisImeEventId::UpdateText;
        event.param.text = eventParam;
        LOG_DEBUG(Lib_Ime,
                  "IME Event queued: UpdateText(type, "
                  "delete)\neventParam.caret_index={}\narea_num={}\neventParam.text_area[0].mode={}"
                  "\neventParam.text_area[0].index={}\neventParam.text_area[0].length={}",
                  eventParam.caret_index, eventParam.area_num,
                  static_cast<s32>(eventParam.text_area[0].mode), eventParam.text_area[0].index,
                  eventParam.text_area[0].length);

        lastText = currentText;
        lastCaretPos = -1;
        ui->state->SendEvent(&event);
    }

    if (lastCaretPos == -1) {
        lastCaretPos = data->CursorPos;
    } else if (data->CursorPos != lastCaretPos) {
        const int delta = data->CursorPos - lastCaretPos;

        // Emit one UpdateCaret per delta step (delta may be ±1 or a jump)
        const bool move_right = delta > 0;
        const u32 steps = static_cast<u32>(std::abs(delta));
        OrbisImeCaretMovementDirection dir = move_right ? OrbisImeCaretMovementDirection::Right
                                                        : OrbisImeCaretMovementDirection::Left;

        for (u32 i = 0; i < steps; ++i) {
            OrbisImeEvent caret_step{};
            caret_step.id = OrbisImeEventId::UpdateCaret;
            caret_step.param.caret_move = dir;
            LOG_DEBUG(Lib_Ime, "IME Event queued: UpdateCaret(step {}/{}), dir={}", i + 1, steps,
                      static_cast<u32>(dir));
            ui->state->SendEvent(&caret_step);
        }

        lastCaretPos = data->CursorPos;
    }

    return 0;
}

void ImeUi::Free() {
    RemoveLayer(this);
}

}; // namespace Libraries::Ime
