import { MobileWebCookie } from 'mweb/common/utils/cookie';
import { ROUTES } from 'mweb/common/routes';
import { Location } from 'mweb/common/reducers/app';

///////////////////////////////////////////
/////                                 /////
/////          PLEASE NOTE            /////
/////                                 /////
///// This code is NOT guaranteed to  /////
///// run with polyfills. DO NOT      /////
///// ASSUME THEY'VE BEEN APPLIED.    /////
///////////////////////////////////////////

// Simple rules:
// 1) All non-asterisks are treated literally.
// 2) '*' is treated as a one-deep wildcard, so */chat matches a/chat but not a/b/chat.
// 3) '**' is treated as an n-deep wildcard, so chat/** matches chat/a and chat/a/b.
//    NOTE This is only supported at the end of the string.
// 4) Asterisks inside of a path part is treated as a literal, e.g. a*/chat only matches 'a*/chat'.
export const SPECIAL_PATHS = [
  '*/chat',
  '*/subscribe',
  'about/**',
  'activate',
  'bits',
  'bits/config.json',
  'bits-checkout',
  'bits-checkout/**',
  'blizzheroes/esports/hgc-2017',
  'blog/**',
  'broadcast',
  'contact',
  'dashboard',
  'email-verification/*',
  'favicon',
  'favicon.ico',
  'friends',
  'gear-on-amazon/**',
  'inbox',
  'inventory',
  'jobs',
  'jobs/**',
  'legal/**',
  'login',
  'logout',
  'manager',
  'messages/**',
  'outbox',
  'p/**',
  'partner/**',
  'passport/callback',
  'passport-callback',
  'payments',
  'prime',
  'prime/**',
  'products/**',
  'settings',
  'settings/**',
  'signup',
  'store',
  'store/games',
  'store/merch',
  'stream/**',
  'subs',
  'subs/**',
  'subscriptions',
  'support',
  'turbo',
  'upload',
  'user/legal',
  'user/remove_me',
  'view/**',
  'watch/**',
];
const FINISHED_EARLY = 'finished early';

export abstract class RedirectManager {
  protected readonly pathParts: ReadonlyArray<string>;
  constructor(
    pathParts: ReadonlyArray<string>,
    protected readonly cookie: Readonly<MobileWebCookie>,
  ) {
    this.pathParts = this.normalizePathParts(pathParts);
  }

  protected buildPath(): string {
    return this.pathParts.join('/');
  }

  protected isUpsellOptOut(): boolean {
    return (
      this.isDesktopElligible() &&
      this.cookie.upsell_opt_out === true &&
      this.isUpsellLocation()
    );
  }

  protected abstract isDesktopElligible(): boolean;

  protected isSpecialPath(): boolean {
    return this.matchesPathGlob(SPECIAL_PATHS);
  }

  protected matchesPathGlob(pathGlobs: string[]): boolean {
    return pathGlobs.some((specialPath: string) => {
      try {
        let pathIndex = 0;
        const matchesAvailableFields = specialPath
          .split('/')
          .every(specialPart => {
            const part = this.pathParts[pathIndex++];
            if (specialPart === '*' && part) {
              return true;
            }
            if (specialPart === '**' && part) {
              // Eject out of the every since we know we've reached an end wildcard.
              throw FINISHED_EARLY;
            }
            return specialPart === part;
          });
        return matchesAvailableFields && pathIndex === this.pathParts.length;
      } catch (error) {
        if (error === FINISHED_EARLY) {
          return true;
        }
        throw error;
      }
    });
  }

  protected normalizePathParts(
    pathParts: ReadonlyArray<string>,
  ): ReadonlyArray<string> {
    return pathParts
      .map(part => part.split('/'))
      .reduce((agg, part) => agg.concat(part), []);
  }

  protected isProbablyAPrimaryRoute(): boolean {
    return ROUTES.some(({ route, location }) => {
      // upsells are not primary
      if (location === Location.Upsell) {
        return false;
      }
      const routePieces = route.split('/').splice(1);
      // route pathParts can't be longer than routePieces because routePieces
      // defines all of the allowed parts. It can be shorter because
      // path pieces might define optional routes with /:foo?.
      if (this.pathParts.length > routePieces.length) {
        return false;
      }
      let currentPathIndex = 0;
      return routePieces.every(piece => {
        const currentPathPart = this.pathParts[currentPathIndex++] || '';
        return [
          this.routePartsMatchThroughWildcard,
          this.routePartStrictlyMatches,
          this.routePartsMatchThroughGroup,
          this.routePartWasOptional,
        ].some(test => test(piece, currentPathPart));
      });
    });
  }

  protected isUpsellLocation(): boolean {
    return !this.isProbablyAPrimaryRoute();
  }

  private routePartsMatchThroughWildcard = (
    routePart: string,
    pathPart: string,
  ): boolean => {
    return !!pathPart && routePart[0] === ':';
  };

  private routePartStrictlyMatches = (
    routePart: string,
    pathPart: string,
  ): boolean => {
    return routePart === pathPart;
  };

  private routePartsMatchThroughGroup = (
    routePart: string,
    pathPart: string,
  ): boolean => {
    return (
      routePart[0] === '(' &&
      routePart[routePart.length - 1] === ')' &&
      new RegExp(routePart).test(pathPart)
    );
  };

  private routePartWasOptional = (routePart: string, pathPart: string) => {
    return !pathPart && routePart[routePart.length - 1] === '?';
  };
}
