text: "use strict";
  /**
   * @module botbuilder-dialogs
   */
  /**
   * Copyright (c) Microsoft Corporation. All rights reserved.
   * Licensed under the MIT License.
   */
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
      function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
      return new (P || (P = Promise))(function (resolve, reject) {
          function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
          function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
          function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
          step((generator = generator.apply(thisArg, _arguments || [])).next());
      });
  };
  Object.defineProperty(exports, "__esModule", { value: true });
  exports.DialogStateManager = void 0;
  const componentMemoryScopes_1 = require("./componentMemoryScopes");
  const componentPathResolvers_1 = require("./componentPathResolvers");
  const botbuilder_core_1 = require("botbuilder-core");
  const dialogPath_1 = require("./dialogPath");
  const dialogsComponentRegistration_1 = require("../dialogsComponentRegistration");
  const PATH_TRACKER = 'dialog._tracker.paths';
  const DIALOG_STATE_MANAGER_CONFIGURATION = 'DialogStateManagerConfiguration';
  /**
   * The DialogStateManager manages memory scopes and path resolvers.
   *
   * @remarks
   * MemoryScopes are named root level objects, which can exist either in the dialog context or off
   * of turn state. Path resolvers allow for shortcut behavior for mapping things like
   * $foo -> dialog.foo
   */
  class DialogStateManager {
      /**
       * Initializes a new instance of the [DialogStateManager](xref:botbuilder-dialogs.DialogStateManager) class.
       *
       * @param dc The dialog context for the current turn of the conversation.
       * @param configuration Configuration for the dialog state manager.
       */
      constructor(dc, configuration) {
          var _a, _b;
          botbuilder_core_1.ComponentRegistration.add(new dialogsComponentRegistration_1.DialogsComponentRegistration());
          this.dialogContext = dc;
          this.configuration = configuration !== null && configuration !== void 0 ? configuration : dc.context.turnState.get(DIALOG_STATE_MANAGER_CONFIGURATION);
          if (!this.configuration) {
              this.configuration = { memoryScopes: [], pathResolvers: [] };
              // get all of the component memory scopes.
              botbuilder_core_1.ComponentRegistration.components
                  .filter((component) => componentMemoryScopes_1.isComponentMemoryScopes(component))
                  .forEach((component) => {
                  this.configuration.memoryScopes.push(...component.getMemoryScopes());
              });
              // merge in turn state memory scopes
              const memoryScopes = (_a = dc.context.turnState.get('memoryScopes')) !== null && _a !== void 0 ? _a : [];
              this.configuration.memoryScopes.push(...memoryScopes);
              // get all of the component path resolvers.
              botbuilder_core_1.ComponentRegistration.components
                  .filter((component) => componentPathResolvers_1.isComponentPathResolvers(component))
                  .forEach((component) => {
                  this.configuration.pathResolvers.push(...component.getPathResolvers());
              });
              // merge in turn state ones path resolvers
              const pathResolvers = (_b = dc.context.turnState.get('pathResolvers')) !== null && _b !== void 0 ? _b : [];
              this.configuration.pathResolvers.push(...pathResolvers);
              // cache for any other new dialogStateManager instances in this turn
              dc.context.turnState.set(DIALOG_STATE_MANAGER_CONFIGURATION, this.configuration);
          }
      }
      /**
       * Get the value from memory using path expression.
       *
       * @remarks
       * This always returns a CLONE of the memory, any modifications to the result will not affect memory.
       * @param T The value type to return.
       * @param pathExpression Path expression to use.
       * @param defaultValue (Optional) default value to use if the path isn't found. May be a function that returns the default value to use.
       * @returns The found value or undefined if not found and no `defaultValue` specified.
       */
      getValue(pathExpression, defaultValue) {
          function returnDefault() {
              return typeof defaultValue == 'function' ? defaultValue() : defaultValue;
          }
          // Get path segments
          const segments = this.parsePath(this.transformPath(pathExpression));
          if (segments.length < 1) {
              return returnDefault();
          }
          // Get memory scope to search over
          const scope = this.getMemoryScope(segments[0].toString());
          if (scope == undefined) {
              throw new Error(`DialogStateManager.getValue: a scope of '${segments[0]}' wasn't found.`);
          }
          // Search over path
          const memory = this.resolveSegments(scope.getMemory(this.dialogContext), segments, false);
          // Return default value if nothing found
          return memory != undefined ? memory : returnDefault();
      }
      /**
       * Set memory to value.
       * @param pathExpression Path to memory.
       * @param value Value to set.
       */
      setValue(pathExpression, value) {
          // Get path segments
          const tpath = this.transformPath(pathExpression);
          const segments = this.parsePath(tpath);
          if (segments.length < 1) {
              throw new Error(`DialogStateManager.setValue: path wasn't specified.`);
          }
          // Track changes
          this.trackChange(tpath);
          // Get memory scope to update
          const scope = this.getMemoryScope(segments[0].toString());
          if (scope == undefined) {
              throw new Error(`DialogStateManager.setValue: a scope of '${segments[0]}' wasn't found.`);
          }
          // Update memory
          if (segments.length > 1) {
              // Find value up to last key
              // - Missing paths will be populated as needed
              let memory = scope.getMemory(this.dialogContext);
              memory = this.resolveSegments(memory, segments, true);
              // Update value
              let key = segments[segments.length - 1];
              if (key === 'first()') {
                  key = 0;
              }
              if (typeof key == 'number' && Array.isArray(memory)) {
                  // Only allow positive indexes
                  if (key < 0) {
                      throw new Error(`DialogStateManager.setValue: unable to update value for '${pathExpression}'. Negative indexes aren't allowed.`);
                  }
                  // Expand array as needed and update array
                  const l = key + 1;
                  while (memory.length < l) {
                      memory.push(undefined);
                  }
                  memory[key] = value;
              }
              else if (typeof key == 'string' &&
                  key.length > 0 &&
                  typeof memory == 'object' &&
                  !Array.isArray(memory)) {
                  // Find key to use and update object
                  key = this.findObjectKey(memory, key) || key;
                  memory[key] = value;
              }
              else {
                  throw new Error(`DialogStateManager.setValue: unable to update value for '${pathExpression}'.`);
              }
          }
          else {
              // Just update memory scope
              scope.setMemory(this.dialogContext, value);
          }
      }
      /**
       * Delete property from memory
       * @param path The leaf property to remove.
       */
      deleteValue(pathExpression) {
          // Get path segments
          const tpath = this.transformPath(pathExpression);
          const segments = this.parsePath(tpath);
          if (segments.length < 2) {
              throw new Error(`DialogStateManager.deleteValue: invalid path of '${pathExpression}'.`);
          }
          // Track change
          this.trackChange(tpath);
          // Get memory scope to update
          const scope = this.getMemoryScope(segments[0].toString());
          if (scope == undefined) {
              throw new Error(`DialogStateManager.deleteValue: a scope of '${segments[0]}' wasn't found.`);
          }
          // Find value up to last key
          const key = segments.pop();
          const memory = this.resolveSegments(scope.getMemory(this.dialogContext), segments, false);
          // Update value
          if (typeof key == 'number' && Array.isArray(memory)) {
              if (key < memory.length) {
                  memory.splice(key, 1);
              }
          }
          else if (typeof key == 'string' && key.length > 0 && typeof memory == 'object' && !Array.isArray(memory)) {
              const found = this.findObjectKey(memory, key);
              if (found) {
                  delete memory[found];
              }
          }
      }
      /**
       * Ensures that all memory scopes have been loaded for the current turn.
       *
       * @remarks
       * This should be called at the beginning of the turn.
       */
      loadAllScopes() {
          return __awaiter(this, void 0, void 0, function* () {
              const scopes = this.configuration.memoryScopes;
              for (let i = 0; i < scopes.length; i++) {
                  yield scopes[i].load(this.dialogContext);
              }
          });
      }
      /**
       * Saves any changes made to memory scopes.
       *
       * @remarks
       * This should be called at the end of the turn.
       */
      saveAllChanges() {
          return __awaiter(this, void 0, void 0, function* () {
              const scopes = this.configuration.memoryScopes;
              for (let i = 0; i < scopes.length; i++) {
                  yield scopes[i].saveChanges(this.dialogContext);
              }
          });
      }
      /**
       * Deletes all of the backing memory for a given scope.
       */
      deleteScopesMemory(name) {
          return __awaiter(this, void 0, void 0, function* () {
              name = name.toLowerCase();
              const scopes = this.configuration.memoryScopes;
              for (let i = 0; i < scopes.length; i++) {
                  const scope = scopes[i];
                  if (scope.name.toLowerCase() == name) {
                      yield scope.delete(this.dialogContext);
                      break;
                  }
              }
          });
      }
      /**
       * Normalizes the path segments of a passed in path.
       *
       * @remarks
       * A path of `profile.address[0]` will be normalized to `profile.address.0`.
       * @param pathExpression The path to normalize.
       * @param allowNestedPaths Optional. If `false` then detection of a nested path will cause an empty path to be returned. Defaults to 'true'.
       * @returns The normalized path.
       */
      parsePath(pathExpression, allowNestedPaths = true) {
          // Expand path segments
          let segment = '';
          let depth = 0;
          let quote = '';
          const output = [];
          for (let i = 0; i < pathExpression.length; i++) {
              const c = pathExpression[i];
              if (depth > 0) {
                  // We're in a set of brackets
                  if (quote.length) {
                      // We're in a string
                      switch (c) {
                          case '\\':
                              // Escape code detected
                              i++;
                              segment += pathExpression[i];
                              break;
                          default:
                              segment += c;
                              if (c == quote) {
                                  quote = '';
                              }
                              break;
                      }
                  }
                  else {
                      // We're in a bracket
                      switch (c) {
                          case '[':
                              depth++;
                              segment += c;
                              break;
                          case ']':
                              depth--;
                              if (depth > 0) {
                                  segment += c;
                              }
                              break;
                          case "'":
                          case '"':
                              quote = c;
                              segment += c;
                              break;
                          default:
                              segment += c;
                              break;
                      }
                      // Are we out of the brackets
                      if (depth == 0) {
                          if (isQuoted(segment)) {
                              // Quoted segment
                              output.push(segment.length > 2 ? segment.substr(1, segment.length - 2) : '');
                          }
                          else if (isIndex(segment)) {
                              // Array index
                              output.push(parseInt(segment));
                          }
                          else if (allowNestedPaths) {
                              // Resolve nested value
                              const val = this.getValue(segment);
                              const t = typeof val;
                              output.push(t == 'string' || t == 'number' ? val : '');
                          }
                          else {
                              // Abort parsing and return empty path (used for change tracking.)
                              return [];
                          }
                          segment = '';
                      }
                  }
              }
              else {
                  // We're parsing the outer path
                  switch (c) {
                      case '[':
                          if (segment.length > 0) {
                              output.push(segment);
                              segment = '';
                          }
                          depth++;
                          break;
                      case '.':
                          if (segment.length > 0) {
                              output.push(segment);
                              segment = '';
                          }
                          else if (i == 0 || i == pathExpression.length - 1) {
                              // Special case a "." at beginning or end of path
                              output.push('');
                          }
                          else if (pathExpression[i - 1] == '.') {
                              // Special case ".."
                              output.push('');
                          }
                          break;
                      default:
                          if (i > 1 && pathExpression[i - 1] == '.' && c == '$') {
                              segment += c; // x.$foo should be valid
                          }
                          else if (isValidPathChar(c)) {
                              segment += c;
                          }
                          else {
                              throw new Error(`DialogStateManager.normalizePath: Invalid path detected - ${pathExpression}`);
                          }
                          break;
                  }
              }
          }
          if (depth > 0) {
              throw new Error(`DialogStateManager.normalizePath: Invalid path detected - ${pathExpression}`);
          }
          else if (segment.length > 0) {
              output.push(segment);
          }
          return output;
      }
      /**
       * Transform the path using the registered path transformers.
       * @param pathExpression The path to transform.
       * @returns The transformed path.
       */
      transformPath(pathExpression) {
          // Run path through registered resolvers.
          const resolvers = this.configuration.pathResolvers;
          for (let i = 0; i < resolvers.length; i++) {
              pathExpression = resolvers[i].transformPath(pathExpression);
          }
          return pathExpression;
      }
      /**
       * Gets all memory scopes suitable for logging.
       * @returns Object which represents all memory scopes.
       */
      getMemorySnapshot() {
          const output = {};
          this.configuration.memoryScopes.forEach((scope) => {
              if (scope.includeInSnapshot) {
                  output[scope.name] = scope.getMemory(this.dialogContext);
              }
          });
          return output;
      }
      /**
       * Track when specific paths are changed.
       * @param paths Paths to track.
       * @returns Normalized paths to pass to [anyPathChanged()](#anypathchanged).
       */
      trackPaths(paths) {
          const allPaths = [];
          paths.forEach((path) => {
              const tpath = this.transformPath(path);
              const segments = this.parsePath(tpath, false);
              if (segments.length > 0 && (segments.length == 1 || !segments[1].toString().startsWith('_'))) {
                  // Normalize path and initialize change tracker
                  const npath = segments.join('_').toLowerCase();
                  this.setValue(`${PATH_TRACKER}.${npath}`, 0);
                  // Return normalized path
                  allPaths.push(npath);
              }
          });
          return allPaths;
      }
      /**
       * Check to see if any path has changed since watermark.
       * @param counter Time counter to compare to.
       * @param paths Paths from [trackPaths()](#trackpaths) to check.
       * @returns True if any path has changed since counter.
       */
      anyPathChanged(counter, paths) {
          let found = false;
          if (paths) {
              for (let i = 0; i < paths.length; i++) {
                  if (this.getValue(`${PATH_TRACKER}.${paths[i]}`, 0) > counter) {
                      found = true;
                      break;
                  }
              }
          }
          return found;
      }
      /**
       * @private
       * @param path Track path to change.
       */
      trackChange(path) {
          // Normalize path and scan for any matches or children that match.
          // - We're appending an extra '_' so that we can do substring matches and
          //   avoid any false positives.
          let counter = undefined;
          const npath = this.parsePath(path, false).join('_') + '_';
          const tracking = this.getValue(PATH_TRACKER) || {};
          for (const key in tracking) {
              if (`${key}_`.startsWith(npath)) {
                  // Populate counter on first use
                  if (counter == undefined) {
                      counter = this.getValue(dialogPath_1.DialogPath.eventCounter);
                  }
                  // Update tracking watermark
                  this.setValue(`${PATH_TRACKER}.${key}`, counter);
              }
          }
      }
      /**
       * @private
       * @param memory Object memory to resolve.
       * @param segments Segments of the memory to resolve.
       * @param assignment Optional.
       * @returns The value of the memory segment.
       */
      resolveSegments(memory, segments, assignment) {
          let value = memory;
          const l = assignment ? segments.length - 1 : segments.length;
          for (let i = 1; i < l && value != undefined; i++) {
              const key = segments[i];
              if (typeof key == 'number') {
                  // Key is an array index
                  if (Array.isArray(value)) {
                      value = value[key];
                  }
                  else {
                      value = undefined;
                  }
              }
              else if (key === 'first()') {
                  // Special case returning the first entity in an array of entities.
                  if (Array.isArray(value) && value.length > 0) {
                      value = value[0];
                      if (Array.isArray(value)) {
                          // Nested array detected
                          if (value.length > 0) {
                              value = value[0];
                          }
                          else {
                              value = undefined;
                          }
                      }
                  }
                  else {
                      value = undefined;
                  }
              }
              else if (typeof key == 'string' && key.length > 0) {
                  // Key is an object index
                  if (typeof value == 'object' && !Array.isArray(value)) {
                      // Case-insensitive search for prop
                      let found = this.findObjectKey(value, key);
                      // Ensure path exists as needed
                      if (assignment) {
                          const nextKey = segments[i + 1];
                          if (typeof nextKey == 'number' || nextKey === 'first()') {
                              // Ensure prop contains an array
                              if (found) {
                                  if (value[found] == undefined) {
                                      value[found] = [];
                                  }
                              }
                              else {
                                  found = key;
                                  value[found] = [];
                              }
                          }
                          else if (typeof nextKey == 'string' && nextKey.length > 0) {
                              // Ensure prop contains an object
                              if (found) {
                                  if (value[found] == undefined) {
                                      value[found] = {};
                                  }
                              }
                              else {
                                  found = key;
                                  value[found] = {};
                              }
                          }
                          else {
                              // We can't determine type so return undefined
                              found = undefined;
                          }
                      }
                      value = found ? value[found] : undefined;
                  }
                  else {
                      value = undefined;
                  }
              }
              else {
                  // Key is missing
                  value = undefined;
              }
          }
          return value;
      }
      /**
       * @private
       */
      findObjectKey(obj, key) {
          const k = key.toLowerCase();
          for (const prop in obj) {
              if (prop.toLowerCase() == k) {
                  return prop;
              }
          }
          return undefined;
      }
      /**
       * @private
       * Gets [MemoryScope](xref:botbuilder-dialogs.MemoryScope) by name.
       * @param name Name of scope.
       * @returns The [MemoryScope](xref:botbuilder-dialogs.MemoryScope).
       */
      getMemoryScope(name) {
          const key = name.toLowerCase();
          const scopes = this.configuration.memoryScopes;
          for (let i = 0; i < scopes.length; i++) {
              const scope = scopes[i];
              if (scope.name.toLowerCase() == key) {
                  return scope;
              }
          }
          return undefined;
      }
      /**
       * Gets the version number.
       * @returns A string with the version number.
       */
      version() {
          return '0';
      }
  }
  exports.DialogStateManager = DialogStateManager;
  /**
   * @private
   */
  function isIndex(segment) {
      const digits = '0123456789';
      for (let i = 0; i < segment.length; i++) {
          const c = segment[i];
          if (digits.indexOf(c) < 0) {
              // Check for negative sign
              if (c != '-' || i > 0 || segment.length < 2) {
                  return false;
              }
          }
      }
      return segment.length > 0;
  }
  /**
   * @private
   */
  function isQuoted(segment) {
      return ((segment.length > 1 && segment.startsWith("'") && segment.endsWith("'")) ||
          (segment.startsWith('"') && segment.endsWith('"')));
  }
  /**
   * @private
   */
  function isValidPathChar(c) {
      return '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-()'.indexOf(c) >= 0;
  }
  //# sourceMappingURL=dialogStateManager.js.map