<commit_before>import { inspect } from 'util';
import { RegisteredComponents } from '../BaseComponent';
import { InternalIntent } from '../enums';
import { MatchingComponentNotFoundError } from '../errors/MatchingComponentNotFoundError';
import { HandleRequest } from '../HandleRequest';
import { Jovo } from '../Jovo';
import { HandlerMetadata } from '../metadata/HandlerMetadata';
import { MetadataStorage } from '../metadata/MetadataStorage';
import { Plugin, PluginConfig } from '../Plugin';
import { findAsync } from '../utilities';

export interface RouterPluginConfig extends PluginConfig {}

declare module '../Extensible' {
  interface ExtensiblePluginConfig {
    RouterPlugin?: RouterPluginConfig;
  }

  interface ExtensiblePlugins {
    RouterPlugin?: RouterPlugin;
  }
}

export interface JovoRoute {
  path: string[];
  handlerKey: string | symbol;
}

export interface RouteMatch {
  path: string[];
  metadata: HandlerMetadata;
}

export class RouterPlugin extends Plugin<RouterPluginConfig> {
  getDefaultConfig() {
    return {};
  }

  mount(handleRequest: HandleRequest): Promise<void> | void {
    handleRequest.middlewareCollection.get('dialog.logic')?.use(this.handle);
    return;
  }

  private handle = async (handleRequest: HandleRequest, jovo: Jovo) => {
    // TODO implement handling of jovo.$type

    const intentName = jovo.$request.getIntentName();
    if (!intentName) {
      // TODO determine what to do if no intent was passed (probably UNHANDLED)
      // in the future other data can be passed and used by the handler, but for now just use the intent-name
      return;
    }

    console.log(
      'Components',
      inspect(handleRequest.components, { depth: null, compact: true, colors: true }),
    );
    console.log('State', inspect(jovo.state, { depth: null, compact: true, colors: true }));

    let routeMatches: RouteMatch[] = [];
    if (!jovo.state) {
      routeMatches = await this.getGlobalRouteMatches(intentName, handleRequest, jovo);
      if (!routeMatches.length) {
        routeMatches = await this.getGlobalRouteMatches(
          InternalIntent.Unhandled,
          handleRequest,
          jovo,
        );
      }
    }

    console.log('Matches', inspect(routeMatches, { depth: null, compact: true, colors: true }));

    if (routeMatches.length) {
      const match = await this.findRouteMatch(routeMatches, handleRequest, jovo);
      console.log('Match', inspect(match, { depth: null, compact: true, colors: true }));
      if (match) {
        jovo.$route = {
          path: match.path,
          handlerKey: match.metadata.propertyKey,
        };
      }
    }
    console.log('Route', jovo.$route);

    if (!jovo.$route) {
      throw new MatchingComponentNotFoundError();
    }
  };

  private async getGlobalRouteMatches(
    intentName: string,
    handleRequest: HandleRequest,
    jovo: Jovo,
  ): Promise<RouteMatch[]> {
    return this.collectGlobalRouteMatchesOfComponents(handleRequest.components, intentName, jovo);
  }

  private async collectGlobalRouteMatchesOfComponents(
    components: RegisteredComponents,
    intentName: string,
    jovo: Jovo,
    path: string[] = [],
    matches: RouteMatch[] = [],
  ): Promise<RouteMatch[]> {
    const componentNames = Object.keys(components);
    for (let i = 0, len = componentNames.length; i < len; i++) {
      const component = components[componentNames[i]];
      if (!component) continue;
      path.push(componentNames[i]);
      // TODO: determine if matching by intent-name and platforms should be done here, will have to be reworked as soon as more input-types are supported
      const relatedHandlerMetadata = MetadataStorage.getInstance()
        .getHandlerMetadataOfComponent(component.target)
        .filter(
          (metadata) =>
            metadata.globalIntentNames.includes(intentName) &&
            (!metadata.options?.platforms?.length ||
              metadata.options?.platforms?.includes(jovo.$platform.constructor.name)),
        );
      if (relatedHandlerMetadata.length) {
        matches.push(
          ...relatedHandlerMetadata.map((metadata) => ({
            path,
            metadata,
          })),
        );
      }
      if (component.components) {
        await this.collectGlobalRouteMatchesOfComponents(
          component.components,
          intentName,
          jovo,
          [...path],
          matches,
        );
      }
    }

    return matches;
  }

  // TODO determine order, refactor then
  private async findRouteMatch(
    matches: RouteMatch[],<commit_msg>:sparkles: :bug: Fix invalid-path-bug and allow using request-type<commit_after>import { inspect } from 'util';
import { RegisteredComponents } from '../BaseComponent';
import { InternalIntent } from '../enums';
import { MatchingComponentNotFoundError } from '../errors/MatchingComponentNotFoundError';
import { HandleRequest } from '../HandleRequest';
import { Jovo } from '../Jovo';
import { HandlerMetadata } from '../metadata/HandlerMetadata';
import { MetadataStorage } from '../metadata/MetadataStorage';
import { Plugin, PluginConfig } from '../Plugin';
import { findAsync } from '../utilities';

export interface RouterPluginConfig extends PluginConfig {}

declare module '../Extensible' {
  interface ExtensiblePluginConfig {
    RouterPlugin?: RouterPluginConfig;
  }

  interface ExtensiblePlugins {
    RouterPlugin?: RouterPlugin;
  }
}

export interface JovoRoute {
  path: string[];
  handlerKey: string | symbol;
}

export interface RouteMatch {
  path: string[];
  metadata: HandlerMetadata;
}

export class RouterPlugin extends Plugin<RouterPluginConfig> {
  getDefaultConfig() {
    return {};
  }

  mount(handleRequest: HandleRequest): Promise<void> | void {
    handleRequest.middlewareCollection.get('dialog.logic')?.use(this.handle);
    return;
  }

  private handle = async (handleRequest: HandleRequest, jovo: Jovo) => {
    const intentName = jovo.$request.getIntentName() || jovo.$request.getRequestType()?.type;
    if (!intentName) {
      // TODO determine what to do if no intent was passed (probably UNHANDLED)
      // in the future other data can be passed and used by the handler, but for now just use the intent-name
      return;
    }

    console.log(
      'Components',
      inspect(handleRequest.components, { depth: null, compact: true, colors: true }),
    );
    console.log('State', inspect(jovo.state, { depth: null, compact: true, colors: true }));

    let routeMatches: RouteMatch[] = [];
    if (!jovo.state) {
      routeMatches = await this.getGlobalRouteMatches(intentName, handleRequest, jovo);
      if (!routeMatches.length) {
        routeMatches = await this.getGlobalRouteMatches(
          InternalIntent.Unhandled,
          handleRequest,
          jovo,
        );
      }
    }

    console.log('Matches', inspect(routeMatches, { depth: null, compact: true, colors: true }));

    if (routeMatches.length) {
      const match = await this.findRouteMatch(routeMatches, handleRequest, jovo);
      console.log('Match', inspect(match, { depth: null, compact: true, colors: true }));
      if (match) {
        jovo.$route = {
          path: match.path,
          handlerKey: match.metadata.propertyKey,
        };
      }
    }
    console.log('Route', jovo.$route);

    if (!jovo.$route) {
      throw new MatchingComponentNotFoundError();
    }
  };

  private async getGlobalRouteMatches(
    intentName: string,
    handleRequest: HandleRequest,
    jovo: Jovo,
  ): Promise<RouteMatch[]> {
    return this.collectGlobalRouteMatchesOfComponents(handleRequest.components, intentName, jovo);
  }

  private async collectGlobalRouteMatchesOfComponents(
    components: RegisteredComponents,
    intentName: string,
    jovo: Jovo,
    path: string[] = [],
    matches: RouteMatch[] = [],
  ): Promise<RouteMatch[]> {
    const componentNames = Object.keys(components);
    for (let i = 0, len = componentNames.length; i < len; i++) {
      const component = components[componentNames[i]];
      if (!component) continue;
      path.push(componentNames[i]);
      // TODO: determine if matching by intent-name and platforms should be done here, will have to be reworked as soon as more input-types are supported
      const relatedHandlerMetadata = MetadataStorage.getInstance()
        .getHandlerMetadataOfComponent(component.target)
        .filter(
          (metadata) =>
            metadata.globalIntentNames.includes(intentName) &&
            (!metadata.options?.platforms?.length ||
              metadata.options?.platforms?.includes(jovo.$platform.constructor.name)),
        );
      if (relatedHandlerMetadata.length) {
        matches.push(
          ...relatedHandlerMetadata.map((metadata) => ({
            path: [...path],
            metadata,
          })),
        );
      }
      if (component.components) {
        await this.collectGlobalRouteMatchesOfComponents(
          component.components,
          intentName,
          jovo,
          [...path],
          matches,
        );
      }
    }

    return matches;
  }

  // TODO determine order, refactor then
  private async findRouteMatch(
    matches: RouteMatch[],
{'fraction_non_alphanumeric': 0.11813426329555361, 'fraction_numerical': 0.00021795989537925023, 'mean_word_length': 3.0004359197907586, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1342073', 'n_tokens_mistral': 2505, 'n_tokens_neox': 2465, 'n_words': 633}