import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { AfterViewInit, Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { isValid, validateForm } from '@ng-cortex/utils';
import { NzModalService } from 'ng-zorro-antd/modal';
import { NzNotificationService } from 'ng-zorro-antd/notification';
import { NzFormatEmitEvent, NzTreeNodeOptions } from 'ng-zorro-antd/tree';
import { Observable, of } from 'rxjs';
import { catchError, map, mergeMap } from 'rxjs/operators';

/** 字段接口 */
interface Field {
  /** 名称 */
  name: string;
  /** 描述 */
  description?: string;
  /** 可见状态 */
  visible?: boolean;
  /** 对齐 */
  align?: 'left' | 'right' | 'center';
  /** 宽度 */
  width?: string;
  /** 排序键 */
  sortKey?: string;
  /** 排序状态 */
  sortOrder?: string | null;
  /** 默认排序 */
  defaultSequence?: number;
  /** 默认可见状态 */
  defaultVisible?: boolean;
  /** 默认宽度 */
  defaultWidth?: string;
  /** 标题模板 */
  titleTemplate?: TemplateRef<any>;
  /** 内容模板 */
  contentTemplate?: TemplateRef<{ $implicit?: any; source?: any; extra?: any; }>;
}

/** 本地文件管理页面 */
@Component({
  selector: 'nc-local-file',
  templateUrl: './nc-local-file.page.html',
  styleUrls: ['./nc-local-file.page.scss']
})
export class NcLocalFilePage implements OnInit, AfterViewInit {

  /** 标题模板 */
  @ViewChild('titleTemplate') _titleTemplate!: TemplateRef<{ $implicit: any; source: any; }>;
  /** 数字模板 */
  @ViewChild('numberContentTemplate') _numberContentTemplate!: TemplateRef<{ $implicit: number; }>;
  /** 时间模板 */
  @ViewChild('timeTemplate') _timeTemplate!: TemplateRef<{ $implicit: any; }>;

  /** 文件夹列表 */
  _folderList: string[] = [];
  /** 文件夹树形节点选项列表 */
  _folderTreeNodeOptionList: NzTreeNodeOptions[] = [];
  /** 文件夹加载状态 */
  _folderLoading: boolean = false;
  /** 本地文件列表 */
  _localFileList: any[] = [];
  /** 本地文件额外属性 */
  _localFileExtras: any = {};
  /** 本地文件总数 */
  _localFileTotal: number = 0;
  /** 本地文件搜索表单 */
  _localFileSearchForm!: FormGroup;
  /** 本地文件参数 */
  _localFileParams = {
    folder: null! as string,
    page: 1,
    size: 20,
    sort: 'id,desc'
  };
  /** 本地文件加载状态 */
  _localFileLoading: boolean = false;
  /** 本地文件全部选中状态 */
  _localFileAllChecked: boolean = false;
  /** 本地文件模糊选中状态 */
  _localFileIndeterminate: boolean = false;
  /** 本地文件字段列表 */
  _localFileFieldList: Field[] = [
    { name: 'entityGuid', description: '实体编码', width: '320px', sortKey: 'entityGuid' },
    { name: 'name', description: '文件名称', visible: true, width: '200px', sortKey: 'name' },
    { name: 'originalName', description: '源文件名称', width: '200px', sortKey: 'originalName' },
    { name: 'format', description: '文件格式', visible: true, width: '120px', sortKey: 'format' },
    { name: 'folder', description: '文件夹', visible: true, width: '200px', sortKey: 'folder' },
    { name: 'path', description: '文件路径', width: '320px', sortKey: 'path' },
    { name: 'size', description: '文件大小', visible: true, align: 'right', width: '120px', sortKey: 'size' },
    { name: 'uploadedBy', description: '上传人', visible: true, width: '120px', sortKey: 'uploadedBy' },
    { name: 'uploadedTime', description: '上传时间', visible: true, width: '200px', sortKey: 'uploadedTime' }
  ];

  /** 取消获取本地文件列表 */
  private _cancelGetLocalFileListFn = () => { };

  constructor(
    private _formBuilder: FormBuilder,
    private _httpClient: HttpClient,
    private _nzModalService: NzModalService,
    private _nzNotificationService: NzNotificationService) { }

  /** 初始化后 */
  ngOnInit(): void {
    this._localFileFieldList.forEach((field, index) => {
      field.defaultSequence = index + 1;
      field.defaultVisible = field.visible;
      field.defaultWidth = field.width;
    });
    this._localFileSearchForm = this._formBuilder.group({
      keywords: [null],
      entityGuid: [null]
    });
    validateForm(this._localFileSearchForm, true).subscribe(() => {
      this._getFolderList();
      this._getLocalFileList();
    });
  }

  /** 初始次渲染后 */
  ngAfterViewInit(): void {
    this._localFileFieldList.forEach(field => {
      if (/^name$/.test(field.name)) {
        field.contentTemplate = this._titleTemplate;
      }
      if (/^size$/.test(field.name)) {
        field.contentTemplate = this._numberContentTemplate;
      }
      if (/^(uploadedTime)$/.test(field.name)) {
        field.contentTemplate = this._timeTemplate;
      }
    });
  }

  /** 查询本地文件列表 */
  _queryLocalFileList(): void {
    validateForm(this._localFileSearchForm).subscribe(() => this._getLocalFileList());
  }

  /** 重新查询本地文件列表 */
  _requeryLocalFileList(): void {
    this._localFileParams.page = 1;
    this._queryLocalFileList();
  }

  /** 重置本地文件搜索表单 */
  _resetLocalFileSearchForm(): void {
    this._localFileSearchForm.reset();
    this._queryLocalFileList();
  }

  /**
   * 拖拽本地文件字段列表
   * @param event 拖拽事件
   */
  _dropLocalFileFieldList(event: CdkDragDrop<string[]>): void {
    moveItemInArray(this._localFileFieldList, event.previousIndex, event.currentIndex);
  }

  /** 重置本地文件字段列表 */
  _resetLocalFileFieldList(): void {
    this._localFileFieldList = this._localFileFieldList
      .map(field => {
        field.visible = field.defaultVisible!;
        field.width = field.defaultWidth;
        return field;
      })
      .sort((fieldA, fieldB) => fieldA.defaultSequence! - fieldB.defaultSequence!);
  }

  /**
   * 改变本地文件字段排序
   * @param field 字段
   */
  _changeLocalFileFieldSort(field: Field): void {
    const targetField = field;
    this._localFileFieldList
      .filter(field => field !== targetField && field.sortOrder)
      .forEach(field => field.sortOrder = null);
    if (field.sortOrder) {
      this._localFileParams.sort = field.sortKey + ',' + field.sortOrder.slice(0, -3);
    } else {
      this._localFileParams.sort = 'id,desc';
    }
    this._requeryLocalFileList();
  }

  /**
   * 选中所有本地文件列表
   * @param checked 选中状态
   */
  _checkAllLocalFileList(checked: boolean): void {
    for (const id in this._localFileExtras) {
      this._localFileExtras[id].checked = checked;
    }
    this._localFileAllChecked = checked;
    this._localFileIndeterminate = false;
  }

  /** 更新本地文件选中状态 */
  _updateLocalFileCheckStatus(): void {
    const checkedLocalFileList = this._localFileList.filter(localFile => this._localFileExtras[localFile.id].checked);
    switch (checkedLocalFileList.length) {
      case 0:
        this._localFileAllChecked = false;
        this._localFileIndeterminate = false;
        break;
      case this._localFileList.length:
        this._localFileAllChecked = true;
        this._localFileIndeterminate = false;
        break;
      default:
        this._localFileAllChecked = false;
        this._localFileIndeterminate = true;
    }
  }

  /**
   * 改变本地文件文件夹
   * @param event 事件
   */
  _changeLocalFileFolder(event: NzFormatEmitEvent): void {
    this._localFileParams.folder = event.node!.isSelected ? event.node!.key : null!;
    this._requeryLocalFileList();
  }

  /**
   * 下载本地文件
   * @param localFile 本地文件
   */
  _downloadLocalFile(localFile: any): void {
    const url = '@<%= getSeparatorCase(serviceName) %>/api/local-files/download';
    const params: any = { id: localFile.id };
    this._httpClient.get(url, { params: params, responseType: 'blob' })
      .subscribe(
        blob => {
          const a = document.createElement('a');
          a.download = localFile.name + '.' + localFile.format;
          a.href = URL.createObjectURL(blob);
          a.click();
          requestAnimationFrame(() => URL.revokeObjectURL(blob as any));
        },
        (httpErrorResponse: HttpErrorResponse) => {
          this._nzNotificationService.error('下载本地文件失败', httpErrorResponse.error && httpErrorResponse.error.message || httpErrorResponse.statusText);
        }
      );
  }

  /**
   * 删除本地文件
   * @param localFile 本地文件
   */
  _deleteLocalFile(localFile: any): void {
    let cancelDeleteLocalFileFn = () => { };
    this._nzModalService.confirm({
      nzTitle: '删除确认',
      nzContent: '是否确认删除本地文件 “' + localFile.name + '”',
      nzOnOk: () => {
        return new Observable(subscriber => {
          const url = '@<%= getSeparatorCase(serviceName) %>/api/local-files/' + localFile.id;
          const deleteLocalFileSubscription = this._httpClient.delete(url, { responseType: 'text' })
            .pipe(map(() => {
              this._queryLocalFileList();
              return true;
            }))
            .pipe(catchError((httpErrorResponse: HttpErrorResponse) => {
              this._nzNotificationService.error('删除本地文件失败', httpErrorResponse.error && httpErrorResponse.error.message || httpErrorResponse.statusText);
              return of(false);
            }))
            .subscribe(subscriber);
          cancelDeleteLocalFileFn = () => {
            deleteLocalFileSubscription.unsubscribe();
          };
        })
          .toPromise();
      },
      nzOnCancel: () => {
        cancelDeleteLocalFileFn();
      }
    });
  }

  /** 获取文件夹列表 */
  private _getFolderList(): void {
    this._folderLoading = true;
    const url = '@<%= getSeparatorCase(serviceName) %>/api/local-files/folders';
    this._httpClient.get<string[]>(url)
      .pipe(catchError((httpErrorResponse: HttpErrorResponse) => {
        this._nzNotificationService.error('获取文件夹列表失败', httpErrorResponse.error && httpErrorResponse.error.message || httpErrorResponse.statusText);
        return of([]);
      }))
      .subscribe(folderList => {
        this._folderList = folderList;
        this._folderLoading = false;
        this._generateFolderTreeNodeOptionList();
      });
  }

  /** 生成文件夹树形节点列表 */
  private _generateFolderTreeNodeOptionList(): void {
    const folderTreeNodeOptionList: NzTreeNodeOptions[] = [];
    const folderTreeNodeOptions: any = {};
    const pathList: any[] = [];
    this._folderList.forEach(folder => {
      const segmentList = folder.split('/').filter(segment => segment);
      segmentList.forEach((segment, index) => {
        pathList.push({
          title: segment,
          key: '/' + segmentList.slice(0, index + 1).join('/'),
          parentKey: '/' + segmentList.slice(0, index).join('/'),
          expanded: true
        });
      });
    });
    pathList.forEach(path => {
      const folderTreeNodeOption: NzTreeNodeOptions = folderTreeNodeOptions[path.key] = folderTreeNodeOptions[path.key] || {};
      folderTreeNodeOption.key = path.key;
      folderTreeNodeOption.title = path.title;
      folderTreeNodeOption.expanded = path.expanded;
      if (path.parentKey !== '/') {
        const parentFolderTreeNodeOption: NzTreeNodeOptions = folderTreeNodeOptions[path.parentKey] = folderTreeNodeOptions[path.parentKey] || {};
        if (!parentFolderTreeNodeOption.children) {
          parentFolderTreeNodeOption.children = [];
        }
        parentFolderTreeNodeOption.children.push(folderTreeNodeOption);
      } else {
        folderTreeNodeOptionList.push(folderTreeNodeOption);
      }
    });
    this._folderTreeNodeOptionList = folderTreeNodeOptionList;
  }

  /** 获取本地文件列表 */
  private _getLocalFileList(): void {
    this._cancelGetLocalFileListFn();
    if (this._localFileSearchForm.valid) {
      this._localFileLoading = true;
      const completeGetLocalFileListFn = () => {
        this._cancelGetLocalFileListFn = () => { };
        this._localFileLoading = false;
      };
      const url = '@<%= getSeparatorCase(serviceName) %>/api/local-files';
      const params: any = {
        ...this._localFileSearchForm.getRawValue(),
        ...this._localFileParams
      };
      for (const key in params) {
        if (!isValid(params[key])) {
          delete params[key];
        }
      }
      params.page--;
      const getLocalFileListSubscription = this._httpClient.get<any[]>(url, { params: params, observe: 'response' })
        .pipe(mergeMap(httpResponse => {
          const localFileList = httpResponse.body!;
          const localFileTotal = Number(httpResponse.headers.get('X-Total-Count'));
          if (localFileList.length === 0 && localFileTotal > 0) {
            this._localFileParams.page = Math.ceil(localFileTotal / params.size);
            params.page = this._localFileParams.page - 1;
            return this._httpClient.get<any[]>(url, { params: params, observe: 'response' })
              .pipe(map(httpResponse => {
                const localFileList = httpResponse.body!;
                const localFileTotal = Number(httpResponse.headers.get('X-Total-Count'));
                return { localFileList, localFileTotal };
              }));
          }
          return of({ localFileList, localFileTotal });
        }))
        .pipe(catchError((httpErrorResponse: HttpErrorResponse) => {
          this._nzNotificationService.error('获取本地文件列表失败', httpErrorResponse.error && httpErrorResponse.error.message || httpErrorResponse.statusText);
          return of({ localFileList: [], localFileTotal: 0 });
        }))
        .subscribe(({ localFileList, localFileTotal }) => {
          completeGetLocalFileListFn();
          const localFileExtras: any = {};
          localFileList.forEach(localFile => {
            localFileExtras[localFile.id] = { checked: false };
          });
          this._localFileList = localFileList;
          this._localFileTotal = localFileTotal;
          this._localFileExtras = localFileExtras;
          this._localFileAllChecked = false;
          this._localFileIndeterminate = false;
        });
      this._cancelGetLocalFileListFn = () => {
        getLocalFileListSubscription.unsubscribe();
        completeGetLocalFileListFn();
      };
    } else {
      this._localFileList = [];
      this._localFileTotal = 0;
      this._localFileExtras = {};
      this._localFileAllChecked = false;
      this._localFileIndeterminate = false;
    }
  }

}
