#pragma once
#include <LibLsp/lsp/general/lsClientCapabilities.h>
#include <LibLsp/lsp/utils.h>
#include <memory>
#include <vector>
#include <string>

class ClientPreferences
{
public:
    std::shared_ptr<lsWorkspaceClientCapabilites> workspace;
    lsTextDocumentClientCapabilities textDocument;

    ClientPreferences(lsClientCapabilities const& capabilities)
    {
        v3supported = capabilities.textDocument.has_value();
        if (v3supported)
        {
            textDocument = capabilities.textDocument.value();
        }
        if (capabilities.workspace)
        {
            workspace = std::make_shared<lsWorkspaceClientCapabilites>(capabilities.workspace.value());
        }
    }

    bool v3supported = false;

    bool isSignatureHelpSupported()
    {

        return v3supported && (textDocument.signatureHelp);
    }
    bool isWorkspaceDidChangeConfigurationSupported() const
    {
        return workspace && isDynamicRegistrationSupported(workspace->didChangeConfiguration);
    }
    bool isWorkspaceFoldersSupported()
    {
        return workspace != nullptr && isTrue(workspace->workspaceFolders);
    }

    bool isCompletionDynamicRegistered()
    {
        return v3supported && isDynamicRegistrationSupported(textDocument.completion);
    }

    bool isCompletionSnippetsSupported()
    {
        //@formatter:off
        if (!v3supported || !textDocument.completion)
        {
            return false;
        }
        auto const& completion = textDocument.completion.value();
        if (completion.completionItem)
        {
            return isTrue(completion.completionItem.value().snippetSupport);
        }
        return false;
    }

    bool isV3Supported()
    {
        return v3supported;
    }

    bool isFormattingDynamicRegistrationSupported()
    {
        return v3supported && isDynamicRegistrationSupported(textDocument.formatting);
    }

    bool isRangeFormattingDynamicRegistrationSupported()
    {
        return v3supported && isDynamicRegistrationSupported(textDocument.rangeFormatting);
    }

    bool isOnTypeFormattingDynamicRegistrationSupported()
    {
        return v3supported && isDynamicRegistrationSupported(textDocument.onTypeFormatting);
    }

    bool isCodeLensDynamicRegistrationSupported()
    {
        return v3supported && isDynamicRegistrationSupported(textDocument.codeLens);
    }

    bool isSignatureHelpDynamicRegistrationSupported()
    {
        return v3supported && isDynamicRegistrationSupported(textDocument.signatureHelp);
    }
    template<typename T>
    static bool isDynamicRegistrationSupported(optional<T>& capability)
    {
        if (capability)
        {
            return (capability.value().dynamicRegistration.value());
        }
        return false;
    }

    bool isTrue(optional<bool> const& value)
    {
        if (value)
        {
            return *value;
        }
        else
        {
            return false;
        }
    }

    bool isRenameDynamicRegistrationSupported()
    {
        return v3supported && isDynamicRegistrationSupported(textDocument.rename);
    }

    bool isExecuteCommandDynamicRegistrationSupported()
    {
        return v3supported && workspace != nullptr && isDynamicRegistrationSupported(workspace->executeCommand);
    }

    bool isWorkspaceSymbolDynamicRegistered()
    {
        return v3supported && workspace != nullptr && isDynamicRegistrationSupported(workspace->symbol);
    }

    bool isWorkspaceChangeWatchedFilesDynamicRegistered()
    {
        return v3supported && workspace != nullptr && isDynamicRegistrationSupported(workspace->didChangeWatchedFiles);
    }

    bool isDocumentSymbolDynamicRegistered()
    {
        return v3supported && isDynamicRegistrationSupported(textDocument.documentSymbol);
    }

    bool isCodeActionDynamicRegistered()
    {
        return v3supported && isDynamicRegistrationSupported(textDocument.codeAction);
    }

    bool isDefinitionDynamicRegistered()
    {
        return v3supported && isDynamicRegistrationSupported(textDocument.definition);
    }

    bool isTypeDefinitionDynamicRegistered()
    {
        return v3supported && isDynamicRegistrationSupported(textDocument.typeDefinition);
    }

    bool isHoverDynamicRegistered()
    {
        return v3supported && isDynamicRegistrationSupported(textDocument.hover);
    }

    bool isReferencesDynamicRegistered()
    {
        return v3supported && isDynamicRegistrationSupported(textDocument.references);
    }

    bool isDocumentHighlightDynamicRegistered()
    {
        return v3supported && isDynamicRegistrationSupported(textDocument.documentHighlight);
    }

    bool isDocumentLinkDynamicRegistered()
    {
        return v3supported && isDynamicRegistrationSupported(textDocument.documentLink);
    }

    bool isFoldgingRangeDynamicRegistered()
    {
        return v3supported && isDynamicRegistrationSupported(textDocument.foldingRange);
    }

    bool isInlayHintDynamicRegistered()
    {
        return v3supported && isDynamicRegistrationSupported(textDocument.inlayHint);
    }

    bool isImplementationDynamicRegistered()
    {
        return v3supported && isDynamicRegistrationSupported(textDocument.implementation);
    }

    bool isSelectionRangeDynamicRegistered()
    {
        return v3supported && isDynamicRegistrationSupported(textDocument.selectionRange);
    }

    bool isWillSaveRegistered()
    {
        return v3supported && isTrue(textDocument.synchronization.willSave);
    }

    bool isWillSaveWaitUntilRegistered()
    {
        return v3supported && isTrue(textDocument.synchronization.willSaveWaitUntil);
    }

    bool isWorkspaceApplyEditSupported()
    {
        return workspace != nullptr && isTrue(workspace->applyEdit);
    }

    bool isSupportsCompletionDocumentationMarkdown()
    {

        if (!v3supported || !textDocument.completion)
        {
            return false;
        }
        auto const& completion = textDocument.completion.value();
        if (completion.completionItem)
        {
            auto& documentationFormat = completion.completionItem.value().documentationFormat;
            if (documentationFormat)
            {
                auto& data = documentationFormat.value();
                for (auto& it : data)
                {
                    if (it == "markdown")
                    {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    bool isWorkspaceEditResourceChangesSupported()
    {
        if (!workspace)
        {
            return false;
        }

        if (workspace->workspaceEdit)
        {
            return isTrue(workspace->workspaceEdit.value().resourceChanges);
        }
        return false;
    }
    static bool contains(std::vector<std::string> const& v, std::string const& target)
    {
        for (auto& it : v)
        {
            if (it == target)
            {
                return true;
            }
        }
        return false;
    }
    bool isResourceOperationSupported() const
    {
        if (!workspace)
        {
            return false;
        }
        if (!workspace->workspaceEdit)
        {
            return false;
        }
        auto& it = (workspace->workspaceEdit.value());
        if (!it.resourceOperations)
        {
            return false;
        }
        auto const& resourceOperations = it.resourceOperations.value();
        return contains(resourceOperations, "create") && contains(resourceOperations, "rename")
               && contains(resourceOperations, "delete");
    }

    /**
         * {@code true} if the client has explicitly set the
         * {@code textDocument.documentSymbol.hierarchicalDocumentSymbolSupport} to
         * {@code true} when initializing the LS. Otherwise, {@code false}.
         */
    bool isHierarchicalDocumentSymbolSupported()
    {
        if (!v3supported || !textDocument.documentSymbol)
        {
            return false;
        }
        return isTrue(textDocument.documentSymbol.value().hierarchicalDocumentSymbolSupport);
    }

    bool isSemanticHighlightingSupported()
    {
        //@formatter:off
        if (!v3supported || !textDocument.semanticHighlightingCapabilities)
        {
            return false;
        }
        return isTrue(textDocument.semanticHighlightingCapabilities.value().semanticHighlighting);
        //@formatter:on
    }

    /**
         * {@code true} if the client has explicitly set the
         * {@code textDocument.codeAction.codeActionLiteralSupport.codeActionKind.valueSet}
         * value. Otherwise, {@code false}.
         */
    bool isSupportedCodeActionKind(std::string const& kind)
    {
        if (!v3supported || !textDocument.codeAction)
        {
            return false;
        }
        //@formatter:off
        auto const& codeAction = textDocument.codeAction.value();
        if (codeAction.codeActionLiteralSupport)
        {
            auto const& codeActionKind = codeAction.codeActionLiteralSupport.value().codeActionKind;
            if (codeActionKind)
            {
                auto const& valueSet = codeActionKind.value().valueSet;
                if (valueSet)
                {
                    for (auto& k : valueSet.value())
                    {
                        if (lsp::StartsWith(kind, k))
                        {
                            return true;
                        }
                    }
                }
            }
        }
        return false;

        //@formatter:on
    }

    /**
         * {@code true} if the client has explicitly set the
         * {@code textDocument.publishDiagnostics.tagSupport} to
         * {@code true} when initializing the LS. Otherwise, {@code false}.
         */
    bool isDiagnosticTagSupported()
    {
        if (!v3supported || !textDocument.publishDiagnostics)
        {
            return false;
        }
        auto const& publishDiagnostics = textDocument.publishDiagnostics.value();
        if (publishDiagnostics.tagSupport)
        {
            isTagSupported(publishDiagnostics.tagSupport);
        }
        return false;
    }

    bool isTagSupported(optional<std::pair<optional<bool>, optional<DiagnosticsTagSupport>>> const& tagSupport)
    {
        if (tagSupport)
        {
            auto& v = tagSupport.value();
            if (v.first)
            {
                return v.first.value();
            }
            if (v.second)
            {
                return !v.second.value().valueSet.empty();
            }
        }
        return false;
    }

    bool isCallHierarchyDynamicRegistered()
    {
        return v3supported && isDynamicRegistrationSupported(textDocument.callHierarchy);
    }
};
