// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// In-app, Material-styled folder picker. A modal overlay that lets the user browse the filesystem
// and pick a directory (used by the Wallets dialog's "Scan another folder" action instead of a
// raw path input field). Single overlay at a time — the overlay framework doesn't nest, so the
// caller suppresses its own overlay while FolderPicker::isOpen().

#pragma once

#include <algorithm>
#include <cctype>
#include <filesystem>
#include <functional>
#include <string>
#include <vector>

#include "imgui.h"

#include "../../util/i18n.h"
#include "../../util/platform.h"
#include "../../embedded/IconsMaterialDesign.h"
#include "../layout.h"
#include "../material/colors.h"
#include "../material/draw_helpers.h"
#include "../material/type.h"

namespace dragonx {
namespace ui {

class FolderPicker {
public:
    // Open the picker starting at `startDir` (falls back to home if empty/invalid). `onPick` is
    // invoked with the chosen absolute path when the user confirms; nothing runs on cancel.
    static void open(const std::string& startDir, std::function<void(const std::string&)> onPick) {
        namespace fs = std::filesystem;
        std::error_code ec;
        if (startDir.empty()) {
            // Reopen where the user last browsed (nice for repeated scans); else start at home.
            if (s_dir.empty() || !fs::is_directory(fs::path(s_dir), ec))
                navigate(util::Platform::getHomeDir());
            else
                navigate(s_dir);
        } else {
            fs::path p(startDir);
            if (!fs::is_directory(p, ec)) p = fs::path(util::Platform::getHomeDir());
            navigate(p.string());
        }
        s_onPick = std::move(onPick);
        s_open = true;
    }

    static bool isOpen() { return s_open; }
    static void close()  { s_open = false; }

    static void render() {
        if (!s_open) return;
        using namespace material;
        const float dp = Layout::dpiScale();

        ImFont* rowFont = Type().body1();
        ImFont* icoFont = Type().iconSmall();
        ImFont* metaFont = Type().caption();

        const float rowH    = rowFont->LegacySize + Layout::spacingMd();
        const float listPad = Layout::spacingXs();

        // The card takes a generous share of the window (a file browser wants room) — not a fixed
        // row count. The list inside FLEXES to fill whatever height is left above the footer, so the
        // footer is always pinned + visible and nothing clips, at any window size or DPI.
        const float vpH   = ImGui::GetMainViewport()->Size.y;
        const float cardH = (vpH * 0.82f) / dp;   // logical; the framework re-applies dp + caps at vp-32

        OverlayDialogSpec ov;
        ov.title      = TR("picker_title");
        ov.p_open     = &s_open;
        ov.style      = OverlayStyle::BlurFloat;
        ov.cardWidth  = 760.0f;
        ov.cardHeight = cardH;
        ov.idSuffix   = "folderpicker";
        if (!BeginOverlayDialog(ov)) return;

        if (ImGui::IsKeyPressed(ImGuiKey_Escape)) s_open = false;

        const float fullW = ImGui::GetContentRegionAvail().x;
        auto tw = [](ImFont* f, const std::string& s){ return f->CalcTextSizeA(f->LegacySize, FLT_MAX, 0, s.c_str()).x; };
        // End-truncate, popping whole UTF-8 code points.
        auto fit = [&](std::string s, ImFont* f, float maxW){
            bool t=false;
            while (s.size()>1 && tw(f,s)>maxW){
                while (s.size()>1 && (static_cast<unsigned char>(s.back()) & 0xC0) == 0x80) s.pop_back();
                if (s.size()>1) s.pop_back();
                t=true;
            }
            if (t) s += "\xE2\x80\xA6";
            return s;
        };
        // Left-truncate a path (keep the tail — the current folder + nearest parents).
        auto fitPath = [&](std::string s, ImFont* f, float maxW){
            const std::string ell = "\xE2\x80\xA6";
            if (tw(f,s) <= maxW) return s;
            while (s.size()>1 && tw(f, ell+s) > maxW){
                s.erase(s.begin());
                while (!s.empty() && (static_cast<unsigned char>(s.front()) & 0xC0) == 0x80) s.erase(s.begin());
            }
            return ell + s;
        };

        // ---- Path bar: Up + Home + current path strip -----------------------------------------
        std::string pendingNav;
        {
            const float bh = ImGui::GetFrameHeight();
            IconButtonStyle st; st.color = OnSurfaceMedium(); st.hoverBg = StateHover();
            st.bgRounding = 5.0f * dp;
            std::filesystem::path cur(s_dir);
            const bool hasParent = cur.has_parent_path() && cur.parent_path() != cur;
            st.tooltip = TR("picker_up");
            if (IconButton("##pickerUp", ICON_MD_ARROW_UPWARD, icoFont, ImVec2(bh, bh), st) && hasParent)
                pendingNav = cur.parent_path().string();
            ImGui::SameLine(0.0f, Layout::spacingXs());
            IconButtonStyle hst = st; hst.tooltip = TR("picker_home");
            if (IconButton("##pickerHome", ICON_MD_HOME, icoFont, ImVec2(bh, bh), hst))
                pendingNav = util::Platform::getHomeDir();
            ImGui::SameLine(0.0f, Layout::spacingSm());

            // Framed path strip
            ImVec2 sMin = ImGui::GetCursorScreenPos();
            const float stripW = ImGui::GetContentRegionAvail().x;
            ImVec2 sMax(sMin.x + stripW, sMin.y + bh);
            ImDrawList* wdl = ImGui::GetWindowDrawList();
            wdl->AddRectFilled(sMin, sMax, WithAlpha(OnSurface(), 14), 6.0f * dp);
            wdl->AddRect(sMin, sMax, WithAlpha(OnSurface(), 40), 6.0f * dp, 0, 1.0f);
            const float tpad = Layout::spacingSm();
            wdl->AddText(rowFont, rowFont->LegacySize,
                         ImVec2(sMin.x + tpad, sMin.y + (bh - rowFont->LegacySize) * 0.5f),
                         OnSurface(), fitPath(s_dir, rowFont, stripW - tpad * 2.0f).c_str());
            ImGui::Dummy(ImVec2(stripW, bh));
        }
        ImGui::Dummy(ImVec2(0, Layout::spacingSm()));

        // ---- Directory list: folders (clickable) then *.dat wallets present (informational) ----
        // Flex: the list fills the space between the path bar and the footer block. Reserving a
        // (slightly generous) footer height keeps the status line + buttons pinned and never clipped.
        const ImGuiStyle& gstyle = ImGui::GetStyle();
        const float footerBlockH = Layout::spacingXs() + metaFont->LegacySize          // status line
                                 + Layout::spacingSm() + 1.0f + Layout::spacingSm()     // separator block
                                 + ImGui::GetFrameHeight()                              // footer buttons
                                 + 6.0f * gstyle.ItemSpacing.y;                         // inter-item gaps
        float listH = ImGui::GetContentRegionAvail().y - footerBlockH;
        listH = std::max(listH, rowH * 3.0f + listPad * 2.0f);
        ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(listPad, listPad));
        ImGui::PushStyleColor(ImGuiCol_ChildBg, WithAlpha(OnSurface(), 20));
        ImGui::BeginChild("##pickerList", ImVec2(fullW, listH), true);
        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
        ImDrawList* dl = ImGui::GetWindowDrawList();
        const float rowW = ImGui::GetContentRegionAvail().x;
        const float padX = Layout::spacingSm();

        if (s_subdirs.empty() && s_datfiles.empty()) {
            ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
            ImGui::Indent(padX);
            Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("picker_empty"));
            ImGui::Unindent(padX);
        }

        // Folder rows
        for (std::size_t i = 0; i < s_subdirs.size(); ++i) {
            ImVec2 rMin = ImGui::GetCursorScreenPos();
            ImVec2 rMax(rMin.x + rowW, rMin.y + rowH);
            ImGui::PushID((int)i);
            const bool clicked = ImGui::InvisibleButton("##dir", ImVec2(rowW, rowH));
            const bool hov = ImGui::IsItemHovered();
            ImGui::PopID();
            if (hov) {
                dl->AddRectFilled(rMin, rMax, WithAlpha(OnSurface(), 22), 6.0f * dp);
                ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
            }
            const float midY = (rMin.y + rMax.y) * 0.5f;
            const float icoSz = rowFont->LegacySize * 1.05f;
            float x = rMin.x + padX;
            dl->AddText(icoFont, icoSz, ImVec2(x, midY - icoSz * 0.5f),
                        hov ? Primary() : OnSurfaceMedium(), ICON_MD_FOLDER);
            x += icoSz + Layout::spacingSm();
            // chevron affordance on the right
            const float chSz = metaFont->LegacySize;
            const float chX = rMax.x - padX - chSz;
            if (hov)
                dl->AddText(icoFont, chSz, ImVec2(chX, midY - chSz * 0.5f), OnSurfaceMedium(), ICON_MD_CHEVRON_RIGHT);
            std::string nm = fit(s_subdirs[i], rowFont, (chX - Layout::spacingSm()) - x);
            dl->AddText(rowFont, rowFont->LegacySize, ImVec2(x, midY - rowFont->LegacySize * 0.5f),
                        OnSurface(), nm.c_str());
            if (clicked) pendingNav = (std::filesystem::path(s_dir) / s_subdirs[i]).string();
        }

        // Wallet-file rows (informational — dimmed, not clickable)
        for (std::size_t i = 0; i < s_datfiles.size(); ++i) {
            ImVec2 rMin = ImGui::GetCursorScreenPos();
            const float midY = rMin.y + rowH * 0.5f;
            const float icoSz = rowFont->LegacySize * 1.05f;
            float x = rMin.x + padX;
            dl->AddText(icoFont, icoSz, ImVec2(x, midY - icoSz * 0.5f), OnSurfaceDisabled(),
                        ICON_MD_ACCOUNT_BALANCE_WALLET);
            x += icoSz + Layout::spacingSm();
            std::string nm = fit(s_datfiles[i], rowFont, (rMin.x + rowW - padX) - x);
            dl->AddText(rowFont, rowFont->LegacySize, ImVec2(x, midY - rowFont->LegacySize * 0.5f),
                        OnSurfaceDisabled(), nm.c_str());
            ImGui::Dummy(ImVec2(rowW, rowH));
        }

        ImGui::PopStyleVar();   // ItemSpacing
        ImGui::EndChild();
        ImGui::PopStyleColor(); // ChildBg
        ImGui::PopStyleVar();   // WindowPadding

        // ---- Status: how many wallet files are in the selected folder --------------------------
        ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
        if (!s_datfiles.empty()) {
            char buf[96];
            snprintf(buf, sizeof(buf), TR("picker_dat_count"), (int)s_datfiles.size());
            Type().textColored(TypeStyle::Caption, Success(), buf);
        } else {
            Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("picker_dat_none"));
        }

        // ---- Footer: Select this folder / Cancel ----------------------------------------------
        ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
        ImGui::Separator();
        ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
        if (StyledButton(TR("picker_select"), ImVec2(200.0f * dp, 0))) {
            if (s_onPick) s_onPick(s_dir);
            s_open = false;
        }
        ImGui::SameLine();
        if (StyledButton(TR("cancel"), ImVec2(120.0f * dp, 0))) s_open = false;

        EndOverlayDialog();

        if (!pendingNav.empty()) navigate(pendingNav);
    }

private:
    static void navigate(const std::string& dir) {
        namespace fs = std::filesystem;
        std::error_code ec;
        fs::path p(dir);
        if (!fs::is_directory(p, ec)) return;
        fs::path abs = fs::absolute(p, ec);
        s_dir = ec ? dir : abs.lexically_normal().string();
        s_subdirs.clear();
        s_datfiles.clear();
        fs::directory_iterator it(p, ec), end;
        int guard = 0;
        for (; !ec && it != end && guard < 4000; it.increment(ec), ++guard) {
            std::error_code fec;
            std::string name = it->path().filename().string();
            if (name.empty()) continue;
            if (it->is_directory(fec)) {
                s_subdirs.push_back(name);
            } else if (it->is_regular_file(fec)) {
                if (name.size() > 4 && name.substr(name.size() - 4) == ".dat")
                    s_datfiles.push_back(name);
            }
        }
        auto ci = [](const std::string& a, const std::string& b){
            return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(),
                [](char x, char y){ return std::tolower((unsigned char)x) < std::tolower((unsigned char)y); });
        };
        std::sort(s_subdirs.begin(), s_subdirs.end(), ci);
        std::sort(s_datfiles.begin(), s_datfiles.end(), ci);
    }

    static inline bool s_open = false;
    static inline std::string s_dir;
    static inline std::vector<std::string> s_subdirs;
    static inline std::vector<std::string> s_datfiles;
    static inline std::function<void(const std::string&)> s_onPick;
};

} // namespace ui
} // namespace dragonx
