import { HttpClient } from '@angular/common/http';
import { Component, EventEmitter, Input, OnInit, Output, TemplateRef } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { differenceInCalendarDays } from 'date-fns';
import { firstValueFrom } from 'rxjs';
import { CommonUtils } from '../utils/common-utils';
import { CacheService } from './cache.service';
import { FilterItem, FilterType } from './pager-filter.component';
import { CellRender } from './pager-table.component';

@Component({
  selector: 'cg-pager',
  template: `


<nz-result *ngIf="error" nzStatus="404" nzTitle="{{error.status}}" nzSubTitle="{{error?.error?.message}}"> </nz-result>
<ng-container *ngIf="!error">
  <!--filters-->
  <cg-pager-filter [filters]="filters" [inputFilters]="inputFilters" [filterConfig]="filterConfig" (filterChange)="filters=$event; delayUpdateQueryString('filters')"></cg-pager-filter>

  <div *ngIf="debug">
    search: {{search}} <br/>
    orderBy : {{orderBy}} <br/>
    order : {{order}} <br/>
    filters : {{filters | json}} <br/>
    params: {{params | json}} <br/>
    resp.meta : {{respMeta | json}} <br/>
  </div>


  <nz-spin [nzDelay]="500" [nzSpinning]="isLoading">
  
  <div class="d-flex flex-row  justify-content-between align-items-center" >

    <!--top pagination -->
    <div *ngIf="topPagination && list" class="d-flex  m-2" >
      <nz-pagination *ngIf="list" 
        [(nzPageIndex)]="page" 
        [nzShowTotal]="rangeTemplate" 
        nzShowSizeChanger 
        nzShowQuickJumper 
        [nzPageSizeOptions]="pageSizeOptions"  
        [(nzPageSize)]="length" 
        nzSize="small"
        (nzPageIndexChange)="delayUpdateQueryString('pageIndex')"
        (nzPageSizeChange)="delayUpdateQueryString('pageSize')"
        [nzTotal]="total">
      </nz-pagination>
    </div>


    <!--sort-->
    <div *ngIf="orderableHeaders.length > 0 && showOrder">
      <span class="me-2"><i class="fal fa-sort-alpha-down"></i></span>
      <nz-button-group>
        <button nz-button  pagerSort 
          *ngFor="let header of orderableHeaders" 
          [order]="header.order"
          [nzType]="orderBy == header.data ? 'primary' : 'default' " 
          (onSortChange)="inputSort=[header.data, $event]">
          <i nz-icon *ngIf="orderBy == header.data" [nzType]="order == 'desc' ? 'sort-descending'  : 'sort-ascending'" nzTheme="outline"></i>{{header.title}}
        </button>
      </nz-button-group>
    </div>

    <!--搜索-->
    <nz-input-group *ngIf="showSearch" nzPrefixIcon="search" [nzSuffix]="clearIcon"  class="search" >
      <input nz-input [(ngModel)]="search" (change)="delayUpdateQueryString('search')"  i18n-placeholder placeholder="搜索">
      <ng-template #clearIcon>
        <i nz-icon nzType="close-circle" *ngIf="search" nzTheme="fill" class="clearIcon" (click)="search=undefined; delayUpdateQueryString('search clear')"  ></i>
      </ng-template>
    </nz-input-group>

  </div>


  <!--content list-->
  <ng-container *ngIf="listTemplate">
    <ng-container *ngTemplateOutlet="listTemplate; context : {list, headers }" >
    </ng-container>
    <nz-empty *ngIf="list && list?.data.length <= 0" i18n-nzNotFoundContent nzNotFoundContent="没有数据"	 ></nz-empty>
  </ng-container>

  <!--default content list-->
  <cg-pager-table *ngIf="!listTemplate" [cellRenders]="cellRenders" [headers]="headers" [list]="list?.data" (onSort)="inputSort=$event" > </cg-pager-table> 


  <!--bottom pagination-->
  <div *ngIf="bottomPagination && list" class="d-flex flex-row m-2 "  >
      <nz-pagination *ngIf="list" 
        [(nzPageIndex)]="page" 
        [nzShowTotal]="rangeTemplate" 
        nzShowSizeChanger 
        nzShowQuickJumper 
        [nzPageSizeOptions]="pageSizeOptions"  
        [(nzPageSize)]="length" 
        nzSize="small"
        (nzPageIndexChange)="delayUpdateQueryString('pageIndex')"
        (nzPageSizeChange)="delayUpdateQueryString('pageSize')"
        [nzTotal]="total">
      </nz-pagination>
  </div>

  </nz-spin>

  <ng-template #rangeTemplate let-range="range" let-total>
        {{ range[0] }}-{{ range[1] }} <ng-container i18n>共</ng-container> {{ total }} <ng-container i18n>项</ng-container> 
  </ng-template>

</ng-container>

  `,
  styles: [
    `
    :host {
    }

    select.length{
      width: auto;
    }

    .search{
      width: 236px;
    }

    .search .clearIcon{
      color: #ccc;
      cursor: pointer;
    }
    .search .clearIcon:hover{
      color: #999;
    }

    `
  ]
})
export class PagerComponent implements OnInit {
  @Input() userRouter: boolean = true;
  @Input() defaultOrderBy: string;
  @Input() defaultOrder: orderType;
  @Input() listTemplate: TemplateRef<any>; //列表渲染模板
  @Input() pageSizeOptions: number[] = [10, 20, 30, 60, 120, 180];
  @Input() showOrder: boolean = true;
  @Input() showSearch: boolean = true;
  @Input() highlightSearch: boolean = true;
  @Input() orderColumns: string[];
  @Input() topPagination: boolean = true;
  @Input() bottomPagination: boolean = true;
  @Input() debug: boolean = false;
  @Input() replaceUrl: boolean = true;
  @Input() inputFilters: any;
  @Input() filterConfig: FilterItem[];
  @Input() cellRenders: CellRender;

  @Input()set refresh(val) { //刷新数据
    this.invalidate();
  } 

  @Input() set inputSort(val: [string, orderType]) {
    console.log('val: ', val);
    if (val) {
      let [orderBy, order] = val;
      this.orderBy = orderBy;
      this.order = order;
      this.delayUpdateQueryString('sort');
    }
  }



  //dateRange: any;

  public get total(): number {
    if (this.list) return this.list.recordsFiltered;
    else return 0;
  }

  filters: any = {};


  /**
   * 远端表头接口
   */
  private _remoteHeaderUrl: string = null;
  public get remoteHeaderUrl(): string {
    return this._remoteHeaderUrl;
  }
  @Input() public set remoteHeaderUrl(value: string) {
    this._remoteHeaderUrl = value;
    this.loadHeader();
  }


  /**
   * 远端数据接口
   */
  private _remoteUrl: string = null;
  public get remoteUrl(): string { return this._remoteUrl; }
  @Input() public set remoteUrl(value: string) {
    this._remoteUrl = value;
  }


  private _page: number = 1;
  public get page(): number { return this._page; }
  public set page(value: number) {
    this._page = value;
  }

  private _length: number = 0;
  public get length(): number { return this._length || this.pageSizeOptions[0] || 10; }
  public set length(value: number) {
    this._length = value;
  }


  private _orderBy: string;
  public get orderBy(): string { return this._orderBy; }
  @Input()
  public set orderBy(value: string) {
    if (this._orderBy && this._orderBy != value) {
      this.page = 1; //set page to the 1st page.
    }
    this._orderBy = value;
  }

  private _order: orderType;
  public get order(): orderType { return this._order; }
  @Input()
  public set order(value: orderType) {
    this._order = value;
  }


  search: string;


  headers: Header[];
  list: PagerResponse<any>;

  private isPendingLoad: boolean = false;
  isLoading: boolean = false;


  error: any;

  constructor(
    private activatedRoute: ActivatedRoute,
    private router: Router,
    private http: HttpClient,
    public cache: CacheService,
  ) {

    //this.router.events.subscribe(e => console.log('routerEvents:', e));
  }

  get orderableHeaders(): Header[] {
    if (!this.headers) return [];

    let orderable = this.headers.filter(h => h.orderable)
    if (!this.orderColumns)
      return orderable;

    return this.orderColumns.map(key => {
      let header: Header = orderable.find(h => h.data == key);
      return header
    }).filter(h => h);
  }

  get params(): any {
    let params = {
      page: this.page,
      length: this.length,
      order: this.order,
      orderBy: this.orderBy,
      search: this.search,
    };

    this.cleanUp(this.filters);
    params['filters'] = JSON.stringify(this.filters);
    this.cleanUp(params);
    return params;
  }

  private loadHeader() {
    if (!this.remoteHeaderUrl) return;
    this.cache.get(this.remoteHeaderUrl).subscribe({
      next: (resp: Header[]) => {
        this.headers = resp;
      },
      error: err => console.warn(err),
      complete: () => this.updateHeader(),
    })
  }



  invalidate() {
    if (this.isPendingLoad) return;
    this.isPendingLoad = true;
    requestAnimationFrame(() => {
      this.loadData();
    })
  }

  get respMeta(): any {
    let resp = { ...this.list };
    delete resp['data'];
    return resp;
  }


  private async loadData() {
    this.isPendingLoad = false;
    this.isLoading = true;
    if (!this.remoteUrl) throw new Error('no remote url.');
    this.cache.get(this.remoteUrl, this.params).subscribe({
      next: resp => {
        console.log('got resp:', resp);
        this.list = resp as PagerResponse<any>;
        this.doHighlight(this.list, this.headers);
        this.error = null;
      },
      error: (err) => {
        console.error(err)
        this.isLoading = false;
        this.error = err;
      },
      complete: () => { this.isLoading = false; this.updateHeader() }
    })
  }

  doHighlight(list: PagerResponse<any>, headers: Header[]) {
    if (!this.highlightSearch || !list.data || !this.search || !headers) return;
    let search = this.search.trim();
    if (!search) return;
    let regex = new RegExp(CommonUtils.escapeRegExp(search), 'gi');
    this.headers.filter(h => h.searchable)
      .forEach(header => {
        this.list.data.forEach(item => {
          let value = item[header.data];
          if (value && value.replace && value.match) { //字符串类型
            let match = value.match(regex)
            if (match) {
              item[header.data] = value.replace(regex, `<span class='highlight'>${match[0]}</span>`)
            }
          } else if (Array.isArray(value) && value.length > 0 && value[0].replace) { //字符串数字类型，如tags
            item[header.data] = value.map(v => {
              let match = v.match(regex)
              if (match) {
                return v.replace(regex, `<span class='highlight'>${match[0]}</span>`)
              } else {
                return v;
              }
            })
          }
        })
      })
  }


  updateHeader() {
    if (!this.headers) return;
    this.headers.forEach(h => {
      if (h.data == this.orderBy) {
        h.order = this.order
      } else {
        h.order = null;
      }
    })
  }



  //当前是否有等待更新queryString的请求
  private isPendingRequest: boolean = false;

  /**
   * 延期更新QueryString 
   * 当同一时间多次出发更新时， 合并操作.
   * @returns 
   */
  delayUpdateQueryString(from: string) {
    console.log('delayUpdateQueryString: ', from, this.isPendingRequest);
    if (this.isPendingRequest) return;
    this.isPendingRequest = true;
    requestAnimationFrame(() => {
      this.updateQueryString();
    })
  }

  /**
   * 更新QueryString, 出发数据重新加载.
   */
  updateQueryString() {
    this.isPendingRequest = false;

    if (!this.userRouter) {  // 不使用路由，则此处就不要更新query param了，直接返回
      this.invalidate();
      return;
    }

    let params: any = {
      page: this.page,
      length: this.length,
    };

    if (this.order) {
      params.order = this.order;
      params.orderBy = this.orderBy;
    };

    if (this.search) {
      params.search = this.search;
    }

    this.cleanUp(this.filters);
    if(Object.keys(this.filters).length > 0) {
      params.filters = JSON.stringify(this.filters);
    }

    let oldParams: any = Object.assign({}, this.activatedRoute.snapshot.queryParams);
    delete oldParams.order;
    delete oldParams.orderBy;
    delete oldParams.page;
    delete oldParams.length;
    delete oldParams.search;
    delete oldParams.filters;

    let obj = Object.assign({}, oldParams, params);
    this.cleanUp(obj);

    console.log('updating qeurystring:', obj);
    this.router.navigate([], { queryParams: obj, replaceUrl: this.replaceUrl });
  }


  cleanUp(obj: any) {
    for (var attrKey in obj) {
      var attrValue = obj[attrKey];
      if (attrValue === null || attrValue === undefined || attrValue === "" || attrValue !== attrValue) {
        delete obj[attrKey];
      } else if (Object.prototype.toString.call(attrValue) === "[object Object]") {
        if (Object.keys(attrValue).length === 0) delete obj[attrKey];
      } else if (Array.isArray(attrValue)) {
        if (attrValue.length === 0) delete obj[attrKey];
      }
    }
  }







  ngOnInit() {

    this.order = this.defaultOrder;
    this.orderBy = this.defaultOrderBy;
    if (this.orderBy && !this.order) this.order = 'desc';

    this.activatedRoute.queryParams.subscribe(params => {
      console.log('params change:', params);
      if (this.userRouter) { // 使用路由的情况下才去解析query param，否则直接略过
        this._page = parseInt(params['page']) || this.page;
        this._length = parseInt(params['length']) || this.length;
        this._order = params['order'] || this.order;
        this._orderBy = params['orderBy'] || this.orderBy;
        this.search = params['search'] || this.search;
  
        if (params['filters']) {
          try {
            let filters = JSON.parse(params['filters']);
            if (Object.keys(filters).length > 0) {
              this.filters = filters;
            }
          } catch (e) {
            console.warn(e);
          }
        }
      }

      this.invalidate(); //load data.
    })

  }





}



export interface PagerResponse<Type> {
  page: number,
  length: number,
  draw: number,
  recordsTotal: number,
  recordsFiltered: number,
  data?: Type[],
}


export interface Header {
  data: string,
  title: string,
  orderable: boolean,
  searchable: boolean,
  type: string,
  order?: orderType,
}


export type orderType = 'asc' | 'desc' | null;


