/*******************************************************************************
Copyright (c) 2021, Jan Koester jan.koester@gmx.net
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the <organization> nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/

#include <iostream>
#include <fstream>

#include <signal.h>
#include <string.h>
#include <errno.h>
#include <thread>

#include <netplus/exception.h>

#include <httppp/exception.h>
#include <httppp/http.h>
#include <httppp/httpd.h>

#include <htmlpp/html.h>
#include <htmlpp/exception.h>

#include <cmdplus/cmdplus.h>

#include <dbpp/database.h>

#ifdef Windows
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <tchar.h>
#include <stdio.h>
#include <Shlwapi.h>
#else
#include <unistd.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#endif

#include "session.h"
#include "auth.h"
#include "conf.h"
#include "i18n.h"

#include "blogi.h"
#include "compress.h"

#define cookieid "2d1b0348-1491-11f0-8c81-3cecefce9ac2"

#define MAX_RETRIES 5

static std::string cookieDomain(libhttppp::HttpRequest &req) {
    std::string h;
    auto *fwd = req.getHeaderData("x-forwarded-host");
    if (fwd)
        h = fwd->at(0).getvalue();
    else
        h = req.getHost();
    if (h.empty()) return std::string();
    // Strip port if present
    auto colon = h.find(':');
    if (colon != std::string::npos) h = h.substr(0, colon);
    // Don't set parent domain for IP addresses
    if (netplus::isIPAddr(h)) return h;
    // Find parent domain (at least two labels required)
    auto dot = h.find('.');
    if (dot != std::string::npos && dot + 1 < h.size()) {
        std::string parent = h.substr(dot); // e.g. ".tuxist.de"
        // Ensure parent itself has a dot (not just ".de")
        if (parent.find('.', 1) != std::string::npos)
            return parent;
    }
    return h;
}

std::unique_ptr<blogi::Config> blogi::Blogi::Cfg;

blogi::Blogi::Blogi(std::vector<netplus::socket*> serversocket,bool debug) : HttpEvent(serversocket){
    PlgArgs = std::make_unique<PluginArgs>();
    PlgArgs->debug=debug;
    PlgArgs->config=Blogi::Cfg.get();

	std::cerr << "Initializing Blogi with DB Driver: " << Blogi::Cfg->getdbdriver() << " on " << Blogi::Cfg->getdbopts() << std::endl;

    try{
        for (int i = 0; i < threads; i++) {
            PlgArgs->database.emplace_back(
                std::make_unique<dbpp::Database>(
                    Blogi::Cfg->getdbdriver(),
                    Blogi::Cfg->getdbopts()
                )
            );
        }
    }catch(std::runtime_error &e){
         std::cerr << "can't connect db reason: " << e.what() << std::endl;
         throw e;
    }

    std::unique_ptr<dbpp::Database> initdb = std::make_unique<dbpp::Database>(
            Blogi::Cfg->getdbdriver(),
            Blogi::Cfg->getdbopts()
    );

    Session sess(*initdb);
    sess._initSession();

    dbpp::SQL optsql;
    dbpp::DBResult optres;

    optsql << "CREATE TABLE IF NOT EXISTS options ("
               << "id " << initdb->getUUIDType(optsql) << " PRIMARY KEY,"
               << "key character varying(255) NOT NULL,"
               << "value text NOT NULL);"
           << "CREATE TABLE IF NOT EXISTS theme_colors ("
               << "id " << initdb->getUUIDType(optsql) << " PRIMARY KEY,"
               << "profile character varying(32) NOT NULL,"
               << "variable character varying(64) NOT NULL,"
               << "value character varying(64) NOT NULL);"
           << "ALTER TABLE options ALTER COLUMN value TYPE text;";

    try{
        initdb->exec(optsql,optres);
    }catch(std::runtime_error &e){
        std::cerr<< e.what() <<std::endl;
        return;
    }

    PlgArgs->smtp=std::make_unique<SmtpSettings>();

    try{
        getConfig(*initdb,"SMTPHOST",PlgArgs->smtp->_SHost);
        getConfig(*initdb,"SMTPPORT",PlgArgs->smtp->_SPort);
        getConfig(*initdb,"SMTPUSER",PlgArgs->smtp->_SUser);
        getConfig(*initdb,"SMTPSENDER",PlgArgs->smtp->_Sender);
        getConfig(*initdb,"SMTPPASSWORD",PlgArgs->smtp->_SPass);
    }catch(std::runtime_error &e){
        std::cerr << "SMTP config not found, skipping: " << e.what() << std::endl;
    }

    PlgArgs->auth=std::make_unique<Auth>(PlgArgs->database,*PlgArgs->config);

    ThemeConfig tplcfg(
        Blogi::Cfg->gettemplate(),
        PlgArgs->database,
        *Blogi::Cfg
    );

    PlgArgs->theme=std::make_unique<Theme>(tplcfg,debug);

    try {
        PlgArgs->theme->renderPage(0,"index.html",Page,Index);
        PlgArgs->theme->renderPage(0,"mobile.html",MPage,MIndex);
    }catch(libhtmlpp::HTMLException &e){
        std::cerr << e.what() << std::endl;
        return;
    }
    PlgArgs->maxthreads=threads;

    libhtmlpp::HtmlPage cookiehtml;

    PlgArgs->theme->renderPage(0, "cookie.html", cookiehtml, CookieEl);

    uuid::uuid id(cookieid);

    CookieBanner cban(*initdb);

    cban.init();

    cban.addRule(id,"Technical Required","This Cookie is Required for Authentication and some Basic Funtions of this Blog",true);

    BlogiPlg = std::make_unique<Plugin>();

    for(size_t i=0; i<PlgArgs->config->getplgdirs(); ++i){
         BlogiPlg->loadPlugins(PlgArgs->config->getplgdir(i),PlgArgs.get());
    }
}

blogi::Blogi::~Blogi(){
}

void blogi::Blogi::api(libhttppp::HttpRequest& curreq, const int tid){
    uuid::uuid aid;
    size_t clen = curreq.getContentLength();
    libhttppp::HttpResponse resp;

    // 1. Basic Content Length Check
    if(clen == 0){
        resp.setState(HTTP400); // Bad Request
        resp.setContentType("text/plain");
        const char* err = "Api: Incomplete or empty request.";
        resp.send(curreq, err, strlen(err));
        return;
    }

    if(clen > curreq.RecvData.size()){
        libhttppp::HTTPException err;
        err[libhttppp::HTTPException::Note] << "api req wait for more data.";
        throw err;
    }

    bool asuccess = false;
    std::vector<char> content;
    // Copy only the content length number of bytes
    std::copy(curreq.RecvData.begin(), curreq.RecvData.begin() + clen, std::back_inserter(content));
    content.emplace_back('\0');

    std::cerr << "JSON BODY: " << content.data() << std::endl;

    json_tokener_error jerr;
    json_object *jrequest = nullptr, *jresponse = nullptr;

    // 2. Parse JSON Request
    if (!(jrequest = json_tokener_parse_verbose(content.data(), &jerr))) {
        std::cerr << "JSON Parsing failed! Error: " << json_tokener_error_desc(jerr) << std::endl;
        std::cerr << "Content Length: " << clen << ", Content Payload: " << content.data() << std::endl;
        resp.setState(HTTP400); // Bad Request
        resp.setContentType("text/plain");
        const char* err = json_tokener_error_desc(jerr);
        resp.send(curreq, err, strlen(err));
        return;
    }

    // Request must be a JSON array of commands
    if (!json_object_is_type(jrequest, json_type_array)) {
        json_object_put(jrequest);
        resp.setState(HTTP400); // Bad Request
        resp.setContentType("text/plain");
        const char* err = "Api: Request must be a JSON array.";
        resp.send(curreq, err, strlen(err));
        return;
    }

    jresponse = json_object_new_array();
    size_t fcount = json_object_array_length(jrequest);

    // 3. Process each command in the request array
    for (size_t i = 0; i < fcount; ++i) {
        json_object* api_call = json_object_array_get_idx(jrequest, i);

        if (!json_object_is_type(api_call, json_type_object)) {
            // Skip non-object array elements
            continue;
        }

        json_object_object_foreach(api_call, key, val) {
            // key = command (e.g., "login", "auth")
            // val = parameters object

            json_object* respid = json_object_new_object(); // Response for this command

            if (strcmp(key, "login") == 0) {
                // --- LOGIN COMMAND (username/password) ---
                const char* username = nullptr;
                const char* password = nullptr;

                json_object* juser = nullptr;
                json_object* jpass = nullptr;

                if (json_object_object_get_ex(val, "username", &juser) &&
                    json_object_object_get_ex(val, "password", &jpass))
                {
                    username = json_object_get_string(juser);
                    password = json_object_get_string(jpass);

                    if (username && password) {
                        try {
                            // Call the authentication service
                            PlgArgs->auth->Apilogin(tid, username, password, aid);

                            // Success response: returns the new authid
                            json_object* jsuccess = json_object_new_object();
                            json_object_object_add(jsuccess, "authid", json_object_new_string(aid.c_str()));
                            json_object_object_add(respid, "login_success", jsuccess);
                            asuccess=true;
                        } catch (libhttppp::HTTPException &e) {
                            // Failure response (e.g., invalid credentials, GPO check failed)
                            json_object* jfail = json_object_new_object();
                            json_object_object_add(jfail, "error", json_object_new_string(e.what().c_str()));
                            json_object_object_add(respid, "login_failure", jfail);
                        }
                    } else {
                        // Missing/invalid username/password strings
                        json_object* jfail = json_object_new_object();
                        json_object_object_add(jfail, "error", json_object_new_string("Missing username or password value."));
                        json_object_object_add(respid, "login_failure", jfail);
                    }
                } else {
                    // Missing keys in parameter object
                    json_object* jfail = json_object_new_object();
                    json_object_object_add(jfail, "error", json_object_new_string("Missing 'username' or 'password' fields."));
                    json_object_object_add(respid, "login_failure", jfail);
                }
            }
            else if (strcmp(key, "authid") == 0) {
                // --- AUTH CHECK COMMAND (Check for existing authid validity) ---
                json_object* value_obj  = json_object_new_object();
                const char* id = json_object_get_string(val);

                if(id && PlgArgs->auth->isLoggedIn(tid,id)){
                    // Success
                    asuccess=true;
                    json_object_object_add(value_obj, "authid", json_object_new_string(id));
                    json_object_object_add(value_obj, "is_valid", json_object_new_boolean(1));
                    json_object_object_add(respid, "auth_success", value_obj);
                } else {
                    // Failure
                    json_object_object_add(value_obj, "authid", json_object_new_string(id ? id : ""));
                    json_object_object_add(value_obj, "is_valid", json_object_new_boolean(0));
                    json_object_object_add(respid, "auth_failure", value_obj);
                }
            }

            // Add the response for this specific command to the response array
            json_object_array_add(jresponse, respid);

            // Only process one command per array element
            break;
        }
    }

    // 4. Process commands in external modules/plugins (Only if authentication succeeded)
    if(asuccess){
        // Extract authid from request for plugin delegation
        uuid::uuid authid;
        for (size_t ri = 0; ri < fcount; ++ri) {
            json_object *reqItem = json_object_array_get_idx(jrequest, ri);
            json_object *authObj = nullptr;
            if (json_object_object_get_ex(reqItem, "authid", &authObj)) {
                const char *authIdStr = json_object_get_string(authObj);
                if (authIdStr && authIdStr[0] != '\0')
                    authid = authIdStr;
                break;
            }
        }

        // Route unhandled commands to server plugins (media, etc.)
        for (size_t i = 0; i < fcount; ++i) {
            json_object *single_request = json_object_array_get_idx(jrequest, i);
            // Skip auth commands
            if (json_object_object_get(single_request, "authid") || json_object_object_get(single_request, "login"))
                continue;

            json_object *cmd_obj = nullptr;
            if (!json_object_object_get_ex(single_request, "command", &cmd_obj))
                continue;

            const char *command = json_object_get_string(cmd_obj);
            json_object *single_response = json_object_new_object();
            bool handled = false;

            // Handle list_publish_targets: enumerate plugins that support publishing
            if (command && strcmp(command, "list_publish_targets") == 0) {
                json_object *targets = json_object_new_array();
                for (const Plugin::PluginData *curplg = BlogiPlg->getFirstPlugin(); curplg; curplg = curplg->getNextPlg()) {
                    PluginApi *api = curplg->getInstace();
                    if (api && api->canPublish()) {
                        json_object *target = json_object_new_object();
                        json_object_object_add(target, "name", json_object_new_string(api->getName().c_str()));
                        json_object_object_add(target, "version", json_object_new_string(api->getVersion().c_str()));
                        json_object *fields = api->getPublishForm(tid);
                        json_object_object_add(target, "fields", fields);
                        json_object_array_add(targets, target);
                    }
                }
                json_object_object_add(single_response, "status", json_object_new_string("success"));
                json_object_object_add(single_response, "targets", targets);
                handled = true;
            }

            // Handle get_theme_colors: return dark/light CSS variable values
            if (!handled && command && strcmp(command, "get_theme_colors") == 0) {
                json_object *colors = PlgArgs->theme->getThemeColors(tid);
                json_object_object_add(single_response, "status", json_object_new_string("success"));
                json_object_object_add(single_response, "colors", colors);
                handled = true;
            }

            // Handle publish: route to the target plugin
            if (!handled && command && strcmp(command, "publish") == 0) {
                json_object *param_obj = nullptr;
                std::string targetPlugin;
                if (json_object_object_get_ex(single_request, "target", &param_obj)) {
                    const char *v = json_object_get_string(param_obj);
                    if (v) targetPlugin = v;
                }

                if (targetPlugin.empty()) {
                    json_object_object_add(single_response, "status", json_object_new_string("error"));
                    json_object_object_add(single_response, "message",
                        json_object_new_string("Missing 'target' parameter."));
                } else {
                    json_object *params = nullptr;
                    json_object_object_get_ex(single_request, "params", &params);
                        if (params) {
                            try {
                                int authorId = PlgArgs->auth->getAuthorId(tid, authid);
                                json_object_object_add(params, "author_id", json_object_new_int(authorId));
                            } catch (...) {}
                        }

                    bool found = false;
                    for (const Plugin::PluginData *curplg = BlogiPlg->getFirstPlugin(); curplg; curplg = curplg->getNextPlg()) {
                        PluginApi *api = curplg->getInstace();
                        if (api && api->canPublish() && api->getName() == targetPlugin) {
                            // For API-only publish: the rendered HTML is not provided here,
                            // the plugin should use an empty string or handle accordingly
                            std::string renderedHtml;
                            json_object *htmlObj = nullptr;
                            if (json_object_object_get_ex(single_request, "html", &htmlObj)) {
                                const char *hstr = json_object_get_string(htmlObj);
                                if (hstr) renderedHtml = hstr;
                            }
                            api->Publish(tid, renderedHtml, params, single_response);
                            found = true;
                            break;
                        }
                    }
                    if (!found) {
                        json_object_object_add(single_response, "status", json_object_new_string("error"));
                        json_object_object_add(single_response, "message",
                            json_object_new_string("Target plugin not found or does not support publishing."));
                    }
                }
                handled = true;
            }

            // Fallback: route to plugin JsonApi handlers
            if (!handled) {
                for (const Plugin::PluginData *curplg = BlogiPlg->getFirstPlugin(); curplg; curplg = curplg->getNextPlg()) {
                    PluginApi *api = curplg->getInstace();
                    if (api) {
                        api->JsonApi(tid, single_request, single_response, authid);
                        if (json_object_object_get(single_response, "status") != nullptr) {
                            handled = true;
                            break;
                        }
                    }
                }
            }

            if (!handled) {
                json_object_object_add(single_response, "status", json_object_new_string("error"));
                json_object_object_add(single_response, "message",
                    json_object_new_string("Command not recognized by any plugin."));
            }
            json_object_array_add(jresponse, single_response);
        }
    }

    // 5. Send JSON Response
    size_t outsize = 0;
    // Generate the final JSON string *after* all plugins have finished modifying jresponse
    const char* out = json_object_to_json_string_length(jresponse, 0, &outsize);

    resp.setContentType("application/json");

    if (!asuccess && fcount > 0) {
        resp.setState(HTTP401);
        resp.send(curreq, out, outsize);
        json_object_put(jresponse);
        json_object_put(jrequest);
        return;
    }

    resp.setState(HTTP200);
    resp.send(curreq, out, outsize);

    // 6. Cleanup
    json_object_put(jresponse);
    json_object_put(jrequest);
}

void blogi::Blogi::loginPage(libhttppp::HttpRequest &curreq,const int tid,const std::string &sessionid,libhtmlpp::HtmlElement &index){
    libhttppp::HTTPException excep;
    if(PlgArgs->auth->isLoggedIn(tid,curreq,sessionid)){
        libhttppp::HTTPException err;
        err[libhttppp::HTTPException::Error] << "you already authenticated please logoff before you login again!";
        throw err;
    }

    libhttppp::HttpForm curform;
    curform.parse(curreq);

    std::string username;
    std::string password;


    for (const auto &cururlform : curform.urlData()) {
        if (cururlform.key== "username") {
            username = cururlform.value;
        }else if (cururlform.key== "password") {
            password = cururlform.value;
        }
    }

    libhtmlpp::HtmlString out;

    if (username.empty() || password.empty()) {
        std::string _lang = blogi::getLang(*PlgArgs->database[tid]);
        libhtmlpp::HtmlString condat;
        condat << "<div id=\"content\">"
               << "<span>" << blogi::tr(_lang,"Login") << "</span>"
               << "<form action=\""<< PlgArgs->config->buildurl("login") << "\" method=\"post\">"
               << blogi::tr(_lang,"username:") << "<br> <input type=\"text\" name=\"username\" value=\"\" placeholder=\"user@domain\"><br>"
               << blogi::tr(_lang,"password:") << "<br> <input type=\"password\" name=\"password\" value=\"\"><br>"
               << "<button type=\"submit\">" << blogi::tr(_lang,"Submit") << "</button>"
               << "</form>"
               << "</div>";

        if(index.getElementbyID("main"))
            index.getElementbyID("main")->insertChild(condat.parse());

        for(const blogi::Plugin::PluginData *curplg=BlogiPlg->getFirstPlugin(); curplg; curplg=curplg->getNextPlg()){
            if(curreq.isMobile())
                curplg->getInstace()->Rendering(tid,curreq,&index,RenderingType::Mobile,sessionid);
            else
                curplg->getInstace()->Rendering(tid,curreq,&index,RenderingType::Desktop,sessionid);
        }

        PlgArgs->theme->printSite(tid,out,&index,curreq.getRequestURL(),false);
        libhttppp::HttpResponse curres;
        // Clear stale auth cookie when showing login form
        libhttppp::HttpCookie cookclr;
        cookclr.setcookie(curres,"authid","","",cookieDomain(curreq).c_str(),0,"/");
        curres.setState(HTTP200);
        curres.setContentType("text/html");
        sendCompressed(curreq,curres,out.c_str(),out.length());
        return;
    }

    uuid::uuid sessid;
    PlgArgs->auth->login(tid,username.c_str(),password.c_str(),sessid,sessionid);

    if(!sessid.empty()){
        libhttppp::HttpResponse curres;
        libhttppp::HttpCookie authid;

        authid.setcookie(curres,"authid",sessid.c_str(),"",cookieDomain(curreq).c_str(),(1000*60*60*1),"/",false,"1","Lax",false);

        curres.setState(HTTP307);
        curres.setHeaderData("location")->push_back(PlgArgs->config->getstartpage());
        curres.setContentType("text/html");
        curres.send(curreq, "", 0);
    }else{
        libhttppp::HttpResponse curres;
        libhttppp::HttpCookie cookie;
        curres.setState(HTTP403);
        curres.setContentType("text/html");

        libhtmlpp::HtmlString condat;
        std::string _lang = blogi::getLang(*PlgArgs->database[tid]);
        condat << "<div id=\"content\">"
               << "<span>" << blogi::tr(_lang,"Wrong Username or Password!") << "</span>"
               << "<span>" << blogi::tr(_lang,"Login") << "</span>"
               << "<form action=\""<< PlgArgs->config->buildurl("login") << "\" method=\"post\">"
               << blogi::tr(_lang,"username:") << "<br> <input type=\"text\" name=\"username\" value=\"\"><br>"
               << blogi::tr(_lang,"password:") << "<br> <input type=\"password\" name=\"password\" value=\"\"><br>"
               << "<button type=\"submit\">" << blogi::tr(_lang,"Submit") << "</button>"
               << "</form>"
               << "</div>";

        sendCompressed(curreq, curres, condat.c_str(), condat.length());
    }

}

void blogi::Blogi::logoutPage(libhttppp::HttpRequest &curreq,const int tid,const std::string &sessiondid){
    libhttppp::HttpCookie cook,cookclr;

    cook.parse(curreq);

    uuid::uuid sessid;

    for(libhttppp::HttpCookie::CookieData *curcook=cook.getfirstCookieData(); curcook; curcook=curcook->nextCookieData()){
        if(curcook->getKey()=="authid"){
            sessid=curcook->getValue();
        }
    }

    if(sessid.empty())
         return;

    authdb::client::ClientConnection authcon;
    authcon.setUrl(PlgArgs->config->getAuthUrl());
    authcon.setClientName(PlgArgs->config->getClientName());
    authcon.setClientSecret(PlgArgs->config->getClientSecret());
    authcon.setSessionID(sessid);

    authdb::client::Client clt(authcon);

    if(clt.ClientAuth()){
        clt.remSession();
    }

    libhttppp::HttpResponse curres;

    cookclr.setcookie(curres,"authid","");

    curres.setState(HTTP307);
    curres.setHeaderData("location")->push_back(PlgArgs->config->getstartpage());
    curres.setContentType("text/html");
    curres.setContentLength(0);
    curres.send(curreq,"",0);
}

// ----------------------------------------------------------------------------
// Render a JSON form descriptor into HTML
// ----------------------------------------------------------------------------
static void renderSettingsJson(json_object *jroot, libhtmlpp::HtmlString &out, const std::string &lang="en"){
    if(!jroot) {
        out << "<div class=\"set-empty\">" << blogi::tr(lang,"No settings available") << "</div>";
        return;
    }

    // Title
    json_object *jtitle = nullptr;
    if(json_object_object_get_ex(jroot, "title", &jtitle)){
        out << "<h3 class=\"set-title\">" << json_object_get_string(jtitle) << "</h3>";
    }

    // Page-level tabs (AJAX link tabs)
    json_object *jptabs = nullptr;
    if(json_object_object_get_ex(jroot, "page_tabs", &jptabs)){
        int ptlen = json_object_array_length(jptabs);
        out << "<div style=\"display:flex;gap:0;border-bottom:2px solid var(--blogi-border,#3a3f44);margin-bottom:12px;\">";
        for(int t=0; t<ptlen; ++t){
            json_object *jpt = json_object_array_get_idx(jptabs, t);
            json_object *jptl=nullptr, *jptu=nullptr, *jpta=nullptr;
            json_object_object_get_ex(jpt, "label", &jptl);
            json_object_object_get_ex(jpt, "url", &jptu);
            json_object_object_get_ex(jpt, "active", &jpta);
            bool active = jpta && json_object_get_boolean(jpta);
            out << "<a href=\"" << (jptu ? json_object_get_string(jptu) : "#") << "\" style=\""
                << "padding:8px 18px;text-decoration:none;font-size:0.95em;border-bottom:2px solid "
                << (active ? "var(--blogi-accent,#3ad43a)" : "transparent")
                << ";margin-bottom:-2px;color:"
                << (active ? "var(--blogi-accent,#3ad43a)" : "var(--blogi-text-muted,#aaa)")
                << ";\">" << (jptl ? json_object_get_string(jptl) : "") << "</a>";
        }
        out << "</div>";
    }

    // Message (success / error)
    json_object *jmsg = nullptr;
    if(json_object_object_get_ex(jroot, "message", &jmsg)){
        json_object *jmtype = nullptr;
        const char *cls = "set-msg";
        if(json_object_object_get_ex(jroot, "message_type", &jmtype)){
            const char *mt = json_object_get_string(jmtype);
            if(strcmp(mt,"success")==0) cls = "set-msg set-msg-ok";
            else if(strcmp(mt,"error")==0) cls = "set-msg set-msg-err";
        }
        out << "<div class=\"" << cls << "\">" << json_object_get_string(jmsg) << "</div>";
    }

    // Nav links (back, breadcrumbs)
    json_object *jnav = nullptr;
    if(json_object_object_get_ex(jroot, "nav", &jnav)){
        out << "<div class=\"set-breadcrumb\">";
        int nlen = json_object_array_length(jnav);
        for(int i=0; i<nlen; ++i){
            json_object *jlink = json_object_array_get_idx(jnav, i);
            json_object *jlabel=nullptr, *jurl=nullptr;
            json_object_object_get_ex(jlink, "label", &jlabel);
            json_object_object_get_ex(jlink, "url", &jurl);
            if(jlabel && jurl){
                out << "<a class=\"set-link\" href=\"" << json_object_get_string(jurl) << "\">"
                    << "&#8592; " << json_object_get_string(jlabel) << "</a> ";
            }
        }
        out << "</div>";
    }

    // Standalone table
    json_object *jtable = nullptr;
    if(json_object_object_get_ex(jroot, "table", &jtable)){
        json_object *jheaders=nullptr, *jrows=nullptr;
        json_object_object_get_ex(jtable, "headers", &jheaders);
        json_object_object_get_ex(jtable, "rows", &jrows);
        out << "<table class=\"set-table\">";
        if(jheaders){
            out << "<thead><tr>";
            int hlen = json_object_array_length(jheaders);
            for(int i=0; i<hlen; ++i){
                out << "<th>" << json_object_get_string(json_object_array_get_idx(jheaders, i)) << "</th>";
            }
            out << "</tr></thead>";
        }
        if(jrows){
            out << "<tbody>";
            int rlen = json_object_array_length(jrows);
            for(int r=0; r<rlen; ++r){
                json_object *jrow = json_object_array_get_idx(jrows, r);
                json_object *jcells=nullptr, *jactions=nullptr;
                json_object_object_get_ex(jrow, "cells", &jcells);
                json_object_object_get_ex(jrow, "actions", &jactions);
                out << "<tr>";
                if(jcells){
                    int clen = json_object_array_length(jcells);
                    for(int c=0; c<clen; ++c){
                        out << "<td>" << json_object_get_string(json_object_array_get_idx(jcells, c)) << "</td>";
                    }
                }
                if(jactions){
                    out << "<td class=\"set-actions\">";
                    int alen = json_object_array_length(jactions);
                    for(int a=0; a<alen; ++a){
                        json_object *jact = json_object_array_get_idx(jactions, a);
                        json_object *jal=nullptr, *jau=nullptr;
                        json_object_object_get_ex(jact, "label", &jal);
                        json_object_object_get_ex(jact, "url", &jau);
                        if(jal && jau){
                            out << "<a class=\"set-btn-sm\" href=\"" << json_object_get_string(jau) << "\">"
                                << json_object_get_string(jal) << "</a> ";
                        }
                    }
                    out << "</td>";
                }
                out << "</tr>";
            }
            out << "</tbody>";
        }
        out << "</table>";
    }

    // Info display (read-only key/value)
    json_object *jinfo = nullptr;
    if(json_object_object_get_ex(jroot, "info", &jinfo)){
        json_object *jilabel=nullptr, *jivalue=nullptr;
        json_object_object_get_ex(jinfo, "label", &jilabel);
        json_object_object_get_ex(jinfo, "value", &jivalue);
        out << "<div class=\"set-field\">";
        if(jilabel)
            out << "<label class=\"set-label\">" << json_object_get_string(jilabel) << "</label>";
        if(jivalue)
            out << "<span class=\"set-input\" style=\"background:transparent;border:1px solid var(--blogi-border,#3a3f44);padding:6px 10px;border-radius:4px;color:var(--blogi-text-muted,#aaa);\">"
                << json_object_get_string(jivalue) << "</span>";
        out << "</div>";
    }

    // Confirm dialog
    json_object *jconfirm = nullptr;
    if(json_object_object_get_ex(jroot, "confirm", &jconfirm)){
        json_object *jcmsg=nullptr, *jyes=nullptr, *jno=nullptr;
        json_object_object_get_ex(jconfirm, "message", &jcmsg);
        json_object_object_get_ex(jconfirm, "yes_url", &jyes);
        json_object_object_get_ex(jconfirm, "no_url", &jno);
        out << "<div class=\"set-confirm\">";
        if(jcmsg) out << "<p class=\"set-confirm-msg\">" << json_object_get_string(jcmsg) << "</p>";
        out << "<div class=\"set-confirm-btns\">";
        if(jyes) out << "<a class=\"set-btn set-btn-danger\" href=\"" << json_object_get_string(jyes) << "\">" << blogi::tr(lang,"Yes") << "</a> ";
        if(jno) out << "<a class=\"set-btn\" href=\"" << json_object_get_string(jno) << "\">" << blogi::tr(lang,"Cancel") << "</a>";
        out << "</div></div>";
    }

    // Form
    json_object *jform = nullptr;
    if(json_object_object_get_ex(jroot, "form", &jform)){
        json_object *jmethod=nullptr, *jaction=nullptr, *jenctype=nullptr;
        json_object_object_get_ex(jform, "method", &jmethod);
        json_object_object_get_ex(jform, "action", &jaction);
        json_object_object_get_ex(jform, "enctype", &jenctype);

        out << "<form class=\"set-form\" method=\"" << (jmethod ? json_object_get_string(jmethod) : "POST") << "\"";
        if(jaction) out << " action=\"" << json_object_get_string(jaction) << "\"";
        if(jenctype) out << " enctype=\"" << json_object_get_string(jenctype) << "\"";
        out << ">";

        // Field rendering helper (used by both flat fields and tabs)
        auto renderFieldArray = [&](json_object *jflds){
            int flen = json_object_array_length(jflds);
            for(int f=0; f<flen; ++f){
                json_object *jf = json_object_array_get_idx(jflds, f);
                json_object *jname=nullptr, *jtype=nullptr, *jlabel=nullptr, *jvalue=nullptr, *joptions=nullptr;
                json_object_object_get_ex(jf, "name", &jname);
                json_object_object_get_ex(jf, "type", &jtype);
                json_object_object_get_ex(jf, "label", &jlabel);
                json_object_object_get_ex(jf, "value", &jvalue);
                json_object_object_get_ex(jf, "options", &joptions);

                const char *type = jtype ? json_object_get_string(jtype) : "text";
                const char *name = jname ? json_object_get_string(jname) : "";
                const char *value = jvalue ? json_object_get_string(jvalue) : "";

                if(strcmp(type,"hidden")==0){
                    out << "<input type=\"hidden\" name=\"" << name << "\" value=\"" << value << "\" />";
                    continue;
                }

                // Section heading — no input, just a styled header
                if(strcmp(type,"heading")==0){
                    if(jlabel){
                        out << "<h4 class=\"set-heading\">" << json_object_get_string(jlabel) << "</h4>";
                    }
                    continue;
                }

                out << "<div class=\"set-field\">";
                if(jlabel){
                    out << "<label class=\"set-label\">" << json_object_get_string(jlabel) << "</label>";
                }

                if(strcmp(type,"textarea")==0){
                    out << "<textarea class=\"set-input set-textarea\" name=\"" << name << "\">" << value << "</textarea>";
                }else if(strcmp(type,"select")==0 && joptions){
                    out << "<select class=\"set-input\" name=\"" << name << "\">";
                    int olen = json_object_array_length(joptions);
                    for(int o=0; o<olen; ++o){
                        json_object *jopt = json_object_array_get_idx(joptions, o);
                        json_object *jov=nullptr, *jol=nullptr, *josel=nullptr;
                        json_object_object_get_ex(jopt, "value", &jov);
                        json_object_object_get_ex(jopt, "label", &jol);
                        json_object_object_get_ex(jopt, "selected", &josel);
                        out << "<option value=\"" << (jov ? json_object_get_string(jov) : "") << "\"";
                        if(josel && json_object_get_boolean(josel)) out << " selected";
                        out << ">" << (jol ? json_object_get_string(jol) : "") << "</option>";
                    }
                    out << "</select>";
                }else if(strcmp(type,"file")==0){
                    out << "<input class=\"set-input\" type=\"file\" name=\"" << name << "\" />";
                }else if(strcmp(type,"media_url")==0){
                    // Media URL browser: hidden input + preview + Browse button + modal
                    std::string mid = std::string("murl_") + name;
                    out << "<div style=\"display:flex;align-items:center;gap:10px;flex-wrap:wrap;\">";
                    out << "<input type=\"hidden\" id=\"" << mid << "\" name=\"" << name << "\" value=\"" << value << "\" />";
                    if(value && value[0] != '\0'){
                        out << "<img id=\"" << mid << "_prev\" src=\"" << value << "\" style=\"max-height:60px;max-width:200px;border-radius:4px;border:1px solid var(--blogi-border);\" />";
                    } else {
                        out << "<img id=\"" << mid << "_prev\" style=\"display:none;max-height:60px;max-width:200px;border-radius:4px;border:1px solid var(--blogi-border);\" />";
                    }
                    out << "<button type=\"button\" class=\"set-btn\" onclick=\"openSettingsMediaBrowser('" << mid << "')\">Browse&hellip;</button>";
                    out << "<button type=\"button\" class=\"set-btn\" style=\"font-size:0.85em;\" onclick=\"document.getElementById('" << mid << "').value='';var p=document.getElementById('" << mid << "_prev');p.style.display='none';p.src='';\">Clear</button>";
                    out << "</div>";
                    // Emit the media browser modal + logic once
                    out << "<script>"
                        << "if(!window._settingsMediaBrowserReady){"
                        << "window._settingsMediaBrowserReady=true;"
                        // Modal overlay
                        << "var mo=document.createElement('div');mo.id='smb-overlay';"
                        << "mo.style.cssText='display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.6);z-index:9999;justify-content:center;align-items:center;';"
                        << "var md=document.createElement('div');md.style.cssText='background:var(--blogi-surface,#31363b);border-radius:8px;width:620px;max-height:80vh;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 8px 32px rgba(0,0,0,0.5);';"
                        // Header
                        << "var hdr=document.createElement('div');hdr.style.cssText='display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--blogi-border,#3a3f44);';"
                        << "var ht=document.createElement('span');ht.id='smb-title';ht.textContent='Select Image';ht.style.cssText='font-weight:600;color:var(--blogi-text,#eee);';"
                        << "var hx=document.createElement('button');hx.textContent='\\u00d7';hx.style.cssText='background:none;border:none;font-size:1.4em;color:var(--blogi-text-muted,#aaa);cursor:pointer;';"
                        << "hx.onclick=function(){mo.style.display='none';};"
                        << "hdr.appendChild(ht);hdr.appendChild(hx);md.appendChild(hdr);"
                        // Content area
                        << "var ct=document.createElement('div');ct.id='smb-content';ct.style.cssText='padding:12px 16px;overflow-y:auto;flex:1;';"
                        << "md.appendChild(ct);mo.appendChild(md);document.body.appendChild(mo);"
                        // State
                        << "var _smbTarget='';"
                        // Fetch helper
                        << "function smbFetch(url,cb){"
                        <<   "fetch(url,{credentials:'same-origin'}).then(function(r){return r.json();}).then(cb).catch(function(e){ct.textContent='Error: '+e;});"
                        << "}"
                        // Open browser
                        << "window.openSettingsMediaBrowser=function(tid){"
                        <<   "_smbTarget=tid;"
                        <<   "mo.style.display='flex';"
                        <<   "ct.innerHTML='<em>Loading albums&hellip;</em>';"
                        <<   "ht.textContent='Select Album';"
                        <<   "smbFetch('settings/api/media/albums',function(d){"
                        <<     "ct.innerHTML='';"
                        <<     "if(!d.albums||!d.albums.length){ct.textContent='No albums found.';return;}"
                        <<     "d.albums.forEach(function(a){"
                        <<       "var b=document.createElement('div');"
                        <<       "b.style.cssText='padding:8px 12px;margin:4px 0;background:var(--blogi-bg-alt,#2a2e32);border-radius:4px;cursor:pointer;color:var(--blogi-text,#eee);';"
                        <<       "b.textContent=a.name+' ('+a.media_count+')';"
                        <<       "b.onmouseover=function(){b.style.background='var(--blogi-accent,#3ad43a)';b.style.color='#000';};"
                        <<       "b.onmouseout=function(){b.style.background='var(--blogi-bg-alt,#2a2e32)';b.style.color='var(--blogi-text,#eee)';};"
                        <<       "b.onclick=function(){smbLoadAlbum(a.id,a.name);};"
                        <<       "ct.appendChild(b);"
                        <<     "});"
                        <<   "});"
                        << "};"
                        // Load album images
                        << "function smbLoadAlbum(aid,aname){"
                        <<   "ct.innerHTML='<em>Loading&hellip;</em>';"
                        <<   "ht.textContent=aname||'Select Image';"
                        <<   "smbFetch('settings/api/media/images?album_id='+encodeURIComponent(aid),function(d){"
                        <<     "ct.innerHTML='';"
                        // Back button
                        <<     "var bk=document.createElement('div');"
                        <<     "bk.textContent='\\u2190 Back to albums';"
                        <<     "bk.style.cssText='cursor:pointer;color:var(--blogi-accent,#3ad43a);margin-bottom:10px;font-size:0.9em;';"
                        <<     "bk.onclick=function(){openSettingsMediaBrowser(_smbTarget);};"
                        <<     "ct.appendChild(bk);"
                        <<     "if(!d.media||!d.media.length){var em=document.createElement('p');em.textContent='No images in this album.';ct.appendChild(em);return;}"
                        // Grid
                        <<     "var g=document.createElement('div');"
                        <<     "g.style.cssText='display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr));gap:8px;';"
                        <<     "d.media.forEach(function(m){"
                        <<       "var c=document.createElement('div');"
                        <<       "c.style.cssText='cursor:pointer;border-radius:4px;overflow:hidden;border:2px solid transparent;aspect-ratio:1;display:flex;align-items:center;justify-content:center;background:var(--blogi-bg-alt,#2a2e32);';"
                        <<       "if(m.kind==='image'){"
                        <<         "var im=document.createElement('img');"
                        <<         "im.src=m.url+'?w=100&h=100';im.style.cssText='max-width:100%;max-height:100%;object-fit:cover;';"
                        <<         "c.appendChild(im);"
                        <<       "}else{"
                        <<         "var sp=document.createElement('span');sp.textContent=m.filename;sp.style.cssText='font-size:0.75em;color:var(--blogi-text-muted,#aaa);text-align:center;padding:4px;word-break:break-all;';"
                        <<         "c.appendChild(sp);"
                        <<       "}"
                        <<       "c.onmouseover=function(){c.style.borderColor='var(--blogi-accent,#3ad43a)';};"
                        <<       "c.onmouseout=function(){c.style.borderColor='transparent';};"
                        <<       "c.onclick=function(){"
                        <<         "var hd=document.getElementById(_smbTarget);"
                        <<         "hd.value=m.url;"
                        <<         "var pv=document.getElementById(_smbTarget+'_prev');"
                        <<         "pv.src=m.url;pv.style.display='inline-block';"
                        <<         "mo.style.display='none';"
                        <<       "};"
                        <<       "g.appendChild(c);"
                        <<     "});"
                        <<     "ct.appendChild(g);"
                        <<   "});"
                        << "}"
                        << "}"
                        << "</script>";
                }else if(strcmp(type,"color")==0){
                    // Custom RGBA color picker
                    std::string uid = std::string("clr_") + name;
                    out << "<input type=\"hidden\" id=\"" << uid << "\" name=\"" << name << "\" value=\"" << value << "\" />";
                    out << "<div id=\"" << uid << "_wrap\"></div>";
                    out << "<script>"
                        // Define shared color picker class once
                        << "if(!window.BlogiCP){"
                        << "window.BlogiCP=function(uid){"
                        << "var hd=document.getElementById(uid);"
                        << "var wr=document.getElementById(uid+'_wrap');"
                        << "var H=0,S=1,V=1,A=1;"
                        // --- Parse initial value ---
                        << "function parseInit(){"
                        <<   "var v=(hd.value||'').trim();"
                        <<   "var r=0,g=0,b=0;"
                        <<   "if(v.startsWith('rgba(')){var p=v.slice(5,-1).split(',');r=+p[0];g=+p[1];b=+p[2];A=parseFloat(p[3]);}"
                        <<   "else if(v.startsWith('rgb(')){var p=v.slice(4,-1).split(',');r=+p[0];g=+p[1];b=+p[2];A=1;}"
                        <<   "else{if(!v.startsWith('#')&&/^[0-9a-fA-F]{6}$/.test(v))v='#'+v;"
                        <<     "if(v.startsWith('#')){if(v.length===4)v='#'+v[1]+v[1]+v[2]+v[2]+v[3]+v[3];"
                        <<     "r=parseInt(v.substr(1,2),16);g=parseInt(v.substr(3,2),16);b=parseInt(v.substr(5,2),16);A=1;}}"
                        <<   "var mx=Math.max(r,g,b)/255,mn=Math.min(r,g,b)/255,d=mx-mn;"
                        <<   "V=mx;S=mx?d/mx:0;"
                        <<   "if(!d)H=0;else if(mx===r/255)H=60*((g/255-b/255)/d%6);"
                        <<   "else if(mx===g/255)H=60*((b/255-r/255)/d+2);"
                        <<   "else H=60*((r/255-g/255)/d+4);"
                        <<   "if(H<0)H+=360;"
                        << "}"
                        // --- HSV to RGB ---
                        << "function hsv2rgb(h,s,v){"
                        <<   "var c=v*s,x=c*(1-Math.abs((h/60)%2-1)),m=v-c;"
                        <<   "var r=0,g=0,b=0;"
                        <<   "if(h<60){r=c;g=x;}else if(h<120){r=x;g=c;}else if(h<180){g=c;b=x;}"
                        <<   "else if(h<240){g=x;b=c;}else if(h<300){r=x;b=c;}else{r=c;b=x;}"
                        <<   "return[Math.round((r+m)*255),Math.round((g+m)*255),Math.round((b+m)*255)];"
                        << "}"
                        // --- Build DOM ---
                        << "var W=160,SH=120,BH=14,gap=6;"
                        << "wr.style.cssText='display:inline-block;';"
                        // Row: SV canvas + hue bar
                        << "var row=document.createElement('div');"
                        << "row.style.cssText='display:flex;gap:'+gap+'px;';"
                        // SV canvas
                        << "var svC=document.createElement('canvas');"
                        << "svC.width=W;svC.height=SH;svC.style.cssText='cursor:crosshair;border:1px solid var(--blogi-border);border-radius:3px;';"
                        << "var svCtx=svC.getContext('2d');"
                        // Hue canvas (vertical)
                        << "var huC=document.createElement('canvas');"
                        << "huC.width=BH;huC.height=SH;huC.style.cssText='cursor:pointer;border:1px solid var(--blogi-border);border-radius:3px;';"
                        << "var huCtx=huC.getContext('2d');"
                        << "row.appendChild(svC);row.appendChild(huC);"
                        << "wr.appendChild(row);"
                        // Alpha bar (horizontal)
                        << "var alC=document.createElement('canvas');"
                        << "alC.width=W+BH+gap;alC.height=BH;alC.style.cssText='cursor:pointer;border:1px solid var(--blogi-border);border-radius:3px;margin-top:'+gap+'px;display:block;';"
                        << "var alCtx=alC.getContext('2d');"
                        << "wr.appendChild(alC);"
                        // Info row: swatch + text input
                        << "var info=document.createElement('div');"
                        << "info.style.cssText='display:flex;align-items:center;gap:6px;margin-top:'+gap+'px;';"
                        << "var sw=document.createElement('span');"
                        << "sw.style.cssText='display:inline-block;width:24px;height:24px;border-radius:4px;border:1px solid var(--blogi-border);flex-shrink:0;';"
                        << "var ti=document.createElement('input');"
                        << "ti.type='text';ti.style.cssText='width:180px;font-size:0.85em;padding:2px 4px;background:var(--blogi-surface,#333);color:var(--blogi-text,#eee);border:1px solid var(--blogi-border,#555);border-radius:3px;';"
                        << "info.appendChild(sw);info.appendChild(ti);"
                        << "wr.appendChild(info);"
                        // --- Draw functions ---
                        << "function drawSV(){"
                        <<   "var rgb=hsv2rgb(H,1,1);"
                        <<   "svCtx.fillStyle='rgb('+rgb[0]+','+rgb[1]+','+rgb[2]+')';"
                        <<   "svCtx.fillRect(0,0,W,SH);"
                        <<   "var gw=svCtx.createLinearGradient(0,0,W,0);"
                        <<   "gw.addColorStop(0,'#fff');gw.addColorStop(1,'rgba(255,255,255,0)');"
                        <<   "svCtx.fillStyle=gw;svCtx.fillRect(0,0,W,SH);"
                        <<   "var gb=svCtx.createLinearGradient(0,0,0,SH);"
                        <<   "gb.addColorStop(0,'rgba(0,0,0,0)');gb.addColorStop(1,'#000');"
                        <<   "svCtx.fillStyle=gb;svCtx.fillRect(0,0,W,SH);"
                        // cursor
                        <<   "var cx=S*W,cy=(1-V)*SH;"
                        <<   "svCtx.beginPath();svCtx.arc(cx,cy,5,0,6.28);"
                        <<   "svCtx.strokeStyle='#fff';svCtx.lineWidth=2;svCtx.stroke();"
                        <<   "svCtx.strokeStyle='#000';svCtx.lineWidth=1;svCtx.stroke();"
                        << "}"
                        << "function drawHue(){"
                        <<   "var g=huCtx.createLinearGradient(0,0,0,SH);"
                        <<   "for(var i=0;i<=6;i++)g.addColorStop(i/6,['#f00','#ff0','#0f0','#0ff','#00f','#f0f','#f00'][i]);"
                        <<   "huCtx.fillStyle=g;huCtx.fillRect(0,0,BH,SH);"
                        // indicator
                        <<   "var y=H/360*SH;"
                        <<   "huCtx.fillStyle='#fff';huCtx.fillRect(0,y-1,BH,3);"
                        <<   "huCtx.fillStyle='#000';huCtx.fillRect(0,y,BH,1);"
                        << "}"
                        << "function drawAlpha(){"
                        <<   "var aw=alC.width;"
                        // checkerboard
                        <<   "alCtx.fillStyle='#ccc';alCtx.fillRect(0,0,aw,BH);"
                        <<   "for(var x=0;x<aw;x+=7)for(var y=0;y<BH;y+=7)"
                        <<     "if((Math.floor(x/7)+Math.floor(y/7))%2){alCtx.fillStyle='#999';alCtx.fillRect(x,y,7,7);}"
                        <<   "var rgb=hsv2rgb(H,S,V);"
                        <<   "var ga=alCtx.createLinearGradient(0,0,aw,0);"
                        <<   "ga.addColorStop(0,'rgba('+rgb[0]+','+rgb[1]+','+rgb[2]+',0)');"
                        <<   "ga.addColorStop(1,'rgba('+rgb[0]+','+rgb[1]+','+rgb[2]+',1)');"
                        <<   "alCtx.fillStyle=ga;alCtx.fillRect(0,0,aw,BH);"
                        // indicator
                        <<   "var ax=A*aw;"
                        <<   "alCtx.fillStyle='#fff';alCtx.fillRect(ax-1,0,3,BH);"
                        <<   "alCtx.fillStyle='#000';alCtx.fillRect(ax,0,1,BH);"
                        << "}"
                        << "function update(){"
                        <<   "drawSV();drawHue();drawAlpha();"
                        <<   "var rgb=hsv2rgb(H,S,V);"
                        <<   "var rgba='rgba('+rgb[0]+','+rgb[1]+','+rgb[2]+','+Math.round(A*100)/100+')';"
                        <<   "hd.value=rgba;ti.value=rgba;"
                        <<   "sw.style.backgroundColor=rgba;"
                        << "}"
                        // --- Drag helpers ---
                        << "function drag(el,fn){"
                        <<   "function mv(e){var r=el.getBoundingClientRect();fn(e.clientX-r.left,e.clientY-r.top,r);}"
                        <<   "el.addEventListener('pointerdown',function(e){el.setPointerCapture(e.pointerId);mv(e);});"
                        <<   "el.addEventListener('pointermove',function(e){if(e.buttons)mv(e);});"
                        << "}"
                        << "drag(svC,function(x,y){S=Math.max(0,Math.min(1,x/W));V=Math.max(0,Math.min(1,1-y/SH));update();});"
                        << "drag(huC,function(x,y){H=Math.max(0,Math.min(360,y/SH*360));update();});"
                        << "drag(alC,function(x,y,r){A=Math.max(0,Math.min(1,x/r.width));update();});"
                        // Text input handler
                        << "ti.addEventListener('change',function(){"
                        <<   "hd.value=ti.value;parseInit();update();"
                        << "});"
                        // Init
                        << "parseInit();update();"
                        << "};"
                        << "}"
                        << "new BlogiCP('" << uid << "');"
                        << "</script>";
                }else if(strcmp(type,"checkbox")==0){
                    out << "<input class=\"set-input\" type=\"checkbox\" name=\"" << name << "\" value=\"on\"";
                    if(value && (strcmp(value,"on")==0 || strcmp(value,"true")==0 || strcmp(value,"1")==0))
                        out << " checked";
                    out << " />";
                }else{
                    out << "<input class=\"set-input\" type=\"" << type << "\" name=\"" << name << "\" value=\"" << value << "\" />";
                }
                out << "</div>";
            }
        };

        // Tabs support
        json_object *jtabs = nullptr;
        json_object_object_get_ex(jform, "tabs", &jtabs);

        if(jtabs){
            int tlen = json_object_array_length(jtabs);
            out << "<style>"
                << ".set-tabs{display:flex;gap:0;border-bottom:2px solid var(--blogi-border,#3a3f44);margin-bottom:12px;}"
                << ".set-tab-btn{padding:8px 18px;border:none;background:none;color:var(--blogi-text-muted,#aaa);cursor:pointer;font-size:0.95em;border-bottom:2px solid transparent;margin-bottom:-2px;}"
                << ".set-tab-btn:hover{color:var(--blogi-text,#eee);}"
                << ".set-tab-btn.active{color:var(--blogi-accent,#3ad43a);border-bottom-color:var(--blogi-accent,#3ad43a);}"
                << ".set-tab-panel{display:none;}"
                << ".set-tab-panel.active{display:block;}"
                << "</style>";
            out << "<div class=\"set-tabs\">";
            for(int t=0; t<tlen; ++t){
                json_object *jtab = json_object_array_get_idx(jtabs, t);
                json_object *jtlabel = nullptr;
                json_object_object_get_ex(jtab, "label", &jtlabel);
                out << "<button type=\"button\" class=\"set-tab-btn" << (t==0?" active":"") << "\" data-tab=\"" << t << "\">"
                    << (jtlabel ? json_object_get_string(jtlabel) : "") << "</button>";
            }
            out << "</div>";
            for(int t=0; t<tlen; ++t){
                json_object *jtab = json_object_array_get_idx(jtabs, t);
                json_object *jtfields = nullptr;
                json_object_object_get_ex(jtab, "fields", &jtfields);
                out << "<div class=\"set-tab-panel" << (t==0?" active":"") << "\" data-tab=\"" << t << "\">";
                if(jtfields) renderFieldArray(jtfields);
                out << "</div>";
            }
            out << "<script>"
                << "document.querySelectorAll('.set-tab-btn').forEach(function(btn){"
                <<   "btn.addEventListener('click',function(){"
                <<     "document.querySelectorAll('.set-tab-btn').forEach(function(b){b.classList.remove('active');});"
                <<     "document.querySelectorAll('.set-tab-panel').forEach(function(p){p.classList.remove('active');});"
                <<     "btn.classList.add('active');"
                <<     "document.querySelector('.set-tab-panel[data-tab=\"'+btn.dataset.tab+'\"]').classList.add('active');"
                <<   "});"
                << "});"
                << "</script>";
        }

        // Form fields (flat, no tabs)
        json_object *jfields = nullptr;
        if(!jtabs && json_object_object_get_ex(jform, "fields", &jfields)){
            renderFieldArray(jfields);
        }

        // Form-embedded table (e.g. navbar items inside a form)
        json_object *jftable = nullptr;
        if(json_object_object_get_ex(jform, "table", &jftable)){
            json_object *jftheaders=nullptr, *jftrows=nullptr;
            json_object_object_get_ex(jftable, "headers", &jftheaders);
            json_object_object_get_ex(jftable, "rows", &jftrows);
            out << "<table class=\"set-table\">";
            if(jftheaders){
                out << "<thead><tr>";
                int hlen = json_object_array_length(jftheaders);
                for(int i=0; i<hlen; ++i){
                    out << "<th>" << json_object_get_string(json_object_array_get_idx(jftheaders, i)) << "</th>";
                }
                out << "</tr></thead>";
            }
            if(jftrows){
                out << "<tbody>";
                int rlen = json_object_array_length(jftrows);
                for(int r=0; r<rlen; ++r){
                    json_object *jrow = json_object_array_get_idx(jftrows, r);
                    json_object *jrfields=nullptr, *jractions=nullptr;
                    json_object_object_get_ex(jrow, "fields", &jrfields);
                    json_object_object_get_ex(jrow, "actions", &jractions);
                    out << "<tr>";
                    if(jrfields){
                        int rflen = json_object_array_length(jrfields);
                        for(int rf=0; rf<rflen; ++rf){
                            json_object *jrf = json_object_array_get_idx(jrfields, rf);
                            json_object *jrfname=nullptr, *jrftype=nullptr, *jrfvalue=nullptr, *jrfoptions=nullptr;
                            json_object_object_get_ex(jrf, "name", &jrfname);
                            json_object_object_get_ex(jrf, "type", &jrftype);
                            json_object_object_get_ex(jrf, "value", &jrfvalue);
                            json_object_object_get_ex(jrf, "options", &jrfoptions);
                            const char *rftype = jrftype ? json_object_get_string(jrftype) : "text";
                            const char *rfname = jrfname ? json_object_get_string(jrfname) : "";
                            const char *rfvalue = jrfvalue ? json_object_get_string(jrfvalue) : "";
                            out << "<td>";
                            if(strcmp(rftype,"select")==0 && jrfoptions){
                                out << "<select class=\"set-input\" name=\"" << rfname << "\">";
                                int olen = json_object_array_length(jrfoptions);
                                for(int o=0; o<olen; ++o){
                                    json_object *jopt = json_object_array_get_idx(jrfoptions, o);
                                    json_object *jov=nullptr, *jol=nullptr, *josel=nullptr;
                                    json_object_object_get_ex(jopt, "value", &jov);
                                    json_object_object_get_ex(jopt, "label", &jol);
                                    json_object_object_get_ex(jopt, "selected", &josel);
                                    out << "<option value=\"" << (jov ? json_object_get_string(jov) : "") << "\"";
                                    if(josel && json_object_get_boolean(josel)) out << " selected";
                                    out << ">" << (jol ? json_object_get_string(jol) : "") << "</option>";
                                }
                                out << "</select>";
                            }else{
                                out << "<input class=\"set-input\" type=\"" << rftype << "\" name=\"" << rfname << "\" value=\"" << rfvalue << "\" />";
                            }
                            out << "</td>";
                        }
                    }
                    if(jractions){
                        out << "<td class=\"set-actions\">";
                        int alen = json_object_array_length(jractions);
                        for(int a=0; a<alen; ++a){
                            json_object *jact = json_object_array_get_idx(jractions, a);
                            json_object *jal=nullptr, *jau=nullptr;
                            json_object_object_get_ex(jact, "label", &jal);
                            json_object_object_get_ex(jact, "url", &jau);
                            if(jal && jau){
                                out << "<a class=\"set-btn-sm\" href=\"" << json_object_get_string(jau) << "\">"
                                    << json_object_get_string(jal) << "</a> ";
                            }
                        }
                        out << "</td>";
                    }
                    out << "</tr>";
                }
                out << "</tbody>";
            }
            out << "</table>";
        }

        // Submit button
        json_object *jsubmit = nullptr;
        if(json_object_object_get_ex(jform, "submit", &jsubmit)){
            out << "<div class=\"set-actions\" style=\"display:flex;gap:8px;margin-top:12px;\">";
            out << "<button class=\"set-btn set-btn-primary\" type=\"submit\">" << json_object_get_string(jsubmit) << "</button>";

            // Optional reset-to-defaults button
            json_object *jresetName = nullptr, *jresetLabel = nullptr;
            if(json_object_object_get_ex(jform, "reset_name", &jresetName) &&
               json_object_object_get_ex(jform, "reset_label", &jresetLabel)){
                const char *rname = json_object_get_string(jresetName);
                const char *rlabel = json_object_get_string(jresetLabel);
                out << "<button class=\"set-btn\" type=\"submit\" name=\"" << rname << "\" value=\"1\""
                    << " onclick=\"return confirm('Reset all values to defaults?');\">"
                    << rlabel << "</button>";
            }
            out << "</div>";
        }

        out << "</form>";
    }

    // Raw HTML passthrough (for media previews etc.)
    json_object *jhtml = nullptr;
    if(json_object_object_get_ex(jroot, "html", &jhtml)){
        out << "<div class=\"set-html\">" << json_object_get_string(jhtml) << "</div>";
    }

    // Render item arrays as tables (items, protocols, videos, etc.)
    auto renderItemArray = [&](const char *key, const char *heading){
        json_object *jarr = nullptr;
        if(!json_object_object_get_ex(jroot, key, &jarr)) return;
        if(json_object_get_type(jarr) != json_type_array) return;
        int alen = json_object_array_length(jarr);
        if(alen < 1) return;

        if(heading) out << "<h4 class=\"set-heading\">" << heading << "</h4>";
        out << "<table class=\"set-table\"><thead><tr>";

        // Collect headers from first item's keys
        json_object *first = json_object_array_get_idx(jarr, 0);
        std::vector<std::string> cols;
        json_object_object_foreach(first, k, v){
            cols.push_back(k);
            out << "<th>" << k << "</th>";
        }
        out << "</tr></thead><tbody>";
        for(int i = 0; i < alen; ++i){
            json_object *jrow = json_object_array_get_idx(jarr, i);
            out << "<tr>";
            for(auto &c : cols){
                json_object *jval = nullptr;
                json_object_object_get_ex(jrow, c.c_str(), &jval);
                out << "<td>" << (jval ? json_object_get_string(jval) : "") << "</td>";
            }
            out << "</tr>";
        }
        out << "</tbody></table>";
    };

    renderItemArray("protocols", "Protocols");
    renderItemArray("items", "Items");
    renderItemArray("videos", "Videos");

    // Render additional named forms (delete_form, sync_form, protocol_form, delete_protocol_form, etc.)
    auto renderExtraForm = [&](const char *key){
        json_object *jxform = nullptr;
        if(!json_object_object_get_ex(jroot, key, &jxform)) return;

        json_object *jmethod=nullptr, *jaction=nullptr;
        json_object_object_get_ex(jxform, "method", &jmethod);
        json_object_object_get_ex(jxform, "action", &jaction);

        out << "<form class=\"set-form\" style=\"margin-top:16px;\" method=\""
            << (jmethod ? json_object_get_string(jmethod) : "POST") << "\"";
        if(jaction) out << " action=\"" << json_object_get_string(jaction) << "\"";
        out << ">";

        json_object *jfields = nullptr;
        if(json_object_object_get_ex(jxform, "fields", &jfields)){
            int flen = json_object_array_length(jfields);
            for(int f=0; f<flen; ++f){
                json_object *jf = json_object_array_get_idx(jfields, f);
                json_object *jname=nullptr, *jtype=nullptr, *jlabel=nullptr, *jvalue=nullptr;
                json_object_object_get_ex(jf, "name", &jname);
                json_object_object_get_ex(jf, "type", &jtype);
                json_object_object_get_ex(jf, "label", &jlabel);
                json_object_object_get_ex(jf, "value", &jvalue);
                const char *type = jtype ? json_object_get_string(jtype) : "text";
                const char *name = jname ? json_object_get_string(jname) : "";
                const char *value = jvalue ? json_object_get_string(jvalue) : "";
                if(strcmp(type,"hidden")==0){
                    out << "<input type=\"hidden\" name=\"" << name << "\" value=\"" << value << "\" />";
                    continue;
                }
                out << "<div class=\"set-field\">";
                if(jlabel) out << "<label class=\"set-label\">" << json_object_get_string(jlabel) << "</label>";
                out << "<input class=\"set-input\" type=\"" << type << "\" name=\"" << name << "\" value=\"" << value << "\" />";
                out << "</div>";
            }
        }
        json_object *jsubmit = nullptr;
        if(json_object_object_get_ex(jxform, "submit", &jsubmit)){
            out << "<div class=\"set-actions\" style=\"margin-top:8px;\">"
                << "<button class=\"set-btn\" type=\"submit\">" << json_object_get_string(jsubmit) << "</button>"
                << "</div>";
        }
        out << "</form>";
    };

    renderExtraForm("protocol_form");
    renderExtraForm("delete_protocol_form");
    renderExtraForm("delete_form");
    renderExtraForm("sync_form");

    // Standalone links (action buttons like "New Navigation")
    json_object *jlinks = nullptr;
    if(json_object_object_get_ex(jroot, "links", &jlinks)){
        out << "<div class=\"set-link-bar\">";
        int llen = json_object_array_length(jlinks);
        for(int i=0; i<llen; ++i){
            json_object *jlink = json_object_array_get_idx(jlinks, i);
            json_object *jlabel=nullptr, *jurl=nullptr;
            json_object_object_get_ex(jlink, "label", &jlabel);
            json_object_object_get_ex(jlink, "url", &jurl);
            if(jlabel && jurl){
                out << "<a class=\"set-btn set-btn-primary\" href=\"" << json_object_get_string(jurl) << "\">"
                    << json_object_get_string(jlabel) << "</a> ";
            }
        }
        out << "</div>";
    }
}

void blogi::Blogi::settingsApi(libhttppp::HttpRequest& curreq,const int tid,const std::string &sessiondid){
    if(!PlgArgs->auth->isLoggedIn(tid,curreq,sessiondid)){
        libhttppp::HttpResponse resp;
        // Clear stale auth cookie
        libhttppp::HttpCookie cookclr;
        cookclr.setcookie(resp,"authid","","",cookieDomain(curreq).c_str(),-1,"/");
        resp.setState(HTTP401);
        resp.setContentType("application/json");
        const char *err = "{\"success\":false,\"error\":\"not logged in\"}";
        resp.send(curreq,err,strlen(err));
        return;
    }

    std::string apipath = curreq.getRequestURL().substr(PlgArgs->config->buildurl("settings/api/").length());

    // Remove trailing slash
    if(!apipath.empty() && apipath.back()=='/') apipath.pop_back();

    json_object *jresponse = json_object_new_object();

    if(apipath == "nav"){
        // Return list of settings sections
        json_object *jnav = json_object_new_array();
        json_object *jmail = json_object_new_object();
        json_object_object_add(jmail,"id",json_object_new_string("mail"));
        json_object_object_add(jmail,"name",json_object_new_string("Mail"));
        json_object_array_add(jnav,jmail);

        for(const blogi::Plugin::PluginData *curplg=BlogiPlg->getFirstPlugin(); curplg; curplg=curplg->getNextPlg()){
            if(curplg->getInstace()->haveSettings()){
                json_object *jplg = json_object_new_object();
                json_object_object_add(jplg,"id",json_object_new_string(curplg->getInstace()->getName().c_str()));
                json_object_object_add(jplg,"name",json_object_new_string(curplg->getInstace()->getName().c_str()));
                json_object_array_add(jnav,jplg);
            }
        }
        json_object_object_add(jresponse,"success",json_object_new_boolean(1));
        json_object_object_add(jresponse,"nav",jnav);
    }else if(apipath == "media/albums" || apipath == "media/images"){
        // Proxy media commands to the media plugin via its JsonApi
        std::string authid;
        try {
            Session sess(*PlgArgs->database[tid]);
            sess.getSessionData(sessiondid, "authid", authid);
        } catch(...) {}

        json_object *jreq = json_object_new_object();
        json_object *jresp = json_object_new_object();

        if(apipath == "media/albums"){
            json_object_object_add(jreq, "command", json_object_new_string("media_list_albums"));
        } else {
            json_object_object_add(jreq, "command", json_object_new_string("media_list_media"));
            // Parse album_id from query string via getRequest()
            std::string fullReq = curreq.getRequest();
            size_t qp = fullReq.find('?');
            if(qp != std::string::npos){
                std::string qs = fullReq.substr(qp + 1);
                size_t ep = qs.find("album_id=");
                if(ep != std::string::npos){
                    std::string aid = qs.substr(ep + 9);
                    size_t amp = aid.find('&');
                    if(amp != std::string::npos) aid = aid.substr(0, amp);
                    json_object_object_add(jreq, "album_id", json_object_new_string(aid.c_str()));
                }
            }
        }

        uuid::uuid uid(authid.c_str());
        for(const blogi::Plugin::PluginData *curplg=BlogiPlg->getFirstPlugin(); curplg; curplg=curplg->getNextPlg()){
            curplg->getInstace()->JsonApi(tid, jreq, jresp, uid);
            if(json_object_object_get(jresp, "status") != nullptr)
                break;
        }

        json_object_object_add(jresponse,"success",json_object_new_boolean(1));
        // Merge plugin response into our response
        json_object_object_foreach(jresp, k, v){
            json_object_object_add(jresponse, k, json_object_get(v));
        }
        json_object_put(jreq);
        json_object_put(jresp);
    }else{
        json_object_object_add(jresponse,"success",json_object_new_boolean(0));
        json_object_object_add(jresponse,"error",json_object_new_string("unknown api endpoint"));
    }

    size_t outsize = 0;
    const char *out = json_object_to_json_string_length(jresponse,0,&outsize);

    libhttppp::HttpResponse resp;
    resp.setState(HTTP200);
    resp.setContentType("application/json");
    resp.send(curreq,out,outsize);

    json_object_put(jresponse);
}

static json_object* SettingsLanguage(dbpp::Database &db, libhttppp::HttpRequest &req){
    std::string lang = "en";
    bool saved = false;

    libhttppp::HttpForm langform;
    langform.parse(req);

    for(const auto &cur : langform.urlData()){
        if(cur.key == "language"){
            blogi::setConfig(db, "LANGUAGE", cur.value);
            saved = true;
        }
    }

    try {
        blogi::getConfig(db, "LANGUAGE", lang);
    } catch(...){
        lang = "en";
    }

    json_object *jroot = json_object_new_object();
    json_object_object_add(jroot, "title", json_object_new_string(blogi::tr(lang,"Language").c_str()));

    if(saved){
        json_object_object_add(jroot, "message", json_object_new_string(blogi::tr(lang,"Saved!").c_str()));
        json_object_object_add(jroot, "message_type", json_object_new_string("success"));
    }

    json_object *jform = json_object_new_object();
    json_object_object_add(jform, "method", json_object_new_string("POST"));
    json_object_object_add(jform, "action", json_object_new_string(req.getRequestURL().c_str()));

    json_object *jfields = json_object_new_array();

    // Language selector
    {
        json_object *jf = json_object_new_object();
        json_object_object_add(jf, "name", json_object_new_string("language"));
        json_object_object_add(jf, "label", json_object_new_string("Language / Sprache"));
        json_object_object_add(jf, "type", json_object_new_string("select"));
        json_object_object_add(jf, "value", json_object_new_string(lang.c_str()));
        json_object *opts = json_object_new_array();

        json_object *o1 = json_object_new_object();
        json_object_object_add(o1, "value", json_object_new_string("de"));
        json_object_object_add(o1, "label", json_object_new_string("Deutsch"));
        if(lang == "de") json_object_object_add(o1, "selected", json_object_new_boolean(1));
        json_object_array_add(opts, o1);

        json_object *o2 = json_object_new_object();
        json_object_object_add(o2, "value", json_object_new_string("en"));
        json_object_object_add(o2, "label", json_object_new_string("English"));
        if(lang == "en") json_object_object_add(o2, "selected", json_object_new_boolean(1));
        json_object_array_add(opts, o2);

        json_object_object_add(jf, "options", opts);
        json_object_array_add(jfields, jf);
    }

    json_object_object_add(jform, "fields", jfields);
    json_object_object_add(jform, "submit", json_object_new_string(blogi::tr(lang,"save").c_str()));
    json_object_object_add(jroot, "form", jform);

    return jroot;
}

void blogi::Blogi::Settings(libhttppp::HttpRequest& curreq,const int tid,const std::string &sessiondid,libhtmlpp::HtmlElement &index){
    libhttppp::HttpCookie cookie;
    cookie.parse(curreq);

    if(!PlgArgs->auth->isLoggedIn(tid,curreq,sessiondid)){
        libhttppp::HTTPException err;
        err[libhttppp::HTTPException::Error] << "you are not logged in permission denied";
        throw err;
    }

    // Route API requests to settingsApi
    if(curreq.getRequestURL().compare(0,PlgArgs->config->buildurl("settings/api/").length(),
       PlgArgs->config->buildurl("settings/api/"))==0){
        settingsApi(curreq,tid,sessiondid);
        return;
    }

    // Check for AJAX request via X-Requested-With header
    bool isAjax = false;
    libhttppp::HttpHeader::HeaderData *xrw = curreq.getHeaderData("x-requested-with");
    if(xrw){
        for(libhttppp::HttpHeader::HeaderData::Values *cval=xrw->getfirstValue(); cval; cval=cval->nextvalue()){
            std::string val = cval->getvalue();
            if(val.find("XMLHttpRequest") != std::string::npos){
                isAjax = true;
                break;
            }
        }
    }

    if(isAjax){
      try {
        // Get JSON form descriptor from plugin's Settings() method
        json_object *jformdata = nullptr;
        std::string url = curreq.getRequestURL();

        try {
        if(url.compare(0, PlgArgs->config->buildurl("settings/mail").length(),
           PlgArgs->config->buildurl("settings/mail")) == 0){
            jformdata = SettingsMail(*PlgArgs->database[tid], curreq, sessiondid);
        }else if(url.compare(0, PlgArgs->config->buildurl("settings/theme").length(),
           PlgArgs->config->buildurl("settings/theme")) == 0){
            jformdata = PlgArgs->theme->SettingsTheme(tid, curreq, sessiondid);
        }else if(url.compare(0, PlgArgs->config->buildurl("settings/language").length(),
           PlgArgs->config->buildurl("settings/language")) == 0){
            jformdata = SettingsLanguage(*PlgArgs->database[tid], curreq);
        }else{
            bool found = false;
            for(const blogi::Plugin::PluginData *curplg=BlogiPlg->getFirstPlugin(); curplg; curplg=curplg->getNextPlg()){
                if(curplg->getInstace()->haveSettings()){
                    std::string prefix = PlgArgs->config->buildurl("settings/" + curplg->getInstace()->getName());
                    if(url.compare(0, prefix.length(), prefix) == 0){
                        try {
                            jformdata = curplg->getInstace()->Settings(tid, curreq, sessiondid);
                        } catch(std::exception &e) {
                            std::cerr << "settings: plugin " << curplg->getInstace()->getName()
                                      << " threw: " << e.what() << std::endl;
                            jformdata = json_object_new_object();
                            json_object_object_add(jformdata, "message", json_object_new_string(e.what()));
                            json_object_object_add(jformdata, "message_type", json_object_new_string("error"));
                        } catch(...) {
                            std::cerr << "settings: plugin " << curplg->getInstace()->getName()
                                      << " threw unknown exception" << std::endl;
                            jformdata = json_object_new_object();
                            json_object_object_add(jformdata, "message", json_object_new_string("internal error"));
                            json_object_object_add(jformdata, "message_type", json_object_new_string("error"));
                        }
                        found = true;
                        break;
                    }
                }
            }
            if(!found){
                jformdata = nullptr;
            }
        }
        } catch(libhttppp::HTTPException &e) {
            std::cerr << "settings: handler threw HTTPException: " << e.what() << std::endl;
            jformdata = json_object_new_object();
            json_object_object_add(jformdata, "message", json_object_new_string(e.what().c_str()));
            json_object_object_add(jformdata, "message_type", json_object_new_string("error"));
        } catch(std::exception &e) {
            std::cerr << "settings: handler threw: " << e.what() << std::endl;
            jformdata = json_object_new_object();
            json_object_object_add(jformdata, "message", json_object_new_string(e.what()));
            json_object_object_add(jformdata, "message_type", json_object_new_string("error"));
        } catch(...) {
            std::cerr << "settings: handler threw unknown exception" << std::endl;
            jformdata = json_object_new_object();
            json_object_object_add(jformdata, "message", json_object_new_string("internal error"));
            json_object_object_add(jformdata, "message_type", json_object_new_string("error"));
        }

        // Render JSON form descriptor to HTML
        std::string _lang = blogi::getLang(*PlgArgs->database[tid]);
        libhtmlpp::HtmlString setcontent;
        renderSettingsJson(jformdata, setcontent, _lang);

        if(jformdata)
            json_object_put(jformdata);

        // Return HTML as JSON
        json_object *jres = json_object_new_object();
        json_object_object_add(jres, "success", json_object_new_boolean(1));
        json_object_object_add(jres, "html", json_object_new_string(setcontent.c_str()));

        size_t outsize = 0;
        const char *out = json_object_to_json_string_length(jres, 0, &outsize);

        libhttppp::HttpResponse resp;
        resp.setState(HTTP200);
        resp.setContentType("application/json");
        resp.send(curreq, out, outsize);

        json_object_put(jres);
        return;
      } catch(libhttppp::HTTPException &e) {
        std::cerr << "settings AJAX: " << e.what() << std::endl;
        json_object *jerr = json_object_new_object();
        json_object_object_add(jerr, "success", json_object_new_boolean(0));
        json_object_object_add(jerr, "html",
            json_object_new_string(
                (std::string("<div class=\"set-msg set-msg-err\">") + e.what() + "</div>").c_str()));
        size_t elen = 0;
        const char *eout = json_object_to_json_string_length(jerr, 0, &elen);
        libhttppp::HttpResponse eresp;
        eresp.setState(HTTP200);
        eresp.setContentType("application/json");
        eresp.send(curreq, eout, elen);
        json_object_put(jerr);
        return;
      } catch(std::exception &e) {
        std::cerr << "settings AJAX: " << e.what() << std::endl;
        json_object *jerr = json_object_new_object();
        json_object_object_add(jerr, "success", json_object_new_boolean(0));
        json_object_object_add(jerr, "html",
            json_object_new_string(
                (std::string("<div class=\"set-msg set-msg-err\">") + e.what() + "</div>").c_str()));
        size_t elen = 0;
        const char *eout = json_object_to_json_string_length(jerr, 0, &elen);
        libhttppp::HttpResponse eresp;
        eresp.setState(HTTP200);
        eresp.setContentType("application/json");
        eresp.send(curreq, eout, elen);
        json_object_put(jerr);
        return;
      } catch(...) {
        std::cerr << "settings AJAX: unknown exception" << std::endl;
        json_object *jerr = json_object_new_object();
        json_object_object_add(jerr, "success", json_object_new_boolean(0));
        json_object_object_add(jerr, "html",
            json_object_new_string("<div class=\"set-msg set-msg-err\">Unknown error in settings handler</div>"));
        size_t elen = 0;
        const char *eout = json_object_to_json_string_length(jerr, 0, &elen);
        libhttppp::HttpResponse eresp;
        eresp.setState(HTTP200);
        eresp.setContentType("application/json");
        eresp.send(curreq, eout, elen);
        json_object_put(jerr);
        return;
      }
    }

    // Non-AJAX: render modal settings UI
    std::string settingsBase = PlgArgs->config->buildurl("settings/");
    std::string _lang = blogi::getLang(*PlgArgs->database[tid]);

    libhtmlpp::HtmlString out;
    libhtmlpp::HtmlString setgui;

    // ── CSS ──
    setgui << "<style>"
    // Overlay
    << ".set-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.6);z-index:1000;"
    <<   "display:flex;align-items:center;justify-content:center;backdrop-filter:blur(0.2em);}"
    // Modal
    << ".set-modal{background:var(--blogi-bg-alt);border:0.06em solid var(--blogi-border);border-radius:0.75em;"
    <<   "box-shadow:0 1.5em 5em rgba(0,0,0,0.5);width:92vw;max-width:70em;height:85vh;display:flex;"
    <<   "overflow:hidden;animation:set-fadein .2s ease-out;}"
    << "@keyframes set-fadein{from{opacity:0;transform:scale(0.96)}to{opacity:1;transform:scale(1)}}"
    // Sidebar
    << ".set-sidebar{width:13em;min-width:13em;background:var(--blogi-bg);border-right:0.06em solid var(--blogi-border);"
    <<   "display:flex;flex-direction:column;}"
    << ".set-sidebar-head{padding:1em 1.25em;font-size:1.1em;font-weight:700;color:var(--blogi-text);"
    <<   "border-bottom:0.06em solid var(--blogi-border);display:flex;align-items:center;gap:0.5em;}"
    << ".set-sidebar-head svg{width:1.125em;height:1.125em;fill:var(--blogi-accent);}"
    << ".set-nav{list-style:none;padding:0.5em;margin:0;flex:1;overflow-y:auto;}"
    << ".set-nav li{padding:0.625em 0.875em;border-radius:0.375em;cursor:pointer;color:var(--blogi-text-muted);"
    <<   "font-size:0.95em;transition:all .15s ease;margin-bottom:0.125em;}"
    << ".set-nav li:hover{background:var(--blogi-surface);color:var(--blogi-text);}"
    << ".set-nav li.active{background:var(--blogi-accent);color:var(--blogi-bg);font-weight:600;}"
    // Close button area
    << ".set-sidebar-foot{padding:0.75em 1.25em;border-top:0.06em solid var(--blogi-border);}"
    << ".set-close{display:block;text-align:center;padding:0.5em;border-radius:0.375em;color:var(--blogi-text-muted);"
    <<   "cursor:pointer;text-decoration:none;transition:all .15s ease;font-size:0.9em;}"
    << ".set-close:hover{background:var(--blogi-surface);color:var(--blogi-text);}"
    // Content area
    << ".set-content{flex:1;padding:1.5em 1.75em;overflow-y:auto;color:var(--blogi-text);font-family:sans-serif;}"
    << ".set-content::-webkit-scrollbar{width:0.375em;}"
    << ".set-content::-webkit-scrollbar-thumb{background:var(--blogi-border);border-radius:0.2em;}"
    << ".set-loading{color:var(--blogi-text-muted);padding:2.5em 0;text-align:center;}"
    // Empty state
    << ".set-empty{color:var(--blogi-text-muted);padding:2.5em 0;text-align:center;font-size:0.95em;}"
    // Titles
    << ".set-title{margin:0 0 1em 0;font-size:1.2em;font-weight:600;color:var(--blogi-text);}"
    // Messages
    << ".set-msg{padding:0.625em 0.875em;border-radius:0.375em;margin-bottom:1em;font-size:0.9em;"
    <<   "background:var(--blogi-surface);border-left:0.2em solid var(--blogi-text-muted);}"
    << ".set-msg-ok{border-left-color:var(--blogi-accent);color:var(--blogi-success-text);}"
    << ".set-msg-err{border-left-color:var(--blogi-error);color:var(--blogi-error-text);}"
    // Breadcrumb / back links
    << ".set-breadcrumb{margin-bottom:1em;}"
    << ".set-link{color:var(--blogi-accent);text-decoration:none;font-size:0.9em;transition:color .15s;}"
    << ".set-link:hover{color:var(--blogi-accent);filter:brightness(1.3);}"
    // Tables
    << ".set-table{width:100%;border-collapse:collapse;margin-bottom:1em;}"
    << ".set-table thead th{text-align:left;padding:0.5em 0.625em;font-size:0.85em;font-weight:600;"
    <<   "color:var(--blogi-text-muted);text-transform:uppercase;letter-spacing:0.03em;"
    <<   "border-bottom:0.06em solid var(--blogi-border);}"
    << ".set-table tbody tr{transition:background .1s;}"
    << ".set-table tbody tr:hover{background:var(--blogi-surface);}"
    << ".set-table td{padding:0.5em 0.625em;border-bottom:0.06em solid var(--blogi-bg-alt);font-size:0.95em;}"
    << ".set-actions{white-space:nowrap;}"
    // Buttons
    << ".set-btn{display:inline-block;padding:0.5em 1em;border-radius:0.375em;font-size:0.9em;cursor:pointer;"
    <<   "text-decoration:none;border:0.06em solid var(--blogi-border);color:var(--blogi-text);"
    <<   "background:var(--blogi-surface);transition:all .15s ease;}"
    << ".set-btn:hover{background:var(--blogi-border);color:var(--blogi-text);}"
    << ".set-btn-primary{background:var(--blogi-accent);color:var(--blogi-bg);border-color:var(--blogi-accent);font-weight:600;}"
    << ".set-btn-primary:hover{filter:brightness(1.15);}"
    << ".set-btn-danger{background:var(--blogi-error);color:#fff;border-color:var(--blogi-error);}"
    << ".set-btn-danger:hover{filter:brightness(1.15);}"
    << ".set-btn-sm{display:inline-block;padding:0.25em 0.625em;border-radius:0.25em;font-size:0.85em;"
    <<   "text-decoration:none;color:var(--blogi-accent);border:0.06em solid var(--blogi-border);"
    <<   "transition:all .12s ease;}"
    << ".set-btn-sm:hover{background:var(--blogi-accent);color:var(--blogi-bg);}"
    // Light theme contrast fixes for accent surfaces
    << ":root[data-theme=\"light\"] .set-nav li.active,"
    << ":root[data-theme=\"light\"] .set-btn-primary,"
    << ":root[data-theme=\"light\"] .set-btn-sm:hover{color:var(--blogi-text);}"
    << "@media(prefers-color-scheme:light){:root:not([data-theme=\"dark\"]) .set-nav li.active,"
    << ":root:not([data-theme=\"dark\"]) .set-btn-primary,"
    << ":root:not([data-theme=\"dark\"]) .set-btn-sm:hover{color:var(--blogi-text);}}"
    // Confirm
    << ".set-confirm{background:var(--blogi-surface);border-radius:0.5em;padding:1.25em;margin:1em 0;text-align:center;}"
    << ".set-confirm-msg{margin-bottom:1em;font-size:1em;}"
    << ".set-confirm-btns{display:flex;gap:0.75em;justify-content:center;}"
    // Form
    << ".set-form{margin:0;}"
    << ".set-field{margin-bottom:0.875em;}"
    << ".set-label{display:block;margin-bottom:0.25em;font-size:0.85em;font-weight:600;color:var(--blogi-text-muted);"
    <<   "text-transform:uppercase;letter-spacing:0.025em;}"
    << ".set-input{width:100%;max-width:25em;padding:0.5em 0.75em;border:0.06em solid var(--blogi-border);border-radius:0.375em;"
    <<   "background:var(--blogi-bg);color:var(--blogi-text);font-size:0.95em;box-sizing:border-box;"
    <<   "transition:border-color .15s;outline:none;}"
    << ".set-input:focus{border-color:var(--blogi-accent);}"
    << ".set-textarea{min-height:10em;max-width:100%;resize:vertical;}"
    << "select.set-input{max-width:25em;}"
    // Link bar
    << ".set-link-bar{margin-top:1em;display:flex;gap:0.5em;flex-wrap:wrap;}"
    // Raw HTML area
    << ".set-html{margin:0.75em 0;}"
    // ── Responsive: tablet ──
    << "@media(max-width:48em){"
    <<   ".set-modal{flex-direction:column;width:96vw;height:90vh;max-width:none;border-radius:0.5em;}"
    <<   ".set-sidebar{width:100%;min-width:0;flex-direction:row;flex-wrap:wrap;border-right:none;border-bottom:0.06em solid var(--blogi-border);}"
    <<   ".set-sidebar-head{padding:0.625em 0.875em;font-size:1em;border-bottom:none;}"
    <<   ".set-nav{display:flex;flex-wrap:wrap;gap:0.25em;padding:0.375em 0.5em;overflow-x:auto;overflow-y:hidden;flex:1;}"
    <<   ".set-nav li{padding:0.375em 0.75em;margin:0;white-space:nowrap;font-size:0.85em;}"
    <<   ".set-sidebar-foot{padding:0.5em 0.75em;border-top:none;border-left:0.06em solid var(--blogi-border);}"
    <<   ".set-close{padding:0.375em 0.5em;font-size:0.85em;}"
    <<   ".set-content{padding:1em;}"
    <<   ".set-input{max-width:100%;}"
    <<   "select.set-input{max-width:100%;}"
    << "}"
    // ── Responsive: mobile ──
    << "@media(max-width:30em){"
    <<   ".set-overlay{align-items:stretch;}"
    <<   ".set-modal{width:100%;height:100%;border-radius:0;max-width:none;}"
    <<   ".set-content{padding:0.75em;}"
    <<   ".set-title{font-size:1.05em;}"
    <<   ".set-table{font-size:0.85em;}"
    <<   ".set-table td,.set-table th{padding:0.375em;}"
    <<   ".set-btn{padding:0.375em 0.75em;font-size:0.85em;}"
    <<   ".set-btn-sm{padding:0.2em 0.5em;font-size:0.8em;}"
    <<   ".set-confirm-btns{flex-direction:column;}"
    << "}"
    << "</style>";

    // ── HTML structure ──
    setgui << "<div class=\"set-overlay\" id=\"set-overlay\">"
    << "<div class=\"set-modal\">"
    << "<div class=\"set-sidebar\">"
    <<   "<div class=\"set-sidebar-head\">"
    <<     "<svg viewBox=\"0 0 24 24\"><path d=\"M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 00.12-.61l-1.92-3.32a.49.49 0 00-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 00-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96a.49.49 0 00-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.63-.07.94s.02.64.07.94l-2.03 1.58a.49.49 0 00-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6A3.6 3.6 0 1115.6 12 3.611 3.611 0 0112 15.6z\"/></svg>"
    <<     blogi::tr(_lang,"Settings")
    <<   "</div>"
    <<   "<ul class=\"set-nav\" id=\"set-nav\">"
    <<     "<li data-url=\"" << PlgArgs->config->buildurl("settings/mail") << "\">Mail</li>"
    <<     "<li data-url=\"" << PlgArgs->config->buildurl("settings/theme") << "\">Theme</li>"
    <<     "<li data-url=\"" << PlgArgs->config->buildurl("settings/language") << "\">" << blogi::tr(_lang,"Language") << "</li>";

    for(const blogi::Plugin::PluginData *curplg=BlogiPlg->getFirstPlugin(); curplg; curplg=curplg->getNextPlg()){
        if(curplg->getInstace()->haveSettings()){
            setgui << "<li data-url=\"" << PlgArgs->config->buildurl("settings/" + curplg->getInstace()->getName())
                   << "\">" << curplg->getInstace()->getName() << "</li>";
            // Extra sub-tabs from plugin
            json_object *tabs = curplg->getInstace()->getSettingsTabs();
            if(tabs){
                int tlen = json_object_array_length(tabs);
                for(int t = 0; t < tlen; ++t){
                    json_object *tab = json_object_array_get_idx(tabs, t);
                    json_object *jlabel = nullptr, *jurl = nullptr;
                    json_object_object_get_ex(tab, "label", &jlabel);
                    json_object_object_get_ex(tab, "url", &jurl);
                    if(jlabel && jurl){
                        setgui << "<li data-url=\"" << json_object_get_string(jurl)
                               << "\">" << json_object_get_string(jlabel) << "</li>";
                    }
                }
                json_object_put(tabs);
            }
        }
    }

    setgui << "</ul>"
    <<   "<div class=\"set-sidebar-foot\">"
    <<     "<a class=\"set-close\" href=\"" << PlgArgs->config->getstartpage() << "\">&#10005; " << blogi::tr(_lang,"Close") << "</a>"
    <<   "</div>"
    << "</div>"
    << "<div class=\"set-content\" id=\"set-content\">"
    <<   "<div class=\"set-empty\">" << blogi::tr(_lang,"Select a section") << "</div>"
    << "</div>"
    << "</div></div>";

    // ── JavaScript ──
    setgui << "<script>"

    << "var SETTINGS_BASE='" << settingsBase << "';"
    << "function runScripts(el){el.querySelectorAll('script').forEach(function(os){var ns=document.createElement('script');ns.textContent=os.textContent;os.parentNode.replaceChild(ns,os);});};"
    << "var _sectionUrl='';"

    // Load section via AJAX
    << "function loadSection(url){"
    <<   "_sectionUrl=url;"
    <<   "document.querySelectorAll('#set-nav li').forEach(function(el){el.classList.toggle('active',el.dataset.url===url);});"
    <<   "document.getElementById('set-content').innerHTML='<div class=\"set-loading\">" << blogi::tr(_lang,"Loading...") << "</div>';"
    <<   "fetch(url,{headers:{'X-Requested-With':'XMLHttpRequest'}})"
    <<     ".then(function(r){return r.json();})"
    <<     ".then(function(d){if(d.html){var c=document.getElementById('set-content');c.innerHTML=d.html;runScripts(c);}})"
    <<     ".catch(function(e){document.getElementById('set-content').innerHTML='<div class=\"set-msg set-msg-err\">" << blogi::tr(_lang,"Error:") << " '+e+'</div>';});"
    << "}"

    // Nav click handlers
    << "document.querySelectorAll('#set-nav li').forEach(function(li){"
    <<   "li.onclick=function(){ loadSection(this.dataset.url); };"
    << "});"

    // Intercept link clicks in content
    << "document.getElementById('set-content').addEventListener('click',function(ev){"
    <<   "var a=ev.target.closest('a');"
    <<   "if(!a) return;"
    <<   "var href=a.getAttribute('href');"
    <<   "if(!href||href.indexOf(SETTINGS_BASE)===-1) return;"
    <<   "ev.preventDefault();"
    <<   "_sectionUrl=href;"
    <<   "document.getElementById('set-content').innerHTML='<div class=\"set-loading\">" << blogi::tr(_lang,"Loading...") << "</div>';"
    <<   "fetch(href,{headers:{'X-Requested-With':'XMLHttpRequest'}})"
    <<     ".then(function(r){return r.json();})"
    <<     ".then(function(d){if(d.html){var c=document.getElementById('set-content');c.innerHTML=d.html;runScripts(c);}})"
    <<     ".catch(function(e){document.getElementById('set-content').innerHTML='<div class=\"set-msg set-msg-err\">" << blogi::tr(_lang,"Error:") << " '+e+'</div>';});"
    << "});"

    // Intercept form submissions in content
    << "document.getElementById('set-content').addEventListener('submit',function(ev){"
    <<   "var form=ev.target;"
    <<   "if(form.tagName!=='FORM') return;"
    <<   "ev.preventDefault();"
    <<   "var method=(form.method||'GET').toUpperCase();"
    <<   "var action=form.getAttribute('action')||_sectionUrl;"
    <<   "var opts={method:method,headers:{'X-Requested-With':'XMLHttpRequest'}};"
    <<   "var fd=new FormData(form);"
    <<   "if(ev.submitter&&ev.submitter.name){fd.append(ev.submitter.name,ev.submitter.value);}"
    <<   "if(method==='POST'){"
    <<     "if(form.enctype==='multipart/form-data'){"
    <<       "opts.body=fd;"
    <<     "}else{"
    <<       "opts.headers['Content-Type']='application/x-www-form-urlencoded';"
    <<       "opts.body=new URLSearchParams(fd).toString();"
    <<     "}"
    <<   "}else{"
    <<     "var params=new URLSearchParams(fd).toString();"
    <<     "if(params) action+=(action.indexOf('?')!==-1?'&':'?')+params;"
    <<   "}"
    <<   "_sectionUrl=action;"
    <<   "fetch(action,opts)"
    <<     ".then(function(r){return r.json();})"
    <<     ".then(function(d){if(d.html){var c=document.getElementById('set-content');c.innerHTML=d.html;runScripts(c);}})"
    <<     ".catch(function(e){document.getElementById('set-content').innerHTML='<div class=\"set-msg set-msg-err\">" << blogi::tr(_lang,"Error:") << " '+e+'</div>';});"
    << "});"

    // Close on overlay background click
    << "document.getElementById('set-overlay').addEventListener('click',function(ev){"
    <<   "if(ev.target===this) window.location.href='" << PlgArgs->config->getstartpage() << "';"
    << "});"

    // Close on Escape key
    << "document.addEventListener('keydown',function(ev){"
    <<   "if(ev.key==='Escape') window.location.href='" << PlgArgs->config->getstartpage() << "';"
    << "});"

    // Auto-load first section
    << "var firstLi=document.querySelector('#set-nav li');"
    << "if(firstLi) loadSection(firstLi.dataset.url);"

    << "</script>";

    index.getElementbyID("main")->appendChild(setgui.parse());

    for(const blogi::Plugin::PluginData *curplg=BlogiPlg->getFirstPlugin(); curplg; curplg=curplg->getNextPlg()){
        if(curreq.isMobile())
            curplg->getInstace()->Rendering(tid,curreq,&index,RenderingType::Mobile);
        else
            curplg->getInstace()->Rendering(tid,curreq,&index,RenderingType::Desktop);
    }

    PlgArgs->theme->printSite(tid,out,&index,curreq.getRequestURL(),true);
    libhttppp::HttpResponse curres;
    curres.setState(HTTP200);
    curres.setContentType("text/html");
    sendCompressed(curreq,curres,out.c_str(),out.length());
}

void blogi::Blogi::searchPage(libhttppp::HttpRequest& curreq, const int tid, const std::string &sessionid,libhtmlpp::HtmlElement &index){
    std::string word,searchstr;
    SearchRet sdat;

    size_t wlen =PlgArgs->config->buildurl("search/").length();

    if(wlen<curreq.getRequestURL().length()){

        std::copy(curreq.getRequestURL().begin()+wlen,curreq.getRequestURL().end(),
                    std::back_inserter(word));

        libhttppp::HttpForm form;

        form.urlDecode(word,searchstr);

        if(!searchstr.empty()){
            for(const blogi::Plugin::PluginData *curplg=BlogiPlg->getFirstPlugin(); curplg; curplg=curplg->getNextPlg()){
                PluginApi *api=curplg->getInstace();
                api->Search(tid,searchstr.data(),sdat,sessionid);
            }
        }
    }

    for(const blogi::Plugin::PluginData *curplg=BlogiPlg->getFirstPlugin(); curplg; curplg=curplg->getNextPlg()){
        if(curreq.isMobile())
            curplg->getInstace()->Rendering(tid,curreq,&index,RenderingType::Mobile);
        else
            curplg->getInstace()->Rendering(tid,curreq,&index,RenderingType::Desktop);
    }

    libhtmlpp::HtmlString search,out;

    search  << "<ul id=\"searchresult\" >"
            << "<li>"
            << "<input type=\"text\" name=\"searchbox\" id=\"searchbox\" value=\"";
    if(!searchstr.empty())
        search  << searchstr;
    search  << "\"><label for=\"searchbox\" style=\"display:none;\">search</label>"
            << "<button id=\"searchbttn\">Search</button>"
            << "<script>  document.getElementById(\"searchbttn\").addEventListener(\"click\", function (e) { "
            << "window.location.href = '"<<PlgArgs->config->buildurl("search/")<<"'+encodeURIComponent(document.getElementById('searchbox').value.trim()); });</script>"
            << "</li>";


    for(SearchRet::Data *cdat=sdat.getfirstData(); cdat; cdat=cdat->nextData()){
        search << "<li>"
               << "<a href=\"" << cdat->anchor << "\" >" << cdat->title << "</a>"
               << "<p> ";
        std::copy(cdat->text.begin(),cdat->text.end(),std::back_inserter(search));
        search << "</p>"
               << "</li>";
    }

    search << "</ul>";

    index.getElementbyID("main")->appendChild(search.parse());

    PlgArgs->theme->printSite(tid,out,&index,curreq.getRequestURL(),PlgArgs->auth->isLoggedIn(tid,curreq,sessionid));

    libhttppp::HttpResponse curres;
    curres.setState(HTTP200);
    curres.setContentType("text/html");
    sendCompressed(curreq,curres,out.c_str(),out.length());

}

void blogi::Blogi::sitemapXML(libhttppp::HttpRequest& curreq, const int tid, const std::string &sessionid){
    std::map<libhttppp::HttpUrl,std::chrono::year_month_day> sitemap;
    for(const blogi::Plugin::PluginData *curplg=BlogiPlg->getFirstPlugin(); curplg; curplg=curplg->getNextPlg()){
            curplg->getInstace()->GetSiteMap(tid,sitemap);
    }

    auto zero_date = std::chrono::year(1970) / std::chrono::month(1) / std::chrono::day(1);
    std::stringstream smap;

    smap << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
              << "<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">";
    for(const auto map : sitemap){
         smap << "<url><loc>" << map.first.print() << "</loc>";
         if(map.second!=zero_date)
                smap << "<lastmod>"<< map.second <<"</lastmod>";
        smap << "</url>";
    }
    smap << "</urlset>";

    libhttppp::HttpResponse curres;
    curres.setState(HTTP200);
    curres.setContentType("text/xml");
    curres.send(curreq,smap.str());
};

void blogi::Blogi::RequestEvent(libhttppp::HttpRequest &curreq,const int tid,ULONG_PTR args){
    try{;
        libhttppp::HttpHeader::HeaderData *hip=curreq.getHeaderData("x-real-ip");
        libhttppp::HttpHeader::HeaderData *useragent=curreq.getHeaderData("user-agent");

        if(hip)
            std::cout <<"Request from: " << hip->at(0).getvalue() << " url: " << curreq.getRequestURL();
        if(useragent){
            std::cout << " agent: ";
            for(libhttppp::HttpHeader::HeaderData::Values *cval=useragent->getfirstValue(); cval; cval=cval->nextvalue()){
                std::cout << cval->getvalue() << " ";
            }
        }
        if(hip || useragent)
            std::cout << std::endl;
        int retries=0;
RETRY_REQUEST:
         if (curreq.getRequestURL().rfind("robots.txt")!=std::string::npos){
                std::string robot = "user-agent: *\r\ndisallow: /blog/settings/";
                libhttppp::HttpResponse resp;
                resp.setState(HTTP200);
                resp.setContentType("text/plain");
                resp.send(curreq,robot,robot.length());
                return;
        }

        libhttppp::HttpCookie cookie;
        libhttppp::HttpResponse curres;
        std::string sessid;
        libhtmlpp::HtmlElement index;
        Session sess(*PlgArgs->database[tid]);

        if(curreq.isMobile())
            index=MIndex;
        else
            index=Index;

        try{
            cookie.parse(curreq);
            for(libhttppp::HttpCookie::CookieData *curcookie=cookie.getfirstCookieData();
                curcookie; curcookie=curcookie->nextCookieData()){
                if(!curcookie->getKey().empty() && curcookie->getKey()=="sessionid")
                    sessid=curcookie->getValue();
            }
            if(!sess.exists(sessid) ){
                throw "session not found !";
            }
        }catch(...){
            libhttppp::HttpForm cookform;
            cookform.parse(curreq);

            bool found=false;

            for (const auto &cururlform : cookform.urlData()) {
                if(cururlform.key==cookieid)
                    found=true;
            }

            if(!found){
                std::string _clang = getLang(*PlgArgs->database[tid]);
                blogi::print(*PlgArgs->database[tid],_clang,curreq.getRequestURL(),index,CookieEl);
            }else{
                std::string buf;

                std::string usa;

                for(libhttppp::HttpHeader::HeaderData::Values *uval=curreq.getHeaderData("user-agent")->getfirstValue(); uval; uval=uval->nextvalue()){
                    usa+=uval->getvalue();
                    usa+="; ";
                }

                sessid=sess.createSession(buf,usa);

                CookieBanner cban(*PlgArgs->database[tid]);

                for (const auto &cururlform : cookform.urlData()) {
                    if(cururlform.value=="on")
                        cban.setPermission(sessid,cururlform.key.c_str(),true);
                    else
                        cban.setPermission(sessid,cururlform.key.c_str(),false);
                }

                cookie.setcookie(curres, "sessionid", sessid,"",cookieDomain(curreq).c_str() ,(1000*60*60*1),"/",false,"1","Lax",false);
                curres.setState(HTTP307);
                curres.setHeaderData("location")->push_back(curreq.getRequestURL());
                curres.setContentType("text/html");
                curres.send(curreq,"", 0);
                return;
            }
        }

        try{
            /*blogi internal pages and redirections*/
            if(curreq.getRequestURL().compare("/")==0 || curreq.getRequestURL().compare(PlgArgs->config->getprefix())==0){
                curres.setState(HTTP307);
                curres.setHeaderData("location")->push_back(PlgArgs->config->getstartpage());
                curres.setContentType("text/html");
                curres.send(curreq,"", 0);
                return;
            }else if(curreq.getRequestURL().compare(0,PlgArgs->config->buildurl("api").length(),PlgArgs->config->buildurl("api"))==0){
                api(curreq,tid);
                return;
            }else if(curreq.getRequestURL().compare(0,PlgArgs->config->buildurl("logout").length(),PlgArgs->config->buildurl("logout"))==0){
                logoutPage(curreq,tid,sessid);
                return;
            }else if(curreq.getRequestURL().compare(0,PlgArgs->config->buildurl("login").length(),PlgArgs->config->buildurl("login"))==0){
                loginPage(curreq,tid,sessid,index);
                return;
            }else if(curreq.getRequestURL().compare(0,PlgArgs->config->buildurl("settings").length(),PlgArgs->config->buildurl("settings"))==0){
                Settings(curreq,tid,sessid,index);
                return;
            }else if(curreq.getRequestURL().compare(0,PlgArgs->config->buildurl("search").length(),PlgArgs->config->buildurl("search"))==0){
                searchPage(curreq,tid,sessid,index);
                return;
            }else if(curreq.getRequestURL().compare(0,PlgArgs->config->buildurl("sitemap.xml").length(),PlgArgs->config->buildurl("sitemap.xml"))==0){
                sitemapXML(curreq,tid,sessid);
                return;
            }else if(curreq.getRequestURL().compare(0,PlgArgs->config->buildurl("render").length(),PlgArgs->config->buildurl("render"))==0){

                libhttppp::HttpForm renderopts;
                renderopts.parse(curreq);

                bool mobile=false;

                std::string publishTarget;
                std::string authid;
                std::string html;
                std::string colorMode;
                std::string colorScheme;
                std::string colorCustom;
                json_object *publishParams = nullptr;

                // Helper lambda to process key/value pairs from either source
                auto processField = [&](const std::string &key, const std::string &value) {
                    if(key=="mobile" && value=="true"){
                        mobile=true;
                    }else if(key=="target"){
                        publishTarget=value;
                    }else if(key=="authid"){
                        authid=value;
                    }else if(key=="html"){
                        html=value;
                    }else if(key=="_color_mode"){
                        colorMode=value;
                    }else if(key=="_color_scheme"){
                        colorScheme=value;
                    }else if(key=="_color_custom"){
                        colorCustom=value;
                    }else if(!key.empty()){
                        if(!publishParams)
                            publishParams = json_object_new_object();
                        json_object_object_add(publishParams, key.c_str(),
                            json_object_new_string(value.c_str()));
                    }
                };

                // Read URL-encoded form data
                for(const auto &curdat : renderopts.urlData()){
                    processField(curdat.key, curdat.value);
                }

                // Read multipart form data (used by Qt editor to avoid %00 encoding issues)
                for(const auto &entry : renderopts.multipartData()){
                    std::string fieldName;
                    for(const auto &dispo : entry.dispositions){
                        if(dispo.key == "name"){
                            fieldName = dispo.value;
                            break;
                        }
                    }
                    if(!fieldName.empty()){
                        std::string fieldValue(entry.value.begin(), entry.value.end());
                        processField(fieldName, fieldValue);
                    }
                }

                // Auth check: try authid from form data first, then cookie
                std::string sessionid;
                bool authenticated = false;
                if(!authid.empty()){
                    authenticated = PlgArgs->auth->isLoggedIn(tid, uuid::uuid(authid));
                }
                if(!authenticated){
                    authenticated = PlgArgs->auth->isLoggedIn(tid, curreq, sessionid);
                }
                if(!authenticated){
                    libhttppp::HTTPException err;
                    err[libhttppp::HTTPException::Error] << "you are not logged in permission denied";
                    throw err;
                }

                if(mobile)
                    index=MIndex;
                else
                    index=Index;

                {
                    libhtmlpp::HtmlString out;

                    // First inject the editor HTML into main (before plugins run,
                    // so the template plugin can wrap it in its layers)
                    libhtmlpp::HtmlElement *root = index.getElementbyID("main");

                    if(!root){
                        std::cerr << "/render: no element with id='main' found in theme index" << std::endl;
                    }

                    if(root && !html.empty()){
                        try {
                            libhtmlpp::HtmlString htmlStr;
                            htmlStr << html.c_str();
                            root->appendChild(htmlStr.parse());
                        } catch(libhtmlpp::HTMLException &e) {
                            std::cerr << "/render: HTML parse error: " << e.what() << std::endl;
                        } catch(const std::exception &e) {
                            std::cerr << "/render: HTML inject error: " << e.what() << std::endl;
                        }
                    }

                    // Run plugin Rendering() for UI-only plugins (navbar, gtm, etc.)
                    // Skip canPublish plugins (template, content, staticpage) — they would
                    // inject their own content into main, duplicating the editor HTML.
                    for(const blogi::Plugin::PluginData *curplg=BlogiPlg->getFirstPlugin(); curplg; curplg=curplg->getNextPlg()){
                        if(curplg->getInstace()->canPublish())
                            continue;
                        try {
                            if(mobile)
                                curplg->getInstace()->Rendering(tid,curreq,&index,RenderingType::Mobile,"");
                            else
                                curplg->getInstace()->Rendering(tid,curreq,&index,RenderingType::Desktop,"");
                        } catch(libhtmlpp::HTMLException &e) {
                            std::cerr << "/render: plugin Rendering error: " << e.what() << std::endl;
                        } catch(const std::exception &e) {
                            std::cerr << "/render: plugin Rendering error: " << e.what() << std::endl;
                        } catch(...) {
                            std::cerr << "/render: plugin Rendering unknown error" << std::endl;
                        }
                    }

                    // If publish preview requested, wrap through the target plugin
                    if (!publishTarget.empty() && BlogiPlg && root) {
                        libhtmlpp::HtmlString rootHtml;
                        libhtmlpp::print(*root, rootHtml);
                        std::string renderedHtml = rootHtml.str();

                        for (const Plugin::PluginData *curplg = BlogiPlg->getFirstPlugin(); curplg; curplg = curplg->getNextPlg()) {
                            PluginApi *api = curplg->getInstace();
                            if (api && api->canPublish() && api->getName() == publishTarget) {
                                api->RenderPublished(tid, renderedHtml, publishParams, index);
                                break;
                            }
                        }
                    }

                    // Apply color mode: set data-theme attribute or inject custom background
                    if(colorMode == "theme" && !colorScheme.empty()){
                        libhtmlpp::HtmlElement *htmlEl = index.getElementbyTag("html");
                        if(htmlEl)
                            htmlEl->setAttribute("data-theme", colorScheme);
                    }else if(colorMode == "custom" && !colorCustom.empty()){
                        libhtmlpp::HtmlElement *body = index.getElementbyTag("body");
                        if(body){
                            std::string existing = body->getAtributte("style");
                            if(!existing.empty())
                                existing += ";";
                            existing += "background:" + colorCustom;
                            body->setAttribute("style", existing);
                        }
                    }

                    PlgArgs->theme->printSite(tid,out,&index,curreq.getRequestURL(),true);

                    libhttppp::HttpResponse curres;
                    curres.setState(HTTP200);
                    curres.setContentType("text/html");
                    curres.send(curreq,out.data(),out.length());
                }
                if(publishParams)
                    json_object_put(publishParams);
                return;
            }


            if(!PlgArgs->theme->Controller(tid,curreq)){
                for(const blogi::Plugin::PluginData *curplg=BlogiPlg->getFirstPlugin(); curplg; curplg=curplg->getNextPlg()){
                    try {
                        if(curreq.isMobile())
                            curplg->getInstace()->Rendering(tid,curreq,&index,RenderingType::Mobile,sessid);
                        else
                            curplg->getInstace()->Rendering(tid,curreq,&index,RenderingType::Desktop,sessid);
                    } catch(libhtmlpp::HTMLException &e) {
                        std::cerr << "Rendering plugin error: " << e.what() << std::endl;
                    } catch(const std::exception &e) {
                        std::cerr << "Rendering plugin error: " << e.what() << std::endl;
                    } catch(...) {
                        std::cerr << "Rendering plugin unknown error" << std::endl;
                    }
                }

                for(const blogi::Plugin::PluginData *curplg=BlogiPlg->getFirstPlugin(); curplg; curplg=curplg->getNextPlg()){
                    PluginApi *api=curplg->getInstace();
                    std::string url= PlgArgs->config->buildurl(api->getName());
                    if(curreq.getRequestURL().compare(0,url.length(),url)==0){
                        std::cerr << url << std::endl;
                        if(api->Controller(tid,curreq,&index,sessid)){
                            return;
                        }
                    }
                }

                libhtmlpp::HtmlString output;
                std::string _lang = blogi::getLang(*PlgArgs->database[tid]);
                libhtmlpp::HtmlString err;
                err << "<div id=\"content\">"
                << "<span>" << blogi::tr(_lang,"Page or content not found") << "</span><br/>"
                << "<a style=\"text-decoration: none; color: rgb(58,212, 58);\" href=\""
                <<  PlgArgs->config->getstartpage()
                << "\" >" << blogi::tr(_lang,"Back to home") << "</a>"
                << " | <a style=\"text-decoration: none; color: rgb(58,212, 58);\" href=\""
                << PlgArgs->config->buildurl("login")
                << "\" >" << blogi::tr(_lang,"Login") << "</a>"
                << " | <a style=\"text-decoration: none; color: rgb(58,212, 58);\" href=\""
                << PlgArgs->config->buildurl("editor")
                << "\" >" << blogi::tr(_lang,"Editor") << "</a>"
                << " | <a style=\"text-decoration: none; color: rgb(58,212, 58);\" href=\""
                << PlgArgs->config->buildurl("settings")
                << "\" >" << blogi::tr(_lang,"Settings") << "</a>"
                << "</div>";
                if(index.getElementbyID("main"))
                    index.getElementbyID("main")->appendChild(err.parse());
                PlgArgs->theme->printSite(tid,output,&index,curreq.getRequestURL(),false);
                libhttppp::HttpResponse resp;
                resp.setState(HTTP404);
                resp.setContentType("text/html");
                sendCompressed(curreq,resp,output.c_str(),output.length());
                return;
            }
        }catch(libhttppp::HTTPException &e){
            if(!PlgArgs->database[tid]->isConnected() && retries<MAX_RETRIES){
                PlgArgs->database[tid]->reset();
                goto RETRY_REQUEST;
            }
            throw e;
        }
    }catch(libhttppp::HTTPException &e){
        if(e.getErrorType() == libhttppp::HTTPException::Note || e.getErrorType() == libhttppp::HTTPException::Warning)
            return;
        libhtmlpp::HtmlString output;
        libhtmlpp::HtmlString err;
        std::string hreason;
        libhtmlpp::HtmlEncode(e.what(),hreason);
        err << "<!DOCTYPE html><html><body style=\"color:rgb(238, 238, 238); background:rgb(35, 38, 39);\"><span>"
        << hreason
        << "</span><br/><a style=\"text-decoration: none; color: rgb(58,212, 58);\" href=\""
        <<  PlgArgs->config->getstartpage()
        << "\" >Zur&uuml;ck zur Startseite</a></body></html>";
        libhtmlpp::print(err.parse(),output);
        libhttppp::HttpResponse resp;
        resp.setState(HTTP500);
        resp.setContentType("text/html");
        sendCompressed(curreq,resp,output.c_str(),output.length());
    }catch(...){
        libhtmlpp::HtmlString output;
        libhtmlpp::HtmlString err;
        err << "<!DOCTYPE html><html><body style=\"color:rgb(238, 238, 238); background:rgb(35, 38, 39);\"><span>"
        << "unknown error"
        << "</span><br/><a style=\"text-decoration: none; color: rgb(58,212, 58);\" href=\""
        <<  PlgArgs->config->getstartpage()
        << "\" >Zur&uuml;ck zur Startseite</a></body></html>";
        libhtmlpp::print(err.parse(),output);
        libhttppp::HttpResponse resp;
        resp.setState(HTTP500);
        resp.setContentType("text/html");
        sendCompressed(curreq,resp,output.c_str(),output.length());
    }
}

void blogi::Blogi::ResponseEvent(libhttppp::HttpRequest& curreq,const int tid,ULONG_PTR args){
    if(PlgArgs->theme->Response(tid,curreq))
        return;

    for(const blogi::Plugin::PluginData *curplg=BlogiPlg->getFirstPlugin(); curplg; curplg=curplg->getNextPlg()){
        PluginApi *api=curplg->getInstace();
        std::string url=PlgArgs->config->getprefix();
        url+="/";
        url+=api->getName();
        if(curreq.getRequestURL().compare(0,url.length(),url)==0){
            if(api->Response(tid,curreq)){
                return;
            }
        }
    }
}


class HttpConD : public libhttppp::HttpD {
public:
    HttpConD(bool debug)
            : HttpD(blogi::Blogi::Cfg->gethttpaddr(),
                blogi::Blogi::Cfg->gethttpport(),
                blogi::Blogi::Cfg->gethttpmaxcon(),
                blogi::Blogi::Cfg->getsslcertpath(),
                blogi::Blogi::Cfg->getsslkeypath(),
                blogi::Blogi::Cfg->getsslpassword()
            ){
        libhttppp::HTTPException httpexception;
        try {


            try{
                blogi::Blogi blg(getServerSockets(),debug);
#ifndef Windows
                if(!debug){
                    struct passwd pwd_storage;
                    char buffer[1024];
                    struct passwd *result = NULL;
                    int s;

                    const char *username = "blogi";

                    if(getpwnam_r(username, &pwd_storage, buffer, sizeof(buffer), &result)!=0){
                        throw httpexception[libhttppp::HTTPException::Error] << "can'T change user !";
                    }

                    seteuid(pwd_storage.pw_uid);
                    setegid(pwd_storage.pw_gid);
                }else{
                    blg.threads=2;
                }
#endif
                blg.runEventloop();
            }catch(libhttppp::HTTPException &e){
                std::cerr << e.what() << std::endl;
            }
        }catch(netplus::NetException &e){
            std::cerr << e.what() << std::endl;
        }
    };
};

#ifndef Windows
void logFiles(const char *path,int fd){
    int pfd=open(path,O_APPEND|O_CREAT|O_WRONLY,0x760);

    struct passwd *pwd=getpwnam("blogi");

    fchown(pfd,pwd->pw_uid,pwd->pw_gid);

    dup2(pfd, fd);
    close(pfd);
}
#endif

#ifndef Windows
int main(int argc, char* argv[]) {
    cmdplus::CmdController BlogiCmdCtl(cmdplus::CmdController::getInstance());

    BlogiCmdCtl.registerCmd("help", 'h', false, "", "Helpmenu");

    if (BlogiCmdCtl.getCmdbyKey("help") && BlogiCmdCtl.getCmdbyKey("help")->getFound()) {
        BlogiCmdCtl.printHelp();
        return 0;
    }

    BlogiCmdCtl.registerCmd("config",'c', true,"","Blogi Config File");
    BlogiCmdCtl.registerCmd("debug",'d', false,(int)0,"Blogi runs Debugmod");
    BlogiCmdCtl.registerCmd("foreground",'F', false,"","Blogi runs Foreground");

    BlogiCmdCtl.parseCmd(argc,argv);

    std::string config = BlogiCmdCtl.getCmdbyKey("config")->getValue();
    int debug = BlogiCmdCtl.getCmdbyKey("debug")->getValueInt();

    bool fground = false;

    if(BlogiCmdCtl.getCmdbyKey("foreground")->getValue()=="true"){
        fground=true;
    }

    if (config.empty()){
        return -1;
    }

    blogi::Blogi::Cfg= std::make_unique<blogi::Config>(config);

    try{
        struct sigaction SignalAction = {0};
        /* Ignore broken pipes, which can happen when the remote side is not behaving well */
        SignalAction.sa_handler = SIG_IGN;
        sigaction (SIGPIPE, &SignalAction, nullptr);

        if(debug==0){
            if(getuid()!=0){
                libhttppp::HTTPException e;
                e[libhttppp::HTTPException::Critical] << "must be run as root!";
                throw e;
            }

            logFiles("/var/log/blogi/access.log",STDOUT_FILENO);
            logFiles("/var/log/blogi/error.log",STDERR_FILENO);

            if(!fground){
                int pid = fork();
                if(pid==0){
                    HttpConD blogiD(false);
                    return 0;
                }

                std::ofstream pidf("/var/run/blogi.pid");
                pidf << pid;
                pidf.close();
            }else{
                HttpConD blogiD(false);
            }
        }else{
            HttpConD blogiD(true);
        }
    }catch(libhttppp::HTTPException &e){
        std::cerr << e.what() << std::endl;
        return -1;
    }
    return 0;
}
#endif
#ifdef Windows

#define SERVICE_NAME LPSTR("BlogiService")
#define EVENT_SOURCE_NAME "Blogi"

#include "blogi_events.h"

SERVICE_STATUS g_ServiceStatus = { 0 };
SERVICE_STATUS_HANDLE g_StatusHandle = NULL;
HANDLE g_ServiceStopEvent = NULL; // Initialized to NULL for proper check



// --- Register event source in the Windows registry ---
BOOL RegisterEventSource_Registry(const std::string& exePath) {
    HKEY hKey;
    std::string regPath = "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\";
    regPath += EVENT_SOURCE_NAME;

    LONG result = RegCreateKeyExA(HKEY_LOCAL_MACHINE, regPath.c_str(),
                                   0, NULL, REG_OPTION_NON_VOLATILE,
                                   KEY_SET_VALUE, NULL, &hKey, NULL);
    if (result != ERROR_SUCCESS) return FALSE;

    RegSetValueExA(hKey, "EventMessageFile", 0, REG_EXPAND_SZ,
                   (const BYTE*)exePath.c_str(), (DWORD)(exePath.size() + 1));

    DWORD types = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE;
    RegSetValueExA(hKey, "TypesSupported", 0, REG_DWORD, (const BYTE*)&types, sizeof(types));

    RegCloseKey(hKey);
    return TRUE;
}

// --- 1. LogToEventViewer Implementation ---
VOID LogToEventViewer(WORD type, const std::string& message) {
    HANDLE hEventSource = RegisterEventSourceA(NULL, EVENT_SOURCE_NAME);
    if (hEventSource == NULL) {
        return;
    }

    DWORD eventId;
    switch (type) {
        case EVENTLOG_ERROR_TYPE:   eventId = BLOGI_MSG_ERROR;   break;
        case EVENTLOG_WARNING_TYPE: eventId = BLOGI_MSG_WARNING; break;
        default:                    eventId = BLOGI_MSG_INFO;    break;
    }

    LPCSTR lpszStrings[1] = { message.c_str() };

    ReportEventA(
        hEventSource,
        type,
        0,
        eventId,
        NULL,
        1,
        0,
        lpszStrings,
        NULL
    );

    DeregisterEventSource(hEventSource);
}


// --- 2. Custom Streambuf and Redirection Logic ---

/**
 * @brief Custom streambuf to redirect stream output to LogToEventViewer
 */
class EventLogStreambuf : public std::streambuf {
private:
    std::string buffer;
    WORD logType;

protected:
    // Called when the internal buffer is full or when a stream is flushed.
    virtual int overflow(int c) override {
        if (c == EOF) {
            return sync() == 0 ? 0 : EOF;
        }
        buffer += (char)c;
        if (c == '\n') {
            if (buffer.size() > 1) {
                // Remove trailing newline before logging
                std::string messageToLog = buffer.substr(0, buffer.length() - 1);
                LogToEventViewer(logType, messageToLog);
            }
            buffer.clear();
        }
        return c; // Success — must return the character, not EOF
    }

    // Called when the stream needs to flush manually
    virtual int sync() override {
        if (!buffer.empty()) {
            LogToEventViewer(logType, buffer);
            buffer.clear();
        }
        return 0; // Success
    }

public:
    EventLogStreambuf(WORD type) : logType(type) {}
    ~EventLogStreambuf() override {
        sync();
    }
};

// Global variables for redirection management
std::streambuf* g_OriginalCerrBuffer = nullptr;
std::unique_ptr<EventLogStreambuf> g_EventLogCerrStreambuf;

std::streambuf* g_OriginalCoutBuffer = nullptr;
std::unique_ptr<EventLogStreambuf> g_EventLogCoutStreambuf;

// --- Combined Redirection Function ---
void RedirectStreamsToEventLog() {
    g_EventLogCerrStreambuf = std::make_unique<EventLogStreambuf>(EVENTLOG_ERROR_TYPE);
    g_OriginalCerrBuffer = std::cerr.rdbuf();
    std::cerr.rdbuf(g_EventLogCerrStreambuf.get());

    g_EventLogCoutStreambuf = std::make_unique<EventLogStreambuf>(EVENTLOG_INFORMATION_TYPE);
    g_OriginalCoutBuffer = std::cout.rdbuf();
    std::cout.rdbuf(g_EventLogCoutStreambuf.get()); 
}

// --- Combined Restoration Function ---
void RestoreStreams() {
    // 1. Restore std::cout
    if (g_OriginalCoutBuffer) {
        std::cout.rdbuf(g_OriginalCoutBuffer);
        g_OriginalCoutBuffer = nullptr;
    }
    g_EventLogCoutStreambuf.reset();

    // 2. Restore std::cerr
    if (g_OriginalCerrBuffer) {
        std::cerr.rdbuf(g_OriginalCerrBuffer);
        g_OriginalCerrBuffer = nullptr;
    }
    g_EventLogCerrStreambuf.reset();
}


// --- 3. Windows Service Boilerplate Functions ---

// Function to report service status
VOID ReportSvcStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint) {
    static DWORD dwCheckPoint = 1;
    g_ServiceStatus.dwCurrentState = dwCurrentState;
    g_ServiceStatus.dwWin32ExitCode = dwWin32ExitCode;
    g_ServiceStatus.dwWaitHint = dwWaitHint;

    if (dwCurrentState == SERVICE_RUNNING || dwCurrentState == SERVICE_STOPPED) {
        g_ServiceStatus.dwCheckPoint = 0;
    }
    else {
        g_ServiceStatus.dwCheckPoint = dwCheckPoint++;
    }

    SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
}

// Service control handler
VOID WINAPI ServiceCtrlHandler(DWORD CtrlCode) {
    switch (CtrlCode) {
    case SERVICE_CONTROL_STOP:
        ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 5000);
        netplus::event::Running = false;
        if (g_ServiceStopEvent) {
            SetEvent(g_ServiceStopEvent);
        }
        return;
    case SERVICE_CONTROL_INTERROGATE:
        ReportSvcStatus(g_ServiceStatus.dwCurrentState, NO_ERROR, 0);
        return;
    default:
        break;
    }
}

// Main service entry point
VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR* lpszArgv) {
    // 1. Register a service control handler
    g_StatusHandle = RegisterServiceCtrlHandler(SERVICE_NAME, ServiceCtrlHandler);
    if (g_StatusHandle == NULL) {
        return;
    }

    // Initialize service status
    g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
    ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000);

    // 2. Create the stop event
    g_ServiceStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    if (g_ServiceStopEvent == NULL) {
        ReportSvcStatus(SERVICE_STOPPED, GetLastError(), 0);
        return;
    }

    // --- START LOGGING REDIRECTION ---
    // Register event source so "Blogi" appears as its own source in Event Viewer
    {
        char exePath[MAX_PATH];
        GetModuleFileNameA(NULL, exePath, MAX_PATH);
        RegisterEventSource_Registry(exePath);
    }
    RedirectStreamsToEventLog();

    // 3. Service initialization and configuration
    try {
        std::cout << "Blogi Service is starting up. Streams redirected to Event Log." << std::endl;

        try {
            blogi::Blogi::Cfg = std::unique_ptr<blogi::Config>(new blogi::Config("regedit:HKEY_LOCAL_MACHINE/SOFTWARE/Tuxist"));
            std::cout << "Configuration loaded successfully." << std::endl;
        } catch (const std::exception& ex) {
            // This error is logged to the Event Log via std::cerr
            std::cerr << "Failed to load config: " << ex.what() << std::endl;
            ReportSvcStatus(SERVICE_STOPPED, ERROR_INVALID_DATA, 0);

            // Critical cleanup needs to happen here before returning on failure
            if (g_ServiceStopEvent) {
                CloseHandle(g_ServiceStopEvent);
            }
            RestoreStreams();
            return;
        }

        ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
        std::cout << "Blogi Service is running." << std::endl;

        // Run the server and wait for the stop event to be signaled
        HttpConD blogiD(false);

    }
    catch (const libhttppp::HTTPException& ex) {
        std::cerr << "Critical HTTP Exception in ServiceMain: " << ex.what() << std::endl;
        ReportSvcStatus(SERVICE_STOPPED, ERROR_SERVICE_SPECIFIC_ERROR, 0);
    }
    catch (const std::exception& ex) {
        // Catch any other exceptions
        std::cerr << "Unknown exception in ServiceMain: " << ex.what() << std::endl;
        ReportSvcStatus(SERVICE_STOPPED, GetLastError(), 0);
    }

    // 4. Final Cleanup (Runs regardless of whether an exception occurred)
    if (g_ServiceStopEvent) {
        CloseHandle(g_ServiceStopEvent);
        g_ServiceStopEvent = NULL;
    }

    ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);

    // --- STOP LOGGING REDIRECTION ---
    RestoreStreams();
    LogToEventViewer(EVENTLOG_INFORMATION_TYPE, "Blogi Service has stopped. Streams restored.");
}


// --- 5. Main entry point (for service dispatcher or debug mode) ---
int _tmain(int argc, TCHAR* argv[]) {
    // Helper lambda to get the executable directory path
    auto getexecdir = []() {
        wchar_t path[MAX_PATH];
        GetModuleFileNameW(NULL, path, MAX_PATH);
        PathRemoveFileSpecW(path);
        return std::wstring(path);
    };

    try {
        std::wstring exe_dir = getexecdir();

        if (!SetCurrentDirectoryW( exe_dir.c_str() ) ) {
            // Note: Use LogToEventViewer here if you couldn't set the directory,
            // but this is before service dispatch, so std::wcerr is safer if not installed.
            std::wcerr << L"Failed to set current directory to: " << exe_dir << std::endl;
            return -1;
        }
    } catch (const std::exception& ex) {
        std::cerr << "Exception while setting current directory: " << ex.what() << std::endl;
        return -1;
    }

    SERVICE_TABLE_ENTRY ServiceTable[] = {
        {SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)ServiceMain},
        {NULL, NULL}
    };

    if (StartServiceCtrlDispatcher(ServiceTable) == FALSE) {
        // Handle running in non-service mode (debug/command line)
        DWORD err = GetLastError();
        if (err == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
            // Command line execution logic
            cmdplus::CmdController* BlogiCmdCtl;
            BlogiCmdCtl = &cmdplus::CmdController::getInstance();

            BlogiCmdCtl->registerCmd("help", 'h', false,"", "Helpmenu");

            if (BlogiCmdCtl->getCmdbyKey("help") && BlogiCmdCtl->getCmdbyKey("help")->getFound()) {
                BlogiCmdCtl->printHelp();
                return 0;
            }

            BlogiCmdCtl->registerCmd("config", 'c', true,"", "Blogi Config File");
            BlogiCmdCtl->registerCmd("debug", 'd', false, (int)0, "Blogi runs Debugmod");
            BlogiCmdCtl->registerCmd("foreground", 'F', false, (int)0, "Blogi runs Foreground");

            BlogiCmdCtl->parseCmd(argc, argv);

            const std::string config = BlogiCmdCtl->getCmdbyKey("config")->getValue();
            int debug = BlogiCmdCtl->getCmdbyKey("debug")->getValueInt();

            if (config.empty())
                return -1;

            blogi::Blogi::Cfg = std::make_unique<blogi::Config>(config);

            HttpConD blogiD(debug);
        }
        else {
            return err;
        }
    }
    return 0;
}
#endif
