/*******************************************************************************
 * Copyright (c) 2025, 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 <httppp/http.h>
#include <htmlpp/css.h>
#include <tinyxml2.h>
#include <json-c/json.h>
#include <map>
#include <vector>
#include <string>

#include <dbpp/database.h>

#include "plugin.h"

#pragma once

namespace blogi {
    namespace webedit {
        class WebEditor{
        public:
                WebEditor(PluginArgs &plgags);
                void initTables(dbpp::Database &db);
                void Editor(libhttppp::HttpRequest& curreq, const int tid, const std::string &sessionid);
                void Renderer(
                    libhttppp::HttpRequest& curreq,
                    const int tid, const std::string &sessionid,const libhtmlpp::HtmlElement &index,
                    Plugin *plugins = nullptr,
                    const std::string &publishTarget = "",
                    json_object *publishParams = nullptr,
                    const std::string &html = ""
                );
                std::vector<std::string> getPluginNames() const;
                void JsonApi(const int tid, json_object *request, json_object *response, Plugin *plugins = nullptr);
                void registerEditorGPOs();

                // --- XML Template Methods ---
                void saveDocumentXml(libhttppp::HttpRequest &curreq);
                void loadDocumentXml(const std::string &templateName);
                std::string exportDocumentXml();

                // --- DB Template Methods ---
                std::string saveTemplateToDb(const int tid, const std::string &name,
                                            const std::string &description, const std::string &comment);
                bool loadTemplateFromDb(const int tid, const std::string &templateId, int revision = -1);
                json_object* listTemplates(const int tid);
                json_object* getTemplateRevisions(const int tid, const std::string &templateId);

        private:
                json_object *serializeDocumentTree(const blogi::webedit::EditPlugin *root);

                // Find an element in the tree by UUID (recursive)
                EditPlugin* findElementByUuid(EditPlugin *root, const std::string &uuid);

                // Context about a node's position in the tree
                struct NodeContext {
                    EditPlugin *node = nullptr;       // The found node
                    EditPlugin *prev = nullptr;       // Previous sibling (nullptr if first child)
                    EditPlugin *parent = nullptr;     // Parent container (nullptr if top-level)
                    bool isTopLevel = false;           // True if node is in the DocRoot sibling chain
                };

                // Find a node and its positional context (parent, previous sibling)
                NodeContext findNodeContext(const std::string &uuid);

                // Create widget instance by plugin name
                EditPlugin* createPluginByName(const std::string &name);
                // Recursively load widgets from XML element tree
                void loadWidgetsFromXml(tinyxml2::XMLElement *xmlEl, EditPlugin *parent);

                // Import arbitrary HTML: convert DOM to widget XML, then load
                void importHtmlToWidgets(const std::string &html);

                // Look up matching CSS rules for an element and merge into inline props.
                // @media rules whose inner selector matches are collected in mediaRules.
                static void mergeStylesheetRules(const libhtmlpp::CSSStyleSheet &sheet,
                                                 const std::string &tag,
                                                 const std::string &cssClass,
                                                 const std::string &id,
                                                 std::map<std::string,std::string> &props,
                                                 std::string &mediaRules);

                // Convert CSSDeclaration to property map
                static std::map<std::string,std::string> declarationToMap(const libhtmlpp::CSSDeclaration &decl);

                // Recursive helper: converts libhtmlpp HTML element tree to widget XML
                void htmlElementToWidgetXml(libhtmlpp::Element *htmlEl,
                                            tinyxml2::XMLDocument &doc,
                                            tinyxml2::XMLElement *parentXml,
                                            const libhtmlpp::CSSStyleSheet &sheet);

                // Helper: extract and remove known CSS props, return remainder as custom_css
                static std::string extractCustomCss(std::map<std::string,std::string> &props,
                                                    const std::vector<std::string> &knownKeys,
                                                    const std::string &mediaRules = "");
                // Helper: serialize libhtmlpp element children to HTML string
                static std::string serializeInnerHtml(libhtmlpp::HtmlElement *el);
                // Helper: serialize a single libhtmlpp element (outer HTML) to string
                static std::string serializeElement(const libhtmlpp::Element *el);

                // Helper: get current XML as string
                std::string serializeCurrentXml();

                // Check GPO for a specific operation; extracts authid from request array
                bool checkEditorGPO(json_object *request, const char *gpoid);

                // Find the PluginData for a given plugin name (case-insensitive)
                const EditPluginLoad::PluginData* findPluginDataByName(const std::string &name);

                // Deep-copy a subtree (element + children) into the document tree, returning the new root
                EditPlugin* deepCopySubtree(tinyxml2::XMLElement *xmlEl, EditPlugin *parent);

                tinyxml2::XMLDocument              CurDoc;
                tinyxml2::XMLElement                *CurRootEl=nullptr;
                tinyxml2::XMLDocument              ClipboardDoc;
                std::unique_ptr<EditPluginLoad> Plugins=nullptr;
                PluginArgs                                  &Args;

                // Global CSS (e.g. :root vars, @keyframes, @font-face) from HTML import
                std::string                         GlobalCss;

                // Document tree: user-created widget instances
                // DocElements owns the memory; DocRoot is the first top-level element
                std::vector<std::unique_ptr<EditPlugin>> DocElements;
                EditPlugin                                              *DocRoot = nullptr;
        };
    };
};
