import {IObjectTree, IRestEntity} from '@jacquesparis/objects-model';
import {AjaxResult} from '@jacquesparis/objects-website';
import {service} from '@loopback/core';
import {Principal} from '@loopback/security';
import {countries, Country, Language, languagesAll} from 'countries-list';
import {JSDOM} from 'jsdom';
import path from 'path';
import {
  FileGeneratedResponse,
  GeneratedResponse,
  JsonGeneratedResponse,
  TextGeneratedResponse,
  XmlGeneratedResponse,
} from '../../helper/generated-response';
import {EntityName} from '../../models';
import {AccessRightsScope, CurrentContext} from '../../services';
import {ApplicationError} from './../../helper/application-error';
import {
  HtmlGeneratedResponse,
  RedirectGeneratedResponse,
} from './../../helper/generated-response';
import {LocalesHelper} from './../../helper/locales-helper';
import {ObjectNode} from './../../models/object-node.model';
import {ObjectTree} from './../../models/object-tree.model';
import {ActionEntityService} from './../../services/action-entity/action-entity.service';
import {
  ApplicationService,
  EntityActionType,
  ExpectedValue,
} from './../../services/application.service';
import {ContentEntityService} from './../../services/content-entity/content-entity.service';
import {NodeInterceptService} from './../../services/entity-intercept/node-intercept.service';
import {InsideRestService} from './../../services/inside-rest/inside-rest.service';
import {ObjectNodeService} from './../../services/object-node/object-node.service';
import {TENANT_TYPE} from './../../services/object-tree/object-tree.const';
import {ObjectTreeService} from './../../services/object-tree/object-tree.service';
import {ObjectTypeService} from './../../services/object-type.service';
import {TransientEntityService} from './../../services/transient-entity/transient-entity.service';
import {TRAVEL_STORY_TYPE} from './../travel-story/travel-story-type.const';
import {ActionWebSiteService} from './../web-site/action-web-site.service';
import {PAGE_TYPE, WEB_SITE_VIEW_TYPE} from './../web-site/web-site.const';
import {Popup} from './../web-site/web-site.interface';
import {
  HOME_PAGE_PROVIDER,
  PAGE_CACHE_TYPE,
  WEB_SITE_CACHE_LANG_TYPE,
  WEB_SITE_VIEW_URL_TYPE,
} from './home-page.const';

export class HomePageService {
  constructor(
    @service(ActionWebSiteService)
    private actionWebSiteService: ActionWebSiteService,
    @service(ObjectTreeService) private objectTreeService: ObjectTreeService,
    @service(ObjectNodeService) private objectNodeService: ObjectNodeService,
    @service(ObjectTypeService) private objectTypeService: ObjectTypeService,
    @service(NodeInterceptService)
    private nodeInterceptService: NodeInterceptService,
    @service(ContentEntityService)
    private contentEntityService: ContentEntityService,
    @service(ApplicationService)
    private applicationService: ApplicationService,
    @service(ActionEntityService)
    private actionEntityService: ActionEntityService,
    @service(TransientEntityService)
    private transientEntityService: TransientEntityService,
    @service(InsideRestService) private insideRestService: InsideRestService,
  ) {
    this.nodeInterceptService.registerEntityInterceptorService(
      HOME_PAGE_PROVIDER,
      HomePageService.name,
      'Calculate name from host and path',
      WEB_SITE_VIEW_URL_TYPE.name,
      EntityActionType.create,
      this.interceptWebSiteViewUrlCreate.bind(this),
    );
    this.nodeInterceptService.registerEntityInterceptorService(
      HOME_PAGE_PROVIDER,
      HomePageService.name,
      'Calculate name from host and path',
      WEB_SITE_VIEW_URL_TYPE.name,
      EntityActionType.update,
      this.interceptWebSiteViewUrlUpdate.bind(this),
    );
    this.actionEntityService.registerNewMethodFunction(
      HOME_PAGE_PROVIDER,
      HomePageService.name,
      'Publish web site',
      EntityName.objectNode,
      'publish',
      WEB_SITE_VIEW_URL_TYPE.name,
      this.publishSite.bind(this),
      'delete',
    );
    this.transientEntityService.registerTransientEntityTypeFunction(
      HOME_PAGE_PROVIDER,
      HomePageService.name,
      'Add publish method definition to publish site for a locale',
      EntityName.objectNode,
      WEB_SITE_VIEW_URL_TYPE.name,
      this.completeWebSiteCacheLangTypeNode.bind(this),
    );

    this.actionEntityService.registerNewViewFunction(
      HOME_PAGE_PROVIDER,
      HomePageService.name,
      'Get site map',
      EntityName.objectTree,
      'sitemap',
      WEB_SITE_CACHE_LANG_TYPE.name,
      this.getSiteMap.bind(this),
      AccessRightsScope.read,
      {
        transientEntityService: this.transientEntityService,
        icon: 'fas fa-sitemap',
        viewName: 'View Site Map',
      },
    );

    this.actionEntityService.registerNewViewFunction(
      HOME_PAGE_PROVIDER,
      HomePageService.name,
      'Preview site map',
      EntityName.objectTree,
      'sitemap',
      WEB_SITE_VIEW_TYPE.name,
      this.previewWebSite.bind(this),
      AccessRightsScope.read,
      {
        transientEntityService: this.transientEntityService,
        icon: 'fas fa-sitemap',
        viewName: 'Preview Site Map',
      },
    );
  }

  private async getSiteUrls(
    webSiteId: string,
    includePopupUrls = false,
  ): Promise<string[]> {
    const urls: string[] = [];
    const pageTypeIds = await this.objectTypeService.getImplementingTypes(
      PAGE_TYPE.name,
    );
    // TO DO : list PAGE_TYPE.name
    const pages: ObjectNode[] = await this.objectNodeService.searchByParentTreeId(
      webSiteId,
      {objectTypeIds: pageTypeIds},
    );
    for (const page of pages) {
      if (page.pageTitle) {
        urls.push('/' + page.name);
      }
    }
    if (includePopupUrls) {
      for (const page of pages) {
        urls.push('/' + page.name + '/popup');
      }
    }
    return urls;
  }

  public async completeWebSiteCacheLangTypeNode(
    objectNode: ObjectNode,
    ctx: CurrentContext,
  ): Promise<void> {
    const urls: string[] = await this.getSiteUrls(
      objectNode.parentTreeId,
      true,
    );

    if (!objectNode.entityCtx) {
      objectNode.entityCtx = {entityType: EntityName.objectNode};
    }
    if (!objectNode.entityCtx?.actions) {
      objectNode.entityCtx.actions = {};
    }
    if (!objectNode.entityCtx.actions.methods) {
      objectNode.entityCtx.actions.methods = [];
    }
    objectNode.entityCtx.actions.methods.push({
      methodId: 'publish',
      methodName: 'Publish site',
      actionName: 'Publish site',
      parameters: {
        type: 'object',
        properties: {
          locale: {
            type: 'string',
            title: 'Language',
            required: true,
            oneOf: [
              /*
            {"enum": ["none"], "title": "never"},
            */
            ],
          },
        },
        icon: 'fas fa-globe-europe',
      },
      handlebarsMethodSampling: `[
          {
            "methodId": "publish",
            "parameters":{
              "init":"true",
              "url":"",
              "locale":"{{locale}}"
            }
          }`,
    });
    for (const url of urls) {
      objectNode.entityCtx.actions.methods[
        objectNode.entityCtx.actions.methods.length - 1
      ].handlebarsMethodSampling += `,
        {
          "methodId": "publish",
          "parameters":{
            "url":"${url}",
            "locale":"{{locale}}"
          }
        }`;
    }
    objectNode.entityCtx.actions.methods[
      objectNode.entityCtx.actions.methods.length - 1
    ].handlebarsMethodSampling += ']';

    const localesChildren = await this.objectNodeService.getOrCreateChildren(
      objectNode.id as string,
      WEB_SITE_CACHE_LANG_TYPE.name,
    );
    if (localesChildren?.length > 0) {
      const allKey = localesChildren.map((child) => child.name).join(',');
      objectNode.entityCtx.actions.methods[
        objectNode.entityCtx.actions.methods.length - 1
      ].parameters.properties.locale.oneOf.push({
        enum: [allKey],
        title: 'Already published countries',
      });
      objectNode.entityCtx.actions.methods[
        objectNode.entityCtx.actions.methods.length - 1
      ].parameters.properties.locale.default = allKey;
      for (const child of localesChildren) {
        const localParts = child.name.split('-');
        if (
          2 === localParts.length &&
          localParts[0] in languagesAll &&
          localParts[1] in countries
        ) {
          objectNode.entityCtx.actions.methods[
            objectNode.entityCtx.actions.methods.length - 1
          ].parameters.properties.locale.oneOf.push({
            enum: [child.name],
            title:
              (countries as {[country: string]: Country})[localParts[1]].name +
              ', ' +
              (languagesAll as {[lang: string]: Language})[localParts[0]].name,
          });
        }
      }
    }

    for (const country in countries) {
      for (const lang of (countries as {[country: string]: Country})[country]
        .languages) {
        objectNode.entityCtx.actions.methods[
          objectNode.entityCtx.actions.methods.length - 1
        ].parameters.properties.locale.oneOf.push({
          enum: [lang + '-' + country],
          title:
            (countries as {[country: string]: Country})[country].name +
            ', ' +
            (languagesAll as {[lang: string]: Language})[lang].name,
        });
      }
    }
  }

  public async publishSite(
    objectNode: ObjectNode,
    _args: Object,
    ctx: CurrentContext,
  ): Promise<ObjectTree> {
    const args: {url: string; locale: string; init?: boolean} = _args as {
      url: string;
      locale: string;
      init?: boolean;
    };

    const locales = args.locale.split(',');

    if (args.init) {
      const localesChildren = await this.objectNodeService.getOrCreateChildren(
        objectNode.id as string,
        WEB_SITE_CACHE_LANG_TYPE.name,
      );
      for (const child of localesChildren) {
        if (-1 < locales.indexOf(child.name)) {
          await this.objectNodeService.removeById(
            child.id as string,
            CurrentContext.get(ctx),
          );
        }
      }
    }

    for (const locale of locales) {
      await this.publishSiteLang(objectNode, locale, args.url, ctx);
    }
    return this.actionEntityService.getEntity<ObjectTree>(
      EntityName.objectTree,
      objectNode.id as string,
      CurrentContext.get(ctx, {}),
    );
  }

  public async publishSiteLang(
    objectNode: ObjectNode,
    lang: Object,
    url: string,
    ctx: CurrentContext,
  ): Promise<void> {
    // TO DO : get WebSite or build the URL ?
    const protocol = objectNode.protocol ? objectNode.protocol : 'http';
    let webSiteUrl = `${protocol}://${objectNode.host}`;
    if (objectNode.path) {
      webSiteUrl += `/site/${objectNode.path}`;
    }
    webSiteUrl += '/' + lang;
    await this.insideRestService.read(webSiteUrl + url, ctx);
  }

  public async interceptWebSiteViewUrlCreate(
    entityId: string | undefined,
    entity: ObjectNode,
    requestEntity: Partial<ObjectNode>,
    ctx: CurrentContext,
  ): Promise<boolean | IRestEntity> {
    if (requestEntity.name) {
      delete requestEntity.name;
    }
    if (requestEntity.title) {
      delete requestEntity.title;
    }
    const host =
      !!requestEntity.host && '' !== requestEntity.host
        ? requestEntity.host
        : false;
    if (!host) {
      return false;
    }
    requestEntity.title =
      'http://' +
      host +
      (requestEntity.path ? '/site/' + requestEntity.path : '');
    requestEntity.path = requestEntity.path ? requestEntity.path : '';

    requestEntity.name = host + '$' + requestEntity.path;

    return true;
  }

  public async interceptWebSiteViewUrlUpdate(
    entityId: string | undefined,
    entity: ObjectNode,
    requestEntity: Partial<ObjectNode>,
    ctx: CurrentContext,
  ): Promise<boolean | IRestEntity> {
    if (requestEntity.name) {
      delete requestEntity.name;
    }
    if (requestEntity.title) {
      delete requestEntity.title;
    }
    requestEntity.host =
      !!requestEntity.host && '' !== requestEntity.host
        ? requestEntity.host
        : entity.host;
    if (!requestEntity.host) {
      return false;
    }

    const protocol = requestEntity.protocol ? requestEntity.protocol : 'http';
    requestEntity.title =
      protocol +
      '://' +
      requestEntity.host +
      (requestEntity.path ? '/site/' + requestEntity.path : '');
    requestEntity.path = requestEntity.path
      ? requestEntity.path
      : entity.path
      ? entity.path
      : '';
    requestEntity.name = requestEntity.host + '$' + requestEntity.path;
    return true;
  }

  public async lookWebSiteEntry(
    subPath: string | undefined,
    ctx: CurrentContext,
  ): Promise<ObjectNode> {
    return ctx.webSiteContext.urlNode.getOrSetValue(async () => {
      const host = ctx.uriContext.uri.value.host;
      subPath = subPath ? subPath : '';
      const uri: ObjectNode = await this.objectNodeService.searchOwner(
        WEB_SITE_VIEW_URL_TYPE.name,
        host + '$' + subPath,
      );
      if (!uri) {
        throw ApplicationError.notFound({host, subPath});
      }
      return uri;
    });
  }

  private initLangAndUser(
    lang: string,
    ctx: CurrentContext,
  ): GeneratedResponse | undefined {
    if (
      'explorer' === lang &&
      ctx.uriContext.uri.value.baseUri.endsWith('/api')
    ) {
      return new RedirectGeneratedResponse(
        ctx.uriContext.uri.value.baseUri.replace('/api', '') +
          ctx.uriContext.uri.value.objectUri.substr(
            0,
            ctx.uriContext.uri.value.objectUri.length - 1,
          ),
      );
    }
    if ('explorer' !== lang) {
      const newLang = LocalesHelper.getValidLocales(lang);
      if (newLang !== lang) {
        return new RedirectGeneratedResponse(
          ctx.uriContext.uri.value.baseUri +
            ctx.uriContext.uri.value.objectUri.replace(
              '/' + lang,
              '/' + newLang,
            ),
        );
      }
      ctx.accessRightsContext.user.value = (null as unknown) as Principal;
      ctx.accessRightsContext.authorization.value = (undefined as unknown) as string;
    }
    return undefined;
  }

  public async getWebSiteEntity(
    lang: string,
    subPath: string | undefined,
    pageName: string,
    ctx: CurrentContext,
    getCacheName: (name: string) => string = this.getCachePageName.bind(this),
  ): Promise<ObjectTree> {
    ctx.uriContext.uri.value.acceptLanguage = lang;

    const webSiteTree = await ctx.webSiteContext.webSiteTree.getOrSetValue(
      async () => {
        const host = ctx.uriContext.uri.value.host;
        subPath = subPath ? subPath : '';
        let langTree: ObjectTree;

        const uri: ObjectNode = await this.lookWebSiteEntry(subPath, ctx);
        try {
          langTree = await this.objectTreeService.getNamespaceTree(
            WEB_SITE_VIEW_URL_TYPE.name,
            host + '$' + subPath,
            WEB_SITE_CACHE_LANG_TYPE.name,
            lang,
            CurrentContext.get(ctx, {}),
          );
        } catch (error) {
          await this.objectNodeService.add(
            {
              parentNodeId: uri.id,
              name: lang,
              objectTypeId: WEB_SITE_CACHE_LANG_TYPE.name,
            },
            CurrentContext.get(ctx, {
              nodeContext: {parent: new ExpectedValue(uri)},
            }),
          );
          langTree = await this.objectTreeService.getNamespaceTree(
            WEB_SITE_VIEW_URL_TYPE.name,
            host + '$' + subPath,
            WEB_SITE_CACHE_LANG_TYPE.name,
            lang,
            CurrentContext.get(ctx, {}),
          );
        }

        ctx.webSiteContext.pageBaseUri.value =
          subPath === ''
            ? '/' + lang + '/'
            : '/site/' + subPath + '/' + lang + '/';
        ctx.webSiteContext.siteBaseUriTree.value = langTree;
        for (const cache of ctx.webSiteContext.siteBaseUriTree.value.children) {
          if (getCacheName(pageName) === cache.treeNode.name) {
            await this.contentEntityService.addTransientContent(
              EntityName.objectNode,
              PAGE_CACHE_TYPE.contentType,
              cache.treeNode,
            );
            ctx.webSiteContext.cachedContent.value =
              cache.treeNode.contentPageCache;
            return (undefined as unknown) as ObjectTree;
          }
        }

        let entity = await this.objectTreeService.loadTree(
          uri.parentNodeId,
          ctx,
        );
        if (!entity) {
          // TODO: look for entity web site
          entity = await this.objectTreeService.getNamespaceTree(
            TENANT_TYPE.name,
            'Demonstration',
            TRAVEL_STORY_TYPE.name,
            'WelcomeSite',
            ctx,
          );
        }
        return entity;
      },
    );
    return webSiteTree;
  }

  private setMeta(adminDOM: JSDOM, name: string, content: string) {
    if (
      adminDOM.window.document.head.querySelector(
        '[name~="' + name + '"][content]',
      )
    ) {
      const meta: HTMLMetaElement = adminDOM.window.document.head.querySelector(
        '[name~="' + name + '"][content]',
      ) as HTMLMetaElement;
      meta.name = name;
      meta.content = content;
    } else {
      const meta: HTMLMetaElement = adminDOM.window.document.createElement(
        'meta',
      );
      meta.name = name;
      meta.content = content;
      adminDOM.window.document.head.append(meta);
    }
  }

  private addLinkElement(
    adminDOM: JSDOM,
    rel: string,
    type: string,
    href: string,
  ) {
    const link: HTMLLinkElement = adminDOM.window.document.createElement(
      'link',
    );
    link.rel = rel;
    link.type = type;
    link.href = href;
    adminDOM.window.document.head.append(link);
  }

  public async renderAdminPage(
    subPath: string | undefined,
    ctx: CurrentContext,
  ): Promise<HtmlGeneratedResponse> {
    const adminDOM: JSDOM = await JSDOM.fromFile(
      path.join(__dirname, '../../www/index.html'),
      {},
    );

    // <meta name="objectTrees:rootState_" content="app:view" />
    this.setMeta(adminDOM, 'objectTrees:rootState', 'app:admin');
    //     <meta name="objectTrees:api_" content="http://to.ochoeurdunet.org/api" />
    this.setMeta(
      adminDOM,
      'objectTrees:api',
      ctx.uriContext.uri.value.baseUri + '/api',
    );

    const siteEntry = await this.lookWebSiteEntry(subPath, ctx);
    const siteOwner: ObjectNode = await this.objectNodeService.searchById(
      siteEntry.parentOwnerId,
    );
    const siteNamespace: ObjectNode =
      siteEntry.parentNamespaceId === siteEntry.parentOwnerId
        ? siteOwner
        : await this.objectNodeService.searchById(siteEntry.parentNamespaceId);
    // <meta name="objectTrees:ownerName" content="Demonstration" />
    this.setMeta(adminDOM, 'objectTrees:ownerName', siteOwner.name);

    // <meta       name="objectTrees:siteId"       content="namespace/Tenant/Demonstration/TravelStory/TravelStoryExample"

    if (siteEntry.parentNamespaceId === siteEntry.parentOwnerId) {
      this.setMeta(
        adminDOM,
        'objectTrees:siteId',
        `owner/${siteOwner.objectTypeId}/${siteOwner.name}`,
      );
    } else {
      this.setMeta(
        adminDOM,
        'objectTrees:siteId',
        `namespace/${siteOwner.objectTypeId}/${siteOwner.name}/${siteNamespace.objectTypeId}/${siteNamespace.name}`,
      );
    }

    this.addLinkElement(
      adminDOM,
      'icon',
      'image/png',
      (subPath ? '/site/' + subPath : '') + '/favicon.png',
    );

    return new HtmlGeneratedResponse(ctx, adminDOM.serialize());
  }

  public async getWebSitePageNode(
    entity: ObjectTree,
    pageName: string,
    ctx: CurrentContext,
  ): Promise<ObjectNode> {
    const webSitePageNode: ObjectNode = await ctx.webSiteContext.webSitePageNode.getOrSetValue(
      async () => {
        let result: ObjectNode = (undefined as unknown) as ObjectNode;
        const treeNodes = await this.objectNodeService.searchByParentTreeId(
          entity.id,
          {
            name: pageName,
            objectTypeIds: await this.objectTypeService.getImplementingTypes(
              PAGE_TYPE.name,
            ),
          },
        );

        if (treeNodes && 1 === treeNodes.length) {
          result = treeNodes[0];
        }
        return result;
      },
    );
    return webSitePageNode;
  }

  public getPageHref(
    page: IObjectTree,
    site: ObjectTree,
    ctx: CurrentContext,
  ): string {
    if (page.parentPage) {
      return (
        ctx.webSiteContext.pageBaseUri.value +
        page.parentPage.pageName +
        '#' +
        page.parentPage.anchor
      );
    }
    return ctx.webSiteContext.pageBaseUri.value + page.treeNode?.name;
  }

  public getPopupHref(
    page: IObjectTree,
    site: ObjectTree,
    ctx: CurrentContext,
  ): string {
    return (
      ctx.webSiteContext.pageBaseUri.value + page.treeNode?.name + '/popup'
    );
  }

  public getAdminHref(
    page: IObjectTree,
    site: ObjectTree,
    ctx: CurrentContext,
  ): string {
    return (
      '/admin/#/admin/owner/' +
      page.ownerType +
      '/' +
      page.ownerName +
      '/namespace/' +
      page.namespaceType +
      '/' +
      page.namespaceName
    );
  }

  public getCachePageName(pageName = '') {
    return 'cache$' + pageName;
  }

  public getCachePopupName(pageName = '') {
    return 'cache$' + pageName + '$popup';
  }

  public async getFavicon(
    ctx: CurrentContext,
    siteName?: string,
  ): Promise<GeneratedResponse> {
    return new FileGeneratedResponse(
      path.join(__dirname, '/img/favicon.png'),
      'favicon.png',
    );
  }

  public async redirectToSiteMap(
    ctx: CurrentContext,
  ): Promise<GeneratedResponse> {
    // TODO : determine site language
    return new RedirectGeneratedResponse('./fr-FR/sitemap.xml');
  }

  public async getLoadingPageResponse(
    ctx: CurrentContext,
    lang?: string,
    siteName?: string,
  ): Promise<GeneratedResponse> {
    if (!lang) {
      return new RedirectGeneratedResponse(
        ctx.uriContext.uri.value.baseUri +
          ctx.uriContext.uri.value.objectUri +
          ctx.uriContext.uri.value.acceptLanguage,
      );
    }

    const redirectResponse:
      | GeneratedResponse
      | undefined = this.initLangAndUser(lang, ctx);
    if (redirectResponse) {
      return redirectResponse;
    }

    const entity = await this.getWebSiteEntity(lang, siteName, '', ctx);
    if (ctx.webSiteContext.cachedContent.value) {
      return new HtmlGeneratedResponse(
        ctx,
        ctx.webSiteContext.cachedContent.value.body,
      );
    }

    const result: JsonGeneratedResponse<AjaxResult> = (await this.actionWebSiteService.getWebSiteAjaxResponse(
      entity,
      this.getPageHref.bind(this),
      this.getAdminHref.bind(this),
      this.getPopupHref.bind(this),
      ctx,
    )) as JsonGeneratedResponse<AjaxResult & {headerTags: string[]}>;
    result.json.headerScripts.pageScript = `
function getPageHref(page) {
  let baseUri = '${ctx.webSiteContext.pageBaseUri.value}';
  if (page && page.parentPage) {
    return baseUri + '/' +
      page.parentPage.pageName +
      '#' +
      page.parentPage.anchor;
  }
  return baseUri +
    (page ? '/' + page.treeNode.name : '');
}
`;

    ctx.webSiteContext.cachedContent.value = {
      body: this.actionWebSiteService.getHtmlDocFromAjaxResult(ctx, result),
    };
    await this.storeCachePage('', ctx);
    return new HtmlGeneratedResponse(
      ctx,
      ctx.webSiteContext.cachedContent.value.body,
    );
  }

  protected async storeCachePage(
    pageName: string,
    ctx: CurrentContext,
    getCacheName: (name: string) => string = this.getCachePageName.bind(this),
  ) {
    try {
      await this.objectNodeService.add(
        {
          parentNodeId: ctx.webSiteContext.siteBaseUriTree.value.id,
          name: getCacheName(pageName),
          objectTypeId: PAGE_CACHE_TYPE.name,
          pageUrl: ctx.uriContext.uri.value.url,
          title: ctx.uriContext.uri.value.url,
          contentPageCache: ctx.webSiteContext.cachedContent.value,
        },
        CurrentContext.get(ctx, {
          nodeContext: {
            parent: new ExpectedValue(
              ctx.webSiteContext.siteBaseUriTree.value.treeNode,
            ),
          },
        }),
      );
      // eslint-disable-next-line no-empty
    } catch (error) {}
  }

  public async getWebSitePageResponse(
    lang: string,
    pageName: string,
    ctx: CurrentContext,
    siteName?: string,
  ): Promise<GeneratedResponse> {
    const redirectResponse:
      | GeneratedResponse
      | undefined = this.initLangAndUser(lang, ctx);
    if (redirectResponse) {
      return redirectResponse;
    }

    const webSiteTree = await this.getWebSiteEntity(
      lang,
      siteName,
      pageName,
      ctx,
    );
    if (ctx.webSiteContext.cachedContent.value) {
      switch (ctx.webSiteContext.cachedContent.value.responseType) {
        case 'xml':
          return new XmlGeneratedResponse(
            ctx,
            ctx.webSiteContext.cachedContent.value.body,
          );
        default:
          return new HtmlGeneratedResponse(
            ctx,
            ctx.webSiteContext.cachedContent.value.body,
          );
      }
    }

    // page not yet in cache
    if ('sitemap.xml' === pageName) {
      ctx.webSiteContext.cachedContent.value = {
        body: await this.generateSiteMap(
          ctx.uriContext.uri.value.protocol +
            '://' +
            ctx.uriContext.uri.value.host +
            ctx.webSiteContext.pageBaseUri.value.substring(
              0,
              ctx.webSiteContext.pageBaseUri.value.length - 1,
            ),
          webSiteTree.id,
        ),
        responseType: 'xml',
      };
      await this.storeCachePage(pageName, ctx);
      return new XmlGeneratedResponse(
        ctx,
        ctx.webSiteContext.cachedContent.value.body,
      );
    }

    const pageNode = await this.getWebSitePageNode(webSiteTree, pageName, ctx);
    if (pageNode) {
      const result: JsonGeneratedResponse<AjaxResult> = (await this.actionWebSiteService.getWebSitePageAjaxResponse(
        webSiteTree,
        pageNode.id as string,
        this.getPageHref.bind(this),
        this.getAdminHref.bind(this),
        this.getPopupHref.bind(this),
        ctx,
      )) as JsonGeneratedResponse<AjaxResult & {headerTags: string[]}>;
      result.json.headerScripts.pageScript = `
    function getPageHref(page) {
      let baseUri = '${ctx.webSiteContext.pageBaseUri.value}';
      if (page && page.parentPage) {
        return baseUri + '/' +
          page.parentPage.pageName +
          '#' +
          page.parentPage.anchor;
      }
      return baseUri +
        (page ? '/' + page.treeNode.name : '');
    }
    `;

      ctx.webSiteContext.cachedContent.value = {
        body: this.actionWebSiteService.getHtmlDocFromAjaxResult(ctx, result),
      };
      await this.storeCachePage(pageName, ctx);
      return new HtmlGeneratedResponse(
        ctx,
        ctx.webSiteContext.cachedContent.value.body,
      );
    } else {
      return this.getLoadingPageResponse(ctx);
    }
  }

  public async getWebSitePopupResponse(
    lang: string,
    pageName: string,
    ctx: CurrentContext,
    siteName?: string,
  ): Promise<GeneratedResponse> {
    const redirectResponse:
      | GeneratedResponse
      | undefined = this.initLangAndUser(lang, ctx);
    if (redirectResponse) {
      return redirectResponse;
    }

    const webSiteTree = await this.getWebSiteEntity(
      lang,
      siteName,
      pageName,
      ctx,
      this.getCachePopupName.bind(this),
    );
    if (ctx.webSiteContext.cachedContent.value) {
      return new JsonGeneratedResponse(
        JSON.parse(ctx.webSiteContext.cachedContent.value.body),
      );
    }
    const pageNode = await this.getWebSitePageNode(webSiteTree, pageName, ctx);
    if (pageNode) {
      const popup: Popup = (
        await this.actionWebSiteService.popupWebSiteViewTree(
          webSiteTree,
          [pageNode.id as string],
          ctx,
        )
      ).json;
      ctx.webSiteContext.cachedContent.value = {
        body: JSON.stringify(popup),
      };
      await this.storeCachePage(
        pageName,
        ctx,
        this.getCachePopupName.bind(this),
      );
      return new JsonGeneratedResponse(popup);
    } else {
      throw ApplicationError.notFound({popupName: pageName});
    }
  }

  public async previewWebSite(
    entity: ObjectTree,
    args: {0?: string; 1?: string; 2?: string},
    ctx: CurrentContext,
  ): Promise<GeneratedResponse> {
    return new XmlGeneratedResponse(
      ctx,
      await this.generateSiteMap('', entity.id),
    );
  }

  public async getSiteMap(
    entity: ObjectTree,
    args: {0?: string; 1?: string; 2?: string},
    ctx: CurrentContext,
  ): Promise<GeneratedResponse> {
    const objectNode = await this.objectNodeService.getNode(
      entity.treeNode.parentNodeId as string,
      CurrentContext.get(ctx),
    );
    const protocol = objectNode.protocol ? objectNode.protocol : 'http';
    let webSiteUrl = `${protocol}://${objectNode.host}`;
    if (objectNode.path) {
      webSiteUrl += `/site/${objectNode.path}`;
    }
    webSiteUrl += '/' + entity.treeNode.name;

    return new XmlGeneratedResponse(
      ctx,
      await this.generateSiteMap(webSiteUrl, entity.parentTreeId as string),
    );
  }

  public async getRobotTxt(entryPath: string, ctx: CurrentContext) {
    return new TextGeneratedResponse(
      ctx,
      `User-agent: *
    Allow: /

    Sitemap: ${ctx.uriContext.uri.value.protocol}://${ctx.uriContext.uri.value.host}${entryPath}sitemap.xml`,
    );
  }

  private async generateSiteMap(
    webSiteUrl: string,
    webSiteId: string,
  ): Promise<string> {
    const urls: string[] = await this.getSiteUrls(webSiteId, false);

    const date = new Date().toISOString().split('T')[0];

    return `<?xml version="1.0" encoding="UTF-8"?>
    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    ${urls
      .map(
        (url) => `
  <url>
    <loc>${webSiteUrl}${url}</loc>
    <lastmod>${date}</lastmod>
  </url>
    `,
      )
      .join('')}

    </urlset>
    `;
  }
}
