/*******************************************************************************
 * 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 "localapi.h"
#include "documentmanager.h"
#include "publishclient.h"

#include <QFileDialog>
#include <QFile>
#include <QGuiApplication>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QSettings>
#include <QUrl>
#include <QQuickItem>

LocalApi::LocalApi(DocumentManager *doc, PublishClient *pub, QObject *parent)
    : QObject(parent), _doc(doc), _pub(pub) {}

QString LocalApi::getPluginTypes() {
    return _doc->getPluginTypes();
}

QString LocalApi::getDocTree() {
    return _doc->getDocTree();
}

QString LocalApi::addElement(const QString &parentUuid, const QString &pluginName, int position) {
    QString r = _doc->addElement(parentUuid, pluginName, position);
    emit documentChanged();
    return r;
}

QString LocalApi::removeElement(const QString &uuid) {
    QString r = _doc->removeElement(uuid);
    emit documentChanged();
    return r;
}

QString LocalApi::moveUp(const QString &uuid) {
    QString r = _doc->moveElementUp(uuid);
    emit documentChanged();
    return r;
}

QString LocalApi::moveDown(const QString &uuid) {
    QString r = _doc->moveElementDown(uuid);
    emit documentChanged();
    return r;
}

QString LocalApi::indent(const QString &uuid) {
    QString r = _doc->indentElement(uuid);
    emit documentChanged();
    return r;
}

QString LocalApi::outdent(const QString &uuid) {
    QString r = _doc->outdentElement(uuid);
    emit documentChanged();
    return r;
}

QString LocalApi::copyElement(const QString &uuid) {
    return _doc->copyElement(uuid);
}

QString LocalApi::pasteElement(const QString &targetUuid) {
    QString r = _doc->pasteElement(targetUuid);
    emit documentChanged();
    return r;
}

QString LocalApi::getElementProperties(const QString &uuid) {
    return _doc->getElementProperties(uuid);
}

QString LocalApi::setElementProperties(const QString &uuid, const QString &propsJson) {
    QString r = _doc->setElementProperties(uuid, propsJson);
    emit documentChanged();
    return r;
}

QString LocalApi::exportXml() {
    return _doc->exportXml();
}

bool LocalApi::importXml(const QString &xml) {
    bool ok = _doc->importXml(xml);
    if (ok) emit documentChanged();
    return ok;
}

QString LocalApi::renderPreview() {
    return _doc->renderToHtml();
}

void LocalApi::renderPreviewOnServer(bool mobile, const QString &target, const QString &paramsJson) {
    if (!_pub->isConfigured()) {
        emit statusMessage("Not connected — using local preview");
        emit serverPreviewReady(QString());
        return;
    }

    QString html = _doc->renderToHtml();
    if (html.isEmpty()) {
        emit serverPreviewReady(QString());
        return;
    }

    QMap<QString,QString> extra;
    if (!paramsJson.isEmpty()) {
        QJsonDocument pdoc = QJsonDocument::fromJson(paramsJson.toUtf8());
        if (pdoc.isObject()) {
            QJsonObject obj = pdoc.object();
            for (auto it = obj.begin(); it != obj.end(); ++it)
                extra.insert(it.key(), it.value().toString());
        }
    }

    _pub->renderOnServer(html, mobile, target, extra,
        [this](bool ok, const QString &rendered) {
            if (ok) {
                emit serverPreviewReady(rendered);
            } else {
                emit statusMessage("Server render failed: " + rendered);
                emit serverPreviewReady(QString());
            }
        });
}

void LocalApi::newDocument() {
    _doc->newDocument();
    _currentFile.clear();
    emit currentFileChanged(_currentFile);
    emit documentChanged();
    emit statusMessage("New document created");
}

void LocalApi::openDocument() {
    QString path = QFileDialog::getOpenFileName(
        nullptr, "Open Document", QString(), "XML Files (*.xml);;All Files (*)");
    if (path.isEmpty()) return;

    if (_doc->loadFromFile(path)) {
        _currentFile = path;
        emit currentFileChanged(_currentFile);
        emit documentChanged();
        emit statusMessage("Opened: " + path);
    } else {
        emit statusMessage("Failed to load " + path);
    }
}

void LocalApi::importHtmlFile() {
    QString path = QFileDialog::getOpenFileName(
        nullptr, "Import HTML", QString(), "HTML Files (*.html *.htm);;All Files (*)");
    if (path.isEmpty()) return;

    QFile file(path);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        emit statusMessage("Failed to open " + path);
        return;
    }
    QString html = QString::fromUtf8(file.readAll());
    file.close();

    if (_doc->importHtml(html)) {
        _currentFile.clear();
        emit currentFileChanged(_currentFile);
        emit documentChanged();
        emit statusMessage("Imported HTML: " + path);
    } else {
        emit statusMessage("Failed to import " + path);
    }
}

void LocalApi::exportHtmlFile() {
    QString html = _doc->renderToHtml();
    if (html.isEmpty()) {
        emit statusMessage("Nothing to export — document is empty.");
        return;
    }

    QString path = QFileDialog::getSaveFileName(
        nullptr, "Export HTML", QString(), "HTML Files (*.html *.htm);;All Files (*)");
    if (path.isEmpty()) return;

    QFile file(path);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        emit statusMessage("Failed to write " + path);
        return;
    }
    file.write(html.toUtf8());
    file.close();
    emit statusMessage("Exported HTML: " + path);
}

QString LocalApi::generateAiPrompt(const QString &userRequest) {
    QString prompt =
        "You are a web designer generating a single embeddable HTML snippet for a CMS widget system.\n"
        "The output will be parsed and converted into a widget tree. Provide the result as a downloadable .html file.\n\n"

        "== OUTPUT FORMAT ==\n"
        "- One self-contained HTML fragment (NO <!DOCTYPE>, <html>, <head>, or <body> tags)\n"
        "- Start with a single root <section> or <div> that wraps everything\n"
        "- Use semantic HTML: <section>, <article>, <div>, <h1>-<h6>, <p>, <ul>/<ol>, <img>, <table>, <a>\n"
        "- Every opened tag MUST be properly closed (e.g. <h1>Title</h1>, not <h1>Title)\n"
        "- No JavaScript, no <script>, no <iframe>, no <form>, no download links\n\n"

        "== STYLING ==\n"
        "The page defines these CSS custom properties. Use them for ALL colors via var(...):\n"
        "  --blogi-bg (page bg), --blogi-surface (card bg), --blogi-surface-a (elevated bg),\n"
        "  --blogi-text (primary text), --blogi-text-muted (secondary text), --blogi-accent (links/buttons),\n"
        "  --blogi-accent-dark (hover accent), --blogi-border (borders), --blogi-bg-alt (alternating bg),\n"
        "  --blogi-error (danger), --blogi-success-bg, --blogi-success-text, --blogi-nav-active,\n"
        "  --blogi-nav-hover, --blogi-nav-border\n\n"
        "- Do NOT use hardcoded colors (#fff, rgb, etc.) — use ONLY var(--blogi-*) for colors\n"
        "- Do NOT define or override these variables\n"
        "- Use relative units (em, rem, %, vw, clamp()) for font-size, padding, margin, gap, max-width\n"
        "- Use CSS Grid or Flexbox for layout (grid-template-columns, gap, etc.)\n"
        "- Place all CSS in a single <style> block at the top with BEM-style scoped class names (e.g. .my-section__card)\n"
        "- Include @media rules for responsive breakpoints (max-width: 768px and 480px)\n"
        "- The design must look good in both light and dark themes without any changes\n\n"

        "== STRUCTURE BEST PRACTICES ==\n"
        "- Use <section> as the outermost wrapper with padding and max-width\n"
        "- Use CSS Grid (<div> with display:grid) for multi-column layouts — these become Grid widgets\n"
        "- Use <div> for cards/containers — these become Container widgets\n"
        "- Use <h1>-<h6> and <p> for text content — these become TextBox widgets\n"
        "- Use <img> with src and alt attributes — these become Image widgets\n"
        "- Use <a> for buttons/links — simple links become Button widgets\n"
        "- Use <ul>/<ol> with <li> for lists — these become List widgets\n"
        "- Use <table> with <thead>/<tbody> for data tables — these become Table widgets\n"
        "- Keep the hierarchy clean: section > grid > cards > content elements\n"
        "- Prefer flat structures over deep nesting\n\n"

        "== OUTPUT ONLY THE HTML, NO EXPLANATION ==\n\n"
        "User request:\n" + userRequest;
    return prompt;
}

void LocalApi::saveDocument() {
    if (_currentFile.isEmpty()) {
        saveAsDocument();
        return;
    }
    if (_doc->saveToFile(_currentFile)) {
        emit statusMessage("Saved: " + _currentFile);
    } else {
        emit statusMessage("Failed to save " + _currentFile);
    }
}

void LocalApi::saveAsDocument() {
    QString path = QFileDialog::getSaveFileName(
        nullptr, "Save Document", QString(), "XML Files (*.xml);;All Files (*)");
    if (path.isEmpty()) return;

    if (_doc->saveToFile(path)) {
        _currentFile = path;
        emit currentFileChanged(_currentFile);
        emit statusMessage("Saved: " + path);
    } else {
        emit statusMessage("Failed to save " + path);
    }
}

void LocalApi::publishToServer() {
    if (!_pub->isConfigured()) {
        emit statusMessage("Not connected — use the connection panel.");
        return;
    }

    QString rendered = _doc->renderToHtml();
    if (rendered.isEmpty()) {
        emit statusMessage("Nothing to publish — document is empty.");
        return;
    }

    emit statusMessage("Rendering on server...");
    _pub->renderOnServer(rendered, false, [this](bool ok, const QString &msg) {
        if (ok) {
            emit statusMessage("Render complete");
            emit publishCompleted(true, msg);
        } else {
            emit statusMessage("Render failed: " + msg);
            emit publishCompleted(false, msg);
        }
    });
}

void LocalApi::connectToServer(const QString &url, const QString &user, const QString &password) {
    _pub->setServerUrl(url);

    if (user.isEmpty()) {
        /* No credentials — verify server is reachable */
        emit statusMessage("Testing connection...");
        _pub->testConnection([this, url](bool ok, const QString &msg) {
            if (ok) {
                emit connectionStatusChanged(true, "Connected to " + url);
                emit statusMessage("Connected to " + url);
            } else {
                emit connectionStatusChanged(false, msg);
                emit statusMessage("Connection failed: " + msg);
            }
        });
        return;
    }

    emit statusMessage("Logging in...");
    _pub->login(user, password, [this, url](bool ok, const QString &msg) {
        if (ok) {
            emit connectionStatusChanged(true, "Connected to " + url);
            emit statusMessage("Logged in to " + url);
        } else {
            emit connectionStatusChanged(false, msg);
            emit statusMessage("Login failed: " + msg);
        }
    });
}

void LocalApi::disconnectServer() {
    _pub->abort();
    _pub->setServerUrl("");
    _pub->setAuthToken("");
    emit connectionStatusChanged(false, "Disconnected");
    emit statusMessage("Disconnected from server");
}

void LocalApi::abortConnection() {
    _pub->abort();
    emit connectionStatusChanged(false, "Aborted");
    emit statusMessage("Connection aborted");
}

void LocalApi::setIgnoreSslErrors(bool ignore) {
    _pub->setIgnoreSslErrors(ignore);
}

QString LocalApi::serverUrl() const {
    return _pub->serverUrl();
}

void LocalApi::fetchAlbums() {
    QJsonArray cmds;
    QJsonObject cmd;
    cmd["command"] = "media_list_albums";
    cmds.append(cmd);

    _pub->apiRequest(cmds, [this](bool ok, const QJsonArray &resp) {
        if (!ok) {
            emit statusMessage("Failed to fetch albums");
            return;
        }
        /* Find the media_list_albums response (skip auth_success) */
        for (int i = 0; i < resp.size(); ++i) {
            QJsonObject obj = resp.at(i).toObject();
            if (obj.contains("albums") || obj.contains("status")) {
                emit albumsLoaded(QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::Compact)));
                return;
            }
        }
        emit albumsLoaded("{}");
    });
}

void LocalApi::fetchMedia(const QString &albumId) {
    QJsonArray cmds;
    QJsonObject cmd;
    cmd["command"] = "media_list_media";
    cmd["album_id"] = albumId;
    cmds.append(cmd);

    _pub->apiRequest(cmds, [this](bool ok, const QJsonArray &resp) {
        if (!ok) {
            emit statusMessage("Failed to fetch media");
            return;
        }
        for (int i = 0; i < resp.size(); ++i) {
            QJsonObject obj = resp.at(i).toObject();
            if (obj.contains("media") || obj.contains("status")) {
                emit mediaLoaded(QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::Compact)));
                return;
            }
        }
        emit mediaLoaded("{}");
    });
}

void LocalApi::fetchPublishTargets() {
    QJsonArray cmds;
    QJsonObject cmd;
    cmd["command"] = "list_publish_targets";
    cmds.append(cmd);

    _pub->apiRequest(cmds, [this](bool ok, const QJsonArray &resp) {
        if (!ok) {
            emit statusMessage("Failed to fetch publish targets");
            return;
        }
        for (int i = 0; i < resp.size(); ++i) {
            QJsonObject obj = resp.at(i).toObject();
            if (obj.contains("targets")) {
                emit publishTargetsLoaded(QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::Compact)));
                return;
            }
        }
        emit publishTargetsLoaded("{}");
    });
}

void LocalApi::fetchThemeColors() {
    QJsonArray cmds;
    QJsonObject cmd;
    cmd["command"] = "get_theme_colors";
    cmds.append(cmd);

    _pub->apiRequest(cmds, [this](bool ok, const QJsonArray &resp) {
        if (!ok) {
            emit statusMessage("Failed to fetch theme colors");
            return;
        }
        for (int i = 0; i < resp.size(); ++i) {
            QJsonObject obj = resp.at(i).toObject();
            if (obj.contains("colors")) {
                emit themeColorsLoaded(QString::fromUtf8(QJsonDocument(obj["colors"].toObject()).toJson(QJsonDocument::Compact)));
                return;
            }
        }
        emit themeColorsLoaded("{}");
    });
}

void LocalApi::fetchSurveyList() {
    QJsonArray cmds;
    QJsonObject cmd;
    cmd["command"] = "list_surveys";
    cmds.append(cmd);

    _pub->apiRequest(cmds, [this](bool ok, const QJsonArray &resp) {
        if (!ok) {
            emit statusMessage("Failed to fetch survey list");
            return;
        }
        for (int i = 0; i < resp.size(); ++i) {
            QJsonObject obj = resp.at(i).toObject();
            if (obj.contains("surveys")) {
                QJsonArray surveys = obj["surveys"].toArray();
                QJsonArray names;
                for (int j = 0; j < surveys.size(); ++j)
                    names.append(surveys.at(j).toString());
                emit surveyListLoaded(QString::fromUtf8(QJsonDocument(names).toJson(QJsonDocument::Compact)));
                return;
            }
        }
        emit surveyListLoaded("[]");
    });
}

QString LocalApi::collectRepeaterParams(QObject *repeater) {
    if (!repeater) return "{}";
    QJsonObject params;
    int count = repeater->property("count").toInt();
    for (int i = 0; i < count; ++i) {
        QQuickItem *delegate = nullptr;
        QMetaObject::invokeMethod(repeater, "itemAt",
                                  Q_RETURN_ARG(QQuickItem*, delegate),
                                  Q_ARG(int, i));
        if (!delegate) continue;
        QString key = delegate->property("fieldKey").toString();
        if (key.isEmpty()) continue;
        QVariant val = delegate->property("fieldValue");
        if (val.typeId() == QMetaType::Bool)
            params[key] = val.toBool();
        else
            params[key] = val.toString();
    }
    return QString::fromUtf8(QJsonDocument(params).toJson(QJsonDocument::Compact));
}

QString LocalApi::jsonSet(const QString &json, const QString &key, const QString &value) {
    QJsonDocument doc = QJsonDocument::fromJson(json.toUtf8());
    QJsonObject obj = doc.isObject() ? doc.object() : QJsonObject();
    obj[key] = value;
    return QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::Compact));
}

void LocalApi::publishToTarget(const QString &target, const QString &paramsJson) {
    if (!_pub->isConfigured()) {
        emit publishCompleted(false, "Not connected");
        return;
    }

    emit statusMessage("Publishing to " + target + "...");

    QJsonArray cmds;
    QJsonObject cmd;
    cmd["command"] = "publish";
    cmd["target"] = target;
    cmd["html"] = _doc->renderToHtml();

    if (!paramsJson.isEmpty()) {
        QJsonDocument pdoc = QJsonDocument::fromJson(paramsJson.toUtf8());
        if (pdoc.isObject())
            cmd["params"] = pdoc.object();
    }
    cmds.append(cmd);

    _pub->apiRequest(cmds, [this, target](bool ok, const QJsonArray &resp) {
        if (!ok) {
            emit publishCompleted(false, "Network error");
            emit statusMessage("Publish failed: network error");
            return;
        }
        for (int i = 0; i < resp.size(); ++i) {
            QJsonObject obj = resp.at(i).toObject();
            QString status = obj["status"].toString();
            if (status == "success") {
                QString url = obj["url"].toString();
                QString msg = url.isEmpty() ? "Published successfully" : url;
                emit publishCompleted(true, msg);
                emit statusMessage("Published to " + target);
                return;
            } else if (status == "duplicate") {
                emit publishCompleted(false, "duplicate:" + obj["existing_id"].toString()
                                             + ":" + obj["message"].toString("Duplicate entry"));
                emit statusMessage("Duplicate — overwrite?");
                return;
            } else if (status == "error") {
                emit publishCompleted(false, obj["message"].toString("Publish failed"));
                emit statusMessage("Publish failed: " + obj["message"].toString());
                return;
            }
        }
        emit publishCompleted(false, "Unexpected response");
    });
}

void LocalApi::publishToTargetOverwrite(const QString &target, const QString &paramsJson, const QString &existingId) {
    if (!_pub->isConfigured()) {
        emit publishCompleted(false, "Not connected");
        return;
    }

    emit statusMessage("Overwriting " + target + "...");

    QJsonArray cmds;
    QJsonObject cmd;
    cmd["command"] = "publish";
    cmd["target"] = target;
    cmd["overwrite"] = true;
    cmd["existing_id"] = existingId;
    cmd["html"] = _doc->renderToHtml();

    if (!paramsJson.isEmpty()) {
        QJsonDocument pdoc = QJsonDocument::fromJson(paramsJson.toUtf8());
        if (pdoc.isObject())
            cmd["params"] = pdoc.object();
    }
    cmds.append(cmd);

    _pub->apiRequest(cmds, [this, target](bool ok, const QJsonArray &resp) {
        if (!ok) {
            emit publishCompleted(false, "Network error");
            return;
        }
        for (int i = 0; i < resp.size(); ++i) {
            QJsonObject obj = resp.at(i).toObject();
            QString status = obj["status"].toString();
            if (status == "success") {
                QString url = obj["url"].toString();
                emit publishCompleted(true, url.isEmpty() ? "Overwritten successfully" : url);
                emit statusMessage("Published to " + target);
                return;
            } else if (status == "error") {
                emit publishCompleted(false, obj["message"].toString("Overwrite failed"));
                emit statusMessage("Overwrite failed: " + obj["message"].toString());
                return;
            }
        }
        emit publishCompleted(false, "Unexpected response");
    });
}

void LocalApi::refresh() {
    emit documentChanged();
}

/* ---------- Saved connections CRUD ---------- */

QString LocalApi::getSavedConnections() {
    QSettings settings;
    QJsonArray arr;
    int count = settings.beginReadArray("connections");
    for (int i = 0; i < count; ++i) {
        settings.setArrayIndex(i);
        QJsonObject obj;
        obj["name"]      = settings.value("name").toString();
        obj["url"]       = settings.value("url").toString();
        obj["user"]      = settings.value("user").toString();
        obj["password"]  = settings.value("password").toString();
        obj["ignoreSsl"] = settings.value("ignoreSsl").toBool();
        arr.append(obj);
    }
    settings.endArray();
    return QString::fromUtf8(QJsonDocument(arr).toJson(QJsonDocument::Compact));
}

void LocalApi::saveConnection(const QString &name, const QString &url, const QString &user, const QString &password, bool ignoreSsl) {
    QJsonArray arr = QJsonDocument::fromJson(getSavedConnections().toUtf8()).array();
    QJsonObject obj;
    obj["name"]      = name;
    obj["url"]       = url;
    obj["user"]      = user;
    obj["password"]  = password;
    obj["ignoreSsl"] = ignoreSsl;
    arr.append(obj);

    QSettings settings;
    settings.beginWriteArray("connections", arr.size());
    for (int i = 0; i < arr.size(); ++i) {
        settings.setArrayIndex(i);
        QJsonObject o = arr.at(i).toObject();
        settings.setValue("name",      o["name"].toString());
        settings.setValue("url",       o["url"].toString());
        settings.setValue("user",      o["user"].toString());
        settings.setValue("password",  o["password"].toString());
        settings.setValue("ignoreSsl", o["ignoreSsl"].toBool());
    }
    settings.endArray();
    emit savedConnectionsChanged();
}

void LocalApi::updateConnection(const QString &oldName, const QString &name, const QString &url, const QString &user, const QString &password, bool ignoreSsl) {
    QJsonArray arr = QJsonDocument::fromJson(getSavedConnections().toUtf8()).array();
    for (int i = 0; i < arr.size(); ++i) {
        QJsonObject o = arr.at(i).toObject();
        if (o["name"].toString() == oldName) {
            o["name"]      = name;
            o["url"]       = url;
            o["user"]      = user;
            o["password"]  = password;
            o["ignoreSsl"] = ignoreSsl;
            arr[i] = o;
            break;
        }
    }

    QSettings settings;
    settings.beginWriteArray("connections", arr.size());
    for (int i = 0; i < arr.size(); ++i) {
        settings.setArrayIndex(i);
        QJsonObject o = arr.at(i).toObject();
        settings.setValue("name",      o["name"].toString());
        settings.setValue("url",       o["url"].toString());
        settings.setValue("user",      o["user"].toString());
        settings.setValue("password",  o["password"].toString());
        settings.setValue("ignoreSsl", o["ignoreSsl"].toBool());
    }
    settings.endArray();
    emit savedConnectionsChanged();
}

void LocalApi::deleteConnection(const QString &name) {
    QJsonArray arr = QJsonDocument::fromJson(getSavedConnections().toUtf8()).array();
    QJsonArray filtered;
    for (int i = 0; i < arr.size(); ++i) {
        QJsonObject o = arr.at(i).toObject();
        if (o["name"].toString() != name)
            filtered.append(o);
    }

    QSettings settings;
    settings.beginWriteArray("connections", filtered.size());
    for (int i = 0; i < filtered.size(); ++i) {
        settings.setArrayIndex(i);
        QJsonObject o = filtered.at(i).toObject();
        settings.setValue("name",      o["name"].toString());
        settings.setValue("url",       o["url"].toString());
        settings.setValue("user",      o["user"].toString());
        settings.setValue("password",  o["password"].toString());
        settings.setValue("ignoreSsl", o["ignoreSsl"].toBool());
    }
    settings.endArray();
    emit savedConnectionsChanged();
}
