// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// chat_tab.cpp — read-only HushChat view (Phase 3): a conversation list + the
// selected thread, both read from the App-owned ChatService store.

#include "chat_tab.h"
#include "../../app.h"
#include "../../data/address_book.h"
#include "../../chat/chat_service.h"
#include "../../util/i18n.h"
#include "../material/colors.h"
#include "../material/type.h"
#include "../layout.h"   // Layout::dpiScale()
#include "imgui.h"

#include <algorithm>
#include <cfloat>
#include <ctime>
#include <string>
#include <vector>

namespace dragonx {
namespace ui {

namespace {

// Selected conversation id (cid). Empty => none selected / auto-select first.
std::string s_selected_cid;
std::string s_scroll_to_cid;  // when set, scroll the thread to the bottom next frame

// Composer + new-conversation UI state.
char s_compose[512] = "";
bool s_show_new_convo = false;
char s_new_zaddr[128] = "";
char s_new_msg[256] = "";

// Effective draw-list font size for a material font (mirrors sidebar's ScaledFontSize).
float scaledSize(ImFont* f) { return f->LegacySize * ImGui::GetStyle().FontScaleMain; }

std::string shorten(const std::string& s, std::size_t head = 12, std::size_t tail = 6) {
    if (s.size() <= head + tail + 3) return s;
    return s.substr(0, head) + "..." + s.substr(s.size() - tail);
}

std::string formatTime(std::int64_t ts) {
    if (ts <= 0) return "";
    std::time_t t = static_cast<std::time_t>(ts);
    std::tm* tm = std::localtime(&t);  // UI thread only
    if (!tm) return "";
    char buf[32];
    std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M", tm);
    return buf;
}

// One line of collapsed body text for the list preview (newlines flattened).
std::string previewOf(const std::string& body) {
    std::string out = body;
    std::replace(out.begin(), out.end(), '\n', ' ');
    std::replace(out.begin(), out.end(), '\r', ' ');
    return out;
}

struct ConvSummary {
    std::string cid;
    std::string peerZaddr;
    std::string peerPubKey;  // peer crypto_kx key (from a received memo); empty => can't reply yet
    std::string peerName;    // contact label if known, else a shortened z-addr / cid
    std::string lastBody;
    std::int64_t lastTs = 0;
    int count = 0;
};

// Centered, muted, wrapped hint for the empty states.
void centeredHint(const char* text) {
    ImVec2 avail = ImGui::GetContentRegionAvail();
    ImGui::PushFont(material::Type().body2());
    const float wrap = std::min(avail.x - 40.0f, 420.0f);
    const ImVec2 sz = ImGui::CalcTextSize(text, nullptr, false, wrap);
    ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x + std::max(0.0f, (avail.x - sz.x) * 0.5f),
                               ImGui::GetCursorPos().y + std::max(0.0f, (avail.y - sz.y) * 0.5f)));
    ImGui::PushStyleColor(ImGuiCol_Text, material::OnSurfaceMedium());
    ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap);
    ImGui::TextUnformatted(text);
    ImGui::PopTextWrapPos();
    ImGui::PopStyleColor();
    ImGui::PopFont();
}

} // namespace

void RenderChatTab(App* app)
{
    auto& service = app->chatService();
    const auto& store = service.store();
    auto& book = app->addressBook();

    // Not unlocked / identity not derived yet → nothing to show.
    if (!service.hasIdentity()) {
        centeredHint(TR("chat_locked_hint"));
        return;
    }

    // Build conversation summaries (single scan per conversation), sorted by most-recent activity.
    std::vector<ConvSummary> convs;
    for (const auto& cid : store.conversationIds()) {
        const auto messages = store.conversation(cid);
        if (messages.empty()) continue;
        ConvSummary c;
        c.cid = cid;
        c.count = static_cast<int>(messages.size());
        for (const auto& m : messages) {  // last non-empty peer z-addr / key across the thread
            if (!m.peer_zaddr.empty()) c.peerZaddr = m.peer_zaddr;
            if (!m.peer_public_key_hex.empty()) c.peerPubKey = m.peer_public_key_hex;
        }
        const auto& last = messages.back();
        c.lastBody = last.body;
        c.lastTs = last.timestamp;
        const int idx = c.peerZaddr.empty() ? -1 : book.findByAddress(c.peerZaddr);
        c.peerName = (idx >= 0) ? book.entries()[idx].label
                                : shorten(!c.peerZaddr.empty() ? c.peerZaddr : cid);
        convs.push_back(std::move(c));
    }
    std::sort(convs.begin(), convs.end(),
              [](const ConvSummary& a, const ConvSummary& b) { return a.lastTs > b.lastTs; });

    // Keep the selection valid (only when there is something to select).
    if (!convs.empty() &&
        std::none_of(convs.begin(), convs.end(),
                     [](const ConvSummary& c) { return c.cid == s_selected_cid; })) {
        s_selected_cid = convs.front().cid;
        s_scroll_to_cid = s_selected_cid;
    }

    const ImVec2 avail = ImGui::GetContentRegionAvail();
    const float listW = std::clamp(avail.x * 0.32f, 220.0f, 360.0f);
    // Row geometry is logical px — scale by dpiScale() so rows/padding grow with the (DPI-scaled)
    // fonts. Left raw, at higher DPI the row was too short for the enlarged text and the preview's
    // right margin (rowW - pad) shrank to ~zero, clipping the last glyph mid-word.
    const float pad = 10.0f * Layout::dpiScale();
    const float rowH = 52.0f * Layout::dpiScale();

    ImFont* nameFont = material::Type().body2();
    ImFont* metaFont = material::Type().caption();
    const float nameSz = scaledSize(nameFont);
    const float metaSz = scaledSize(metaFont);

    // ---- Left: new-conversation button + conversation list ----
    ImGui::BeginChild("##ChatList", ImVec2(listW, avail.y), true);
    {
        if (ImGui::Button(TR("chat_new_button"), ImVec2(-FLT_MIN, 0.0f))) {
            s_show_new_convo = true;
            s_new_zaddr[0] = '\0';
            s_new_msg[0] = '\0';
        }
        ImGui::Separator();
        if (convs.empty()) {
            ImGui::PushFont(metaFont);
            ImGui::PushStyleColor(ImGuiCol_Text, material::OnSurfaceMedium());
            ImGui::PushTextWrapPos(0.0f);
            ImGui::TextUnformatted(TR("chat_empty_hint"));
            ImGui::PopTextWrapPos();
            ImGui::PopStyleColor();
            ImGui::PopFont();
        }

        ImDrawList* dl = ImGui::GetWindowDrawList();  // the list child's draw list (correct clip/z-order)
        const ImU32 selBg = ImGui::GetColorU32(ImGuiCol_Header);
        const ImU32 hoverBg = ImGui::GetColorU32(ImGuiCol_HeaderHovered);
        for (std::size_t i = 0; i < convs.size(); ++i) {
            const ConvSummary& c = convs[i];
            ImGui::PushID(static_cast<int>(i));
            const ImVec2 p = ImGui::GetCursorScreenPos();
            const float rowW = ImGui::GetContentRegionAvail().x;
            const bool clicked = ImGui::InvisibleButton("##row", ImVec2(rowW, rowH));
            const bool hovered = ImGui::IsItemHovered();
            const bool selected = (c.cid == s_selected_cid);
            if (clicked) { s_selected_cid = c.cid; s_scroll_to_cid = c.cid; }
            if (selected || hovered)
                dl->AddRectFilled(p, ImVec2(p.x + rowW, p.y + rowH), selected ? selBg : hoverBg, 6.0f);

            // Name (top-left).
            dl->AddText(nameFont, nameSz, ImVec2(p.x + pad, p.y + pad), material::OnSurface(),
                        c.peerName.c_str());
            // Time (top-right, muted).
            const std::string when = formatTime(c.lastTs);
            if (!when.empty()) {
                const ImVec2 wsz = metaFont->CalcTextSizeA(metaSz, FLT_MAX, 0.0f, when.c_str());
                dl->AddText(metaFont, metaSz, ImVec2(p.x + rowW - pad - wsz.x, p.y + pad + 1.0f),
                            material::OnSurfaceMedium(), when.c_str());
            }
            // Preview (bottom, clipped, muted).
            const std::string preview = previewOf(c.lastBody);
            dl->PushClipRect(p, ImVec2(p.x + rowW - pad, p.y + rowH), true);
            dl->AddText(metaFont, metaSz, ImVec2(p.x + pad, p.y + rowH - pad - metaSz),
                        material::OnSurfaceMedium(), preview.c_str());
            dl->PopClipRect();
            ImGui::PopID();
        }
    }
    ImGui::EndChild();

    ImGui::SameLine();

    // ---- Right: selected conversation thread ----
    ImGui::BeginChild("##ChatThread", ImVec2(0, avail.y), true);
    {
        const ConvSummary* sel = nullptr;
        for (const auto& c : convs) if (c.cid == s_selected_cid) { sel = &c; break; }

        if (sel) {
            // Header: peer name + z-address.
            ImGui::PushFont(material::Type().subtitle1());
            ImGui::TextUnformatted(sel->peerName.c_str());
            ImGui::PopFont();
            if (!sel->peerZaddr.empty()) {
                ImGui::PushFont(metaFont);
                ImGui::PushStyleColor(ImGuiCol_Text, material::OnSurfaceMedium());
                ImGui::TextUnformatted(shorten(sel->peerZaddr, 20, 12).c_str());
                ImGui::PopStyleColor();
                ImGui::PopFont();
            }
            ImGui::Separator();

            const float footerH = ImGui::GetTextLineHeightWithSpacing() + 12.0f;
            ImGui::BeginChild("##ChatMessages", ImVec2(0, ImGui::GetContentRegionAvail().y - footerH), false);
            {
                const auto messages = store.conversation(s_selected_cid);
                for (const auto& m : messages) {
                    const bool outgoing = (m.direction == chat::ChatDirection::Outgoing);
                    const bool request = (m.kind == chat::ChatMessageKind::ContactRequest);
                    const bool failed = outgoing && m.delivery == chat::ChatDelivery::Failed;
                    // Meta line: who + time (+ request tag / failed marker).
                    std::string who = outgoing ? std::string(TR("chat_you")) : sel->peerName;
                    const std::string when = formatTime(m.timestamp);
                    if (!when.empty()) who += "  " + when;
                    if (request) who += "  [" + std::string(TR("chat_contact_request")) + "]";
                    if (failed) who += "  · " + std::string(TR("chat_send_failed"));
                    ImGui::PushFont(metaFont);
                    ImGui::PushStyleColor(ImGuiCol_Text,
                        failed ? material::Error() : (outgoing ? material::Primary() : material::OnSurfaceMedium()));
                    ImGui::TextUnformatted(who.c_str());
                    ImGui::PopStyleColor();
                    ImGui::PopFont();
                    // Body (wrapped).
                    ImGui::PushFont(nameFont);
                    ImGui::PushTextWrapPos(0.0f);
                    ImGui::TextWrapped("%s", m.body.c_str());
                    ImGui::PopTextWrapPos();
                    ImGui::PopFont();
                    ImGui::Dummy(ImVec2(0.0f, 6.0f));
                }
                if (s_scroll_to_cid == s_selected_cid) {
                    ImGui::SetScrollHereY(1.0f);
                    s_scroll_to_cid.clear();
                }
            }
            ImGui::EndChild();

            // Composer footer: message input + send (only once we know the peer's key), else a hint.
            ImGui::Separator();
            if (sel->peerPubKey.empty()) {
                ImGui::PushFont(metaFont);
                ImGui::PushStyleColor(ImGuiCol_Text, material::OnSurfaceMedium());
                ImGui::PushTextWrapPos(0.0f);
                ImGui::TextUnformatted(TR("chat_waiting_reply"));
                ImGui::PopTextWrapPos();
                ImGui::PopStyleColor();
                ImGui::PopFont();
            } else {
                const float sendW = 74.0f;
                ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - sendW - ImGui::GetStyle().ItemSpacing.x);
                bool submit = ImGui::InputText("##compose", s_compose, sizeof(s_compose),
                                               ImGuiInputTextFlags_EnterReturnsTrue);
                ImGui::SameLine();
                if (ImGui::Button(TR("chat_send"), ImVec2(sendW, 0.0f))) submit = true;
                if (submit && s_compose[0] != '\0') {
                    app->sendChatMessage(sel->cid, s_compose);
                    s_compose[0] = '\0';
                    s_scroll_to_cid = sel->cid;
                }
            }
        } else {
            centeredHint(convs.empty() ? TR("chat_empty_hint") : TR("chat_select_hint"));
        }
    }
    ImGui::EndChild();

    // ---- New-conversation popup (send a contact request to a z-address) ----
    if (s_show_new_convo) {
        ImGui::OpenPopup("##NewConversation");
        s_show_new_convo = false;
    }
    ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
    if (ImGui::BeginPopupModal("##NewConversation", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
        ImGui::PushFont(material::Type().subtitle1());
        ImGui::TextUnformatted(TR("chat_new_title"));
        ImGui::PopFont();
        ImGui::Spacing();
        ImGui::TextUnformatted(TR("chat_new_zaddr"));
        ImGui::SetNextItemWidth(440.0f);
        ImGui::InputText("##newz", s_new_zaddr, sizeof(s_new_zaddr));
        ImGui::TextUnformatted(TR("chat_new_message"));
        ImGui::SetNextItemWidth(440.0f);
        ImGui::InputText("##newm", s_new_msg, sizeof(s_new_msg));
        ImGui::Spacing();
        const bool canSend = s_new_zaddr[0] != '\0' && s_new_msg[0] != '\0';
        if (ImGui::Button(TR("chat_new_send"), ImVec2(150.0f, 0.0f)) && canSend) {
            app->startChatConversation(s_new_zaddr, s_new_msg);
            s_new_zaddr[0] = '\0';
            s_new_msg[0] = '\0';
            ImGui::CloseCurrentPopup();
        }
        ImGui::SameLine();
        if (ImGui::Button(TR("chat_cancel"), ImVec2(100.0f, 0.0f))) ImGui::CloseCurrentPopup();
        ImGui::EndPopup();
    }
}

} // namespace ui
} // namespace dragonx
