#pragma once
// Pure helpers for the provisioning Portal.
//
// The Portal is the only browser-facing surface in the firmware, so its
// escaping and body-size rules must be testable without ESP-IDF. Nothing here
// touches the network, the store or a credential value.
#include <cstddef>
#include <string>

namespace wearable {

// Maximum accepted POST body for the provisioning form. An SSID is at most 32
// bytes and a password at most 64; form encoding can triple each byte, so a
// small bound comfortably covers every legitimate submission while refusing a
// client-controlled allocation.
constexpr size_t PORTAL_MAX_BODY_BYTES = 512;

// HTML-escape a value for an element's *text* content. Covers &, <, >, ", '.
// Characters are replaced, never dropped, so the original SSID text is still
// readable and selectable after escaping.
inline std::string portal_html_escape(const std::string &raw) {
    std::string out;
    out.reserve(raw.size());
    for (char c : raw) {
        switch (c) {
        case '&': out += "&amp;"; break;
        case '<': out += "&lt;"; break;
        case '>': out += "&gt;"; break;
        case '"': out += "&quot;"; break;
        case '\'': out += "&#39;"; break;
        default: out += c;
        }
    }
    return out;
}

// HTML-escape a value for an attribute delimited by double quotes. Same
// replacements as the text escaper, but the result is guaranteed to contain no
// raw '"' and no raw '<', so it cannot terminate the attribute or open a tag.
inline std::string portal_attr_escape(const std::string &raw) {
    return portal_html_escape(raw);
}

// Reject a body that exceeds the bound before any allocation happens. The
// caller turns `false` into an HTTP error and must not read the body.
inline bool portal_body_within_limit(size_t content_length) {
    return content_length <= PORTAL_MAX_BODY_BYTES;
}

// Percent- and plus-decode one application/x-www-form-urlencoded value.
// Invalid escapes are preserved literally rather than dropped.
inline std::string portal_url_decode(const std::string &input) {
    auto hex = [](char c) -> int {
        if (c >= '0' && c <= '9') return c - '0';
        if (c >= 'a' && c <= 'f') return c - 'a' + 10;
        if (c >= 'A' && c <= 'F') return c - 'A' + 10;
        return -1;
    };
    std::string out;
    for (size_t i = 0; i < input.size(); ++i) {
        if (input[i] == '+') { out += ' '; continue; }
        if (input[i] == '%' && i + 2 < input.size()) {
            const int hi = hex(input[i + 1]);
            const int lo = hex(input[i + 2]);
            if (hi >= 0 && lo >= 0) {
                out += char((hi << 4) | lo);
                i += 2;
                continue;
            }
        }
        out += input[i];
    }
    return out;
}

// Read one key from an application/x-www-form-urlencoded body.
inline std::string portal_form_value(const std::string &body, const std::string &key) {
    size_t pos = 0;
    while (pos <= body.size()) {
        const size_t next = body.find('&', pos);
        const std::string pair = body.substr(
            pos, next == std::string::npos ? std::string::npos : next - pos);
        const size_t equals = pair.find('=');
        if (equals != std::string::npos && pair.substr(0, equals) == key)
            return portal_url_decode(pair.substr(equals + 1));
        if (next == std::string::npos) break;
        pos = next + 1;
    }
    return {};
}

}  // namespace wearable