package com.hubspot.jinjava.el.ext.eager;

import com.google.common.primitives.Primitives;
import com.hubspot.jinjava.el.ext.DeferredParsingException;
import com.hubspot.jinjava.el.ext.ExtendedParser;
import com.hubspot.jinjava.interpret.DeferredValueException;
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
import com.hubspot.jinjava.util.EagerExpressionResolver;
import de.odysseus.el.tree.Bindings;
import de.odysseus.el.tree.impl.ast.AstIdentifier;
import de.odysseus.el.tree.impl.ast.AstNode;
import java.util.Collection;
import java.util.function.Supplier;
import javax.el.ELContext;
import javax.el.ELException;

public interface EvalResultHolder {
  Object getEvalResult();

  void setEvalResult(Object evalResult);

  boolean hasEvalResult();

  default Object eval(
    Supplier<Object> evalSupplier,
    Bindings bindings,
    ELContext context
  ) {
    try {
      setEvalResult(evalSupplier.get());
      return checkEvalResultSize(context);
    } catch (DeferredValueException | ELException originalException) {
      DeferredParsingException e = EvalResultHolder.convertToDeferredParsingException(
        originalException
      );
      throw new DeferredParsingException(
        this,
        getPartiallyResolved(bindings, context, e, false)
      );
    }
  }

  default Object checkEvalResultSize(ELContext context) {
    Object evalResult = getEvalResult();
    if (
      evalResult instanceof Collection &&
      ((Collection<?>) evalResult).size() > 100 && // TODO make size configurable
      (
        (JinjavaInterpreter) context
          .getELResolver()
          .getValue(context, null, ExtendedParser.INTERPRETER)
      ).getContext()
        .isDeferLargeObjects()
    ) {
      throw new DeferredValueException("Collection too big");
    }
    return evalResult;
  }

  String getPartiallyResolved(
    Bindings bindings,
    ELContext context,
    DeferredParsingException deferredParsingException,
    boolean preserveIdentifier
  );

  static String reconstructNode(
    Bindings bindings,
    ELContext context,
    EvalResultHolder astNode,
    DeferredParsingException exception,
    boolean preserveIdentifier
  ) {
    if (astNode == null) {
      return "";
    }
    if (
      preserveIdentifier &&
      !astNode.hasEvalResult() &&
      !(exception != null && exception.getSourceNode() == astNode)
    ) {
      try {
        EagerExpressionResolver.getValueAsJinjavaStringSafe(
          ((AstNode) astNode).eval(bindings, context)
        );
      } catch (DeferredParsingException ignored) {}
    }
    Object evalResult = astNode.getEvalResult();
    if (astNode.hasEvalResult() && (!preserveIdentifier || isPrimitive(evalResult))) {
      try {
        return EagerExpressionResolver.getValueAsJinjavaStringSafe(evalResult);
      } catch (DeferredValueException e) {
        preserveIdentifier = true;
      }
    }
    if (
      preserveIdentifier ||
      (
        astNode instanceof AstIdentifier &&
        ExtendedParser.INTERPRETER.equals(((AstIdentifier) astNode).getName())
      )
    ) {
      return astNode.getPartiallyResolved(bindings, context, exception, true);
    }
    if (exception != null && exception.getSourceNode() == astNode) {
      return exception.getDeferredEvalResult();
    }
    if (!astNode.hasEvalResult()) {
      try {
        evalResult = ((AstNode) astNode).eval(bindings, context);
      } catch (DeferredParsingException e) {
        return e.getDeferredEvalResult();
      }
    }
    try {
      return EagerExpressionResolver.getValueAsJinjavaStringSafe(evalResult);
    } catch (DeferredValueException e) {
      return astNode.getPartiallyResolved(bindings, context, exception, true);
    }
  }

  static DeferredParsingException convertToDeferredParsingException(
    RuntimeException original
  ) {
    DeferredValueException deferredValueException;
    if (!(original instanceof DeferredValueException)) {
      if (original.getCause() instanceof DeferredValueException) {
        deferredValueException = (DeferredValueException) original.getCause();
      } else {
        throw original;
      }
    } else {
      deferredValueException = (DeferredValueException) original;
    }
    if (deferredValueException instanceof DeferredParsingException) {
      return (DeferredParsingException) deferredValueException;
    }
    return null;
  }

  static boolean isPrimitive(Object evalResult) {
    return (
      evalResult == null ||
      Primitives.isWrapperType(evalResult.getClass()) ||
      evalResult instanceof String
    );
  }
}

@@ -2207,32 +2207,178 @@
 eturn %22%22;%0A    %7D%0A
+    preserveIdentifier %7C=%0A      astNode instanceof AstIdentifier &&%0A      ExtendedParser.INTERPRETER.equals(((AstIdentifier) astNode).getName());%0A
     if (%0A      p
@@ -2764,540 +2764,89 @@
 if (
-astNode.hasEvalResult() && (!preserveIdentifier %7C%7C isPrimitive(evalResult))) %7B%0A      try %7B%0A        return EagerExpressionResolver.getValueAsJinjavaStringSafe(evalResult);%0A      %7D catch (DeferredValueException e) %7B%0A        preserveIdentifier = true;%0A      %7D%0A    %7D%0A    if (%0A      preserveIdentifier %7C%7C%0A      (%0A        astNode instanceof AstIdentifier &&%0A        ExtendedParser.INTERPRETER.equals(((AstIdentifier) astNode).getName())%0A      )%0A    ) %7B%0A      return astNode.getPartiallyResolved(bindings, context, exception, true);%0A    %7D%0A
+!preserveIdentifier %7C%7C (astNode.hasEvalResult() && isPrimitive(evalResult))) %7B%0A  
     
@@ -2912,24 +2912,26 @@
 de) %7B%0A      
+ 
+ 
 return excep
@@ -2960,30 +2960,34 @@
 sult();%0A    
+  
 %7D%0A    
+ 
+ 
 if (!astNode
@@ -3004,32 +3004,34 @@
 sult()) %7B%0A      
+  
 try %7B%0A        ev
@@ -3024,24 +3024,26 @@
 y %7B%0A        
+ 
+ 
 evalResult =
@@ -3086,32 +3086,34 @@
 context);%0A      
+  
 %7D catch (Deferre
@@ -3135,32 +3135,34 @@
 on e) %7B%0A        
+ 
+ 
 return e.getDefe
@@ -3185,24 +3185,30 @@
 ;%0A      
+  
 %7D%0A    
+ 
+ 
 %7D%0A
+  
     try 
@@ -3205,24 +3205,26 @@
       try %7B%0A
+  
       return
@@ -3285,32 +3285,34 @@
 valResult);%0A    
+  
 %7D catch (Deferre
@@ -3323,31 +3323,42 @@
 ueException 
-e
+ignored
 ) %7B
+%7D
 %0A  
+  %7D%0A
     return a
@@ -3422,22 +3422,16 @@
  true);%0A
-    %7D%0A
   %7D%0A%0A  s

Simplify logic in reconstructNode