/*******************************************************************************
 * Copyright (c) 2026, 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 "htmlimport.h"

#include <algorithm>
#include <sstream>
#include <stack>

using namespace blogi::htmlimport;

std::map<std::string,std::string> blogi::htmlimport::declarationToMap(
    const libhtmlpp::CSSDeclaration &decl)
{
    std::map<std::string,std::string> result;
    for (const auto &prop : decl.getProperties()) {
        result[prop.getName()] = prop.getValue();
    }
    return result;
}

void blogi::htmlimport::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)
{
    std::vector<std::string> classes;
    if (!cssClass.empty()) {
        std::istringstream iss(cssClass);
        std::string cls;
        while (iss >> cls) {
            classes.push_back(cls);
        }
    }

    for (const auto &rule : sheet.getRules()) {
        const std::string &sel = rule.getSelector();
        if (sel.empty()) continue;

        bool isAtRule = (!sel.empty() && sel[0] == '@');
        std::string atWrapper;
        std::string innerSel;

        if (isAtRule) {
            size_t parenDepth = 0;
            size_t splitPos = std::string::npos;
            for (size_t i = 0; i < sel.size(); ++i) {
                if (sel[i] == '(') ++parenDepth;
                else if (sel[i] == ')') {
                    if (parenDepth > 0) --parenDepth;
                    if (parenDepth == 0) {
                        splitPos = i + 1;
                        break;
                    }
                }
            }
            if (splitPos != std::string::npos && splitPos < sel.size()) {
                atWrapper = sel.substr(0, splitPos);
                size_t innerStart = sel.find_first_not_of(" \t\n\r", splitPos);
                if (innerStart != std::string::npos) {
                    innerSel = sel.substr(innerStart);
                }
            }
            if (innerSel.empty()) continue;
        }

        const std::string &matchTarget = isAtRule ? innerSel : sel;

        std::istringstream selStream(matchTarget);
        std::string singleSel;
        while (std::getline(selStream, singleSel, ',')) {
            size_t start = singleSel.find_first_not_of(" \t\n\r");
            size_t end = singleSel.find_last_not_of(" \t\n\r");
            if (start == std::string::npos) continue;
            singleSel = singleSel.substr(start, end - start + 1);

            std::string matchSel = singleSel;
            {
                size_t lastSep = matchSel.find_last_of(" >+~");
                if (lastSep != std::string::npos) {
                    size_t mstart = matchSel.find_first_not_of(" >+~", lastSep);
                    if (mstart != std::string::npos) {
                        matchSel = matchSel.substr(mstart);
                    }
                }
            }

            bool matches = false;

            if (!matchSel.empty() && matchSel[0] == '#') {
                if (!id.empty() && matchSel == "#" + id) matches = true;
            } else if (!matchSel.empty() && matchSel[0] == '.') {
                std::string clsSel = matchSel.substr(1);
                for (const auto &c : classes) {
                    if (c == clsSel) { matches = true; break; }
                }
            } else {
                std::string selLower = matchSel;
                std::transform(selLower.begin(), selLower.end(), selLower.begin(),
                    [](unsigned char c) { return std::tolower(c); });
                if (selLower == tag) matches = true;

                auto dotPos = matchSel.find('.');
                if (!matches && dotPos != std::string::npos) {
                    std::string tagPart = matchSel.substr(0, dotPos);
                    std::string clsPart = matchSel.substr(dotPos + 1);
                    std::transform(tagPart.begin(), tagPart.end(), tagPart.begin(),
                        [](unsigned char c) { return std::tolower(c); });
                    if (tagPart == tag) {
                        for (const auto &c : classes) {
                            if (c == clsPart) { matches = true; break; }
                        }
                    }
                }

                auto hashPos = matchSel.find('#');
                if (!matches && hashPos != std::string::npos) {
                    std::string tagPart = matchSel.substr(0, hashPos);
                    std::string idPart = matchSel.substr(hashPos + 1);
                    std::transform(tagPart.begin(), tagPart.end(), tagPart.begin(),
                        [](unsigned char c) { return std::tolower(c); });
                    if (tagPart == tag && idPart == id) matches = true;
                }
            }

            if (matches) {
                if (isAtRule) {
                    std::string block = atWrapper + " { " + singleSel + " { ";
                    for (const auto &prop : rule.getDeclaration().getProperties()) {
                        block += prop.getName() + ": " + prop.getValue() + "; ";
                    }
                    block += "} } ";
                    mediaRules += block;
                } else {
                    for (const auto &prop : rule.getDeclaration().getProperties()) {
                        if (props.find(prop.getName()) == props.end()) {
                            props[prop.getName()] = prop.getValue();
                        }
                    }
                }
            }
        }
    }
}

std::string blogi::htmlimport::extractCustomCss(
    std::map<std::string,std::string> &props,
    const std::vector<std::string> &knownKeys,
    const std::string &mediaRules)
{
    std::string customCss;
    std::vector<std::string> toRemove;
    for (auto &[k, v] : props) {
        bool known = false;
        for (auto &kk : knownKeys) {
            if (k == kk) { known = true; break; }
        }
        if (!known) {
            if (!customCss.empty()) customCss += " ";
            customCss += k + ": " + v + ";";
            toRemove.push_back(k);
        }
    }
    for (auto &k : toRemove) props.erase(k);
    if (!mediaRules.empty()) {
        if (!customCss.empty()) customCss += " ";
        customCss += mediaRules;
    }
    return customCss;
}

std::string blogi::htmlimport::serializeElement(const libhtmlpp::Element *el) {
    if (!el) return "";
    if (el->getType() == libhtmlpp::HtmlEl || el->getType() == libhtmlpp::SvgEL ||
        el->getType() == libhtmlpp::TextAreaEL) {
        const libhtmlpp::HtmlElement *hel = static_cast<const libhtmlpp::HtmlElement*>(el);
        std::string tag = hel->getTagname();
        std::string result = "<" + tag;
        for (const auto *attr = hel->firstAttribute(); attr; attr = attr->nextAttribute()) {
            result += " " + attr->getKey();
            std::string val = attr->getValue();
            if (!val.empty()) {
                result += "=\"" + val + "\"";
            }
        }
        result += ">";
        result += serializeInnerHtml(const_cast<libhtmlpp::HtmlElement*>(hel));
        result += "</" + tag + ">";
        return result;
    } else if (el->getType() == libhtmlpp::TextEl) {
        return const_cast<libhtmlpp::TextElement*>(static_cast<const libhtmlpp::TextElement*>(el))->getText();
    } else if (el->getType() == libhtmlpp::ScriptEL) {
        const libhtmlpp::ScriptElement *sel = static_cast<const libhtmlpp::ScriptElement*>(el);
        std::string tag = sel->getTagname();
        std::string result = "<" + tag;
        for (const auto *attr = sel->firstAttribute(); attr; attr = attr->nextAttribute()) {
            result += " " + attr->getKey();
            std::string val = attr->getValue();
            if (!val.empty()) {
                result += "=\"" + val + "\"";
            }
        }
        result += ">";
        result += const_cast<libhtmlpp::ScriptElement*>(sel)->getScript();
        result += "</" + tag + ">";
        return result;
    } else if (el->getType() == libhtmlpp::CommentEl) {
        return "<!--" + const_cast<libhtmlpp::CommentElement*>(
            static_cast<const libhtmlpp::CommentElement*>(el))->getComment() + "-->";
    }
    return "";
}

std::string blogi::htmlimport::serializeInnerHtml(libhtmlpp::HtmlElement *el) {
    if (!el) return "";
    libhtmlpp::Element *child = el->firstChild();
    if (!child) return "";
    libhtmlpp::HtmlString out;
    libhtmlpp::print(*child, out);
    return out.str();
}

void blogi::htmlimport::htmlElementToWidgetXml(
    libhtmlpp::Element *htmlEl,
    tinyxml2::XMLDocument &doc,
    tinyxml2::XMLElement *parentXml,
    const libhtmlpp::CSSStyleSheet &sheet)
{
    if (!htmlEl) return;

    struct Frame { libhtmlpp::Element *cur; tinyxml2::XMLElement *pXml; };
    std::stack<Frame> workStack;
    workStack.push({htmlEl, parentXml});

    while (!workStack.empty()) {
        auto frame = workStack.top();
        workStack.pop();

        std::vector<Frame> childFrames;

        libhtmlpp::Element *cur = frame.cur;
        tinyxml2::XMLElement *pXml = frame.pXml;

        while (cur) {
            int type = cur->getType();

            if (type == libhtmlpp::TextEl) {
                libhtmlpp::TextElement *textEl = static_cast<libhtmlpp::TextElement*>(cur);
                std::string text = textEl->getText();
                if (text.find_first_not_of(" \t\n\r") != std::string::npos) {
                    tinyxml2::XMLElement *tb = doc.NewElement("TextBox");
                    tb->SetText(text.c_str());
                    pXml->InsertEndChild(tb);
                }
                cur = cur->nextElement();
                continue;
            }

            if (type == libhtmlpp::CommentEl || type == libhtmlpp::ScriptEL) {
                cur = cur->nextElement();
                continue;
            }

            if (type != libhtmlpp::HtmlEl && type != libhtmlpp::SvgEL && type != libhtmlpp::TextAreaEL) {
                cur = cur->nextElement();
                continue;
            }

            libhtmlpp::HtmlElement *el = static_cast<libhtmlpp::HtmlElement*>(cur);
            std::string tag = el->getTagname();
            std::transform(tag.begin(), tag.end(), tag.begin(),
                [](unsigned char c) { return std::tolower(c); });

            // Skip structural HTML tags, process their children instead
            if (tag == "html" || tag == "body" || tag == "head" || tag == "main" ||
                tag == "header" || tag == "footer" || tag == "nav" ||
                tag == "aside") {
                libhtmlpp::Element *child = el->firstChild();
                if (child) {
                    childFrames.push_back({child, pXml});
                }
                cur = cur->nextElement();
                continue;
            }

            // Get inline style
            std::string style = el->getAtributte("style");
            auto cssProps = declarationToMap(libhtmlpp::CSSStyleSheet::parseInlineStyle(style));

            std::string cssClass = el->getAtributte("class");
            std::string elemId = el->getAtributte("id");

            std::string mediaRules;
            mergeStylesheetRules(sheet, tag, cssClass, elemId, cssProps, mediaRules);

            // ---- Map HTML tags to widget types ----

            if (tag == "section") {
                tinyxml2::XMLElement *w = doc.NewElement("Section");
                if (cssProps.count("background-color")) w->SetAttribute("background_color", cssProps["background-color"].c_str());
                if (cssProps.count("border-radius")) w->SetAttribute("border_radius", cssProps["border-radius"].c_str());
                if (cssProps.count("border")) w->SetAttribute("border_style", cssProps["border"].c_str());
                if (cssProps.count("opacity")) w->SetAttribute("opacity", cssProps["opacity"].c_str());
                if (cssProps.count("padding")) w->SetAttribute("padding", cssProps["padding"].c_str());
                if (cssProps.count("margin")) w->SetAttribute("margin", cssProps["margin"].c_str());
                if (cssProps.count("max-width")) w->SetAttribute("max_width", cssProps["max-width"].c_str());
                if (cssProps.count("min-height")) w->SetAttribute("min_height", cssProps["min-height"].c_str());
                if (cssProps.count("width")) w->SetAttribute("width", cssProps["width"].c_str());
                if (cssProps.count("height")) w->SetAttribute("height", cssProps["height"].c_str());
                if (!cssClass.empty()) w->SetAttribute("css_class", cssClass.c_str());
                if (!elemId.empty()) w->SetAttribute("css_id", elemId.c_str());

                std::vector<std::string> knownCss = {"background-color","border-radius","border","opacity",
                    "padding","margin","max-width","min-height","width","height","display","flex-direction","justify-content"};
                std::string custom = extractCustomCss(cssProps, knownCss, mediaRules);
                if (!custom.empty()) {
                    tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss");
                    ccEl->SetText(custom.c_str());
                    w->InsertEndChild(ccEl);
                }
                pXml->InsertEndChild(w);
                libhtmlpp::Element *child = el->firstChild();
                if (child) childFrames.push_back({child, w});

            } else if (tag == "article") {
                tinyxml2::XMLElement *w = doc.NewElement("Article");
                if (cssProps.count("background-color")) w->SetAttribute("background_color", cssProps["background-color"].c_str());
                if (cssProps.count("border-radius")) w->SetAttribute("border_radius", cssProps["border-radius"].c_str());
                if (cssProps.count("border")) w->SetAttribute("border_style", cssProps["border"].c_str());
                if (cssProps.count("opacity")) w->SetAttribute("opacity", cssProps["opacity"].c_str());
                if (cssProps.count("padding")) w->SetAttribute("padding", cssProps["padding"].c_str());
                if (cssProps.count("margin")) w->SetAttribute("margin", cssProps["margin"].c_str());
                if (cssProps.count("max-width")) w->SetAttribute("max_width", cssProps["max-width"].c_str());
                if (cssProps.count("min-height")) w->SetAttribute("min_height", cssProps["min-height"].c_str());
                if (cssProps.count("width")) w->SetAttribute("width", cssProps["width"].c_str());
                if (cssProps.count("height")) w->SetAttribute("height", cssProps["height"].c_str());
                if (!cssClass.empty()) w->SetAttribute("css_class", cssClass.c_str());
                if (!elemId.empty()) w->SetAttribute("css_id", elemId.c_str());

                std::vector<std::string> knownCss = {"background-color","border-radius","border","opacity",
                    "padding","margin","max-width","min-height","width","height","display","flex-direction","justify-content"};
                std::string custom = extractCustomCss(cssProps, knownCss, mediaRules);
                if (!custom.empty()) {
                    tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss");
                    ccEl->SetText(custom.c_str());
                    w->InsertEndChild(ccEl);
                }
                pXml->InsertEndChild(w);
                libhtmlpp::Element *child = el->firstChild();
                if (child) childFrames.push_back({child, w});

            } else if (tag == "div") {
                tinyxml2::XMLElement *w = doc.NewElement("Container");
                if (cssProps.count("background-color")) w->SetAttribute("background_color", cssProps["background-color"].c_str());
                if (cssProps.count("border-radius")) w->SetAttribute("border_radius", cssProps["border-radius"].c_str());
                if (cssProps.count("border")) w->SetAttribute("border_style", cssProps["border"].c_str());
                if (cssProps.count("opacity")) w->SetAttribute("opacity", cssProps["opacity"].c_str());
                if (cssProps.count("padding")) w->SetAttribute("padding", cssProps["padding"].c_str());
                if (cssProps.count("margin")) w->SetAttribute("margin", cssProps["margin"].c_str());
                if (cssProps.count("width")) w->SetAttribute("width", cssProps["width"].c_str());
                if (cssProps.count("height")) w->SetAttribute("height", cssProps["height"].c_str());
                if (!cssClass.empty()) w->SetAttribute("css_class", cssClass.c_str());
                if (!elemId.empty()) w->SetAttribute("css_id", elemId.c_str());

                bool isGrid = cssProps.count("display") && cssProps["display"] == "grid";

                if (isGrid) {
                    w->SetName("Grid");
                    if (cssProps.count("grid-template-columns")) w->SetAttribute("columns", cssProps["grid-template-columns"].c_str());
                    if (cssProps.count("grid-template-rows")) w->SetAttribute("rows", cssProps["grid-template-rows"].c_str());
                    if (cssProps.count("gap")) w->SetAttribute("gap", cssProps["gap"].c_str());
                    std::vector<std::string> knownCss = {"background-color","border-radius","border","opacity",
                        "padding","margin","width","height","display","grid-template-columns","grid-template-rows","gap","align-items"};
                    std::string custom = extractCustomCss(cssProps, knownCss, mediaRules);
                    if (!custom.empty()) {
                        tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss");
                        ccEl->SetText(custom.c_str());
                        w->InsertEndChild(ccEl);
                    }
                } else {
                    std::vector<std::string> knownCss = {"background-color","border-radius","border","opacity",
                        "padding","margin","width","height","display","flex-direction","justify-content"};
                    std::string custom = extractCustomCss(cssProps, knownCss, mediaRules);
                    if (!custom.empty()) {
                        tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss");
                        ccEl->SetText(custom.c_str());
                        w->InsertEndChild(ccEl);
                    }
                }
                pXml->InsertEndChild(w);
                libhtmlpp::Element *child = el->firstChild();
                if (child) childFrames.push_back({child, w});

            } else if (tag == "img") {
                tinyxml2::XMLElement *w = doc.NewElement("Image");
                std::string src = el->getAtributte("src");
                std::string alt = el->getAtributte("alt");
                if (!src.empty()) w->SetAttribute("src", src.c_str());
                if (!alt.empty()) w->SetAttribute("alt", alt.c_str());
                std::string widthAttr = el->getAtributte("width");
                std::string heightAttr = el->getAtributte("height");
                if (!widthAttr.empty() && !cssProps.count("width")) cssProps["width"] = widthAttr + "px";
                if (!heightAttr.empty() && !cssProps.count("height")) cssProps["height"] = heightAttr + "px";
                if (cssProps.count("width")) w->SetAttribute("width", cssProps["width"].c_str());
                if (cssProps.count("height")) w->SetAttribute("height", cssProps["height"].c_str());
                if (cssProps.count("object-fit")) w->SetAttribute("object_fit", cssProps["object-fit"].c_str());
                if (cssProps.count("border-radius")) w->SetAttribute("border_radius", cssProps["border-radius"].c_str());

                std::vector<std::string> knownCss = {"width","height","object-fit","border-radius","display"};
                std::string custom = extractCustomCss(cssProps, knownCss, mediaRules);
                if (!custom.empty()) {
                    tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss");
                    ccEl->SetText(custom.c_str());
                    w->InsertEndChild(ccEl);
                }
                pXml->InsertEndChild(w);

            } else if (tag == "video") {
                tinyxml2::XMLElement *w = doc.NewElement("Video");
                std::string src = el->getAtributte("src");
                if (!src.empty()) w->SetAttribute("src", src.c_str());
                if (!el->getAtributte("controls").empty()) w->SetAttribute("controls", "true");
                if (cssProps.count("width")) w->SetAttribute("width", cssProps["width"].c_str());
                if (cssProps.count("height")) w->SetAttribute("height", cssProps["height"].c_str());

                std::vector<std::string> knownCss = {"width","height","display"};
                std::string custom = extractCustomCss(cssProps, knownCss, mediaRules);
                if (!custom.empty()) {
                    tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss");
                    ccEl->SetText(custom.c_str());
                    w->InsertEndChild(ccEl);
                }
                pXml->InsertEndChild(w);

            } else if (tag == "table") {
                tinyxml2::XMLElement *w = doc.NewElement("Table");
                if (cssProps.count("border")) w->SetAttribute("border_style", cssProps["border"].c_str());
                if (cssProps.count("background-color")) w->SetAttribute("background_color", cssProps["background-color"].c_str());

                int numRows = 0, numCols = 0;
                bool hasHeader = false;

                struct RowData { std::vector<std::string> cells; bool isHeader; };
                std::vector<RowData> rows;

                auto processTr = [&](libhtmlpp::HtmlElement *tr, bool header) {
                    RowData rd;
                    rd.isHeader = header;
                    libhtmlpp::Element *tdEl = tr->firstChild();
                    while (tdEl) {
                        if (tdEl->getType() == libhtmlpp::HtmlEl) {
                            libhtmlpp::HtmlElement *td = static_cast<libhtmlpp::HtmlElement*>(tdEl);
                            std::string tdTag = td->getTagname();
                            std::transform(tdTag.begin(), tdTag.end(), tdTag.begin(),
                                [](unsigned char c) { return std::tolower(c); });
                            if (tdTag == "td" || tdTag == "th") {
                                rd.cells.push_back(serializeInnerHtml(td));
                            }
                        }
                        tdEl = tdEl->nextElement();
                    }
                    if ((int)rd.cells.size() > numCols) numCols = (int)rd.cells.size();
                    rows.push_back(rd);
                    numRows++;
                    if (header) hasHeader = true;
                };

                libhtmlpp::Element *tblChild = el->firstChild();
                while (tblChild) {
                    if (tblChild->getType() == libhtmlpp::HtmlEl) {
                        libhtmlpp::HtmlElement *tblHEl = static_cast<libhtmlpp::HtmlElement*>(tblChild);
                        std::string tblTag = tblHEl->getTagname();
                        std::transform(tblTag.begin(), tblTag.end(), tblTag.begin(),
                            [](unsigned char c) { return std::tolower(c); });

                        if (tblTag == "thead") {
                            libhtmlpp::Element *headChild = tblHEl->firstChild();
                            while (headChild) {
                                if (headChild->getType() == libhtmlpp::HtmlEl) {
                                    libhtmlpp::HtmlElement *tr = static_cast<libhtmlpp::HtmlElement*>(headChild);
                                    std::string trTag = tr->getTagname();
                                    std::transform(trTag.begin(), trTag.end(), trTag.begin(),
                                        [](unsigned char c) { return std::tolower(c); });
                                    if (trTag == "tr") processTr(tr, true);
                                }
                                headChild = headChild->nextElement();
                            }
                        } else if (tblTag == "tbody") {
                            libhtmlpp::Element *bodyChild = tblHEl->firstChild();
                            while (bodyChild) {
                                if (bodyChild->getType() == libhtmlpp::HtmlEl) {
                                    libhtmlpp::HtmlElement *tr = static_cast<libhtmlpp::HtmlElement*>(bodyChild);
                                    std::string trTag = tr->getTagname();
                                    std::transform(trTag.begin(), trTag.end(), trTag.begin(),
                                        [](unsigned char c) { return std::tolower(c); });
                                    if (trTag == "tr") processTr(tr, false);
                                }
                                bodyChild = bodyChild->nextElement();
                            }
                        } else if (tblTag == "tr") {
                            processTr(tblHEl, false);
                        }
                    }
                    tblChild = tblChild->nextElement();
                }

                w->SetAttribute("num_rows", numRows);
                w->SetAttribute("num_cols", numCols);
                w->SetAttribute("has_header", hasHeader ? "true" : "false");

                int row = 0;
                for (const auto &rd : rows) {
                    tinyxml2::XMLElement *rowEl = doc.NewElement("Row");
                    rowEl->SetAttribute("index", row);
                    int col = 0;
                    for (const auto &cellText : rd.cells) {
                        tinyxml2::XMLElement *cellEl = doc.NewElement("Cell");
                        cellEl->SetAttribute("col", col);
                        cellEl->SetText(cellText.c_str());
                        rowEl->InsertEndChild(cellEl);
                        col++;
                    }
                    w->InsertEndChild(rowEl);
                    row++;
                }

                std::vector<std::string> knownCss = {"border","background-color","border-collapse","width","height"};
                std::string custom = extractCustomCss(cssProps, knownCss, mediaRules);
                if (!custom.empty()) {
                    tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss");
                    ccEl->SetText(custom.c_str());
                    w->InsertEndChild(ccEl);
                }
                pXml->InsertEndChild(w);

            } else if (tag == "ul" || tag == "ol") {
                tinyxml2::XMLElement *w = doc.NewElement("List");
                w->SetAttribute("list_type", tag.c_str());

                if (cssProps.count("font-size")) w->SetAttribute("font_size", cssProps["font-size"].c_str());
                if (cssProps.count("color")) w->SetAttribute("font_color", cssProps["color"].c_str());
                if (cssProps.count("line-height")) w->SetAttribute("line_height", cssProps["line-height"].c_str());
                if (cssProps.count("list-style-type")) w->SetAttribute("list_style_type", cssProps["list-style-type"].c_str());
                if (cssProps.count("padding")) w->SetAttribute("padding", cssProps["padding"].c_str());
                if (cssProps.count("margin")) w->SetAttribute("margin", cssProps["margin"].c_str());
                if (cssProps.count("width")) w->SetAttribute("width", cssProps["width"].c_str());
                if (cssProps.count("height")) w->SetAttribute("height", cssProps["height"].c_str());

                libhtmlpp::Element *liEl = el->firstChild();
                while (liEl) {
                    if (liEl->getType() == libhtmlpp::HtmlEl) {
                        libhtmlpp::HtmlElement *li = static_cast<libhtmlpp::HtmlElement*>(liEl);
                        std::string liTag = li->getTagname();
                        std::transform(liTag.begin(), liTag.end(), liTag.begin(),
                            [](unsigned char c) { return std::tolower(c); });
                        if (liTag == "li") {
                            tinyxml2::XMLElement *itemEl = doc.NewElement("Item");
                            std::string itemHtml = serializeInnerHtml(li);
                            itemEl->SetText(itemHtml.c_str());
                            w->InsertEndChild(itemEl);
                        }
                    }
                    liEl = liEl->nextElement();
                }

                std::vector<std::string> knownCss = {"font-size","color","line-height","list-style-type",
                    "padding","margin","width","height","display","flex-direction","gap"};
                std::string custom = extractCustomCss(cssProps, knownCss, mediaRules);
                if (!custom.empty()) {
                    tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss");
                    ccEl->SetText(custom.c_str());
                    w->InsertEndChild(ccEl);
                }
                pXml->InsertEndChild(w);

            } else if (tag == "a") {
                bool hasChildElements = false;
                libhtmlpp::Element *aChild = el->firstChild();
                while (aChild) {
                    if (aChild->getType() == libhtmlpp::HtmlEl) { hasChildElements = true; break; }
                    aChild = aChild->nextElement();
                }

                if (!hasChildElements) {
                    tinyxml2::XMLElement *w = doc.NewElement("Button");
                    std::string href = el->getAtributte("href");
                    if (!href.empty()) w->SetAttribute("link_url", href.c_str());
                    std::string target = el->getAtributte("target");
                    if (target == "_blank") w->SetAttribute("open_new_tab", "true");

                    std::string label = serializeInnerHtml(el);
                    w->SetAttribute("label", label.c_str());

                    if (cssProps.count("background-color")) w->SetAttribute("background_color", cssProps["background-color"].c_str());
                    if (cssProps.count("color")) w->SetAttribute("text_color", cssProps["color"].c_str());
                    if (cssProps.count("font-size")) w->SetAttribute("font_size", cssProps["font-size"].c_str());
                    if (cssProps.count("padding")) w->SetAttribute("padding", cssProps["padding"].c_str());
                    if (cssProps.count("border-radius")) w->SetAttribute("border_radius", cssProps["border-radius"].c_str());
                    if (cssProps.count("border")) w->SetAttribute("border_style", cssProps["border"].c_str());
                    if (cssProps.count("width")) w->SetAttribute("width", cssProps["width"].c_str());
                    if (cssProps.count("height")) w->SetAttribute("height", cssProps["height"].c_str());

                    std::vector<std::string> knownCss = {"background-color","color","font-size","padding",
                        "border-radius","border","width","height","display","text-decoration","cursor","text-align"};
                    std::string custom = extractCustomCss(cssProps, knownCss, mediaRules);
                    if (!custom.empty()) {
                        tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss");
                        ccEl->SetText(custom.c_str());
                        w->InsertEndChild(ccEl);
                    }
                    pXml->InsertEndChild(w);
                } else {
                    tinyxml2::XMLElement *w = doc.NewElement("CustomHtml");
                    tinyxml2::XMLElement *htmlContent = doc.NewElement("HtmlContent");
                    htmlContent->SetText(serializeElement(cur).c_str());
                    w->InsertEndChild(htmlContent);
                    pXml->InsertEndChild(w);
                }

            } else if (tag == "h1" || tag == "h2" || tag == "h3" || tag == "h4" ||
                       tag == "h5" || tag == "h6" || tag == "p" || tag == "span" ||
                       tag == "b" || tag == "i" || tag == "em" || tag == "strong" ||
                       tag == "blockquote" || tag == "pre" || tag == "code") {
                tinyxml2::XMLElement *w = doc.NewElement("TextBox");

                std::string innerHtml;
                if ((tag[0] == 'h' && tag.size() == 2) || tag == "blockquote" || tag == "pre") {
                    innerHtml = "<" + tag + ">" + serializeInnerHtml(el) + "</" + tag + ">";
                } else {
                    innerHtml = serializeInnerHtml(el);
                }
                w->SetText(innerHtml.c_str());

                if (cssProps.count("font-family")) w->SetAttribute("font_family", cssProps["font-family"].c_str());
                if (cssProps.count("font-size")) w->SetAttribute("font_size", cssProps["font-size"].c_str());
                if (cssProps.count("color")) w->SetAttribute("font_color", cssProps["color"].c_str());
                if (cssProps.count("text-align")) w->SetAttribute("text_align", cssProps["text-align"].c_str());
                if (cssProps.count("font-weight")) w->SetAttribute("font_weight", cssProps["font-weight"].c_str());
                if (cssProps.count("font-style")) w->SetAttribute("font_style", cssProps["font-style"].c_str());
                if (cssProps.count("width")) w->SetAttribute("width", cssProps["width"].c_str());
                if (cssProps.count("height")) w->SetAttribute("height", cssProps["height"].c_str());
                if (!cssClass.empty()) w->SetAttribute("css_class", cssClass.c_str());
                if (!elemId.empty()) w->SetAttribute("css_id", elemId.c_str());

                std::vector<std::string> knownCss = {"font-family","font-size","color","text-align",
                    "font-weight","font-style","width","height","padding","margin","line-height"};
                std::string custom = extractCustomCss(cssProps, knownCss, mediaRules);
                if (!custom.empty()) {
                    tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss");
                    ccEl->SetText(custom.c_str());
                    w->InsertEndChild(ccEl);
                }
                pXml->InsertEndChild(w);

            } else if (tag == "style") {
                // CSS rules already collected — skip

            } else if (tag == "br" || tag == "hr") {
                tinyxml2::XMLElement *w = doc.NewElement("CustomHtml");
                tinyxml2::XMLElement *htmlContent = doc.NewElement("HtmlContent");
                htmlContent->SetText(("<" + tag + ">").c_str());
                w->InsertEndChild(htmlContent);
                pXml->InsertEndChild(w);

            } else if (tag == "meta" || tag == "link" || tag == "title" || tag == "base" ||
                       tag == "DOCTYPE" || tag == "!doctype") {
                // Skip document-level elements

            } else {
                tinyxml2::XMLElement *w = doc.NewElement("CustomHtml");
                tinyxml2::XMLElement *htmlContent = doc.NewElement("HtmlContent");
                htmlContent->SetText(serializeElement(cur).c_str());
                w->InsertEndChild(htmlContent);

                if (!style.empty()) {
                    tinyxml2::XMLElement *cssEl = doc.NewElement("CssContent");
                    cssEl->SetText(style.c_str());
                    w->InsertEndChild(cssEl);
                }
                pXml->InsertEndChild(w);
            }

            cur = cur->nextElement();
        }

        for (auto it = childFrames.rbegin(); it != childFrames.rend(); ++it) {
            workStack.push(*it);
        }
    }
}

bool blogi::htmlimport::convertHtmlToWidgetXml(const std::string &html,
                                               std::string &outXml,
                                               std::string &outGlobalCss)
{
    libhtmlpp::HtmlPage page;
    libhtmlpp::HtmlElement index;
    libhtmlpp::Element *root = nullptr;

    tinyxml2::XMLDocument xmlDoc;
    tinyxml2::XMLElement *xmlRoot = xmlDoc.NewElement("WebEditor");
    xmlRoot->SetAttribute("Version", "1.0");
    xmlRoot->SetAttribute("editor", "blogi");
    xmlDoc.InsertEndChild(xmlRoot);

    try {
        page.loadString(index, html);
        root = &index;
    } catch (...) {
        // Fallback: single CustomHtml widget with raw HTML
        tinyxml2::XMLElement *w = xmlDoc.NewElement("CustomHtml");
        tinyxml2::XMLElement *htmlContent = xmlDoc.NewElement("HtmlContent");
        htmlContent->SetText(html.c_str());
        w->InsertEndChild(htmlContent);
        xmlRoot->InsertEndChild(w);

        tinyxml2::XMLPrinter printer;
        xmlDoc.Print(&printer);
        outXml = printer.CStr();
        outGlobalCss.clear();
        return true;
    }

    // Collect all <style> elements and parse CSS
    libhtmlpp::CSSStyleSheet stylesheet;
    std::stack<libhtmlpp::Element*> searchStack;
    searchStack.push(root);
    while (!searchStack.empty()) {
        libhtmlpp::Element *cur = searchStack.top();
        searchStack.pop();
        while (cur) {
            if (cur->getType() == libhtmlpp::HtmlEl) {
                libhtmlpp::HtmlElement *hel = static_cast<libhtmlpp::HtmlElement*>(cur);
                std::string tagName = hel->getTagname();
                std::transform(tagName.begin(), tagName.end(), tagName.begin(),
                    [](unsigned char c) { return std::tolower(c); });
                if (tagName == "style") {
                    std::string cssText;
                    libhtmlpp::Element *styleChild = hel->firstChild();
                    while (styleChild) {
                        if (styleChild->getType() == libhtmlpp::TextEl) {
                            cssText += static_cast<libhtmlpp::TextElement*>(styleChild)->getText();
                        }
                        styleChild = styleChild->nextElement();
                    }
                    if (!cssText.empty()) {
                        libhtmlpp::CSSStyleSheet partial;
                        partial.parse(cssText);
                        for (const auto &rule : partial.getRules()) {
                            stylesheet.addRule(rule);
                        }
                    }
                }
                if (tagName != "style") {
                    libhtmlpp::Element *child = hel->firstChild();
                    if (child) searchStack.push(child);
                }
            }
            cur = cur->nextElement();
        }
    }

    // Extract global CSS rules
    std::string globalCss;
    for (const auto &rule : stylesheet.getRules()) {
        const std::string &sel = rule.getSelector();
        if (sel.empty()) continue;

        bool isGlobal = false;

        if (sel[0] == '@') {
            size_t parenDepth = 0;
            size_t splitPos = std::string::npos;
            for (size_t i = 0; i < sel.size(); ++i) {
                if (sel[i] == '(') ++parenDepth;
                else if (sel[i] == ')') {
                    if (parenDepth > 0) --parenDepth;
                    if (parenDepth == 0) { splitPos = i + 1; break; }
                }
            }
            if (splitPos != std::string::npos && splitPos + 1 < sel.size()) {
                size_t innerStart = sel.find_first_not_of(" \t\n\r", splitPos);
                if (innerStart != std::string::npos) {
                    std::string inner = sel.substr(innerStart);
                    std::string innerLower = inner;
                    std::transform(innerLower.begin(), innerLower.end(), innerLower.begin(),
                        [](unsigned char c) { return std::tolower(c); });
                    if (innerLower.compare(0, 5, ":root") == 0 ||
                        innerLower.find("::") != std::string::npos ||
                        innerLower == "*" || innerLower == "html" || innerLower == "body") {
                        isGlobal = true;
                    }
                }
            } else {
                isGlobal = true;
            }
        } else {
            std::string selLower = sel;
            std::transform(selLower.begin(), selLower.end(), selLower.begin(),
                [](unsigned char c) { return std::tolower(c); });
            if (selLower == ":root" || selLower == "*" || selLower == "html" ||
                selLower == "body" || selLower.compare(0, 5, ":root") == 0 ||
                selLower.find("::") != std::string::npos) {
                isGlobal = true;
            }
        }

        if (isGlobal) {
            globalCss += rule.serialize(true);
            globalCss += "\n";
        }
    }

    if (!globalCss.empty()) {
        xmlRoot->SetAttribute("GlobalCss", globalCss.c_str());
    }

    if (root) {
        htmlElementToWidgetXml(root, xmlDoc, xmlRoot, stylesheet);
    }

    tinyxml2::XMLPrinter printer;
    xmlDoc.Print(&printer);
    outXml = printer.CStr();
    outGlobalCss = globalCss;
    return true;
}
