import { EventUtils, TrashEntry, ContextSpaceListOptions, IFile, BroadcastEvent, BroadcastCreatedEvent } from "@api-client/core/build/browser.js";
import { html, TemplateResult } from "lit";
import '@github/relative-time-element';
import { navigate } from "../../../lib/route.js";
import { SnackNotifications } from "../../../ui/notification/SnackNotifications.js";
import { DataTable } from "../../../ui/table/DataTable.js";
import { fileIcon } from "../../../elements/store/FilesLib.js";
import type ApiClientScreen from "../ApiClient.screen.js";
import '../../../define/ui/ui-icon.js';

/**
 * A class that specializes in rendering the list of trash in HTTP Client.
 */
export class TrashPage {
  files: TrashEntry[] = [];

  /**
   * The pagination cursor for trash.
   * As long there's a cursor, there are more files to request.
   */
  cursor?: string;

  dt = new DataTable(() => this.files, { ariaLabel: 'Trashed files' });

  readingFiles?: boolean;

  /**
   * The list of keys of files to delete.
   */
  deleteKeys?: string[];

  get deleteItems(): TrashEntry[] {
    const { deleteKeys, files } = this;
    if (!deleteKeys) {
      return [];
    }
    const result: TrashEntry[] = [];
    deleteKeys.forEach((key) => {
      const item = files.find(i => i.key === key);
      if (item) {
        result.push(item);
      }
    });
    return result;
  }

  constructor(protected screen: ApiClientScreen) {
    this.dt.addEventListener('empty', this.renderEmptyTable.bind(this));
    this.dt.addEventListener('header', this.renderTableHeader.bind(this));
    this.dt.addEventListener('item', this.renderTableItem.bind(this));

    this.itemOptionsSelectHandler = this.itemOptionsSelectHandler.bind(this);
    this.cancelDeleteAllDialog = this.cancelDeleteAllDialog.bind(this);
    this.deleteAllHandler = this.deleteAllHandler.bind(this);
    this.cancelDeleteFilesDialog = this.cancelDeleteFilesDialog.bind(this);
    this.deleteFilesHandler = this.deleteFilesHandler.bind(this);
    this.openEmptyAllDialogHandler = this.openEmptyAllDialogHandler.bind(this);
  }

  handleFileEvent(event: BroadcastEvent): void {
    switch (event.operation) {
      case 'created': this.handleTrashAdded((event as BroadcastCreatedEvent).data as TrashEntry); break;
      case 'deleted': this.handleTrashRemoved(event.key as string); break;
      default:
    }
  }

  protected handleTrashAdded(item: TrashEntry): void {
    this.files.push(item);
    this.files.sort(this.filesSortFunction.bind(this));
    this.screen.render();
  }

  protected handleTrashRemoved(key: string): void {
    const index = this.files.findIndex(i => i.key === key);
    if (index === -1) {
      return;
    }
    this.files.splice(index, 1);
    this.screen.render();
  }

  async refresh(): Promise<void> {
    this.resetList();
    this.screen.render();
    await this.readFiles();
  }

  resetList(): void {
    this.files = [];
    this.cursor = undefined;
  }

  async readFiles(): Promise<void> {
    const { selectedSpace } = this.screen;
    if (!selectedSpace) {
      return;
    }
    if (this.readingFiles) {
      return;
    }
    this.readingFiles = true;
    const { cursor: currentCursor } = this;
    const opts: ContextSpaceListOptions = {
      space: selectedSpace,
      descending: true,
    };
    if (currentCursor) {
      opts.cursor = currentCursor;
    }
    try {
      const result = await this.screen.bindings.store.trash.list(opts);
      const { items, cursor } = result;
      this.files = this.files.concat(items).sort(this.filesSortFunction.bind(this));
      this.cursor = cursor;
    } finally {
      this.readingFiles = false;
    }
    this.screen.render();
  }

  /**
   * Sorts files on the list.
   * 
   * Folders are always on top.
   */
  filesSortFunction(a: TrashEntry, b: TrashEntry): number {
    return b.info.time - a.info.time;
  }

  protected itemOptionsSelectHandler(e: CustomEvent): void {
    const item = e.detail.item as HTMLElement;
    const list = e.target as HTMLElement;
    const operation = item.dataset.option as string;
    const key = list.dataset.key as string;
    if (!key || !operation) {
      return;
    }
    if (operation === 'restore') {
      this.restoreItem(key);
    } else if (operation === 'delete') {
      this.deleteKeys = [key];
      this.screen.render();
      this.screen.openDialog('deleteFilesDialog');
    }
  }

  async restoreItem(key: string): Promise<void> {
    const { bindings, selectedSpace } = this.screen;
    if (!selectedSpace) {
      throw new Error(`Invalid state: Folder is not selected.`);
    }
    const record = await bindings.store.trash.restore(key);
    const file = record.item as IFile;
    const parentId = file.parents && file.parents.length ? file.parents[file.parents.length - 1] : undefined;
    SnackNotifications.notify(`Restored file: ${file.info.name}`, {
      actionLabel: 'Go to file location',
      actionCallback: () => {
        if (parentId) {
          navigate('files', parentId);
        } else {
          navigate('files');
        }
      },
    });
  }

  async deleteItem(key: string): Promise<void> {
    const { bindings, selectedSpace } = this.screen;
    if (!selectedSpace) {
      throw new Error(`Invalid state: Folder is not selected.`);
    }
    await bindings.store.trash.delete(key);
  }

  protected openEmptyAllDialogHandler(): void {
    this.screen.openDialog('deleteAllDialog');
  }

  protected cancelDeleteAllDialog(): void {
    this.screen.closeDialog('deleteAllDialog');
  }

  protected deleteAllHandler(): void {
    this.screen.closeDialog('deleteAllDialog');
    const { bindings, selectedSpace } = this.screen;
    if (!selectedSpace) {
      throw new Error(`Invalid state: Folder is not selected.`);
    }
    bindings.store.trash.empty(selectedSpace);
  }

  protected cancelDeleteFilesDialog(): void {
    this.deleteKeys = undefined;
    this.screen.closeDialog('deleteFilesDialog');
  }

  protected deleteFilesHandler(): void {
    this.screen.closeDialog('deleteFilesDialog');
    this.screen.render();
    const { deleteKeys } = this;
    this.deleteKeys = undefined;
    if (!deleteKeys || !deleteKeys.length) {
      return;
    }
    deleteKeys.forEach(k => this.deleteItem(k));
  }

  render(): TemplateResult {
    return html`
    <div class="section-header">
      <h2 class="headline-small">Trash</h2>
      <div class="spacer"></div>
      ${this.renderEmptyTrash()}
    </div>
    ${this.renderTrashTable()}
    ${this.renderDeleteAllDialog()}
    ${this.renderDeleteFilesDialog()}
    `;
  }

  protected renderEmptyTrash(): TemplateResult {
    const { files } = this;
    const disabled = !files || !files.length;
    return html`
    <ui-button type="outlined" ?disabled="${disabled}" @click="${this.openEmptyAllDialogHandler}">
      <ui-icon icon="deleteOutline" slot="icon"></ui-icon>
      Empty trash
    </ui-button>
    `;
  }

  protected renderTrashTable(): TemplateResult {
    return this.dt.render();
  }

  protected renderEmptyTable(): TemplateResult {
    return html`
    <p>The trash bin is empty.</p>
    `;
  }

  protected renderTableHeader(): TemplateResult[] {
    const name = this.dt.header('Name', { class: 'name-cell' });
    const deleted = this.dt.header('Deleted', { class: 'updated-cell' });
    const menu = this.dt.header(html`<span hidden>Menu</span>`, { class: 'menu-cell' });
    return [name, deleted, menu];
  }

  protected renderTableItem(item: TrashEntry): TemplateResult[] {
    const { kind, name, info } = item;
    const icon = fileIcon(item);

    const nameCell = this.dt.cell(html`
      ${icon ? html`<ui-icon icon="${icon}" class="cell-icon"></ui-icon>` : ''}
      ${name || 'Unnamed file'}
    `, { class: 'name-cell', data: { kind } });

    const deleted = this.dt.cell(html`
      <relative-time datetime="${new Date(info.time).toISOString()}"></relative-time>
    `, { class: 'updated-cell' });

    const menu = this.dt.cell(this.renderItemOptions(item), { class: 'menu-cell' });

    return [nameCell, deleted, menu];
  }

  protected renderItemOptions(item: TrashEntry): TemplateResult {
    return html`
    <ui-dropdown-list
      horizontalAlign="auto"
      verticalAlign="auto"
      closeOnOutsideClick
      @mousedown="${EventUtils.cancelEvent}" 
      @mouseup="${EventUtils.cancelEvent}" 
      @click="${EventUtils.cancelEvent}"
      @dblclick="${EventUtils.cancelEvent}"
      @select="${this.itemOptionsSelectHandler}"
      data-key="${item.key}"
    >
      <ui-icon-button title="Project options" aria-label="Activate for more options">
        <ui-icon icon="moreVert"></ui-icon>
      </ui-icon-button>
      <ui-list slot="dropdown" role="menu">
        <ui-list-item image="icon" data-option="restore" role="menuitem" title="Restores the file to its original folder.">
          <ui-icon icon="restoreFromTrash" slot="start"></ui-icon>
          Restore
        </ui-list-item>
        <ui-list-item image="icon" data-option="delete" role="menuitem" title="Permanently deleted the item.">
          <ui-icon icon="deleteOutline" slot="start"></ui-icon>
          Delete forever
        </ui-list-item>
      </ui-list>
    </ui-dropdown-list>
    `;
  }

  protected renderDeleteAllDialog(): TemplateResult {
    return html`
    <dialog id="deleteAllDialog" class="warning">
      <h3>Empty Trash?</h3>
      <div>
        <p class="body-medium">
          This will permanently delete all files in the trash.
        </p>
      </div>
      <div class="dialog-buttons">
        <ui-button type="text" value="cancel" @click="${this.cancelDeleteAllDialog}">Cancel</ui-button>
        <ui-button type="text" value="delete" @click="${this.deleteAllHandler}">Delete</ui-button>
      </div>
    </dialog>
    `;
  }

  protected renderDeleteFilesDialog(): TemplateResult {
    const { deleteItems } = this;
    return html`
    <dialog id="deleteFilesDialog" class="warning">
      <h3>Delete Forever?</h3>
      <div>
        ${deleteItems.length > 1 ? this.renderDeleteMultiFile(deleteItems) : this.renderDeleteSingleFile(deleteItems[0])}
      </div>
      <div class="dialog-buttons">
        <ui-button type="text" value="cancel" @click="${this.cancelDeleteFilesDialog}">Cancel</ui-button>
        <ui-button type="text" value="delete" @click="${this.deleteFilesHandler}">Delete</ui-button>
      </div>
    </dialog>
    `;
  }

  protected renderDeleteSingleFile(item: TrashEntry): TemplateResult {
    if (!item) {
      return html`
      <p class="body-medium">Invalid state. Couldn't find files to delete.</p>
      `;
    }
    return html`
    <p class="body-medium">
      <b>"${item.name}"</b> will be deleted forever and you won't be able to restore it.
    </p>
    `;
  }

  protected renderDeleteMultiFile(items: TrashEntry[]): TemplateResult {
    return html`
    <p class="body-medium">
      The following files will be deleted forever and you won't be able to restore them:
    </p>
    <ui-divider></ui-divider>
    <ul>
      ${items.map(i => html`<li>${i.name}</li>`)}
    </ul>
    <ui-divider></ui-divider>
    `;
  }
}
