content: package com.termux.shared.markdown;
  
  import android.content.Context;
  import android.graphics.Typeface;
  import android.text.Spanned;
  import android.text.style.AbsoluteSizeSpan;
  import android.text.style.BackgroundColorSpan;
  import android.text.style.BulletSpan;
  import android.text.style.QuoteSpan;
  import android.text.style.StrikethroughSpan;
  import android.text.style.StyleSpan;
  import android.text.style.TypefaceSpan;
  import android.text.util.Linkify;
  
  import androidx.annotation.NonNull;
  import androidx.core.content.ContextCompat;
  
  import com.google.common.base.Strings;
  import com.termux.shared.R;
  import com.termux.shared.theme.ThemeUtils;
  
  import org.commonmark.ext.gfm.strikethrough.Strikethrough;
  import org.commonmark.node.BlockQuote;
  import org.commonmark.node.Code;
  import org.commonmark.node.Emphasis;
  import org.commonmark.node.FencedCodeBlock;
  import org.commonmark.node.ListItem;
  import org.commonmark.node.StrongEmphasis;
  
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  import io.noties.markwon.AbstractMarkwonPlugin;
  import io.noties.markwon.Markwon;
  import io.noties.markwon.MarkwonSpansFactory;
  import io.noties.markwon.MarkwonVisitor;
  import io.noties.markwon.ext.strikethrough.StrikethroughPlugin;
  import io.noties.markwon.linkify.LinkifyPlugin;
  
  public class MarkdownUtils {
  
      public static final String backtick = "`";
      public static final Pattern backticksPattern = Pattern.compile("(" + backtick + "+)");
  
      /**
       * Get the markdown code {@link String} for a {@link String}. This ensures all backticks "`" are
       * properly escaped so that markdown does not break.
       *
       * @param string The {@link String} to convert.
       * @param codeBlock If the {@link String} is to be converted to a code block or inline code.
       * @return Returns the markdown code {@link String}.
       */
      public static String getMarkdownCodeForString(String string, boolean codeBlock) {
          if (string == null) return null;
          if (string.isEmpty()) return "";
  
          int maxConsecutiveBackTicksCount = getMaxConsecutiveBackTicksCount(string);
  
          // markdown requires surrounding backticks count to be at least one more than the count
          // of consecutive ticks in the string itself
          int backticksCountToUse;
          if (codeBlock)
              backticksCountToUse = maxConsecutiveBackTicksCount + 3;
          else
              backticksCountToUse = maxConsecutiveBackTicksCount + 1;
  
          // create a string with n backticks where n==backticksCountToUse
          String backticksToUse = Strings.repeat(backtick, backticksCountToUse);
  
          if (codeBlock)
              return backticksToUse + "\n" + string + "\n" + backticksToUse;
          else {
              // add a space to any prefixed or suffixed backtick characters
              if (string.startsWith(backtick))
                  string = " " + string;
              if (string.endsWith(backtick))
                  string = string + " ";
  
              return backticksToUse + string + backticksToUse;
          }
      }
  
      /**
       * Get the max consecutive backticks "`" in a {@link String}.
       *
       * @param string The {@link String} to check.
       * @return Returns the max consecutive backticks count.
       */
      public static int getMaxConsecutiveBackTicksCount(String string) {
          if (string == null || string.isEmpty()) return 0;
  
          int maxCount = 0;
          int matchCount;
          String match;
  
          Matcher matcher = backticksPattern.matcher(string);
          while(matcher.find()) {
              match = matcher.group(1);
              matchCount = match != null ? match.length() : 0;
              if (matchCount > maxCount)
                  maxCount = matchCount;
          }
  
          return maxCount;
      }
  
  
  
      public static String getLiteralSingleLineMarkdownStringEntry(String label, Object object, String def) {
          return "**" + label + "**: " + (object != null ? object.toString() : def) +  "  ";
      }
  
      public static String getSingleLineMarkdownStringEntry(String label, Object object, String def) {
          if (object != null)
              return "**" + label + "**: " + getMarkdownCodeForString(object.toString(), false) +  "  ";
          else
              return "**" + label + "**: " + def +  "  ";
      }
  
      public static String getMultiLineMarkdownStringEntry(String label, Object object, String def) {
          if (object != null)
              return "**" + label + "**:\n" + getMarkdownCodeForString(object.toString(), true) + "\n";
          else
              return "**" + label + "**: " + def + "\n";
      }
  
      public static String getLinkMarkdownString(String label, String url) {
          if (url != null)
              return "[" + label.replaceAll("]", "\\\\]") + "](" + url.replaceAll("\\)", "\\\\)") +  ")";
          else
              return label;
      }
  
  
      /** Check following for more info:
       * https://github.com/noties/Markwon/tree/v4.6.2/app-sample
       * https://noties.io/Markwon/docs/v4/recycler/
       * https://github.com/noties/Markwon/blob/v4.6.2/app-sample/src/main/java/io/noties/markwon/app/readme/ReadMeActivity.kt
       */
      public static Markwon getRecyclerMarkwonBuilder(Context context) {
          return Markwon.builder(context)
              .usePlugin(LinkifyPlugin.create(Linkify.EMAIL_ADDRESSES | Linkify.WEB_URLS))
              .usePlugin(new AbstractMarkwonPlugin() {
                  @Override
                  public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
                      builder.on(FencedCodeBlock.class, (visitor, fencedCodeBlock) -> {
                          // we actually won't be applying code spans here, as our custom xml view will
                          // draw background and apply mono typeface
                          //
                          // NB the `trim` operation on literal (as code will have a new line at the end)
                          final CharSequence code = visitor.configuration()
                              .syntaxHighlight()
                              .highlight(fencedCodeBlock.getInfo(), fencedCodeBlock.getLiteral().trim());
                          visitor.builder().append(code);
                      });
                  }
  
                  @Override
                  public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
                      // Do not change color for night themes
                      if (!ThemeUtils.isNightModeEnabled(context)) {
                          builder
                              // set color for inline code
                              .setFactory(Code.class, (configuration, props) -> new Object[]{
                                  new BackgroundColorSpan(ContextCompat.getColor(context, R.color.background_markdown_code_inline)),
                              });
                      }
                  }
              })
              .build();
      }
  
      /** Check following for more info:
       * https://github.com/noties/Markwon/tree/v4.6.2/app-sample
       * https://github.com/noties/Markwon/blob/v4.6.2/app-sample/src/main/java/io/noties/markwon/app/samples/notification/NotificationSample.java
       */
      public static Markwon getSpannedMarkwonBuilder(Context context) {
          return Markwon.builder(context)
                  .usePlugin(StrikethroughPlugin.create())
                  .usePlugin(new AbstractMarkwonPlugin() {
                      @Override
                      public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
                          builder
                              .setFactory(Emphasis.class, (configuration, props) -> new StyleSpan(Typeface.ITALIC))
                              .setFactory(StrongEmphasis.class, (configuration, props) -> new StyleSpan(Typeface.BOLD))
                              .setFactory(BlockQuote.class, (configuration, props) -> new QuoteSpan())
                              .setFactory(Strikethrough.class, (configuration, props) -> new StrikethroughSpan())
                              // NB! notification does not handle background color
                              .setFactory(Code.class, (configuration, props) -> new Object[]{
                                  new BackgroundColorSpan(ContextCompat.getColor(context, R.color.background_markdown_code_inline)),
                                  new TypefaceSpan("monospace"),
                                  new AbsoluteSizeSpan(48)
                              })
                              // NB! both ordered and bullet list items
                              .setFactory(ListItem.class, (configuration, props) -> new BulletSpan());
                      }
                  })
                  .build();
      }
  
      public static Spanned getSpannedMarkdownText(Context context, String string) {
          if (context == null || string == null) return null;
          final Markwon markwon = getSpannedMarkwonBuilder(context);
          return markwon.toMarkdown(string);
      }
  
  }
  