// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant

#ifndef QQMLDOMCODEFORMATTER_P_H
#define QQMLDOMCODEFORMATTER_P_H

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists purely as an
// implementation detail.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

#include "qqmldom_global.h"
#include "qqmldomfunctionref_p.h"
#include "qqmldomscanner_p.h"
#include "qqmldomlinewriter_p.h"

#include <QtCore/QStack>
#include <QtCore/QList>
#include <QtCore/QSet>
#include <QtCore/QMetaObject>

QT_BEGIN_NAMESPACE

namespace QQmlJS {
namespace Dom {

class QMLDOM_EXPORT FormatTextStatus
{
    Q_GADGET
public:
    enum class StateType : quint8 {
        Invalid = 0,

        TopmostIntro, // The first line in a "topmost" definition.

        TopQml, // root state for qml
        TopJs, // root for js
        ObjectdefinitionOrJs, // file starts with identifier

        MultilineCommentStart,
        MultilineCommentCont,

        PragmaStart, // after 'pragma'
        PragmaMaybeValue, // after pragma name
        PragmaValue, // after ':' or ','
        PragmaMaybeMoreValues, // after pragma value

        ImportStart, // after 'import'
        ImportMaybeDotOrVersionOrAs, // after string or identifier
        ImportDot, // after .
        ImportMaybeAs, // after version
        ImportAs,

        PropertyStart, // after 'property'
        PropertyModifiers, // after 'default' or readonly
        RequiredProperty, // after required
        PropertyListOpen, // after 'list' as a type
        PropertyName, // after the type
        PropertyMaybeInitializer, // after the identifier
        ComponentStart, // after component
        ComponentName, // after component Name

        TypeAnnotation, // after a : starting a type annotation
        TypeParameter, // after a < in a type annotation (starting type parameters)

        EnumStart, // after 'enum'

        SignalStart, // after 'signal'
        SignalMaybeArglist, // after identifier
        SignalArglistOpen, // after '('

        LambdaStart, // after '=>'
        FunctionStart, // after 'function'
        FunctionArglistOpen, // after '(' starting function argument list
        FunctionArglistClosed, // after ')' in argument list, expecting '{'

        BindingOrObjectdefinition, // after an identifier

        BindingAssignment, // after : in a binding
        ObjectdefinitionOpen, // after {

        Expression,
        ExpressionContinuation, // at the end of the line, when the next line definitely is a
                                // continuation
        ExpressionMaybeContinuation, // at the end of the line, when the next line may be an
                                     // expression
        ExpressionOrObjectdefinition, // after a binding starting with an identifier ("x: foo")
        ExpressionOrLabel, // when expecting a statement and getting an identifier

        ParenOpen, // opening ( in expression
        BracketOpen, // opening [ in expression
        ObjectliteralOpen, // opening { in expression

        ObjectliteralAssignment, // after : in object literal

        BracketElementStart, // after starting bracket_open or after ',' in bracket_open
        BracketElementMaybeObjectdefinition, // after an identifier in bracket_element_start

        TernaryOp, // The ? : operator
        TernaryOpAfterColon, // after the : in a ternary

        JsblockOpen,

        EmptyStatement, // for a ';', will be popped directly
        BreakcontinueStatement, // for continue/break, may be followed by identifier

        IfStatement, // After 'if'
        MaybeElse, // after the first substatement in an if
        ElseClause, // The else line of an if-else construct.

        ConditionOpen, // Start of a condition in 'if', 'while', entered after opening paren

        Substatement, // The first line after a conditional or loop construct.
        SubstatementOpen, // The brace that opens a substatement block.

        LabelledStatement, // after a label

        ReturnStatement, // After 'return'
        ThrowStatement, // After 'throw'

        StatementWithCondition, // After the 'for', 'while', ... token
        StatementWithConditionParenOpen, // While inside the (...)

        TryStatement, // after 'try'
        CatchStatement, // after 'catch', nested in try_statement
        FinallyStatement, // after 'finally', nested in try_statement
        MaybeCatchOrFinally, // after ther closing '}' of try_statement and catch_statement,
                             // nested in try_statement

        DoStatement, // after 'do'
        DoStatementWhileParenOpen, // after '(' in while clause

        SwitchStatement, // After 'switch' token
        CaseStart, // after a 'case' or 'default' token
        CaseCont // after the colon in a case/default
    };
    Q_ENUM(StateType)

    static QString stateToString(StateType type);

    class State
    {
    public:
        quint16 savedIndentDepth = 0;
        StateType type = StateType::Invalid;
        bool operator==(const State &other) const
        {
            return type == other.type && savedIndentDepth == other.savedIndentDepth;
        }
        QString typeStr() const { return FormatTextStatus::stateToString(type); }
    };

    static bool isBracelessState(StateType type)
    {
        return type == StateType::IfStatement || type == StateType::ElseClause
                || type == StateType::Substatement || type == StateType::BindingAssignment
                || type == StateType::BindingOrObjectdefinition;
    }

    static bool isExpressionEndState(StateType type)
    {
        return type == StateType::TopmostIntro || type == StateType::TopJs
                || type == StateType::ObjectdefinitionOpen || type == StateType::DoStatement
                || type == StateType::JsblockOpen || type == StateType::SubstatementOpen
                || type == StateType::BracketOpen || type == StateType::ParenOpen
                || type == StateType::CaseCont || type == StateType::ObjectliteralOpen;
    }

    static FormatTextStatus initialStatus(int baseIndent = 0)
    {
        return FormatTextStatus {
            Scanner::State {},
            QList<State>({ State { quint16(baseIndent), StateType::TopmostIntro } }), baseIndent
        };
    }

    size_t size() const { return states.size(); }

    State state(int belowTop = 0) const;

    void pushState(StateType type, quint16 savedIndentDepth)
    {
        states.append(State { savedIndentDepth, type });
    }

    State popState()
    {
        if (states.isEmpty()) {
            Q_ASSERT(false);
            return State();
        }
        State res = states.last();
        states.removeLast();
        return res;
    }

    Scanner::State lexerState = {};
    QList<State> states;
    int finalIndent = 0;
};

class QMLDOM_EXPORT FormatPartialStatus
{
    Q_GADGET
public:

    using OnEnterCallback =
            function_ref<void(FormatTextStatus::StateType newState, int *indentDepth,
                              int *savedIndentDepth, const FormatPartialStatus &fStatus)>;

    // to determine whether a line was joined, Tokenizer needs a
    // newline character at the end, lease ensure that line contains it
    FormatPartialStatus() = default;
    FormatPartialStatus(const FormatPartialStatus &o) = default;
    FormatPartialStatus &operator=(const FormatPartialStatus &o) = default;
    FormatPartialStatus(QStringView line, const FormatOptions &options,
                        const FormatTextStatus &initialStatus)
        : line(line),
          options(options),
          initialStatus(initialStatus),
          currentStatus(initialStatus),
          currentIndent(0),
          tokenIndex(0)
    {
        Scanner::State startState = initialStatus.lexerState;
        currentIndent = initialStatus.finalIndent;
        Scanner tokenize;
        lineTokens = tokenize(line, startState);
        currentStatus.lexerState = tokenize.state();
    }

    void enterState(FormatTextStatus::StateType newState);
    void leaveState(bool statementDone);
    void turnIntoState(FormatTextStatus::StateType newState);

    const Token &tokenAt(int idx) const;
    int tokenCount() const { return lineTokens.size(); }
    int column(int index) const;
    QStringView tokenText(const Token &token) const;
    void handleTokens();

    bool tryInsideExpression(bool alsoExpression);
    bool tryStatement();

    void defaultOnEnter(FormatTextStatus::StateType newState, int *indentDepth,
                        int *savedIndentDepth) const;

    int indentLine();
    int indentForNewLineAfter() const;
    void recalculateWithIndent(int indent);

    void dump() const;

    QStringView line;
    FormatOptions options;
    FormatTextStatus initialStatus;
    FormatTextStatus currentStatus;
    int indentOffset = 0;
    int currentIndent = 0;
    QList<Token> lineTokens;
    int tokenIndex = 0;
};

QMLDOM_EXPORT int indentForLineStartingWithToken(const FormatTextStatus &oldStatus,
                                                 const FormatOptions &options,
                                                 int token = QQmlJSGrammar::T_ERROR);

QMLDOM_EXPORT FormatPartialStatus formatCodeLine(QStringView line, const FormatOptions &options,
                                                 const FormatTextStatus &initialStatus);

} // namespace Dom
} // namespace QQmlJs
QT_END_NAMESPACE
#endif // QQMLDOMCODEFORMATTER_P_H
