/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.gwt.elemento.template;

import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.Widget;
import elemental2.dom.Attr;
import elemental2.dom.HTMLElement;
import elemental2.dom.NamedNodeMap;
import elemental2.dom.Node;
import elemental2.dom.NodeFilter;
import elemental2.dom.TreeWalker;
import jsinterop.base.Js;
import org.jboss.gwt.elemento.core.Elements;
import org.jboss.gwt.elemento.core.IsElement;

import static elemental2.dom.DomGlobal.document;

/**
 * Static helper methods used from code generated by {@code @Templated} annotation processors. You should not need to
 * call any of these methods manually.
 */
public final class TemplateUtil {

    @FunctionalInterface
    private interface SelectorFunction {

        HTMLElement select(HTMLElement context, String identifier);
    }


    private static SelectorFunction DATA_ELEMENT = (context, identifier) ->
            ((HTMLElement) context.querySelector("[data-element=" + identifier + "]"));

    private TemplateUtil() {}


    // ------------------------------------------------------ HTMLElement methods

    public static HTMLElement resolveElement(HTMLElement context, String identifier) {
        return DATA_ELEMENT.select(context, identifier);
    }

    public static <E extends HTMLElement> E resolveElementAs(HTMLElement context, String identifier) {
        HTMLElement element = DATA_ELEMENT.select(context, identifier);
        return Js.cast(element);
    }

    public static void replaceElement(HTMLElement context, String identifier, HTMLElement newElement) {
        if (newElement == null) {
            throw new NullPointerException("New element must not be null in TemplateUtils.replaceElement()");
        }
        HTMLElement oldElement = resolveElement(context, identifier);
        if (oldElement != null && oldElement.parentNode != null) {
            oldElement.parentNode.replaceChild(newElement, oldElement);
        }
    }


    // ------------------------------------------------------ IsElement / (Is)Widget methods

    public static void replaceIsElement(HTMLElement context, String identifier, IsElement newElement) {
        replaceElement(context, identifier, newElement.asElement());
    }

    public static void replaceWidget(HTMLElement context, String identifier, Widget newWidget) {
        replaceElement(context, identifier, Elements.asElement(newWidget));
    }

    public static void replaceIsWidget(HTMLElement context, String identifier, IsWidget newWidget) {
        replaceElement(context, identifier, Elements.asElement(newWidget));
    }


    // ------------------------------------------------------ custom elements

    public static <E> E resolveCustomElement(HTMLElement context, String identifier) {
        HTMLElement element = DATA_ELEMENT.select(context, identifier);
        return Js.cast(element);
    }

    public static <E> void replaceCustomElement(HTMLElement context, String identifier, E customElement) {
        HTMLElement element = Js.cast(customElement);
        replaceElement(context, identifier, element);
    }


    // ------------------------------------------------------ handlebars

    public static void replaceHandlebar(HTMLElement context, String expression, String value) {
        replaceNestedHandlebarInText(context, expression, value);
        replaceNestedHandlebarInAttributes(context, expression, value);
        // The call above does not catch the attributes in 'context', we need to replace them explicitly.
        replaceHandlebarInAttributes(context, expression, value);
    }

    private static void replaceNestedHandlebarInText(HTMLElement context, String expression, String value) {
        TreeWalker treeWalker = document.createTreeWalker(context, NodeFilter.SHOW_TEXT, node -> {
            if (node.nodeValue != null && node.nodeValue.contains(expression)) {
                return NodeFilter.FILTER_ACCEPT;
            }
            return NodeFilter.FILTER_SKIP;
        }, false);

        while (treeWalker.nextNode() != null) {
            treeWalker.getCurrentNode().nodeValue = treeWalker.getCurrentNode().nodeValue.replace(expression, value);
        }
    }

    private static void replaceNestedHandlebarInAttributes(HTMLElement context, String expression, String value) {
        TreeWalker treeWalker = document.createTreeWalker(context, NodeFilter.SHOW_ELEMENT, null, false);
        while (treeWalker.nextNode() != null) {
            if (treeWalker.getCurrentNode() instanceof HTMLElement) {
                replaceHandlebarInAttributes((HTMLElement) treeWalker.getCurrentNode(), expression, value);
            }
        }
    }

    private static void replaceHandlebarInAttributes(HTMLElement context, String expression, String value) {
        NamedNodeMap<Attr> attributes = context.attributes;
        for (int i = 0; i < attributes.getLength(); i++) {
            Node attribute = attributes.item(i);
            String currentValue = attribute.nodeValue;
            if (currentValue.contains(expression)) {
                attribute.nodeValue = currentValue.replace(expression, value);
            }
        }
    }
}

@@ -713,16 +713,47 @@
 m.Attr;%0A
+import elemental2.dom.Element;%0A
 import e
@@ -1244,16 +1244,62 @@
 ly.%0A */%0A
+@SuppressWarnings(%22UnnecessaryLocalVariable%22)%0A
 public f
@@ -1392,36 +1392,32 @@
 tion %7B%0A%0A        
-HTML
 Element select(H
@@ -1555,23 +1555,8 @@
     
- ((HTMLElement)
  con
@@ -1610,17 +1610,16 @@
 r + %22%5D%22)
-)
 ;%0A%0A    p
@@ -1823,22 +1823,33 @@
         
-return
+Element element =
  DATA_EL
@@ -1879,24 +1879,104 @@
 dentifier);%0A
+        HTMLElement htmlElement = Js.cast(element);%0A        return htmlElement;%0A
     %7D%0A%0A    p
@@ -2073,36 +2073,32 @@
 fier) %7B%0A        
-HTML
 Element element 
@@ -2141,38 +2141,47 @@
 ifier);%0A        
-return
+E htmlElement =
  Js.cast(element
@@ -2175,32 +2175,60 @@
 .cast(element);%0A
+        return htmlElement;%0A
     %7D%0A%0A    publi
@@ -3508,36 +3508,32 @@
 fier) %7B%0A        
-HTML
 Element element 
@@ -3588,31 +3588,72 @@
     
-return Js.cast(element)
+E customElement = Js.cast(element);%0A        return customElement
 ;%0A  

Add code to ease debugging in TemplateUtil when casting elements