/**
 * @license
 * Copyright 2018 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import '../gr-labeled-autocomplete/gr-labeled-autocomplete';
import '../gr-icon/gr-icon';
import {singleDecodeURL} from '../../../utils/url-util';
import {AutocompleteQuery} from '../gr-autocomplete/gr-autocomplete';
import {
  BranchName,
  RepoName,
  ProjectInfoWithName,
  BranchInfo,
} from '../../../types/common';
import {GrLabeledAutocomplete} from '../gr-labeled-autocomplete/gr-labeled-autocomplete';
import {getAppContext} from '../../../services/app-context';
import {sharedStyles} from '../../../styles/shared-styles';
import {LitElement, PropertyValues, html, css} from 'lit';
import {customElement, property, state, query} from 'lit/decorators.js';
import {assertIsDefined} from '../../../utils/common-util';
import {fire} from '../../../utils/event-util';
import {BindValueChangeEvent} from '../../../types/events';

const SUGGESTIONS_LIMIT = 15;
const REF_PREFIX = 'refs/heads/';

@customElement('gr-repo-branch-picker')
export class GrRepoBranchPicker extends LitElement {
  @query('#repoInput') protected repoInput?: GrLabeledAutocomplete;

  @query('#branchInput') protected branchInput?: GrLabeledAutocomplete;

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

  @property({type: String})
  branch?: BranchName;

  @state() private branchDisabled = false;

  private readonly query: AutocompleteQuery = () => Promise.resolve([]);

  private readonly repoQuery: AutocompleteQuery = () => Promise.resolve([]);

  private readonly restApiService = getAppContext().restApiService;

  constructor() {
    super();
    this.query = input => this.getRepoBranchesSuggestions(input);
    this.repoQuery = input => this.getRepoSuggestions(input);
  }

  override connectedCallback() {
    super.connectedCallback();
    if (this.repo) {
      assertIsDefined(this.repoInput, 'repoInput');
      this.repoInput.setText(this.repo);
    }
    this.branchDisabled = !this.repo;
  }

  static override get styles() {
    return [
      sharedStyles,
      css`
        :host {
          display: block;
        }
        gr-labeled-autocomplete {
          display: inline-block;
        }
        gr-icon {
          margin-bottom: var(--spacing-l);
        }
      `,
    ];
  }

  override render() {
    return html`
      <div>
        <gr-labeled-autocomplete
          id="repoInput"
          label="Repository"
          placeholder="Select repo"
          .query=${this.repoQuery}
          @commit=${(e: CustomEvent<{value: string}>) => {
            this.repoCommitted(e);
          }}
        >
        </gr-labeled-autocomplete>
        <gr-icon icon="chevron_right"></gr-icon>
        <gr-labeled-autocomplete
          id="branchInput"
          label="Branch"
          placeholder="Select branch"
          ?disabled=${this.branchDisabled}
          .query=${this.query}
          @commit=${(e: CustomEvent<{value: string}>) => {
            this.branchCommitted(e);
          }}
        >
        </gr-labeled-autocomplete>
      </div>
    `;
  }

  override willUpdate(changedProperties: PropertyValues) {
    if (changedProperties.has('repo')) {
      this.repoChanged();
    }
  }

  // private but used in test
  getRepoBranchesSuggestions(input: string) {
    if (!this.repo) {
      return Promise.resolve([]);
    }
    if (input.startsWith(REF_PREFIX)) {
      input = input.substring(REF_PREFIX.length);
    }
    return this.restApiService
      .getRepoBranches(input, this.repo, SUGGESTIONS_LIMIT)
      .then(res => this.branchResponseToSuggestions(res));
  }

  private branchResponseToSuggestions(res: BranchInfo[] | undefined) {
    if (!res) return [];
    return res
      .filter(branchInfo => branchInfo.ref !== 'HEAD')
      .map(branchInfo => {
        let branch;
        if (branchInfo.ref.startsWith(REF_PREFIX)) {
          branch = branchInfo.ref.substring(REF_PREFIX.length);
        } else {
          branch = branchInfo.ref;
        }
        return {name: branch, value: branch};
      });
  }

  // private but used in test
  getRepoSuggestions(input: string) {
    return this.restApiService
      .getRepos(input, SUGGESTIONS_LIMIT)
      .then(res => this.repoResponseToSuggestions(res));
  }

  private repoResponseToSuggestions(res: ProjectInfoWithName[] | undefined) {
    if (!res) return [];
    return res.map(repo => {
      return {
        name: repo.name,
        value: singleDecodeURL(repo.id),
      };
    });
  }

  private repoCommitted(e: CustomEvent<{value: string}>) {
    this.repo = e.detail.value as RepoName;
    fire(this, 'repo-changed', {value: e.detail.value});
  }

  private branchCommitted(e: CustomEvent<{value: string}>) {
    this.branch = e.detail.value as BranchName;
    fire(this, 'branch-changed', {value: e.detail.value});
  }

  private repoChanged() {
    assertIsDefined(this.branchInput, 'branchInput');
    this.branchInput.clear();
    this.branchDisabled = !this.repo;
  }
}

declare global {
  interface HTMLElementEventMap {
    'branch-changed': BindValueChangeEvent;
    'repo-changed': BindValueChangeEvent;
  }
  interface HTMLElementTagNameMap {
    'gr-repo-branch-picker': GrRepoBranchPicker;
  }
}
