/**
 * @license
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import '../../../styles/gr-menu-page-styles';
import '../../../styles/gr-page-nav-styles';
import '../../../styles/shared-styles';
import '../../shared/gr-dropdown-list/gr-dropdown-list';
import '../../shared/gr-icons/gr-icons';
import '../../shared/gr-page-nav/gr-page-nav';
import '../gr-admin-group-list/gr-admin-group-list';
import '../gr-group/gr-group';
import '../gr-group-audit-log/gr-group-audit-log';
import '../gr-group-members/gr-group-members';
import '../gr-plugin-list/gr-plugin-list';
import '../gr-repo/gr-repo';
import '../gr-repo-access/gr-repo-access';
import '../gr-repo-commands/gr-repo-commands';
import '../gr-repo-dashboards/gr-repo-dashboards';
import '../gr-repo-detail-list/gr-repo-detail-list';
import '../gr-repo-list/gr-repo-list';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-admin-view_html';
import {getBaseUrl} from '../../../utils/url-util';
import {
  GerritNav,
  GroupDetailView,
  RepoDetailView,
} from '../../core/gr-navigation/gr-navigation';
import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader';
import {
  AdminNavLinksOption,
  getAdminLinks,
  NavLink,
  SubsectionInterface,
} from '../../../utils/admin-nav-util';
import {customElement, observe, property} from '@polymer/decorators';
import {
  AppElementAdminParams,
  AppElementGroupParams,
  AppElementRepoParams,
} from '../../gr-app-types';
import {
  AccountDetailInfo,
  GroupId,
  GroupName,
  RepoName,
} from '../../../types/common';
import {GroupNameChangedDetail} from '../gr-group/gr-group';
import {ValueChangeDetail} from '../../shared/gr-dropdown-list/gr-dropdown-list';
import {appContext} from '../../../services/app-context';
import {GerritView} from '../../../services/router/router-model';

const INTERNAL_GROUP_REGEX = /^[\da-f]{40}$/;

interface AdminSubsectionLink {
  text: string;
  value: string;
  view: GerritView;
  url: string;
  detailType?: GroupDetailView | RepoDetailView;
  parent?: GroupId | RepoName;
}

// The type is matched to the _showAdminView function from the gr-app-element
type AdminViewParams =
  | AppElementAdminParams
  | AppElementGroupParams
  | AppElementRepoParams;

function getAdminViewParamsDetail(
  params: AdminViewParams
): GroupDetailView | RepoDetailView | undefined {
  if (params.view !== GerritView.ADMIN) {
    return params.detail;
  }
  return undefined;
}

@customElement('gr-admin-view')
export class GrAdminView extends PolymerElement {
  static get template() {
    return htmlTemplate;
  }

  private account?: AccountDetailInfo;

  @property({type: Object})
  params?: AdminViewParams;

  @property({type: String})
  path?: string;

  @property({type: String})
  adminView?: string;

  @property({type: String})
  _breadcrumbParentName?: string;

  @property({type: String})
  _repoName?: RepoName;

  @property({type: String, observer: '_computeGroupName'})
  _groupId?: GroupId;

  @property({type: Boolean})
  _groupIsInternal?: boolean;

  @property({type: String})
  _groupName?: GroupName;

  @property({type: Boolean})
  _groupOwner = false;

  @property({type: Array})
  _subsectionLinks?: AdminSubsectionLink[];

  @property({type: Array})
  _filteredLinks?: NavLink[];

  @property({type: Boolean})
  _showDownload = false;

  @property({type: Boolean})
  _isAdmin = false;

  @property({type: Boolean})
  _showGroup?: boolean;

  @property({type: Boolean})
  _showGroupAuditLog?: boolean;

  @property({type: Boolean})
  _showGroupList?: boolean;

  @property({type: Boolean})
  _showGroupMembers?: boolean;

  @property({type: Boolean})
  _showRepoAccess?: boolean;

  @property({type: Boolean})
  _showRepoCommands?: boolean;

  @property({type: Boolean})
  _showRepoDashboards?: boolean;

  @property({type: Boolean})
  _showRepoDetailList?: boolean;

  @property({type: Boolean})
  _showRepoMain?: boolean;

  @property({type: Boolean})
  _showRepoList?: boolean;

  @property({type: Boolean})
  _showPluginList?: boolean;

  private readonly restApiService = appContext.restApiService;

  private readonly jsAPI = appContext.jsApiService;

  /** @override */
  connectedCallback() {
    super.connectedCallback();
    this.reload();
  }

  reload() {
    const promises: [Promise<AccountDetailInfo | undefined>, Promise<void>] = [
      this.restApiService.getAccount(),
      getPluginLoader().awaitPluginsLoaded(),
    ];
    return Promise.all(promises).then(result => {
      this.account = result[0];
      let options: AdminNavLinksOption | undefined = undefined;
      if (this._repoName) {
        options = {repoName: this._repoName};
      } else if (this._groupId) {
        options = {
          groupId: this._groupId,
          groupName: this._groupName,
          groupIsInternal: this._groupIsInternal,
          isAdmin: this._isAdmin,
          groupOwner: this._groupOwner,
        };
      }

      return getAdminLinks(
        this.account,
        () =>
          this.restApiService.getAccountCapabilities().then(capabilities => {
            if (!capabilities) {
              throw new Error('getAccountCapabilities returns undefined');
            }
            return capabilities;
          }),
        () => this.jsAPI.getAdminMenuLinks(),
        options
      ).then(res => {
        this._filteredLinks = res.links;
        this._breadcrumbParentName = res.expandedSection
          ? res.expandedSection.name
          : '';

        if (!res.expandedSection) {
          this._subsectionLinks = [];
          return;
        }
        this._subsectionLinks = [res.expandedSection]
          .concat(res.expandedSection.children ?? [])
          .map(section => {
            return {
              text: !section.detailType ? 'Home' : section.name,
              value: section.view + (section.detailType ?? ''),
              view: section.view,
              url: section.url,
              detailType: section.detailType,
              parent: this._groupId ?? this._repoName,
            };
          });
      });
    });
  }

  _computeSelectValue(params: AdminViewParams) {
    if (!params || !params.view) return;
    return `${params.view}${getAdminViewParamsDetail(params) ?? ''}`;
  }

  _selectedIsCurrentPage(selected: AdminSubsectionLink) {
    if (!this.params) return false;

    return (
      selected.parent === (this._repoName ?? this._groupId) &&
      selected.view === this.params.view &&
      selected.detailType === getAdminViewParamsDetail(this.params)
    );
  }

  _handleSubsectionChange(e: CustomEvent<ValueChangeDetail>) {
    if (!this._subsectionLinks) return;

    // The GrDropdownList items are _subsectionLinks, so find(...) always return
    // an item _subsectionLinks and never returns undefined
    const selected = this._subsectionLinks.find(
      section => section.value === e.detail.value
    )!;

    // This is when it gets set initially.
    if (this._selectedIsCurrentPage(selected)) return;
    GerritNav.navigateToRelativeUrl(selected.url);
  }

  @observe('params')
  _paramsChanged(params: AdminViewParams) {
    this.set('_showGroup', params.view === GerritView.GROUP && !params.detail);
    this.set(
      '_showGroupAuditLog',
      params.view === GerritView.GROUP && params.detail === GroupDetailView.LOG
    );
    this.set(
      '_showGroupMembers',
      params.view === GerritView.GROUP &&
        params.detail === GroupDetailView.MEMBERS
    );

    this.set(
      '_showGroupList',
      params.view === GerritView.ADMIN &&
        params.adminView === 'gr-admin-group-list'
    );

    this.set(
      '_showRepoAccess',
      params.view === GerritView.REPO && params.detail === RepoDetailView.ACCESS
    );
    this.set(
      '_showRepoCommands',
      params.view === GerritView.REPO &&
        params.detail === RepoDetailView.COMMANDS
    );
    this.set(
      '_showRepoDetailList',
      params.view === GerritView.REPO &&
        (params.detail === RepoDetailView.BRANCHES ||
          params.detail === RepoDetailView.TAGS)
    );
    this.set(
      '_showRepoDashboards',
      params.view === GerritView.REPO &&
        params.detail === RepoDetailView.DASHBOARDS
    );
    this.set(
      '_showRepoMain',
      params.view === GerritView.REPO && !params.detail
    );

    this.set(
      '_showRepoList',
      params.view === GerritView.ADMIN && params.adminView === 'gr-repo-list'
    );

    this.set(
      '_showPluginList',
      params.view === GerritView.ADMIN && params.adminView === 'gr-plugin-list'
    );

    let needsReload = false;
    const newRepoName =
      params.view === GerritView.REPO ? params.repo : undefined;
    if (newRepoName !== this._repoName) {
      this._repoName = newRepoName;
      // Reloads the admin menu.
      needsReload = true;
    }
    const newGroupId =
      params.view === GerritView.GROUP ? params.groupId : undefined;
    if (newGroupId !== this._groupId) {
      this._groupId = newGroupId;
      // Reloads the admin menu.
      needsReload = true;
    }
    if (
      this._breadcrumbParentName &&
      (params.view !== GerritView.GROUP || !params.groupId) &&
      (params.view !== GerritView.REPO || !params.repo)
    ) {
      needsReload = true;
    }
    if (!needsReload) {
      return;
    }
    this.reload();
  }

  // TODO (beckysiegel): Update these functions after router abstraction is
  // updated. They are currently copied from gr-dropdown (and should be
  // updated there as well once complete).
  _computeURLHelper(host: string, path: string) {
    return '//' + host + getBaseUrl() + path;
  }

  _computeRelativeURL(path: string) {
    const host = window.location.host;
    return this._computeURLHelper(host, path);
  }

  _computeLinkURL(link: NavLink | SubsectionInterface) {
    if (!link || typeof link.url === 'undefined') return '';

    if ((link as NavLink).target || !(link as NavLink).noBaseUrl) {
      return link.url;
    }
    return this._computeRelativeURL(link.url);
  }

  _computeSelectedClass(
    itemView?: GerritView,
    params?: AdminViewParams,
    detailType?: GroupDetailView | RepoDetailView
  ) {
    if (!params) return '';
    // Group params are structured differently from admin params. Compute
    // selected differently for groups.
    // TODO(wyatta): Simplify this when all routes work like group params.
    if (params.view === GerritView.GROUP && itemView === GerritView.GROUP) {
      if (!params.detail && !detailType) {
        return 'selected';
      }
      if (params.detail === detailType) {
        return 'selected';
      }
      return '';
    }

    if (params.view === GerritView.REPO && itemView === GerritView.REPO) {
      if (!params.detail && !detailType) {
        return 'selected';
      }
      if (params.detail === detailType) {
        return 'selected';
      }
      return '';
    }
    // TODO(TS): The following condition seems always false, because params
    // never has detailType property. Remove it.
    if (
      ((params as unknown) as AdminSubsectionLink).detailType &&
      ((params as unknown) as AdminSubsectionLink).detailType !== detailType
    ) {
      return '';
    }
    return params.view === GerritView.ADMIN && itemView === params.adminView
      ? 'selected'
      : '';
  }

  _computeGroupName(groupId?: GroupId) {
    if (!groupId) return;

    const promises: Array<Promise<void>> = [];
    this.restApiService.getGroupConfig(groupId).then(group => {
      if (!group || !group.name) {
        return;
      }

      this._groupName = group.name;
      this._groupIsInternal = !!group.id.match(INTERNAL_GROUP_REGEX);
      this.reload();

      promises.push(
        this.restApiService.getIsAdmin().then(isAdmin => {
          this._isAdmin = !!isAdmin;
        })
      );

      promises.push(
        this.restApiService.getIsGroupOwner(group.name).then(isOwner => {
          this._groupOwner = isOwner;
        })
      );

      return Promise.all(promises).then(() => {
        this.reload();
      });
    });
  }

  _updateGroupName(e: CustomEvent<GroupNameChangedDetail>) {
    this._groupName = e.detail.name;
    this.reload();
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'gr-admin-view': GrAdminView;
  }
}
