/*
 * SPDX-FileCopyrightText: Copyright 2023-2025 Open Mobile Platform LLC <community@omp.ru>
 * SPDX-License-Identifier: Proprietary
 */

#pragma once

#include "favicon.h"
#include "iwebview.h"

#include <memory>
#include <QJSValue>
#include <QObject>
#include <QString>

namespace Cef {

class Browser;

}

namespace Aurora {
namespace WebView {

/**
 * @class WebView
 * @brief Use it to load web content and interact with it.
 *
 * WebView is the main class of WebView library.
 * It loads webpages and provides interaction mechanisms for them.
 *
 * You can create multiple WebViews to render different webpages simultaneously.
 * For instance, web browser tabs could be implemented as distinct WebView instances.
 *
 * If you need to render content in QML, you can instead use WebViewItem.
 * You can also implement your own rendering, use IRendering for this purpose.
 *
 */
class WebView : public QObject, public IWebView
{
    Q_OBJECT

    /// unique identifier of the current instance
    Q_PROPERTY(int id READ id)
    /// URL of a loaded web-page.
    ///
    /// Setting this property leads to loading of a new web-page.
    ///
    /// On successful load, loadFinished() signal is emmited, on fail, loadError() signal is emmited.
    Q_PROPERTY(QString url READ url WRITE setUrl NOTIFY urlChanged)
    /// the title of the loaded URL (web-page). Can be empty.
    Q_PROPERTY(QString title READ title NOTIFY titleChanged)
    /// returns true when WebView instance is loading a URL
    Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged)
    /// loading progress, ranges from 0.0 to 1.0
    Q_PROPERTY(double loadingProgress READ loadingProgress NOTIFY loadingProgressChanged)
    /// User-Agent
    ///
    /// When setting a value, userAgentOverride becomes true.
    ///
    /// formFactor has effect only when userAgent is default.
    ///
    /// Set an empty value to reset userAgent to a default value.
    ///
    /// @link useragent.qml Usage Example @endlink
    Q_PROPERTY(QString userAgent READ userAgent WRITE setUserAgent NOTIFY userAgentChanged)
    /// Device Pixel Ratio
    Q_PROPERTY(qreal dpr READ DPR WRITE setDPR)
    /// Area to render content
    Q_PROPERTY(QRect renderRect READ renderRect WRITE setRenderRect)
    /// Current read-only scroll offset value along x axis
    Q_PROPERTY(double scrollOffsetX READ scrollOffsetX NOTIFY scrollOffsetXChanged)
    /// Current read-only scroll offset value along y axis
    Q_PROPERTY(double scrollOffsetY READ scrollOffsetY NOTIFY scrollOffsetYChanged)
    /// Device form-factor hint. Use values from DeviceMode::FormFactor.
    ///
    /// Has effect only when the userAgent is default.
    ///
    /// @link useragent.qml Usage Example @endlink
    Q_PROPERTY(int formFactor READ formFactor WRITE setFormFactor NOTIFY formFactorChanged)
    /// true when userAgent property is set to some custom value, false otherwise.
    ///
    /// @link useragent.qml Usage Example @endlink
    Q_PROPERTY(bool userAgentOverride READ userAgentOverride NOTIFY userAgentOverrideChanged)
    /// @brief PWA manifest URL of current website (empty if not present or page is still loading)
    Q_PROPERTY(QString pwaManifest READ pwaManifest NOTIFY pwaManifestChanged)
    /// @brief indicates if current WebView is a Popup
    Q_PROPERTY(bool isPopup READ isPopup)

public:
    /// the instance will be functional only after WebEngineContext initialization
    WebView(QObject *parent = Q_NULLPTR, bool isPrivate = false, bool isPopup = false);
    virtual ~WebView();

    /// reload opened URL
    Q_INVOKABLE void reload() override;
    /// stop loading a URL if loading
    Q_INVOKABLE void stopLoad() override;
    /// continue rendering
    Q_INVOKABLE void startRender() override;
    /// pause rendering (image will become still, device resource usage will be decreased)
    Q_INVOKABLE void stopRender() override;
    /**
     * @brief search for a text on a webpage
     *
     * @param text text to search for
     * @param forward indicates whether to search forward or backward within the page
     * @param matchCase indicates whether the search should be case-sensitive
     * @param findNext indicates whether to move to the next found element and highlight it or not
     *
     * @link searchonpage.qml Usage Example @endlink
     */
    Q_INVOKABLE void find(const QString &text, bool forward, bool matchCase, bool findNext) override;
    /**
     * @brief cancel all searches that are currently active
     *
     * @param clearSelection clear current selection
     */
    Q_INVOKABLE void stopFinding(bool clearSelection) override;
    /// returns true if the WebView can navigate backwards
    Q_INVOKABLE bool canGoBack() override;
    /// navigate backwards to the previous page in the WebView history
    Q_INVOKABLE void goBack() override;
    /// returns true if the WebView can navigate forwards
    Q_INVOKABLE bool canGoForward() override;
    /// navigate forwards to the next page in the WebView history
    Q_INVOKABLE void goForward() override;

    // properties
    /// unique identifier
    int id() const override;
    /// true if the WebView is valid
    bool isValid() const override;
    /// currently loaded URL
    QString url() const override;
    /// URL property setter
    void setUrl(const QString &url) override;
    /// current title of the loaded web-page
    QString title() const override;
    /// check if WebView instance is loading a web-page
    bool loading() const override;
    /// current loading progress, ranges from 0.0 to 1.0
    double loadingProgress() const override;
    /// User-Agent
    QString userAgent() const override;
    /// set the User-Agent. Page reload is required for new value to be applied.
    ///
    /// Empty string disables override mode and sets the default value.
    void setUserAgent(const QString &userAgent) override;
    /// Device Pixel Ratio
    qreal DPR() const override;
    /// set the DPR. Reload to apply the new value.
    void setDPR(qreal DRP) override;
    /// area to render content
    QRect renderRect() const override;
    /// set area to render content
    void setRenderRect(QRect renderRect) override;

    void setOrientation(int orientation) override;

    void updateSize(const QRectF rect) override;

    /// current scroll offset along x axis
    double scrollOffsetX() const override;
    /// current scroll offset along y axis
    double scrollOffsetY() const override;
    /// device form-factor
    int formFactor() const override;
    /// set device form-factor
    void setFormFactor(int mode) override;
    /// User-Agent override mode
    bool userAgentOverride() const override;
    /// @brief get a PWA manifest URL of current website (if present)
    /// @return manifest URL (empty if does not exist)
    QString pwaManifest() override;
    /// @brief indicates if current WebView is a Popup
    bool isPopup() const override;

    /// sets insets values.
    ///
    /// The values define a rectangle by its top, right, bottom, and left insets
    /// from the edge of the viewport, which is safe to put content into without
    /// risking it being cut off by the shape of a non‑rectangular display. The
    /// CSS global variables (safe-area-inset-*) take that values respectively.
    /// The values take effect only in the fullscreen mode. To reset insets,
    /// pass zeroed inset parameters.
    Q_INVOKABLE void setInsets(int top, int right, int bottom, int left) override;

    /// load raw HTML text
    Q_INVOKABLE void loadHtml(const QString &html) override;
    /// clear text selection
    Q_INVOKABLE void clearTextSelection() override;
    /// run JavaScript snippet in the context of the loaded document
    ///
    /// @link runjs.qml Usage Example @endlink
    Q_INVOKABLE void runJavaScript(
        const QString &script, const QJSValue &callback, const QJSValue &errorCallback) override;
    /// send an asynchronous message with the specified name and data
    ///
    /// @link asyncmessages.qml Usage Example @endlink
    Q_INVOKABLE void sendAsyncMessage(const QString &name, const QVariant &data) override;
    /// register a listener for asynchronous messages with the specified name
    Q_INVOKABLE void addMessageListener(const QString &name) override;
    /// exit from fullscreen mode
    Q_INVOKABLE void exitFullscreenMode() override;
    /// If |isFavicon| is true, cookies are not
    /// sent and not accepted during download
    Q_INVOKABLE void downloadImage(const QString &url, bool isFavicon = true) override;
    /// download a string resource (e.g., a json file)
    /// content size must not exceed 5mb, otherwise it won't be downloaded
    Q_INVOKABLE void downloadStringResource(const QString &url) override;

    IRendering *renderingExtension() const override;
    IInputExtension *inputExtension() const override;
    ITextExtension *textExtension() const override;
    IContextMenuExtension *contextMenuExtension() const override;
    IPdfPrint *pdfPrintExtension() const override;
    IMediaPlaybackExtension *mediaPlaybackExtension() const override;
    IOverscrollExtension *overscrollExtension() const override;
    ILoadRequestExtension *loadRequestExtension() const override;
    IPopupExtension *popupExtension() const override;
    IFaviconExtension *faviconExtension() const override;
    IConnectionInfoExtension *connectionInfoExtension() const override;
    IJavaScriptExtension *javaScriptExtension() const override;
    IPasswordManagerExtension *passwordManagerExtension() const override;

signals:
    /**
     * @brief signal on URL change
     *
     * @param url loaded URL
     */
    void urlChanged(QString url);
    /**
     * @brief signal on title change
     *
     * @param title web-page title
     */
    void titleChanged(QString title);
    /**
     * @brief signal on loading status change
     *
     * @param status loading status. True if a URL is being loaded.
     */
    void loadingChanged(bool status);
    /**
     * @brief emitted when loading progress changed
     *
     * @param progress determines the page loading progress, ranges from 0.0 to 1.0
     */
    void loadingProgressChanged(double progress);
    /**
     * @brief signal on main frame load start
     *
     */
    void loadStart();
    /**
     * @brief signal on main frame document available
     *
     */
    void documentAvailable();
    /**
     * @brief emitted when a related renderer was terminated for some reason
     *
     * It can be sent due to memory release by termination of a renderer or due
     * to an uncaught JavaScript exception in a renderer (renderer crash).
     *
     */
    void rendererTerminated();
    /**
     * @brief signal on load finish
     *
     * @param httpStatusCode status code returned by the server.
     */
    void loadFinished(int httpStatusCode);
    /**
     * @brief signal on loading error
     *
     * @param url url which led to the error.
     * @param errorCode error code. See NetError.
     */
    void loadError(QString url, int errorCode);
    /**
     * @brief signal on receival of an asynchronous message from JavaScript code
     *
     * @param name message name
     * @param data message data
     */
    void recvAsyncMessage(const QString name, const QVariant data);
    /**
     * @brief signal on User-Agent change
     *
     */
    void userAgentChanged();
    /**
     * @brief signal on device form-factor change
     *
     */
    void formFactorChanged();
    /**
     * @brief signal on User-Agent override mode change
     *
     */
    void userAgentOverrideChanged();
    /**
     * @brief signal is sent to report the search results
     *
     * For each call of CEF Find function, FindHandler function is called once or multiple times.
     * If the element is not found, only one FindHandler call will be made with the
     * finalUpdate parameter set to true. If multiple items are found, FindHandler is called
     * multiple times. The last call to FindHandler has the finalUpdate parameter set
     * to true.
     *
     * @param identifier current find notification identifier
     * @param count is the number of matches currently identified
     * @param selectionRect is the location of where the match was found (in window coordinates)
     * @param activeMatchOrdinal ordinal number of this position in the search results
     * @param finalUpdate is true if this is the last FindHandler call for this Find request
     *
     */
    void findResultAvailable(
        int identifier, int count, QRect selectionRect, int activeMatchOrdinal, bool finalUpdate);
    /**
     * @brief emitted when scroll offset changes along x axis
     */
    void scrollOffsetXChanged();
    /**
     * @brief emitted when scroll offset changes along y axis
     */
    void scrollOffsetYChanged();
    /**
     * @brief emitted when fullscreen mode is activated or deactivated.
     *
     * @param fullscreen true if fullscreen mode was activated
     */
    void fullscreenModeChanged(bool fullscreen);
    /**
     * @brief emitted when a PWA manifest url is updated
     *
     * PWA manifest url update happens when a PWA is detected
     * on current website, or when current webview url is changed
     *
     * @param manifestUrl url to PWA manifest, might be empty if
     * a PWA is not available or when a page is still loading
     */
    void pwaManifestChanged(QString manifestUrl);

    /**
     * @brief emitted when WebView is requested to close
     */
    void closing();
    /**
     * @brief emitted when a result of `downloadImage()` call arrives
     */
    void imageDownloadFinished(const Favicon &image);
    /**
     * @brief emitted when string resource download result is available
     */
    void stringResourceDownloadFinished(
        bool success, const QString &resourceUrl, const QString &resourceContent);

private slots:
    void updateScrollOffsets(double x, double y);
    void onRecvAsyncMessage(const QString name, const QVariant data);
    void invalidate();

private:
    std::shared_ptr<Cef::Browser> m_browser;
    bool m_isValid; // WebView is valid as long as the browser is valid

    // render to item's size by default
    bool m_defaultRenderRect;
    double m_scrollOffsetX, m_scrollOffsetY;
};

} // namespace WebView
} // namespace Aurora
