content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You 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.apache.camel.karavan.generator;
  
  import io.vertx.core.json.JsonObject;
  import org.apache.camel.util.SensitiveUtils;
  
  import java.util.*;
  import java.util.stream.Collectors;
  
  public final class CamelMetadataGenerator extends AbstractGenerator {
  
      final static String modelHeader = "karavan-generator/src/main/resources/CamelMetadata.header.ts";
      final static String targetModel = "karavan-core/src/core/model/CamelMetadata.ts";
  
      public CamelMetadataGenerator(String rootPath) {
          super(rootPath);
      }
  
      public static void main(String[] args) throws Exception {
          CamelMetadataGenerator.generate(args[0]);
          System.exit(0);
      }
  
      public static void generate(String rootPath) throws Exception {
          CamelMetadataGenerator g = new CamelMetadataGenerator(rootPath);
          g.createCamelDefinitions();
      }
  
      private void createCamelDefinitions() throws Exception {
          StringBuilder camelModel = new StringBuilder();
          camelModel.append(readFileText(modelHeader));
  
          String camelYamlDSL = getCamelYamlDSL();
          JsonObject definitions = new JsonObject(camelYamlDSL).getJsonObject("items").getJsonObject("definitions");
  
          // Generate DataFormats
          JsonObject dataformats = getProperties(definitions, "org.apache.camel.model.dataformat.DataFormatsDefinition");
          camelModel.append("\nexport const DataFormats: [string, string, string][] = [\n");
          var keys = new ArrayList<>(dataformats.getMap().keySet());
          keys.add("dfdl"); // Fix
          keys.stream().sorted().forEach((name) -> {
              String json = getMetaDataFormat(name);
              JsonObject model = new JsonObject(json).getJsonObject("model");
              String title = model.getString("title");
              String description = model.getString("description");
              camelModel.append(String.format("    ['%s','%s',\"%s\"],\n", name, title, description));
          });
          camelModel.append("]\n\n");
  
          // Prepare stepNames map
          Map<String, String> stepNames = getProcessorStepNameMap();
  
          Map<String, JsonObject> classProps = new LinkedHashMap<>();
          // Generate DataFormatMetadata
          definitions.getMap().forEach((s, o) -> {
              if (s.startsWith("org.apache.camel.model.dataformat")) {
                  String name = classSimple(s);
                  JsonObject obj = getDefinition(definitions, s);
                  JsonObject props = obj.containsKey("oneOf") ? obj.getJsonArray("oneOf").getJsonObject(1).getJsonObject("properties") : obj.getJsonObject("properties");
                  classProps.put(name, props);
              }
          });
  
          camelModel.append(getMetadataCode("CamelDataFormatMetadata", classProps, stepNames, "dataformat"));
  
  
          // Generate Languages
          JsonObject expression = getProperties(definitions, "org.apache.camel.model.language.ExpressionDefinition");
          camelModel.append("export const Languages: [string, string, string][] = [\n");
          expression.getMap().forEach((name, val) -> {
              String json = getMetaModel(name);
              JsonObject model = new JsonObject(json).getJsonObject("model");
              String title = model.getString("title");
              String description = model.getString("description");
              if (!name.equalsIgnoreCase("joor")) { // exception for deprecated
                  camelModel.append(String.format("    ['%s','%s',\"%s\"],\n", name, title, description));
              }
          });
          camelModel.append("]\n\n");
  
          // Generate LanguageMetadata
          classProps.clear();
          definitions.getMap().forEach((s, o) -> {
              if (s.startsWith("org.apache.camel.model.language") && !s.endsWith("ExpressionDefinition")) {
                  String name = classSimple(s);
                  JsonObject obj = getDefinition(definitions, s);
                  JsonObject props = obj.containsKey("oneOf") ? obj.getJsonArray("oneOf").getJsonObject(1).getJsonObject("properties") : obj.getJsonObject("properties");
                  classProps.put(name, props);
              }
          });
  
          camelModel.append(getMetadataCode("CamelLanguageMetadata", classProps, stepNames, "language"));
  
          // Generate DSL Metadata
          Map<String, Object> defsMap = new LinkedHashMap<>();
          defsMap.putAll(definitions.getJsonObject("org.apache.camel.model.ProcessorDefinition").getJsonObject("properties").getMap());
          defsMap.putAll(new JsonObject(camelYamlDSL).getJsonObject("items").getJsonObject("properties").getMap());
  
          classProps.clear();
          defsMap.forEach((s, o) -> {
              String ref = ((Map<?, ?>) o).get("$ref").toString();
              String name = classSimple(ref);
              if (!getDeprecatedClasses().contains(name)) {
                  JsonObject obj = getDefinition(definitions, ref);
                  JsonObject props = obj.containsKey("oneOf") ? obj.getJsonArray("oneOf").getJsonObject(1).getJsonObject("properties") : obj.getJsonObject("properties");
                  classProps.put(name, props);
              }
          });
  
  
          // add additional classes
          getClasses(definitions, "org.apache.camel.model").forEach(s -> {
              String className = classSimple(s);
              if (!stepNames.containsKey(className)) {
                  String stepName = deCapitalize(className.replace("Definition", ""));
                  stepNames.put(className, stepName);
              }
          });
  
          definitions.getMap().forEach((s, o) -> {
              if (s.startsWith("org.apache.camel.model.") && (s.endsWith("Definition") || s.endsWith("Config"))) {
                  String name = classSimple(s);
                  String stepName = getStepNameForClass(name);
                  JsonObject obj = getDefinition(definitions, s);
  //                JsonObject props = obj.containsKey("oneOf") ? obj.getJsonArray("oneOf").getJsonObject(1).getJsonObject("properties") : obj.getJsonObject("properties");
                  Map<String, JsonObject> properties = getClassProperties(stepName, obj);
                  classProps.put(name, JsonObject.mapFrom(properties));
              }
          });
  
          camelModel.append(getMetadataCode("CamelModelMetadata", classProps, stepNames, "model"));
  
          // add Sensitive keys
          List<String> sk = new ArrayList(SensitiveUtils.getSensitiveKeys());
          camelModel.append("export const SensitiveKeys: string[] = [\n");
          for (int i = 0; i < sk.size(); i++) {
              camelModel.append("    \"").append(sk.get(i)).append("\"").append(i < sk.size() - 1 ? "," : "").append("\n");
          }
          camelModel.append("]");
  
          writeFileText(targetModel, camelModel.toString());
      }
  
      private String getMetadataCode(String className, Map<String, JsonObject> classProps, Map<String, String> stepNames, String folder) {
          StringBuilder code = new StringBuilder();
          code.append(String.format("export const %s: ElementMeta[] = [\n", className));
          classProps.entrySet().stream().filter(entry -> {
              if (getDeprecatedClasses().contains(entry.getKey())) {
                  return false;
              } else if (entry.getValue() == null) {
                  return false;
              } else {
                  return true;
              }
          }).forEach(entry -> {
              String name = entry.getKey();
              JsonObject properties = entry.getValue();
              String stepName = getStepNameForClass(name);
  //            String json = folder.equals("model") ? getMetaModel(stepName) : (folder.equals("language") ? getMetaLanguage(stepName) : getMetaDataFormat(stepName));
              String json = null;
              if (Objects.equals(name, "BeanFactoryDefinition")) {
                  json = getMetaModelApp("bean");
              } else if (folder.equals("model")) {
                  json = getMetaModel(stepName);
              } else {
                  if (folder.equals("language")) {
                      json = getMetaLanguage(stepName);
                  } else {
                      json = getMetaDataFormat(stepName);
                  }
              }
  
              if (json != null) {
                  JsonObject model = new JsonObject(json).getJsonObject("model");
                  JsonObject props = new JsonObject(json).getJsonObject("properties");
                  List<String> propsLowerCase = props.getMap().keySet().stream().map(String::toLowerCase).collect(Collectors.toList());
  
                  Comparator<String> comparator = Comparator.comparing(e -> {
                      if (propsLowerCase.contains(e.toLowerCase())) return propsLowerCase.indexOf(e.toLowerCase());
                      else return propsLowerCase.size() + 1;
                  });
  
                  String title = model.getString("title");
                  String description = model.getString("description");
                  String label = model.getString("label");
                  code.append(String.format("    new ElementMeta('%s', '%s', '%s', \"%s\", '%s', [\n", stepName, name, title, description, label));
                  properties.getMap().keySet().stream().sorted(comparator).forEach((pname) -> {
                      Object v = properties.getMap().get(pname);
                      JsonObject p = props.getJsonObject(pname);
                      if ("inheritErrorHandler".equals(pname) && p == null) {
                      } else {
                          String typeInCatalog = getPropertyTypeInCatalog(stepName, pname);
                          PropertyMeta pm = getAttributeType(pname, new JsonObject((Map) v));
                          String displayName = p != null && p.containsKey("displayName") ? p.getString("displayName") : pname;
                          String desc = p != null && p.containsKey("description") ? p.getString("description") : pname;
                          String en = p != null && p.containsKey("enum") ? p.getString("enum").replace("[", "").replace("]", "") : "";
                          Boolean required = p != null && p.containsKey("required") ? p.getBoolean("required") : false;
                          Boolean secret = p != null && p.containsKey("secret") ? p.getBoolean("secret") : false;
                          String defaultValue = p != null && p.containsKey("defaultValue") ? p.getString("defaultValue") : "";
                          defaultValue = defaultValue.length() == 1 && defaultValue.toCharArray()[0] == '\\' ? "\\\\" : defaultValue;
                          String labels = p != null && p.containsKey("label") ? p.getString("label") : "";
                          String javaType = getJavaType(name, p);
                          if (name.equals("ProcessDefinition") && pname.equals("ref")) { // exception for processor
                              javaType = "org.apache.camel.Processor";
                          } else if ((name.equals("ParamDefinition") || name.equals("ResponseHeaderDefinition")) && pname.equals("allowableValues")) { // exception for ParamDefinition
                              pm.type = "string";
                          }
                          if (!getDeprecatedClasses().contains(pm.type)) {
                              var propertyName = "constructor".equals(pname) ? "_constructor" : pname;
                              code.append(String.format(
                                      "        new PropertyMeta('%s', '%s', \"%s\", '%s', '%s', '%s', %b, %b, %b, %b, '%s', '%s'),\n",
                                      propertyName, displayName, desc, pm.type, en, defaultValue, required, secret, pm.isArray, (pm.isArray ? pm.type : pm.isObject), labels, javaType));
                          }
                      }
                  });
                  code.append("    ], [\n");
  //                exchangeProperties
                  JsonObject exchangeProperties = new JsonObject(json).getJsonObject("exchangeProperties");
                  if (exchangeProperties != null) {
                      exchangeProperties.getMap().forEach((ep, eps) -> {
                          Map<String, String> vals = (HashMap<String, String>) eps;
                          code.append(String.format("        new ExchangePropertyMeta('%s', '%s', '%s', '%s', \"%s\"),\n",
                                  ep, vals.get("displayName"), vals.get("label"), vals.get("javaType"), vals.get("description")));
                      });
                  }
  
                  code.append("    ]),\n");
              } else {
  //                System.out.println("Empty JSON for class: " + name + " as a stepName " + stepName);
              }
          });
          code.append("]\n\n");
          return code.toString();
      }
  
      private String getJavaType(String dslName, JsonObject p) {
          if (p != null
                  && p.containsKey("type")
                  && p.getString("type").equals("object")
                  && (p.getString("javaType").equals("org.apache.camel.AggregationStrategy")
                      || p.getString("javaType").equals("org.apache.camel.Processor")
                      || p.getString("javaType").startsWith("org.apache.camel.spi")
                      )
          ) {
              String javaName = p.getString("javaType");
              try {
                  Class clazz = Class.forName(javaName);
                  if (clazz.isInterface() && (
                          clazz.getPackageName().equals("org.apache.camel") || clazz.getPackageName().equals("org.apache.camel.spi")
                  )) return javaName;
              } catch (ClassNotFoundException e) {
                 return "";
              }
          }
          return "";
      }
  
      private PropertyMeta getAttributeType(String pname, JsonObject attribute) {
          if (attribute.containsKey("$ref")) {
              String classFullName = attribute.getString("$ref");
              String className = classSimple(classFullName);
              if (className.equals("SagaActionUriDefinition"))
                  return new PropertyMeta("string", false, false);  // exception for SagaActionUriDefinition
              if (className.equals("ToDefinition"))
                  return new PropertyMeta("string", false, false);  // exception for ToDefinition (in REST Methods)
              if (className.equals("ToDynamicDefinition"))
                  return new PropertyMeta("string", false, false);  // exception for ToDynamicDefinition (in REST Methods)
              return new PropertyMeta(className, false, true);
          } else if (attribute.containsKey("type") && attribute.getString("type").equals("array")) {
              JsonObject items = attribute.getJsonObject("items");
              if (items.containsKey("properties") && items.getJsonObject("properties").containsKey(pname)) {
                  String t = items.getJsonObject("properties").getJsonObject(pname).getString("$ref");
                  if (t.equals("#/items/definitions/org.apache.camel.model.ProcessorDefinition")) {
                      return new PropertyMeta("CamelElement", true, true);
                  } else {
                      String className = classSimple(t);
                      return new PropertyMeta(className, true, true);
                  }
              } else if (items.containsKey("$ref")) {
                  String t = items.getString("$ref");
                  if (t.equals("#/items/definitions/org.apache.camel.model.ProcessorDefinition")) {
                      return new PropertyMeta("CamelElement", true, true);
                  } else {
                      String className = classSimple(t);
                      return new PropertyMeta(className, true, true);
                  }
              } else {
                  return new PropertyMeta(items.getString("type"), true, false);
              }
          } else if (attribute.containsKey("type") && attribute.getString("type").equals("object")) {
              return new PropertyMeta(attribute.getString("type"), false, false);
          } else {
              return new PropertyMeta(attribute.getString("type"), false, false);
          }
      }
  
      class PropertyMeta {
          public String type;
          public Boolean isArray;
          public Boolean isObject;
  
          public PropertyMeta(String type, Boolean isArray, Boolean isObject) {
              this.type = type;
              this.isArray = isArray;
              this.isObject = isObject;
          }
  
          @Override
          public String toString() {
              return "PropertyMeta{" +
                      "type='" + type + '\'' +
                      ", isArray=" + isArray +
                      ", isObject=" + isObject +
                      '}';
          }
      }
  }
  