<commit_before>package com.fwdekker.randomness

import com.intellij.ide.BrowserUtil
import com.intellij.ide.DataManager
import com.intellij.ide.plugins.IdeaPluginDescriptor
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.application.ApplicationInfo
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.ErrorReportSubmitter
import com.intellij.openapi.diagnostic.IdeaLoggingEvent
import com.intellij.openapi.diagnostic.SubmittedReportInfo
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.Task.Backgroundable
import com.intellij.util.Consumer
import java.awt.Component
import java.net.IDN
import java.net.URI
import java.net.URL


/**
 * A report submitter that opens a pre-filled issue creation form on Randomness' GitHub repository.
 *
 * This class pertains to reports of exceptions that are not caught by the plugin and end up being shown to the user
 * as a notification by IntelliJ.
 */
class ErrorReporter : ErrorReportSubmitter() {
    companion object {
        /**
         * Maximum URL length supported by GitHub, experimentally verified.
         */
        const val MAX_URL_LENGTH = 8000
    }


    /**
     * Returns the text that is displayed in the button to report the error.
     *
     * @return the text that is displayed in the button to report the error
     */
    override fun getReportActionText() = "Report on GitHub"

    /**
     * Submits the exception as desired by the user.
     *
     * @param events the events that caused the exception
     * @param additionalInfo additional information provided by the user
     * @param parentComponent ignored
     * @param consumer ignored
     * @return `true`
     */
    override fun submit(
        events: Array<out IdeaLoggingEvent>,
        additionalInfo: String?,
        parentComponent: Component,
        consumer: Consumer<SubmittedReportInfo>
    ): Boolean {
        val project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(parentComponent))
        object : Backgroundable(project, "Opening GitHub in Browser") {
            override fun run(indicator: ProgressIndicator) {
                BrowserUtil.open(getIssueUrl(events, additionalInfo))
                ApplicationManager.getApplication().invokeLater {
                    consumer.consume(
                        SubmittedReportInfo(
                            "https://github.com/FWDekker/intellij-randomness/issues",
                            "Issue on GitHub",
                            SubmittedReportInfo.SubmissionStatus.NEW_ISSUE
                        )
                    )
                }
            }
        }.queue()
        return true
    }

    /**
     * Returns the privacy notice text.
     *
     * @return the privacy notice text
     */
    override fun getPrivacyNoticeText() =
        """
        Pressing the Report button will open a form on a web page with the details of this error filled in.
        Submitting the form requires a GitHub account and is subject to <a href="https://github.com/site/privacy">
        GitHub's privacy policy</a>.
        """.trimIndent()


    /**
     * Constructs a URL to create an issue with the given information that is below the maximum URL limit.
     *
     * @param events the events that caused the exception
     * @param additionalInfo additional information provided by the user
     * @return a URL to create an issue with the given information that is below the maximum URL limit
     */
    // Public for testability
    fun getIssueUrl(events: Array<out IdeaLoggingEvent>, additionalInfo: String?): String {
        val baseUrl = "https://github.com/FWDekker/intellij-randomness/issues/new" +
            "?labels=bug" +
            "&assignee=FWDekker" +
            "&title=Bug report" +
            "&body="
        val additionalInfoSection = createMarkdownSection(
            "Additional info",
            if (additionalInfo.isNullOrBlank()) "_No additional information provided._"
            else additionalInfo
        )
        val stackTracesSection = createMarkdownSection("Stacktraces", formatEvents(events))
        val versionSection = createMarkdownSection("Version information", getFormattedVersionInformation())

        val candidates = listOf(
            additionalInfoSection + stackTracesSection + versionSection,
            additionalInfoSection + versionSection,
            stackTracesSection + versionSection,
            versionSection,
            ""
        )
        return baseUrl + candidates.first { encodeUrl(baseUrl + it).length <= MAX_URL_LENGTH }.replace(' ', '+')
    }

    /**
     * Creates a Markdown "section" containing the title in bold followed by the contents on the next line, finalized by
     * two newlines.
     *
     * @param title the title of the section
     * @param contents the contents of the section
     * @return a Markdown "section" with the given title and contents
     */
    private fun createMarkdownSection(title: String, contents: String) = "**${title.trim()}**\n${contents.trim()}\n\n"

    /**
     * Formats IDEA events as Markdown-style code blocks inside spoilers.
     *
     * @param events the events to format
     */
    private fun formatEvents(events: Array<out IdeaLoggingEvent>) =
        events.mapIndexed { i, event ->
            wrapInMarkdownSpoiler(
                title = "Stacktrace ${i + 1}/${events.size}",
                contents = wrapInCodeBlock(contents = event.throwableText.trim(), language = "java")
            )
        }.joinToString("\n\n")

    /**<commit_msg>Truncate ampersands in user input<commit_after>package com.fwdekker.randomness

import com.intellij.ide.BrowserUtil
import com.intellij.ide.DataManager
import com.intellij.ide.plugins.IdeaPluginDescriptor
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.application.ApplicationInfo
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.ErrorReportSubmitter
import com.intellij.openapi.diagnostic.IdeaLoggingEvent
import com.intellij.openapi.diagnostic.SubmittedReportInfo
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.Task.Backgroundable
import com.intellij.util.Consumer
import java.awt.Component
import java.net.IDN
import java.net.URI
import java.net.URL


/**
 * A report submitter that opens a pre-filled issue creation form on Randomness' GitHub repository.
 *
 * This class pertains to reports of exceptions that are not caught by the plugin and end up being shown to the user
 * as a notification by IntelliJ.
 */
class ErrorReporter : ErrorReportSubmitter() {
    companion object {
        /**
         * Maximum URL length supported by GitHub, experimentally verified.
         */
        const val MAX_URL_LENGTH = 8000
    }


    /**
     * Returns the text that is displayed in the button to report the error.
     *
     * @return the text that is displayed in the button to report the error
     */
    override fun getReportActionText() = "Report on GitHub"

    /**
     * Submits the exception as desired by the user.
     *
     * @param events the events that caused the exception
     * @param additionalInfo additional information provided by the user
     * @param parentComponent ignored
     * @param consumer ignored
     * @return `true`
     */
    override fun submit(
        events: Array<out IdeaLoggingEvent>,
        additionalInfo: String?,
        parentComponent: Component,
        consumer: Consumer<SubmittedReportInfo>
    ): Boolean {
        val project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(parentComponent))
        object : Backgroundable(project, "Opening GitHub in Browser") {
            override fun run(indicator: ProgressIndicator) {
                BrowserUtil.open(getIssueUrl(events, additionalInfo))
                ApplicationManager.getApplication().invokeLater {
                    consumer.consume(
                        SubmittedReportInfo(
                            "https://github.com/FWDekker/intellij-randomness/issues",
                            "Issue on GitHub",
                            SubmittedReportInfo.SubmissionStatus.NEW_ISSUE
                        )
                    )
                }
            }
        }.queue()
        return true
    }

    /**
     * Returns the privacy notice text.
     *
     * @return the privacy notice text
     */
    override fun getPrivacyNoticeText() =
        """
        Pressing the Report button will open a form on a web page with the details of this error filled in.
        Submitting the form requires a GitHub account and is subject to <a href="https://github.com/site/privacy">
        GitHub's privacy policy</a>.
        """.trimIndent()


    /**
     * Constructs a URL to create an issue with the given information that is below the maximum URL limit.
     *
     * @param events the events that caused the exception
     * @param additionalInfo additional information provided by the user
     * @return a URL to create an issue with the given information that is below the maximum URL limit
     */
    // Public for testability
    fun getIssueUrl(events: Array<out IdeaLoggingEvent>, additionalInfo: String?): String {
        val baseUrl = "https://github.com/FWDekker/intellij-randomness/issues/new?body="
        val additionalInfoSection = createMarkdownSection(
            "Additional info",
            if (additionalInfo.isNullOrBlank()) "_No additional information provided._"
            else additionalInfo
        )
        val stackTracesSection = createMarkdownSection("Stacktraces", formatEvents(events))
        val versionSection = createMarkdownSection("Version information", getFormattedVersionInformation())

        val candidates = listOf(
            additionalInfoSection + stackTracesSection + versionSection,
            additionalInfoSection + versionSection,
            stackTracesSection + versionSection,
            versionSection,
            ""
        )
        return baseUrl + candidates.first { encodeUrl(baseUrl + it).length <= MAX_URL_LENGTH }
            .replace(' ', '+')
            .replace("&", "")
    }

    /**
     * Creates a Markdown "section" containing the title in bold followed by the contents on the next line, finalized by
     * two newlines.
     *
     * @param title the title of the section
     * @param contents the contents of the section
     * @return a Markdown "section" with the given title and contents
     */
    private fun createMarkdownSection(title: String, contents: String) = "**${title.trim()}**\n${contents.trim()}\n\n"

    /**
     * Formats IDEA events as Markdown-style code blocks inside spoilers.
     *
     * @param events the events to format
     */
    private fun formatEvents(events: Array<out IdeaLoggingEvent>) =
        events.mapIndexed { i, event ->
            wrapInMarkdownSpoiler(
                title = "Stacktrace ${i + 1}/${events.size}",
                contents = wrapInCodeBlock(contents = event.throwableText.trim(), language = "java")
            )
        }.joinToString("\n\n")

    /**
{'fraction_non_alphanumeric': 0.08186252771618625, 'fraction_numerical': 0.0008869179600886918, 'mean_word_length': 2.539234149403641, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 17, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3136288', 'n_tokens_mistral': 2920, 'n_tokens_neox': 2810, 'n_words': 882}