package cn.ibizlab.codegen.templating;

import cn.ibizlab.codegen.templating.handlebars.BaseModelValueResolver;
import com.github.jknack.handlebars.*;
import com.github.jknack.handlebars.context.FieldValueResolver;
import com.github.jknack.handlebars.context.JavaBeanValueResolver;
import com.github.jknack.handlebars.context.MapValueResolver;
import com.github.jknack.handlebars.context.MethodValueResolver;
import com.github.jknack.handlebars.helper.ConditionalHelpers;
import com.github.jknack.handlebars.helper.StringHelpers;
import com.github.jknack.handlebars.helper.WithHelper;
import com.github.jknack.handlebars.io.AbstractTemplateLoader;
import com.github.jknack.handlebars.io.StringTemplateSource;
import com.github.jknack.handlebars.io.TemplateLoader;
import com.github.jknack.handlebars.io.TemplateSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Arrays;
import java.util.Locale;
import java.util.Map;

public class HandlebarsEngineAdapter extends AbstractTemplatingEngineAdapter {
     final Logger LOGGER = LoggerFactory.getLogger(HandlebarsEngineAdapter.class);
    private final String[] extensions = {"handlebars", "hbs"};

    // We use this as a simple lookup for valid file name extensions. This adapter will inspect .mustache (built-in) and infer the relevant handlebars filename
    private final String[] canCompileFromExtensions = {".handlebars",".hbs",".mustache"};
    private boolean infiniteLoops = true;
    private boolean prettyPrint = true;

    /**
     * Provides an identifier used to load the adapter. This could be a name, uuid, or any other string.
     *
     * @return A string identifier.
     */
    @Override
    public String getIdentifier() {
        return "handlebars";
    }

    public String compileTemplate(TemplatingExecutor executor,
                                  Map<String, Object> bundle, String templateFile) throws IOException {
        TemplateLoader loader = new AbstractTemplateLoader() {
            @Override
            public TemplateSource sourceAt(String location) {
                return findTemplate(executor, location);
            }
        };

        Context context = Context
                .newBuilder(bundle)
                .resolver(
                        MapValueResolver.INSTANCE,
//                        JavaBeanValueResolver.INSTANCE,
                        BaseModelValueResolver.INSTANCE,
                        //FieldValueResolver.INSTANCE,
                        MethodValueResolver.INSTANCE)
                .build();

        Handlebars handlebars = new Handlebars(loader);
        handlebars.registerHelperMissing((obj, options) -> {
            //LOGGER.warn(String.format(Locale.ROOT, "Unregistered helper name '%s', processing template:%n%s", options.helperName, options.fn.text()));
            Options.Buffer buffer = options.buffer();
            if("with".equalsIgnoreCase(options.helperName))
            {
                if (options.isFalsy(obj)) {
                    buffer.append(options.inverse());
                } else {
                    buffer.append(options.fn());
                }
            }
            else if("unless".equalsIgnoreCase(options.helperName))
            {
                if (options.isFalsy(obj)) {
                    buffer.append(options.fn());
                } else {
                    buffer.append(options.inverse());
                }
            }
            else
            {
                LOGGER.warn(String.format(Locale.ROOT, "Unregistered helper name '%s', processing template:%n%s", options.helperName, options.fn.text()));
                return "";
            }

            return buffer;
        });
        handlebars.registerHelper("json", Jackson2Helper.INSTANCE);
        StringHelpers.register(handlebars);
        handlebars.registerHelpers(ConditionalHelpers.class);
        handlebars.with(EscapingStrategy.NOOP);
        handlebars.registerHelpers(cn.ibizlab.codegen.templating.handlebars.StringHelpers.class);
        handlebars.setInfiniteLoops(infiniteLoops);
        handlebars.setPrettyPrint(prettyPrint);
        Template tmpl = handlebars.compile(templateFile);
        return tmpl.apply(context);
    }

    @Override
    public String compilePath(TemplatingExecutor executor, Map<String, Object> bundle, String templateFile) throws IOException {

        Context context = Context
                .newBuilder(bundle)
                .resolver(
                        MapValueResolver.INSTANCE,
                        JavaBeanValueResolver.INSTANCE,
                        FieldValueResolver.INSTANCE,
                        MethodValueResolver.INSTANCE)
                .build();

        Handlebars handlebars = new Handlebars();
        handlebars.registerHelperMissing((obj, options) -> {
            LOGGER.warn(String.format(Locale.ROOT, "Unregistered helper name '%s', processing template:%n%s", options.helperName, options.fn.text()));
            return "";
        });
        handlebars.registerHelper("json", Jackson2Helper.INSTANCE);
        StringHelpers.register(handlebars);
        handlebars.registerHelpers(ConditionalHelpers.class);
        handlebars.registerHelpers(cn.ibizlab.codegen.templating.handlebars.StringHelpers.class);
        handlebars.setInfiniteLoops(infiniteLoops);
        handlebars.setPrettyPrint(prettyPrint);
        Template tmpl = handlebars.compileInline(templateFile);
        String path = tmpl.apply(context);
        for(String ext:getFileExtensions())
        {
            if(path.endsWith("."+ext))
            {
                path=this.getPathWithoutExtension(path);
                break;
            }
        }

        return path;
    }

    @SuppressWarnings("java:S108")
    public TemplateSource findTemplate(TemplatingExecutor generator, String templateFile) {
        String[] possibilities = getModifiedFileLocation(templateFile);
        for (String file : possibilities) {
            try {
                return new StringTemplateSource(file, generator.getFullTemplateContents(file));
            } catch (Exception ignored) {
            }
        }

        return new StringTemplateSource(templateFile, "/*未找到模板["+templateFile+"]*/");
    }

    @Override
    public String[] getFileExtensions() {
        return extensions;
    }

    /**
     * Determine if the adapter handles compilation of the file
     *
     * @param filename The template filename
     * @return True if the file should be compiled by this adapter, else false.
     */
    @Override
    public boolean handlesFile(String filename) {
        // disallow any extension-only files like ".hbs" or ".mustache", and only consider a file compilable if it's handlebars or mustache (from which we later infer the handlebars filename)
        return Arrays.stream(canCompileFromExtensions).anyMatch(suffix -> !suffix.equalsIgnoreCase(filename) && filename.endsWith(suffix));
    }

    /**
     * Enable/disable infiniteLoops setting for the Handlebars engine. Enabling this allows for recursive partial inclusion.
     *
     * @param infiniteLoops Whether to enable (true) or disable (false)
     * @return this object
     */
    public HandlebarsEngineAdapter infiniteLoops(boolean infiniteLoops) {
        this.infiniteLoops = infiniteLoops;
        return this;
    }

    public void setPrettyPrint(boolean prettyPrint) {
        this.prettyPrint = prettyPrint;
    }
}

