#include "fgc/HelpText.h"

#include <algorithm>
#include <cctype>

namespace fgc {

namespace {

std::string lower(std::string s) {
    std::transform(s.begin(), s.end(), s.begin(),
                   [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
    return s;
}

// First whitespace-delimited token of a syntax string (the command verb), lowercased.
std::string verbOf(const std::string& syntax) {
    auto end = syntax.find_first_of(" \t");
    return lower(syntax.substr(0, end == std::string::npos ? syntax.size() : end));
}

}  // namespace

const std::vector<HelpSection>& helpCatalog() {
    // clang-format off
    static const std::vector<HelpSection> catalog = {
        {"Positioning", "Aim the gimbal. 'goto' is degrees; raw MOVE is encoder counts.", {
            {"goto <yaw_deg> <pitch_deg>",
             "Point the gimbal at an absolute heading/elevation in degrees.", {
                "Converts degrees to encoder counts (operator-calibrated) and sends a",
                "two-axis MOVE so both axes start together. Degrees are soft-clamped to",
                "the configured travel limits.",
                "Example:  goto 30 -10     (yaw 30 deg, pitch -10 deg)"}},
            {"set motorctl MOVE <yaw>,<pitch>",
             "Move both axes to absolute encoder counts (no degree conversion).", {
                "Example:  set motorctl MOVE 100000,250000",
                "Single axis:  set motorctl MOVE Y 100000   /   set motorctl MOVE P 250000"}},
            {"set motorctl HOME [Y|P]",
             "Run the endstop-finding home sequence (both axes, or one).", {
                "Example:  set motorctl HOME      /      set motorctl HOME Y"}},
            {"set motorctl STOP <Y|P|ALL>",
             "Stop motion immediately on an axis or both.", {}},
            {"set motorctl SPEED <Y|P> <vel>",
             "Set the max slew speed (counts/s) for an axis.", {}},
        }},
        {"Diagnostics", "Inspect the firmware/driver state for debugging.", {
            {"dump",
             "Request a full firmware state dump and show it here.", {
                "Sends DUMP to the firmware; the captured DUMP BEGIN..END block (build,",
                "uptime, reset cause, and per-axis TMC5160 registers) is logged and, in",
                "the TUI, shown in the Diagnostics help section.",
                "Equivalent offline tool:  ./firmware/dump.sh"}},
            {"set motorctl DIAG [Y|P|ALL]",
             "Run the motor self-test (emits DG lines, ends with DG DONE).", {
                "Each axis is swept at several speeds/directions; results stream as DG",
                "lines in the log. Axis must be homed first."}},
            {"set motorctl STATUS",
             "Ask the firmware to emit one telemetry (ST) line now.", {}},
        }},
        {"Capture", "Control image capture and encoding.", {
            {"start", "Begin the capture scan.", {}},
            {"stop",  "Halt the capture scan.", {}},
            {"set fps <rate>", "Set capture rate in images/second.", {}},
            {"set camera fps <rate>", "Set the camera sensor frame rate.", {}},
            {"set camera jxlq <dist>", "Set JPEG-XL distance (lower = higher quality).", {}},
            {"set camera jxle <effort>", "Set JPEG-XL encode effort.", {}},
            {"set camera display <0|1>", "Toggle the local display window.", {}},
        }},
        {"Logging", "Control console/log verbosity.", {
            {"debug", "Toggle debug-level logging on/off.", {}},
            {"trace <serial|mqtt|camera|control|all|off> [on|off]",
             "Toggle verbatim wire-trace categories.", {
                "Example:  trace serial on      /      trace off"}},
        }},
        {"Session", "Help and exit.", {
            {"help [topic]",
             "Show this reference; 'help <topic>' expands one section.", {
                "Example:  help positioning      /      help dump"}},
            {"exit", "Shut down and quit.", {}},
        }},
    };
    // clang-format on
    return catalog;
}

std::vector<std::string> renderHelp(const std::string& topic) {
    std::vector<std::string> out;
    const std::string q = lower(topic);

    if (q.empty()) {
        out.emplace_back("Available commands (type 'help <topic>' for detail, e.g. 'help goto'):");
        for (const auto& sec : helpCatalog()) {
            out.emplace_back("");
            out.emplace_back("== " + sec.title + " ==  " + sec.blurb);
            for (const auto& e : sec.entries)
                out.emplace_back("  " + e.syntax + "  -  " + e.summary);
        }
        return out;
    }

    // Topic mode: match a section by title, or an entry by command verb.
    bool matched = false;
    for (const auto& sec : helpCatalog()) {
        const bool sec_match = lower(sec.title).find(q) != std::string::npos;
        for (const auto& e : sec.entries) {
            if (!sec_match && verbOf(e.syntax) != q) continue;
            matched = true;
            out.emplace_back(e.syntax);
            out.emplace_back("    " + e.summary);
            for (const auto& d : e.detail) out.emplace_back("    " + d);
            out.emplace_back("");
        }
    }
    if (!matched) out.emplace_back("No help topic matching '" + topic + "'. Try 'help'.");
    return out;
}

}  // namespace fgc
