import { 
  FolderKind, IFile, 
  ListFileKind 
} from "@api-client/core/build/browser.js";
import { CSSResult, html, nothing, TemplateResult } from "lit";
import { eventOptions, property, query, state } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
import ApiElement from "../ApiElement.js";
import styles from './FilePicker.styles.js';
import { fileIcon } from "./FilesLib.js";
import type UiList from "../../ui/list/UiList.js";
import type { UiListSelection } from "../../ui/list/UiList.js";
import { FileSystem } from "../../store/FileSystem.js";
import '../../define/ui/ui-divider.js';
import '../../define/ui/ui-button.js';
import '../../define/ui/ui-icon-button.js';
import '../../define/ui/ui-icon.js';
import '../../define/ui/ui-list.js';
import '../../define/ui/ui-list-item.js';
import '../../define/ui/ui-progress.js';

export interface FilePickerClosingReason {
  /**
   * Whether the picker was canceled.
   */
  canceled: boolean;
  /**
   * The file selected by the user. This is always and only selected when the `canceled` is false.
   */
  file?: IFile;
}

/**
 * A portable element that allow to pick a file from the Store.
 * 
 * @fires close - When a file is selected or the user cancelled the dialog. The detail object is the picked file object. Note, this event bubbles.
 * @fires querycomplete - When the page of results was loaded.
 */
export default class FilePicker extends ApiElement {
  static override get styles(): CSSResult[] {
    return styles;
  }

  /**
   * The key of the parent folder to query for files.
   * @attribute
   */
  @property({ type: String, hasChanged: () => false }) 
  get folder(): string | undefined {
    return this.fs.parent;
  }

  set folder(value: string | undefined) {
    this.fs.selectFolder(value);
  }

  /**
   * The page limit. Defaults to the store defaults.
   * @attribute
   */
  @property({ type: Number, hasChanged: () => false }) 
  set limit(value: number | undefined) {
    this.fs.limit = value;
  }

  get limit(): number | undefined {
    return this.fs.limit;
  }

  /**
   * The timeout for the page query debouncer.
   * @attribute
   */
  @property({ type: Number, hasChanged: () => false }) 
  get debounceTimeout(): number {
    return this.fs.debounceTimeout;
  }

  set debounceTimeout(value: number) {
    this.fs.debounceTimeout = value || 0;
  }

  /**
   * The list of file kinds to list.
   * Folders are always included.
   */
  @property({ type: Array, hasChanged: () => false }) 
  get kinds(): ListFileKind[] | undefined {
    return this.fs.kinds;
  }

  set kinds(value: ListFileKind[] | undefined) {
    this.fs.kinds = value;
    this.fs.resetList();
    this.fs.debounceQuery();
  }

  @query('ui-list') protected list?: UiList;

  /**
   * The currently selected in the picker file.
   */
  @state() protected selectedFile?: IFile;

  @state() errorMessage?: string;

  /**
   * When set it does not query for files when attached to the DOM. 
   * It will still query for files when attributes change. This is primarily used in tests.
   * @attribute
   */
  @property({ type: Boolean }) manualQuery?: boolean;

  get selectedKey(): string | undefined {
    return this.selectedFile?.key;
  }

  fs = new FileSystem();

  constructor() {
    super();
    this.fs.eventsTarget = this;
    this.fs.addEventListener('change', () => this.requestUpdate());
    this.fs.addEventListener('error', (e: Event) => {
      const event = e as CustomEvent<string>;
      this.errorMessage = event.detail;
    });
    this.fs.addEventListener('querycomplete', () => {
      this.dispatchEvent(new Event('querycomplete'));
    });
    this.fs.addEventListener('delete', (e: Event) => {
      const event = e as CustomEvent<string>;
      if (this.selectedFile && this.selectedFile.key === event.detail) {
        this.selectedFile = undefined;
      }
    });
  }
  
  override connectedCallback(): void {
    super.connectedCallback();
    this.fs.observe();
    if (!this.manualQuery) {
      this.fs.debounceQuery();
    }
    if (!this.hasAttribute('tabindex')) {
      this.setAttribute('tabindex', '0');
    }
  }
  
  override disconnectedCallback(): void {
    super.disconnectedCallback();
    this.fs.unobserve();
  }

  override focus(options?: FocusOptions | undefined): void {
    const { list } = this;
    if (list) {
      list.focus(options);
    }
  }

  protected handleFileSelect(e: CustomEvent<UiListSelection>): void {
    // this prevents the dropdown list to close the UI.
    e.preventDefault();
    const file = this.fs.files[e.detail.index];
    if (!file) {
      return;
    }
    if (file.kind === FolderKind) {
      this.fs.selectFolder(file.key);
      this.selectedFile = undefined;
      return;
    }
    this.selectedFile = file;
  }

  protected handleParentUp(): void {
    this.fs.parentUp();
    this.selectedFile = undefined;
  }

  protected notifyClose(canceled: boolean): void {
    if (!canceled && !this.selectedFile) {
      return;
    }
    const detail: FilePickerClosingReason = {
      canceled,
    };
    if (!canceled) {
      detail.file = this.selectedFile;
    }
    // this event bubbles so any dropdown on the way can close itself.
    const e = new CustomEvent<FilePickerClosingReason>('close', {
      bubbles: true,
      cancelable: true,
      composed: true,
      detail,
    });
    this.dispatchEvent(e);
  }

  protected handleSelect(): void {
    this.notifyClose(false);
  }

  protected handleCancel(): void {
    this.notifyClose(true);
  }

  @eventOptions({ passive: true })
  protected handleListScroll(e: Event): void {
    const list = e.target as UiList;
    if (this.fs.isListEnd(list)) {
      this.fs.debounceQuery();
    }
  }

  protected override render(): TemplateResult {
    return html`
    <div class="content">
      ${this.renderHeader()}
      <ui-divider></ui-divider>
      ${this.renderFiles()}
      ${this.errorMessage ? html`<p class="error">${this.errorMessage}</p>` : nothing}
      <ui-divider></ui-divider>
      ${this.renderActions()}
    </div>
    `;
  }

  protected renderHeader(): TemplateResult {
    const { breadcrumbs = [], reading } = this.fs;
    let content: TemplateResult;
    let withIcon = false;
    if (!breadcrumbs.length) {
      content = html`<span class="title-large label">My files</span>`;
    } else {
      withIcon = true;
      const [parent] = breadcrumbs;
      content = html`
        <ui-icon-button aria-label="Open parent folder" @click="${this.handleParentUp}" class="back-button">
          <ui-icon role="presentation" icon="arrowBack"></ui-icon>
        </ui-icon-button>
        <span class="title-large label">${parent.name}</span>
      `;
    }
    const classes = {
      header: true,
      withIcon,
    }
    return html`
    <div class="${classMap(classes)}">
      ${content}
      ${reading ? html`<ui-progress indeterminate class="progress"></ui-progress>` : nothing}
    </div>
    `;
  }

  protected renderFiles(): TemplateResult {
    const { files } = this.fs;
    if (!files || !files.length) {
      return this.renderEmptyList();
    }
    return html`
    <ui-list class="list" role="menu" @select="${this.handleFileSelect}" selectActive @scroll="${this.handleListScroll}">
      ${files.map(item => this.renderFileItem(item))}
    </ui-list>
    `;
  }

  protected renderEmptyList(): TemplateResult {
    return html`
    <p class="no-files body-large">No files in this view.</p>
    `;
  }

  protected renderFileItem(item: IFile): TemplateResult {
    const icon = fileIcon(item);
    return html`
    <ui-list-item role="menuitem" data-key="${item.key}" data-kind="${item.kind}">
      ${icon ? html`<ui-icon icon="${icon}" class="file-icon"></ui-icon>` : nothing}
      ${item.info.name || 'Unnamed file'}
    </ui-list-item>
    `;
  }

  protected renderActions(): TemplateResult {
    const { selectedFile } = this;
    const disableSelect = !selectedFile;
    return html`
    <div class="actions">
      <ui-button type="text" @click="${this.handleCancel}" value="cancel">Cancel</ui-button>
      <ui-button type="tonal" ?disabled="${disableSelect}" @click="${this.handleSelect}" value="select">Select</ui-button>
    </div>
    `;
  }
}
