package grammar;

import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.services.LanguageClient;

import java.util.*;
import java.util.stream.Collectors;

/**
 * Adapter class for integrating ANTLR parsing with the Language Server Protocol (LSP).
 *
 * <p>This class provides utilities to:
 * <ul>
 *     <li>Lex and parse Progress language code using ANTLR</li>
 *     <li>Convert ANTLR tokens into LSP {@link SemanticToken} objects</li>
 *     <li>Generate semantic tokens for full documents or specific ranges</li>
 *     <li>Provide code completions based on lexer keywords</li>
 * </ul>
 * </p>
 *
 * <p>The adapter maintains a mapping from ANTLR token types to semantic token types
 * (for syntax highlighting) and supports semantic token modifiers.</p>
 */
public class AntlrLspAdapter {

   /**
    * Mapping from ANTLR token type to LSP semantic token type index.
    */
   public final Map<Integer, Integer> ANTLR_TO_SEMANTIC_TYPE = new HashMap<>();

   /** Predefined semantic token types used for LSP highlighting. */
   public static final List<String> TOKEN_TYPES = List.of(
           "keyword",      // e.g., MESSAGE, DEFINE
           "string",       // STRING_LITERAL
           "operator",     // e.g., AMP, DOT
           "delimiter",    // e.g., LCURLY, RCURLY, DQUOTE
           "variable",     // TEXT
           "include",      // custom token
           "comment"       // reserved for future use
   );

   /** Semantic token modifiers for additional context information. */
   public static final List<String> TOKEN_MODIFIERS = List.of(
           "declaration",
           "definition",
           "readonly",
           "static",
           "deprecated",
           "abstract",
           "async",
           "modification",
           "documentation",
           "defaultLibrary"
   );

   /**
    * Lexes and analyzes the given text, returning a list of {@link SemanticToken}.
    *
    * @param text the source code to analyze
    * @return list of semantic tokens
    */
   public List<SemanticToken> analyzeText(String text) {
      List<SemanticToken> tokens = new ArrayList<>();

      try {
         ANTLRInputStream input = new ANTLRInputStream(text);
         ProgressLexer lexer = new ProgressLexer(input);
         lexer.removeErrorListeners();

         CommonTokenStream tokenStream = new CommonTokenStream(lexer);
         tokenStream.fill();

         // Map ANTLR token types to LSP semantic token indices
         ANTLR_TO_SEMANTIC_TYPE.putAll(
                 lexer.types.entrySet().stream()
                         .collect(Collectors.toMap(
                                 Map.Entry::getKey,
                                 entry -> TOKEN_TYPES.indexOf(entry.getValue())
                         ))
         );

         for (Token token : tokenStream.getTokens()) {
            if (token.getType() == Token.EOF || token.getType() == ProgressLexer.WS) continue;

            int line = token.getLine() - 1;
            int startChar = token.getCharPositionInLine();
            int length = token.getStopIndex() - token.getStartIndex() + 1;

            Integer stt = ANTLR_TO_SEMANTIC_TYPE.getOrDefault(
                    token.getType(), TOKEN_TYPES.indexOf("variable")
            );

            int modifiers = getTokenModifiers(token);

            tokens.add(new SemanticToken() {
               @Override public int line() { return line; }
               @Override public int startChar() { return startChar; }
               @Override public int length() { return length; }
               @Override public int tokenType() { return stt; }
               @Override public int tokenModifiers() { return modifiers; }
            });
         }

      } catch (Exception e) {
         System.err.println("Error analyzing tokens with ANTLR: " + e.getMessage());
      }

      return tokens;
   }

   /**
    * Determines semantic token modifiers based on the token context.
    *
    * @param token the ANTLR token
    * @return integer encoding of applicable modifiers
    */
   private int getTokenModifiers(Token token) {
      int modifiers = 0;
      if (token.getType() == ProgressLexer.DEFINE) {
         modifiers |= (1 << TOKEN_MODIFIERS.indexOf("definition"));
      }
      return modifiers;
   }

   /**
    * Converts a list of {@link SemanticToken} to the LSP integer array format.
    *
    * <p>LSP uses delta encoding for line and start character positions.</p>
    *
    * @param tokens list of semantic tokens
    * @return list of integers in LSP semantic tokens format
    */
   public List<Integer> tokensToLSPFormat(List<SemanticToken> tokens) {
      List<Integer> data = new ArrayList<>();
      int prevLine = 0;
      int prevStart = 0;

      for (SemanticToken token : tokens) {
         int deltaLine = token.line() - prevLine;
         int deltaStart = deltaLine == 0 ? token.startChar() - prevStart : token.startChar();

         data.add(deltaLine);
         data.add(deltaStart);
         data.add(token.length());
         data.add(token.tokenType());
         data.add(token.tokenModifiers());

         prevLine = token.line();
         prevStart = token.startChar();
      }

      return data;
   }

   /**
    * Returns semantic tokens restricted to a given {@link Range}.
    *
    * @param text full document text
    * @param range the range to filter
    * @return list of semantic tokens within the range
    */
   public List<SemanticToken> analyzeTextRange(String text, Range range) {
      List<SemanticToken> allTokens = analyzeText(text);
      List<SemanticToken> rangeTokens = new ArrayList<>();

      for (SemanticToken token : allTokens) {
         Position tokenStart = new Position(token.line(), token.startChar());
         Position tokenEnd = new Position(token.line(), token.startChar() + token.length());

         if (isPositionInRange(tokenStart, range) || isPositionInRange(tokenEnd, range)) {
            rangeTokens.add(token);
         }
      }

      return rangeTokens;
   }

   /**
    * Checks if a position falls within a given range.
    *
    * @param pos the position to check
    * @param range the range
    * @return true if the position is inside the range, false otherwise
    */
   private boolean isPositionInRange(Position pos, Range range) {
      Position start = range.getStart();
      Position end = range.getEnd();

      if (pos.getLine() < start.getLine() || pos.getLine() > end.getLine()) return false;
      if (pos.getLine() == start.getLine() && pos.getCharacter() < start.getCharacter()) return false;
      if (pos.getLine() == end.getLine() && pos.getCharacter() > end.getCharacter()) return false;

      return true;
   }

   /**
    * Provides code completion suggestions based on lexer keywords.
    *
    * @param text the current partial token
    * @return list of matching {@link CompletionItem}
    */
   public List<CompletionItem> getCompletions(String text) {
      List<CompletionItem> res = new ArrayList<>();
      for (var kw : ProgressLexer.keywords) {
         if (kw.startsWith(text)) {
            CompletionItem completion = new CompletionItem();
            completion.setLabel(kw);
            completion.setInsertText(kw);
            res.add(completion);
         }
      }
      return res;
   }
}
