text: "use strict";
  /* eslint-disable security/detect-object-injection */
  /**
   * @module botbuilder-lg
   */
  /**
   * Copyright (c) Microsoft Corporation. All rights reserved.
   * Licensed under the MIT License.
   */
  Object.defineProperty(exports, "__esModule", { value: true });
  const uuid_1 = require("uuid");
  const diagnostic_1 = require("./diagnostic");
  const adaptive_expressions_1 = require("adaptive-expressions");
  const templatesParser_1 = require("./templatesParser");
  const evaluator_1 = require("./evaluator");
  const expander_1 = require("./expander");
  const analyzer_1 = require("./analyzer");
  const templatesParser_2 = require("./templatesParser");
  const templateErrors_1 = require("./templateErrors");
  const templateExtensions_1 = require("./templateExtensions");
  const evaluationOptions_1 = require("./evaluationOptions");
  const path_1 = require("path");
  const staticChecker_1 = require("./staticChecker");
  const lgResource_1 = require("./lgResource");
  const customizedMemory_1 = require("./customizedMemory");
  /**
   * LG entrance, including properties that LG file has, and evaluate functions.
   */
  class Templates {
      /**
       * Creates a new instance of the [Templates](xref:botbuilder-lg.Templates) class.
       * @param items Optional. List of [Template](xref:botbuilder-lg.Template) instances.
       * @param imports Optional. List of [TemplateImport](xref:botbuilder-lg.TemplateImport) instances.
       * @param diagnostics Optional. List of [Diagnostic](xref:botbuilder-lg.Diagnostic) instances.
       * @param references Optional. List of [Templates](xref:botbuilder-lg.Templates) instances.
       * @param content Optional. Content of the current Templates instance.
       * @param id Optional. Id of the current Templates instance.
       * @param expressionParser Optional. [ExpressionParser](xref:adaptive-expressions.ExpressionParser) to parse the expressions in the content.
       * @param importResolverDelegate Optional. Resolver to resolve LG import id to template text.
       * @param options Optional. List of strings representing the options during evaluation of the templates.
       * @param source Optional. Templates source.
       * @param namedReferences Optional. eferences that imported with the "as" syntax，for example: [import](path.lg) as myAlias.
       */
      constructor(items, imports, diagnostics, references, content, id, expressionParser, importResolverDelegate, options, source, namedReferences) {
          this.newLineRegex = /(\r?\n)/g;
          this.newLine = '\r\n';
          this.namespaceKey = '@namespace';
          this.exportsKey = '@exports';
          this.items = items || [];
          this.imports = imports || [];
          this.diagnostics = diagnostics || [];
          this.references = references || [];
          this.content = content || '';
          this.id = id || '';
          this.expressionParser = expressionParser || new adaptive_expressions_1.ExpressionParser();
          this.importResolver = importResolverDelegate;
          this.options = options || [];
          this.source = source;
          this.namedReferences = namedReferences || {};
          this.injectToExpressionFunction();
      }
      /**
       * Returns a new iterator for the template collection.
       */
      [Symbol.iterator]() {
          let index = 0;
          return {
              next: () => {
                  if (index < this.items.length) {
                      return { done: false, value: this.items[index++] };
                  }
                  else {
                      return { done: true, value: undefined };
                  }
              },
          };
      }
      /**
       * Returns a reference to the internal list of collection templates.
       */
      toArray() {
          return this.items;
      }
      /**
       * Appends 1 or more templates to the collection.
       * @param args List of templates to add.
       */
      push(...args) {
          args.forEach((t) => this.items.push(t));
      }
      /**
       * A value indicating whether the options when evaluation LG templates.
       */
      get lgOptions() {
          return new evaluationOptions_1.EvaluationOptions(this.options);
      }
      /**
       * A string value represents the namespace to register for current LG file.
       */
      get namespace() {
          return this.extractNamespace(this.options);
      }
      /**
       * All templates from current lg file and reference lg files.
       */
      get allTemplates() {
          let result = this.items;
          this.references.forEach((ref) => (result = result.concat(ref.items)));
          return Array.from(new Set(result));
      }
      /**
       * All diagnostics from current lg file and reference lg files.
       */
      get allDiagnostics() {
          let result = this.diagnostics;
          this.references.forEach((ref) => (result = result.concat(ref.diagnostics)));
          return Array.from(new Set(result));
      }
      /**
       * parse a file and return LG file.
       * @param filePath LG absolute file path..
       * @param importResolver Resolver to resolve LG import id to template text.
       * @param expressionParser Expression parser for evaluating expressions.
       * @returns New lg file.
       */
      static parseFile(filePath, importResolver, expressionParser) {
          return templatesParser_2.TemplatesParser.parseFile(filePath, importResolver, expressionParser).injectToExpressionFunction();
      }
      /**
       * Parser to turn lg content into a Templates.
       * @deprecated This method will soon be deprecated. Use ParseResource instead.
       * @param content Text content contains lg templates.
       * @param id Id is the identifier of content. If importResolver is undefined, id must be a full path string.
       * @param importResolver Resolver to resolve LG import id to template text.
       * @param expressionParser Expression parser for evaluating expressions.
       * @returns Entity.
       */
      static parseText(content, id = '', importResolver, expressionParser) {
          return templatesParser_2.TemplatesParser.parseText(content, id, importResolver, expressionParser).injectToExpressionFunction();
      }
      /**
       * Parser to turn lg content into a Templates.
       * @param aresource LG resource.
       * @param importResolver Resolver to resolve LG import id to template text.
       * @param expressionParser Expression parser for evaluating expressions.
       * @returns Entity.
       */
      static parseResource(resource, importResolver, expressionParser) {
          return templatesParser_2.TemplatesParser.parseResource(resource, importResolver, expressionParser).injectToExpressionFunction();
      }
      /**
       * Evaluate a template with given name and scope.
       * @param templateName Template name to be evaluated.
       * @param scope The state visible in the evaluation.
       * @returns Evaluate result.
       */
      evaluate(templateName, scope, opt = undefined) {
          this.checkErrors();
          const evalOpt = opt !== undefined ? opt.merge(this.lgOptions) : this.lgOptions;
          const evaluator = new evaluator_1.Evaluator(this, evalOpt);
          let result = evaluator.evaluateTemplate(templateName, scope);
          if (evalOpt.LineBreakStyle === evaluationOptions_1.LGLineBreakStyle.Markdown && typeof result === 'string') {
              result = result.replace(this.newLineRegex, '$1$1');
          }
          return result;
      }
      /**
       * Expand a template with given name and scope.
       * Return all possible responses instead of random one.
       * @param templateName Template name to be evaluated.
       * @param scope The state visible in the evaluation.
       * @returns Expand result.
       */
      expandTemplate(templateName, scope, opt = undefined) {
          this.checkErrors();
          const evalOpt = opt !== undefined ? opt.merge(this.lgOptions) : this.lgOptions;
          const expander = new expander_1.Expander(this, evalOpt);
          return expander.expandTemplate(templateName, scope);
      }
      /**
       * Analyze a template to get the static analyzer results including variables and template references.
       * @param templateName Template name to be evaluated.
       * @returns Analyzer result.
       */
      analyzeTemplate(templateName) {
          this.checkErrors();
          const analyzer = new analyzer_1.Analyzer(this);
          return analyzer.analyzeTemplate(templateName);
      }
      /**
       * Use to evaluate an inline template str.
       * @param inlineStr Inline string which will be evaluated.
       * @param scope Scope object or JToken.
       */
      evaluateText(inlineStr, scope, opt = undefined) {
          if (inlineStr === undefined) {
              throw Error('inline string is empty');
          }
          this.checkErrors();
          const inlineTemplateId = `${Templates.inlineTemplateIdPrefix}${this.getRandomTemplateId()}`;
          // wrap inline string with "# name and -" to align the evaluation process
          const multiLineMark = '```';
          inlineStr =
              !inlineStr.trim().startsWith(multiLineMark) && inlineStr.includes('\n')
                  ? `${multiLineMark}${inlineStr}${multiLineMark}`
                  : inlineStr;
          const newContent = `#${inlineTemplateId} ${this.newLine} - ${inlineStr}`;
          const newTemplates = templatesParser_2.TemplatesParser.parseTextWithRef(newContent, this);
          const evalOpt = opt !== undefined ? opt.merge(this.lgOptions) : this.lgOptions;
          return newTemplates.evaluate(inlineTemplateId, scope, evalOpt);
      }
      /**
       * Update a template and return LG file.
       * @param templateName Orignial template name.
       * @param newTemplateName New template name.
       * @param parameters New params.
       * @param templateBody New template body.
       * @returns New lg file.
       */
      updateTemplate(templateName, newTemplateName, parameters, templateBody) {
          const template = this.items.find((u) => u.name === templateName);
          if (template) {
              this.clearDiagnostic();
              const templateNameLine = this.buildTemplateNameLine(newTemplateName, parameters);
              const newTemplateBody = this.convertTemplateBody(templateBody);
              const content = `${templateNameLine}${this.newLine}${newTemplateBody}`;
              // update content
              this.content = this.replaceRangeContent(this.content, template.sourceRange.range.start.line - 1, template.sourceRange.range.end.line - 1, content);
              let updatedTemplates = new Templates();
              updatedTemplates.id = this.id;
              updatedTemplates.expressionParser = this.expressionParser;
              updatedTemplates.importResolver = this.importResolver;
              updatedTemplates.namedReferences = this.namedReferences;
              const resource = new lgResource_1.LGResource(this.id, this.id, content);
              updatedTemplates = new templatesParser_1.TemplatesTransformer(updatedTemplates).transform(templatesParser_2.TemplatesParser.antlrParseTemplates(resource));
              const originalStartLine = template.sourceRange.range.start.line - 1;
              this.appendDiagnosticWithOffset(updatedTemplates.diagnostics, originalStartLine);
              if (updatedTemplates.toArray().length > 0) {
                  const newTemplate = this.recomputeSourceRange(updatedTemplates.toArray()[0], content);
                  this.adjustRangeForUpdateTemplate(template, newTemplate);
                  new staticChecker_1.StaticChecker(this).check().forEach((u) => this.diagnostics.push(u));
              }
          }
          return this;
      }
      /**
       * Add a new template and return LG file.
       * @param templateName New template name.
       * @param parameters New params.
       * @param templateBody New  template body.
       * @returns New lg file.
       */
      addTemplate(templateName, parameters, templateBody) {
          const template = this.items.find((u) => u.name === templateName);
          if (template) {
              throw new Error(templateErrors_1.TemplateErrors.templateExist(templateName));
          }
          this.clearDiagnostic();
          const templateNameLine = this.buildTemplateNameLine(templateName, parameters);
          const newTemplateBody = this.convertTemplateBody(templateBody);
          const content = `${templateNameLine}${this.newLine}${newTemplateBody}`;
          const originalStartLine = templateExtensions_1.TemplateExtensions.readLine(this.content).length;
          // update content
          this.content = `${this.content}${this.newLine}${templateNameLine}${this.newLine}${newTemplateBody}`;
          let updatedTemplates = new Templates();
          updatedTemplates.id = this.id;
          updatedTemplates.expressionParser = this.expressionParser;
          updatedTemplates.importResolver = this.importResolver;
          updatedTemplates.namedReferences = this.namedReferences;
          const resource = new lgResource_1.LGResource(this.id, this.id, content);
          updatedTemplates = new templatesParser_1.TemplatesTransformer(updatedTemplates).transform(templatesParser_2.TemplatesParser.antlrParseTemplates(resource));
          this.appendDiagnosticWithOffset(updatedTemplates.diagnostics, originalStartLine);
          if (updatedTemplates.toArray().length > 0) {
              const newTemplate = this.recomputeSourceRange(updatedTemplates.toArray()[0], content);
              this.adjustRangeForAddTemplate(newTemplate, originalStartLine);
              // adjust the last template's range when adding the template
              if (this.items.length > 0) {
                  this.items[this.items.length - 1].sourceRange.range.end.line =
                      newTemplate.sourceRange.range.start.line - 1;
              }
              this.items.push(newTemplate);
              new staticChecker_1.StaticChecker(this).check().forEach((u) => this.diagnostics.push(u));
          }
          return this;
      }
      /**
       * Delete an exist template.
       * @param templateName Which template should delete.
       * @returns Return the new lg file.
       */
      deleteTemplate(templateName) {
          const templateIndex = this.items.findIndex((u) => u.name === templateName);
          if (templateIndex >= 0) {
              const template = this.items[templateIndex];
              this.clearDiagnostic();
              const startLine = template.sourceRange.range.start.line - 1;
              const stopLine = template.sourceRange.range.end.line - 1;
              this.content = this.replaceRangeContent(this.content, startLine, stopLine, undefined);
              this.adjustRangeForDeleteTemplate(template);
              this.items.splice(templateIndex, 1);
              new staticChecker_1.StaticChecker(this).check().forEach((u) => this.diagnostics.push(u));
          }
          return this;
      }
      /**
       * Returns a string representation of a [Templates](xref:botbuilder-lg.Templates) content.
       * @returns A string representation of a [Templates](xref:botbuilder-lg.Templates) content.
       */
      toString() {
          return this.content;
      }
      /**
       * @private
       */
      getRandomTemplateId() {
          return uuid_1.v4().split('-').join('');
      }
      /**
       * @private
       */
      appendDiagnosticWithOffset(diagnostics, offset) {
          if (diagnostics) {
              diagnostics.forEach((u) => {
                  u.range.start.line += offset;
                  u.range.end.line += offset;
                  this.diagnostics.push(u);
              });
          }
      }
      /**
       * @private
       */
      adjustRangeForUpdateTemplate(oldTemplate, newTemplate) {
          const newRange = newTemplate.sourceRange.range.end.line - newTemplate.sourceRange.range.start.line;
          const oldRange = oldTemplate.sourceRange.range.end.line - oldTemplate.sourceRange.range.start.line;
          const lineOffset = newRange - oldRange;
          let hasFound = false;
          for (let i = 0; i < this.items.length; i++) {
              if (hasFound) {
                  this.items[i].sourceRange.range.start.line += lineOffset;
                  this.items[i].sourceRange.range.end.line += lineOffset;
              }
              else if (this.items[i].name === oldTemplate.name) {
                  hasFound = true;
                  newTemplate.sourceRange.range.start.line = oldTemplate.sourceRange.range.start.line;
                  newTemplate.sourceRange.range.end.line = oldTemplate.sourceRange.range.end.line + lineOffset;
                  this.items[i] = newTemplate;
              }
          }
      }
      /**
       * @private
       */
      adjustRangeForAddTemplate(newTemplate, lineOffset) {
          const lineLength = newTemplate.sourceRange.range.end.line - newTemplate.sourceRange.range.start.line;
          newTemplate.sourceRange.range.start.line = lineOffset + 1;
          newTemplate.sourceRange.range.end.line = lineOffset + lineLength + 1;
      }
      /**
       * @private
       */
      adjustRangeForDeleteTemplate(oldTemplate) {
          const lineOffset = oldTemplate.sourceRange.range.end.line - oldTemplate.sourceRange.range.start.line + 1;
          let hasFound = false;
          for (let i = 0; i < this.items.length; i++) {
              if (hasFound) {
                  this.items[i].sourceRange.range.start.line -= lineOffset;
                  this.items[i].sourceRange.range.end.line -= lineOffset;
              }
              else if (this.items[i].name == oldTemplate.name) {
                  hasFound = true;
              }
          }
      }
      /**
       * @private
       */
      clearDiagnostic() {
          this.diagnostics = [];
      }
      /**
       * @private
       */
      replaceRangeContent(originString, startLine, stopLine, replaceString) {
          const originList = templateExtensions_1.TemplateExtensions.readLine(originString);
          if (startLine < 0 || startLine > stopLine || stopLine >= originList.length) {
              throw new Error('index out of range.');
          }
          const destList = [];
          destList.push(...originList.slice(0, startLine));
          if (replaceString !== undefined && replaceString !== null) {
              destList.push(replaceString);
          }
          destList.push(...originList.slice(stopLine + 1));
          return destList.join(this.newLine);
      }
      /**
       * @private
       */
      convertTemplateBody(templateBody) {
          if (!templateBody) {
              return '';
          }
          const replaceList = templateExtensions_1.TemplateExtensions.readLine(templateBody);
          const destList = replaceList.map((u) => {
              return u.trimLeft().startsWith('#') ? `- ${u.trimLeft()}` : u;
          });
          return destList.join(this.newLine);
      }
      /**
       * @private
       * Compute LG SourceRange based on content instead of parsed token.
       * */
      recomputeSourceRange(template, content) {
          if (content != null) {
              const contentList = templateExtensions_1.TemplateExtensions.readLine(content);
              template.sourceRange.range.start.line = 1;
              template.sourceRange.range.end.line = contentList.length;
          }
          return template;
      }
      /**
       * @private
       */
      buildTemplateNameLine(templateName, parameters) {
          // if parameters is null or undefined, ignore ()
          if (parameters === undefined || parameters === undefined) {
              return `# ${templateName}`;
          }
          else {
              return `# ${templateName}(${parameters.join(', ')})`;
          }
      }
      /**
       * @private
       */
      checkErrors() {
          if (this.allDiagnostics) {
              const errors = this.allDiagnostics.filter((u) => u.severity === diagnostic_1.DiagnosticSeverity.Error);
              if (errors.length !== 0) {
                  throw Error(errors.join(this.newLine));
              }
          }
      }
      /**
       * @private
       */
      injectToExpressionFunction() {
          const totalTemplates = [this].concat(this.references);
          for (const curTemplates of totalTemplates) {
              const globalFuncs = curTemplates.getGlobalFunctionTable(curTemplates.options);
              for (const templateName of globalFuncs) {
                  if (curTemplates.items.find((u) => u.name === templateName) !== undefined) {
                      const prefix = !curTemplates.namespace || !curTemplates.namespace.trim() ? '' : curTemplates.namespace + '.';
                      const newGlobalName = prefix + templateName;
                      adaptive_expressions_1.Expression.functions.add(newGlobalName, new adaptive_expressions_1.ExpressionEvaluator(newGlobalName, (expr, state, options) => {
                          let value;
                          let error;
                          let args;
                          const evaluator = new evaluator_1.Evaluator(this, this.lgOptions);
                          // eslint-disable-next-line prefer-const
                          ({ args, error } = adaptive_expressions_1.FunctionUtils.evaluateChildren(expr, state, options));
                          if (!error) {
                              const parameters = evaluator.templateMap[templateName].parameters;
                              const newScope = {};
                              parameters.map((e, i) => (newScope[e] = args[i]));
                              const scope = new customizedMemory_1.CustomizedMemory(state, new adaptive_expressions_1.SimpleObjectMemory(newScope));
                              try {
                                  value = evaluator.evaluateTemplate(templateName, scope);
                              }
                              catch (e) {
                                  error = e.message;
                              }
                          }
                          return { value, error };
                      }, adaptive_expressions_1.ReturnType.Object));
                  }
              }
          }
          return this;
      }
      /**
       * @private
       */
      extractOptionByKey(nameOfKey, options) {
          let result = undefined;
          for (const option of options) {
              if (nameOfKey && option.includes('=')) {
                  const index = option.indexOf('=');
                  const key = option.substring(0, index).trim().toLowerCase();
                  const value = option.substring(index + 1).trim();
                  if (key === nameOfKey) {
                      result = value;
                  }
              }
          }
          return result;
      }
      /**
       * @private
       */
      extractNamespace(options) {
          let result = this.extractOptionByKey(this.namespaceKey, options);
          if (!result) {
              result = path_1.basename(this.id || '').split('.')[0];
          }
          return result;
      }
      /**
       * @private
       */
      getGlobalFunctionTable(options) {
          const result = [];
          const value = this.extractOptionByKey(this.exportsKey, options);
          if (value) {
              const templateList = value.split(',');
              templateList.forEach((u) => {
                  result.push(u.trim());
              });
          }
          return result;
      }
  }
  /**
   * Temp Template ID for inline content.
   */
  Templates.inlineTemplateIdPrefix = '__temp__';
  exports.Templates = Templates;
  //# sourceMappingURL=templates.js.map