package org.jabref.model.entry;

import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.jabref.model.ChainNode;

import org.jspecify.annotations.NonNull;

/// Represents a keyword in a chain of keywords.
/// For example, "JabRef" in "Bibliographic manager > Awesome ones > JabRef"
public class Keyword extends ChainNode<Keyword> implements Comparable<Keyword> {

    public static Character DEFAULT_HIERARCHICAL_DELIMITER = '>';
    public static final Character DEFAULT_ESCAPE_SYMBOL = '\\';
    private final String keyword;

    public Keyword(@NonNull String keyword) {
        super(Keyword.class);
        this.keyword = keyword.trim();
    }

    /// Connects all the given keywords into one chain and returns its root,
    /// e.g. "A", "B", "C" is transformed into "A > B > C".
    public static Keyword of(List<String> keywords) {
        if (keywords.isEmpty()) {
            return new Keyword("");
        }

        Keyword root = new Keyword(keywords.getFirst());
        for (int i = 1; i < keywords.size(); i++) {
            root.addAtEnd(keywords.get(i));
        }
        return root;
    }

    /// Converts a raw String to a single Keyword
    public static Keyword ofHierarchical(String rawString) {
        return Keyword.of(Stream.of(rawString.split(Keyword.DEFAULT_HIERARCHICAL_DELIMITER.toString()))
                                .map(String::trim)
                                .toList());
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Keyword other = (Keyword) o;
        return Objects.equals(this.keyword, other.keyword)
                // && Objects.equals(this.getParent(), other.getParent()) : we can't check the parents because then we would run in circles
                && Objects.equals(this.getChild(), other.getChild());
    }

    @Override
    public int hashCode() {
        return Objects.hash(keyword);
    }

    @Override
    public String toString() {
        return getSubchainAsString(DEFAULT_HIERARCHICAL_DELIMITER);
    }

    @Override
    public int compareTo(Keyword o) {
        return keyword.compareTo(o.keyword);
    }

    /// Adds the given keyword at the end of the chain.
    /// E.g., "A > B > C" + "D" -> "A > B > C > D".
    private void addAtEnd(String keyword) {
        addAtEnd(new Keyword(keyword));
    }

    /// Returns a text representation of the subchain starting at this item.
    /// E.g., calling {@link #getSubchainAsString(Character)} on the node "B" in "A > B > C" returns "B > C".
    private String getSubchainAsString(Character hierarchicalDelimiter) {
        return keyword +
                getChild().map(child -> " " + hierarchicalDelimiter + " " + child.getSubchainAsString(hierarchicalDelimiter))
                          .orElse("");
    }

    /// Gets the keyword of this node in the chain.
    public String get() {
        return keyword;
    }

    /// Returns a text representation of the path from the root to this item.
    /// E.g., calling {@link #getPathFromRootAsString(Character)} on the node "B" in "A > B > C" returns "A > B".
    public String getPathFromRootAsString(Character hierarchicalDelimiter) {
        return getParent()
                .map(parent -> parent.getPathFromRootAsString(hierarchicalDelimiter) + " " + hierarchicalDelimiter + " ")
                .orElse("")
                + keyword;
    }

    /// Returns all nodes in this chain as separate keywords.
    /// E.g, for "A > B > C" we get {"A", "B", "C"}.
    public Set<Keyword> flatten() {
        return Stream
                .concat(Stream.of(this),
                        getChild().stream()
                                  .flatMap(child -> child.flatten().stream()))
                .collect(Collectors.toSet());
    }

    /// Returns all subchains starting at this node.
    /// E.g., for the chain "A > B > C" the subchains {"A", "A > B", "A > B > C"} are returned.
    public Set<String> getAllSubchainsAsString(Character hierarchicalDelimiter) {
        return flatten().stream()
                        .map(subchain -> subchain.getPathFromRootAsString(hierarchicalDelimiter))
                        .collect(Collectors.toSet());
    }
}
