/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import '@polymer/iron-input/iron-input';
import '../gr-button/gr-button';
import '../gr-icon/gr-icon';
import {encodeURL, getBaseUrl} from '../../../utils/url-util';
import {page} from '../../../utils/page-wrapper-utils';
import {fireEvent} from '../../../utils/event-util';
import {debounce, DelayedTask} from '../../../utils/async-util';
import {sharedStyles} from '../../../styles/shared-styles';
import {LitElement, PropertyValues, css, html} from 'lit';
import {customElement, property} from 'lit/decorators.js';
import {BindValueChangeEvent} from '../../../types/events';

const REQUEST_DEBOUNCE_INTERVAL_MS = 200;

declare global {
  interface HTMLElementTagNameMap {
    'gr-list-view': GrListView;
  }
}

@customElement('gr-list-view')
export class GrListView extends LitElement {
  @property({type: Boolean})
  createNew?: boolean;

  @property({type: Array})
  items?: unknown[];

  @property({type: Number})
  itemsPerPage = 25;

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

  @property({type: Number})
  offset = 0;

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

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

  private reloadTask?: DelayedTask;

  override disconnectedCallback() {
    this.reloadTask?.cancel();
    super.disconnectedCallback();
  }

  static override get styles() {
    return [
      sharedStyles,
      css`
        #filter {
          max-width: 25em;
        }
        #filter:focus {
          outline: none;
        }
        #topContainer {
          align-items: center;
          display: flex;
          height: 3rem;
          justify-content: space-between;
          margin: 0 var(--spacing-l);
        }
        #createNewContainer:not(.show) {
          display: none;
        }
        a {
          color: var(--primary-text-color);
          text-decoration: none;
        }
        a:hover {
          text-decoration: underline;
        }
        nav {
          align-items: center;
          display: flex;
          height: 3rem;
          justify-content: flex-end;
          margin-right: 20px;
          color: var(--deemphasized-text-color);
        }
        gr-icon {
          font-size: 1.85rem;
          margin-left: 16px;
        }
      `,
    ];
  }

  override render() {
    return html`
      <div id="topContainer">
        <div class="filterContainer">
          <label>Filter:</label>
          <iron-input
            .bindValue=${this.filter}
            @bind-value-changed=${this.handleFilterBindValueChanged}
          >
            <input type="text" id="filter" />
          </iron-input>
        </div>
        <div id="createNewContainer" class=${this.createNew ? 'show' : ''}>
          <gr-button
            id="createNew"
            primary
            link
            @click=${() => this.createNewItem()}
          >
            Create New
          </gr-button>
        </div>
      </div>
      <slot></slot>
      <nav>
        Page ${this.computePage(this.offset, this.itemsPerPage)}
        <a
          id="prevArrow"
          href=${this.computeNavLink(
            this.offset,
            -1,
            this.itemsPerPage,
            this.filter,
            this.path
          )}
          ?hidden=${this.loading || this.offset === 0}
        >
          <gr-icon icon="chevron_left"></gr-icon>
        </a>
        <a
          id="nextArrow"
          href=${this.computeNavLink(
            this.offset,
            1,
            this.itemsPerPage,
            this.filter,
            this.path
          )}
          ?hidden=${this.hideNextArrow(this.loading, this.items)}
        >
          <gr-icon icon="chevron_right"></gr-icon>
        </a>
      </nav>
    `;
  }

  override willUpdate(changedProperties: PropertyValues) {
    // We have to do this for the tests.
    if (changedProperties.has('filter')) {
      this.filterChanged(
        this.filter,
        changedProperties.get('filter') as string
      );
    }
  }

  private filterChanged(newFilter?: string, oldFilter?: string) {
    // newFilter can be empty string and then !newFilter === true
    if (!newFilter && !oldFilter) {
      return;
    }
    this.debounceReload(newFilter);
  }

  // private but used in test
  debounceReload(filter?: string) {
    this.reloadTask = debounce(
      this.reloadTask,
      () => {
        if (!this.isConnected || !this.path) return;
        if (filter) {
          page.show(`${this.path}/q/filter:${encodeURL(filter, false)}`);
          return;
        }
        page.show(this.path);
      },
      REQUEST_DEBOUNCE_INTERVAL_MS
    );
  }

  private createNewItem() {
    fireEvent(this, 'create-clicked');
  }

  // private but used in test
  computeNavLink(
    offset: number,
    direction: number,
    itemsPerPage: number,
    filter: string | undefined,
    path = ''
  ) {
    // Offset could be a string when passed from the router.
    offset = +(offset || 0);
    const newOffset = Math.max(0, offset + itemsPerPage * direction);
    let href = getBaseUrl() + path;
    if (filter) {
      href += '/q/filter:' + encodeURL(filter, false);
    }
    if (newOffset > 0) {
      href += `,${newOffset}`;
    }
    return href;
  }

  // private but used in test
  hideNextArrow(loading?: boolean, items?: unknown[]) {
    if (loading || !items || !items.length) {
      return true;
    }
    const lastPage = items.length < this.itemsPerPage + 1;
    return lastPage;
  }

  // TODO: fix offset (including itemsPerPage)
  // to either support a decimal or make it go to the nearest
  // whole number (e.g 3).
  // private but used in test
  computePage(offset: number, itemsPerPage: number) {
    return offset / itemsPerPage + 1;
  }

  private handleFilterBindValueChanged(e: BindValueChangeEvent) {
    this.filter = e.detail.value;
  }
}
