// MIT License
//
// Copyright (c) 2018-2025 Jakub Melka and Contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#ifndef PDFTEXTEDITPSEUDOWIDGET_H
#define PDFTEXTEDITPSEUDOWIDGET_H

#include "pdfglobal.h"
#include "pdfform.h"

#include <QRectF>
#include <QColor>

class QWidget;
class QKeyEvent;

namespace pdf
{

/// "Pseudo" widget, which is emulating text editor, which can be single line, or multiline.
/// Passwords can also be edited and editor can be read only.
class PDFTextEditPseudowidget
{
public:
    explicit PDFTextEditPseudowidget(PDFFormField::FieldFlags flags);

    void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event);
    void keyPressEvent(QWidget* widget, QKeyEvent* event);

    inline bool isReadonly() const { return m_flags.testFlag(PDFFormField::ReadOnly); }
    inline bool isMultiline() const { return m_flags.testFlag(PDFFormField::Multiline); }
    inline bool isPassword() const { return m_flags.testFlag(PDFFormField::Password); }
    inline bool isFileSelect() const { return m_flags.testFlag(PDFFormField::FileSelect); }
    inline bool isComb() const { return m_flags.testFlag(PDFFormField::Comb); }

    inline bool isEmpty() const { return m_editText.isEmpty(); }
    inline bool isTextSelected() const { return !isEmpty() && getSelectionLength() > 0; }
    inline bool isWholeTextSelected() const { return !isEmpty() && getSelectionLength() == m_editText.length(); }

    inline int getTextLength() const { return m_editText.length(); }
    inline int getSelectionLength() const { return m_selectionEnd - m_selectionStart; }

    inline int getPositionCursor() const { return m_positionCursor; }
    inline int getPositionStart() const { return 0; }
    inline int getPositionEnd() const { return getTextLength(); }

    inline void clearSelection() { m_selectionStart = m_selectionEnd = 0; }

    inline const QString& getText() const { return m_editText; }
    inline QString getSelectedText() const { return m_editText.mid(m_selectionStart, getSelectionLength()); }

    /// Sets (updates) text selection
    /// \param startPosition From where we are selecting text
    /// \param selectionLength Selection length (positive - to the right, negative - to the left)
    void setSelection(int startPosition, int selectionLength);

    /// Moves cursor position. It behaves as usual in text boxes,
    /// when user moves the cursor. If \p select is true, then
    /// selection is updated.
    /// \param position New position of the cursor
    /// \param select Select text when moving the cursor?
    void setCursorPosition(int position, bool select);

    /// Sets text content of the widget. This functions sets the text,
    /// even if widget is readonly.
    /// \param text Text to be set
    void setText(const QString& text);

    /// Sets widget appearance, such as font, font size, color, text alignment,
    /// and rectangle, in which widget resides on page (in page coordinates)
    /// \param appearance Appearance
    /// \param textAlignment Text alignment
    /// \param rect Widget rectangle in page coordinates
    /// \param maxTextLength Maximal text length
    void setAppearance(const PDFAnnotationDefaultAppearance& appearance,
                       Qt::Alignment textAlignment,
                       QRectF rect,
                       int maxTextLength);

    /// Sets widget appearance, such as font, font size, color, text alignment,
    /// and rectangle, in which widget resides on page (in page coordinates)
    /// \param font Font
    /// \param textAlignment Text alignment
    /// \param rect Widget rectangle in page coordinates
    /// \param maxTextLength Maximal text length
    /// \param textColor Color
    void setAppearance(const QFont& font,
                       Qt::Alignment textAlignment,
                       QRectF rect,
                       int maxTextLength,
                       QColor textColor);

    void performCut();
    void performCopy();
    void performPaste();
    void performClear();
    void performSelectAll();
    void performBackspace();
    void performDelete();
    void performRemoveSelectedText();
    void performInsertText(const QString& text);

    /// Draw text edit using given parameters
    /// \param parameters Parameters
    void draw(AnnotationDrawParameters& parameters, bool edit) const;

    /// Returns valid cursor position retrieved from position in the widget.
    /// \param point Point in page coordinate space
    /// \param edit Are we using edit transformations?
    /// \returns Cursor position
    int getCursorPositionFromWidgetPosition(const QPointF& point, bool edit) const;

    inline int getCursorForward(QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(getSingleStepForward(), mode); }
    inline int getCursorBackward(QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(getSingleStepBackward(), mode); }
    inline int getCursorCharacterForward() const { return getCursorForward(QTextLayout::SkipCharacters); }
    inline int getCursorCharacterBackward() const { return getCursorBackward(QTextLayout::SkipCharacters); }
    inline int getCursorWordForward() const { return getCursorForward(QTextLayout::SkipWords); }
    inline int getCursorWordBackward() const { return getCursorBackward(QTextLayout::SkipWords); }
    inline int getCursorDocumentStart() const { return (getSingleStepForward() > 0) ? getPositionStart() : getPositionEnd(); }
    inline int getCursorDocumentEnd() const { return (getSingleStepForward() > 0) ? getPositionEnd() : getPositionStart(); }
    inline int getCursorLineStart() const { return (getSingleStepForward() > 0) ? getCurrentLineTextStart() : getCurrentLineTextEnd(); }
    inline int getCursorLineEnd() const { return (getSingleStepForward() > 0) ? getCurrentLineTextEnd() : getCurrentLineTextStart(); }
    inline int getCursorNextLine() const { return getCurrentLineTextEnd(); }
    inline int getCursorPreviousLine() const { return getNextPrevCursorPosition(getCurrentLineTextStart(), -1, QTextLayout::SkipCharacters); }

    const QRectF& getWidgetRect() const { return m_widgetRect; }
    QFont getFont() const { return m_textLayout.font(); }
    QColor getFontColor() const { return m_textColor; }

private:
    /// This function does following things:
    ///     1) Clamps edit text to fit maximum length
    ///     2) Creates display string from edit string
    ///     3) Updates text layout
    void updateTextLayout();

    /// Returns single step forward, which is determined
    /// by cursor move style and layout direction.
    int getSingleStepForward() const;

    /// Returns single step backward, which is determined
    /// by cursor move style and layout direction.
    int getSingleStepBackward() const { return -getSingleStepForward(); }

    /// Returns next/previous position, by number of steps,
    /// using given cursor mode (skipping characters or whole words).
    /// \param steps Number of steps to proceed (can be negative number)
    /// \param mode Skip mode - letters or words?
    int getNextPrevCursorPosition(int steps, QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(m_positionCursor, steps, mode); }

    /// Returns next/previous position from reference cursor position, by number of steps,
    /// using given cursor mode (skipping characters or whole words).
    /// \param referencePosition Reference cursor position
    /// \param steps Number of steps to proceed (can be negative number)
    /// \param mode Skip mode - letters or words?
    int getNextPrevCursorPosition(int referencePosition, int steps, QTextLayout::CursorMode mode) const;

    /// Returns current line text start position
    int getCurrentLineTextStart() const;

    /// Returns current line text end position
    int getCurrentLineTextEnd() const;

    /// Creates text box transform matrix, which transforms from
    /// widget space to page space.
    /// \param edit Create matrix for text editing?
    QTransform createTextBoxTransformMatrix(bool edit) const;

    /// Returns vector of cursor positions (which may be not equal
    /// to edit string length, because edit string can contain surrogates,
    /// or graphemes, which are single glyphs, but represented by more
    /// 16-bit unicode codes).
    std::vector<int> getCursorPositions() const;

    int getCursorLineUp() const;
    int getCursorLineDown() const;

    PDFFormField::FieldFlags m_flags;

    /// Text edited by the user
    QString m_editText;

    /// Text, which is displayed. It can differ from text
    /// edited by user, in case password is being entered.
    QString m_displayText;

    /// Text layout
    QTextLayout m_textLayout;

    /// Character for displaying passwords
    QChar m_passwordReplacementCharacter;

    int m_selectionStart;
    int m_selectionEnd;
    int m_positionCursor;
    int m_maxTextLength;

    QRectF m_widgetRect;
    QColor m_textColor;
};

}   // namespace pdf

#endif // PDFTEXTEDITPSEUDOWIDGET_H
