package org.jabref.logic.openoffice.oocsltext;

import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jabref.logic.citationkeypattern.BracketedPattern;
import org.jabref.logic.citationstyle.CitationStyle;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.AuthorList;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.field.StandardField;

import org.apache.commons.text.StringEscapeUtils;

/// Contains utility constants and methods for processing of CSL citations as generated by methods of <a href="https://github.com/michel-kraemer/citeproc-java">citeproc-java</a> ({@link org.jabref.logic.citationstyle.CitationStyleGenerator}).
/// These methods are used in {@link CSLCitationOOAdapter} which inserts CSL citation text into an OO document.
public final class CSLFormatUtils {

    /// The formatting names specified to and understood by LibreOffice internally via code are often different from their displayed names in the LibreOffice application.
    /// For example, "Hanging Indent" as visible in LO styles list should be specified as "Hanging indent" (note casing) internally. Similarly, "Body Text, Indented" is specified as "Text body indent".
    /// This is also why we don't let users manually input the name of any pre-set format.
    /// The following two lists below are taken from https://github.com/LibreOffice/core/blob/0891df6b21fd95ec7c9614509d92829c0f17c353/sw/qa/python/check_styles.py#L132
    public static final List<String> BIBLIOGRAPHY_TITLE_FORMATS = List.of(
            "Bibliography Heading",
            "Title",
            "Text body",
            "Subtitle",
            "Heading 1",
            "Heading 2",
            "Heading 3",
            "Heading 4"
    );

    public static final List<String> BIBLIOGRAPHY_BODY_FORMATS = List.of(
            "Bibliography 1",
            "Text body",
            "Text body indent",
            "First line indent",
            "Hanging indent",
            "List",
            "List Indent",
            "Marginalia"
    );

    private static final String DEFAULT_BIBLIOGRAPHY_BODY_FORMAT = "Text body";
    private static final String DEFAULT_HANGING_INDENT_BIBLIOGRAPHY_BODY_FORMAT = "Hanging indent";

    private static final Pattern YEAR_IN_CITATION_PATTERN = Pattern.compile("(.)(.*), (\\d{4}.*)");

    private CSLFormatUtils() {
        // prevent instantiation
    }

    public static String getDefaultBodyFormatForStyle(CitationStyle style) {
        if (style.usesHangingIndent()) {
            return DEFAULT_HANGING_INDENT_BIBLIOGRAPHY_BODY_FORMAT;
        }
        return DEFAULT_BIBLIOGRAPHY_BODY_FORMAT;
    }

    /// Transforms provided HTML into a format that can be fully parsed and inserted into an OO document.
    /// Context: The HTML produced by {@link org.jabref.logic.citationstyle.CitationStyleGenerator#generateBibliography(List, String, org.jabref.logic.citationstyle.CitationStyleOutputFormat, BibDatabaseContext, org.jabref.model.entry.BibEntryTypesManager) generateBibliography} or {@link org.jabref.logic.citationstyle.CitationStyleGenerator#generateCitation(List, String, org.jabref.logic.citationstyle.CitationStyleOutputFormat, BibDatabaseContext, org.jabref.model.entry.BibEntryTypesManager) generateCitation} is not directly (completely) parsable by {@link org.jabref.model.openoffice.ootext.OOTextIntoOO#write(com.sun.star.text.XTextDocument, com.sun.star.text.XTextCursor, org.jabref.model.openoffice.ootext.OOText) write}.
    /// For more details, read the documentation for the {@link org.jabref.model.openoffice.ootext.OOTextIntoOO} class.
    /// <a href="https://devdocs.jabref.org/code-howtos/openoffice/code-reorganization.html">Additional Information</a>.
    ///
    /// @param html The HTML string to be transformed into OO-write ready HTML.
    /// @return The formatted html string.
    public static String transformHTML(String html) {
        // Initial clean up of escaped characters
        html = StringEscapeUtils.unescapeHtml4(html);

        // Handle margins (spaces between citation number and text)
        html = html.replaceAll("<div class=\"csl-left-margin\">(.*?)</div><div class=\"csl-right-inline\">(.*?)</div>", "$1 $2");

        // Remove unsupported tags
        html = html.replaceAll("<div[^>]*>", "");
        html = html.replace("</div>", "");

        // Remove unsupported links
        html = html.replaceAll("<a[^>]*>", "");
        html = html.replace("</a>", "");

        // Replace span tags with inline styles for bold
        html = html.replaceAll("<span style=\"font-weight: ?bold;?\">(.*?)</span>", "<b>$1</b>");

        // Replace span tags with inline styles for italic
        html = html.replaceAll("<span style=\"font-style: ?italic;?\">(.*?)</span>", "<i>$1</i>");

        // Replace span tags with inline styles for underline
        html = html.replaceAll("<span style=\"text-decoration: ?underline;?\">(.*?)</span>", "<u>$1</u>");

        html = html.replaceAll("<span style=\"font-variant: ?small-caps;?\">(.*?)</span>", "<smallcaps>$1</smallcaps>");

        // Clean up any remaining span tags
        html = html.replaceAll("</?span[^>]*>", "");

        // Convert line breaks to paragraph breaks
        html = html.replaceAll("[\n\r]+", "<p></p>");

        // Remove leading paragraph tags (preserving any whitespaces after them for indentation)
        html = html.replaceAll("^\\s*<p>\\s*</p>", "");

        // Remove extra trailing paragraph tags when there are multiple (keeping one)
        html = html.replaceAll("(?:<p>\\s*</p>\\s*){2,}$", "<p></p>");

        // In bibliography entries, citeproc-java adds 4 leading spaces for numeric styles, and 2 for non-numeric
        html = html.trim();

        return html;
    }

    /// Alphanumeric citations are not natively supported by citeproc-java (see {@link org.jabref.logic.citationstyle.CitationStyleGenerator#generateCitation(List, String, org.jabref.logic.citationstyle.CitationStyleOutputFormat, BibDatabaseContext, org.jabref.model.entry.BibEntryTypesManager) generateCitation}).
    /// Thus, we manually format a citation to produce its alphanumeric form.
    ///
    /// @param entries the list of entries for which the alphanumeric citation is to be generated.
    /// @return the alphanumeric citation (for a single entry or a group of entries).
    public static String generateAlphanumericCitation(List<BibEntry> entries, BibDatabaseContext bibDatabaseContext) {
        StringBuilder citation = new StringBuilder("[");
        for (int i = 0; i < entries.size(); i++) {
            BibEntry entry = entries.get(i);
            Optional<String> author = entry.getResolvedFieldOrAlias(StandardField.AUTHOR, bibDatabaseContext.getDatabase());
            Optional<String> year = entry.getResolvedFieldOrAlias(StandardField.YEAR, bibDatabaseContext.getDatabase());

            if (author.isPresent() && year.isPresent()) {
                AuthorList authorList = AuthorList.parse(author.get());
                String alphaKey = BracketedPattern.authorsAlphaLNI(authorList);

                // Extract last two digits of the year
                String shortYear = year.get().length() >= 2 ?
                                   year.get().substring(year.get().length() - 2) :
                                   year.get();

                citation.append(alphaKey).append(shortYear);
            } else {
                citation.append(entry.getCitationKey().orElse(""));
            }

            if (i < entries.size() - 1) {
                citation.append("; ");
            }
        }
        citation.append("]");
        return citation.toString();
    }

    public static String generateAlphanumericInTextCitation(BibEntry entry, BibDatabaseContext bibDatabaseContext) {
        String inTextCitation = generateAlphanumericCitation(List.of(entry), bibDatabaseContext);

        String authorName = entry.getResolvedFieldOrAlias(StandardField.AUTHOR, bibDatabaseContext.getDatabase())
                                 .map(AuthorList::parse)
                                 .map(list -> BracketedPattern.joinAuthorsOnLastName(list, 1, "", " et al."))
                                 .orElse("");

        return authorName + " " + inTextCitation;
    }

    /// Extracts year from a citation having single or multiple entries, for the purpose of using in in-text citations.
    ///
    /// @param formattedCitation the citation cleaned up and formatted using {@link CSLFormatUtils#transformHTML transformHTML}.
    public static String changeToInText(String formattedCitation) {
        Matcher matcher = YEAR_IN_CITATION_PATTERN.matcher(formattedCitation);
        if (matcher.find()) {
            return matcher.group(2) + " " + matcher.group(1) + matcher.group(3);
        }
        return formattedCitation;
    }

    /// Generates Author Prefix for an in-text citation
    public static String generateAuthorPrefix(BibEntry currentEntry, BibDatabaseContext bibDatabaseContext) {
        return currentEntry.getResolvedFieldOrAlias(StandardField.AUTHOR, bibDatabaseContext.getDatabase())
                           .map(AuthorList::parse)
                           .map(list -> BracketedPattern.joinAuthorsOnLastName(list, 1, "", " et al.") + " ")
                           .orElse("");
    }
}
