package org.jabref.logic.openoffice.backend;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.jabref.model.openoffice.style.CitationType;

import org.jspecify.annotations.NonNull;

/// How and what is encoded in reference mark names under JabRef 5.2.
///
/// - pageInfo does not appear here. It is not encoded in the mark name.
class JStyleReferenceMark {
    private static final String BIB_CITATION = "JR_cite";
    private static final Pattern CITE_PATTERN =
            // Pattern.compile(BIB_CITATION + "(\\d*)_(\\d*)_(.*)");
            // citationType is "1".."3" (JabRef 5.2) or "4".."6" (partial markers, see #7861)
            Pattern.compile(BIB_CITATION + "(\\d*)_([1-6])_(.*)");

    private JStyleReferenceMark() {
    }

    /// This is what we get back from parsing a refMarkName.
    public static class ParsedMarkName {
        /// "", "0", "1" ...
        public final String index;
        /// in-text-citation type
        public final CitationType citationType;
        /// Citation keys embedded in the reference mark.
        public final List<String> citationKeys;

        ParsedMarkName(@NonNull String index, CitationType citationType, @NonNull List<String> citationKeys) {
            this.index = index;
            this.citationType = citationType;
            this.citationKeys = citationKeys;
        }
    }

    /// Integer representation was written into the document in JabRef52, keep it for compatibility.
    private static CitationType citationTypeFromInt(int code) {
        return switch (code) {
            case 1 ->
                    CitationType.AUTHORYEAR_PAR;
            case 2 ->
                    CitationType.AUTHORYEAR_INTEXT;
            case 3 ->
                    CitationType.INVISIBLE_CIT;
            case 4 ->
                    CitationType.AUTHORYEAR_NOPAR;
            case 5 ->
                    CitationType.AUTHOR_ONLY;
            case 6 ->
                    CitationType.YEAR_ONLY;
            default ->
                    throw new IllegalArgumentException("Invalid CitationType code");
        };
    }

    private static int citationTypeToInt(CitationType type) {
        return switch (type) {
            case AUTHORYEAR_PAR ->
                    1;
            case AUTHORYEAR_INTEXT ->
                    2;
            case INVISIBLE_CIT ->
                    3;
            case AUTHORYEAR_NOPAR ->
                    4;
            case AUTHOR_ONLY ->
                    5;
            case YEAR_ONLY ->
                    6;
        };
    }

    /// Produce a reference mark name for JabRef for the given citationType and list citation keys that does not yet appear among the reference marks of the document.
    ///
    /// The first occurrence of citationKeys gets no serial number, the second gets 0, the third 1 ...
    /// Or the first unused in this series, after removals.
    ///
    /// @param usedNames    Reference mark names already in use.
    /// @param citationKeys Identifies the cited sources.
    /// @param citationType Encodes the effect of withText and inParenthesis options.
    ///
    public static String getUniqueMarkName(Set<String> usedNames,
                                           List<String> citationKeys,
                                           CitationType citationType) {

        String citationKeysPart = String.join(",", citationKeys);

        int index = 0;
        int citTypeCode = citationTypeToInt(citationType);
        String name = BIB_CITATION + '_' + citTypeCode + '_' + citationKeysPart;
        while (usedNames.contains(name)) {
            name = BIB_CITATION + index + '_' + citTypeCode + '_' + citationKeysPart;
            index++;
        }
        return name;
    }

    /// Parse a JabRef (reference) mark name.
    ///
    /// @return Optional.empty() on failure.
    public static Optional<ParsedMarkName> parseMarkName(String refMarkName) {
        Matcher citeMatcher = CITE_PATTERN.matcher(refMarkName);
        if (!citeMatcher.find()) {
            return Optional.empty();
        }

        List<String> keys = Arrays.asList(citeMatcher.group(3).split(","));
        String index = citeMatcher.group(1);
        int citTypeCode = Integer.parseInt(citeMatcher.group(2));
        CitationType citationType = citationTypeFromInt(citTypeCode);
        return Optional.of(new JStyleReferenceMark.ParsedMarkName(index, citationType, keys));
    }

    /// @return true if name matches the pattern used for JabRef reference mark names.
    public static boolean isJabRefReferenceMarkName(String name) {
        return CITE_PATTERN.matcher(name).find();
    }

    /// Filter a list of reference mark names by `isJabRefReferenceMarkName`
    ///
    /// @param names The list to be filtered.
    public static List<String> filterIsJabRefReferenceMarkName(List<String> names) {
        return names.stream()
                    .filter(JStyleReferenceMark::isJabRefReferenceMarkName)
                    .collect(Collectors.toList());
    }
}
