import * as React from "react";
import IJitzSPContext from "../data/interfaces/IJitzSPContext";
import UtilityService from "../services/UtilityService";
import {
  buildColumns,
  CommandBar,
  DetailsList,
  IColumn,
  IconButton,
  Label,
  SelectionMode,
  TooltipHost,
  Selection,
  ActionButton,
  Spinner,
} from "@fluentui/react";
import CommonRepository from "../data/context/CommonRepository";
import { IList } from "../data/interfaces/IList";
import { IModel } from "../common/IModels";
import IJitzContext from "../data/interfaces/IJitzContext";

export enum SortOrder {
  Asc = "asc",
  Desc = "desc",
}

export enum DisplayMode {
  Grid = "Grid",
  Tile = "Tile",
}

export interface IJitzGridState<T extends IModel> {
  list: IList<T>;
  listForDownload: IList<T>;
  items: any[];
  showingItems: any[];
  rawData: any[];
  showingRawData: any[];
  exportData: any[];
  totalCount: number;
  columns?: IColumn[];
  selectedItems?: {};

  displayMode?: DisplayMode;
  filterQuery?: string;

  isItIndexedSearch?: boolean;
  isItIndexedDownload?: boolean;
  hasNextPage?: boolean;
  hasNextPageToDownload?: boolean;
  processing?: boolean;
  downloading?: boolean;
}

export interface IJitzGridProps<T extends IModel> {
  context: IJitzContext;
  list: IList<T>;
  filterQuery?: string;
  orderBy?: string;
  displayFields?: string[];
  exportFields?: string[];
  hasExport?: boolean;
  pageSize?: number;
  renderCustomColumns?: (
    content: any,
    fieldName: string,
    rowItem?: any
  ) => React.ReactElement<{}>;
  modifyColumns?: (columns: IColumn[]) => IColumn[];
  onItemSelect?: (items: any) => void;
  onItemInvoked?: (item: any, index: number | undefined) => void;
  selectionMode?: SelectionMode;
  noRecordsMessage?: string;
  noRecordsMessageCssClass?: string;
  tileComponent?: (content: any) => React.ReactElement<{}>;
  refreshedOn?: Date;
  displayMode?: DisplayMode;
  downloadFileName?: string;
  outerGridClass?: string;
  fixedGridWidthCss?: string;
}

export class JitzGrid<T extends IModel> extends React.Component<
  IJitzGridProps<T>,
  IJitzGridState<T>
> {
  private _selection: Selection;

  constructor(props: IJitzGridProps<T>) {
    super(props);

    let item: any = {};
    if (props.displayFields) {
      props.displayFields.map((field: any) => {
        item[field] = "";
      });
    }

    this.state = {
      list: this.props.list,
      listForDownload: this.props.list,
      items: [],
      showingItems: [],
      rawData: [],
      showingRawData: [],
      exportData: [],
      totalCount: 0,
      columns: [],
      displayMode: props.displayMode || DisplayMode.Tile,
      filterQuery: props.filterQuery,
      isItIndexedSearch: false,
      isItIndexedDownload: false,
      processing: false,
      downloading: false,
      hasNextPage: false,
      hasNextPageToDownload: false,
    };

    this._selection = new Selection({
      onSelectionChanged: () =>
        this.setState({ selectedItems: this._getSelectedItems() }),
    });
  }

  private _getSelectedItems = () => {
    if (this.props.onItemSelect != undefined) {
      this.props.onItemSelect(this._selection.getSelection());
    }
    return {};
  };

  private getSortOrderString = (
    sortOrder: SortOrder,
    isPreviousNavigation: boolean
  ): string => {
    if (sortOrder == SortOrder.Asc) {
      return isPreviousNavigation ? "desc" : "asc";
    } else {
      return isPreviousNavigation ? "asc" : "desc";
    }
  };

  // private hasKey<O>(obj: O, key: keyof any): key is keyof O {
  //   return key in obj;
  // }

  private hasKey(obj: object, key: string): boolean {
    return key in obj;
  }

  private initalLoad = async () => {
    await this.setState({
      items: [],
      showingItems: [],
      rawData: [],
      showingRawData: [],
    });
    var list = this.props.list;
    var data: T[] = [];
    let items: any[] = [];
    await list
      .getItems(
        this.props.filterQuery,
        this.props.orderBy,
        this.props.pageSize || 100
      )
      .then((results: any) => {
        data = results;
        this.setState({ isItIndexedSearch: false });
      })
      .catch(async (err: any) => {
        data = await list.getItemsIndexed(
          this.props.filterQuery,
          this.props.orderBy,
          this.props.pageSize || 100
        );
        this.setState({ isItIndexedSearch: true });
      });

    if (this.props.displayFields != undefined) {
      let rawShowingData: any[] = [];
      data.map((record: any) => {
        let item: any = {};
        if (this.props.displayFields != undefined) {
          this.props.displayFields.map((field) => {
            if (this.hasKey(record, field)) {
              item[field] =
                typeof record[field] == "object"
                  ? JSON.stringify(record[field])
                  : record[field]; // works fine!
            }
          });
          items.push(item);
          rawShowingData.push(record);
        }
      });

      this.setState(
        {
          items: items,
          showingItems: items,
          rawData: items,
          showingRawData: rawShowingData,
          columns: this._buildColumns(items),
          list: list,
          hasNextPage:
            (list._nextPageLink != undefined &&
              list._nextPageLink.length > 0) ||
            list.lowerId > 0,
        },
        () => {}
      );
    }
  };

  private loadMore = async () => {
    try {
      var list = this.state.list;
      var data: T[] = [];
      if (this.state.isItIndexedSearch) data = await list.loadMoreIndexed();
      else {
        data = await list.loadMore();
      }
      if (this.props.displayFields != undefined) {
        let items: any[] = [];
        let rawShowingData: any[] = [];
        data.map((record: any) => {
          let item: any = {};
          if (this.props.displayFields != undefined) {
            this.props.displayFields.map((field) => {
              if (this.hasKey(record, field)) {
                item[field] =
                  typeof record[field] == "object"
                    ? JSON.stringify(record[field])
                    : record[field]; // works fine!
              }
            });
            items.push(item);
            rawShowingData.push(record);
          }
        });

        if (data.length == 0) {
          //TODO: No more records message
        }

        await this.setState(
          {
            items: [...this.state.items, ...items],
            showingItems: [...this.state.showingItems, ...items],
            rawData: items,
            showingRawData: [...this.state.showingRawData, ...rawShowingData],
            list: list,
            hasNextPage:
              (list._nextPageLink != undefined &&
                list._nextPageLink.length > 0) ||
              list.lowerId > 0,
          },
          () => {}
        );
      }
    } catch {}
  };

  public componentDidMount(): void {
    this.initalLoad();
  }

  public async componentWillReceiveProps(nextProps: IJitzGridProps<T>) {
    if (
      nextProps.filterQuery != this.state.filterQuery
      // ||
      // nextProps.refreshedOn != this.props.refreshedOn
    ) {
      await this.setState({ filterQuery: nextProps.filterQuery });
      this.initalLoad();
    }
  }

  public downloadData = async () => {
    try {
      await this.setState({ downloading: true });
      await this.initiateDownload();
      let items: any[] = [];
      var data: any[] = this.state.exportData;
      data.map((record: any) => {
        let item: any = {};
        if (this.props.exportFields != undefined) {
          this.props.exportFields.map((field: string) => {
            if (this.hasKey(record, field)) {
              if (typeof record[field] == "object") {
                if (record[field] == null || record[field] == undefined) {
                  item[field] = "";
                } else if (record[field].EMail != undefined) {
                  if (record[field].Title != undefined) {
                    item[field + "_Title"] = record[field].Title || "";
                  }
                  item[field + "_Email"] = record[field].EMail || "";
                } else if (record[field].Name != undefined) {
                  item[field + "_Name"] = record[field].Name || "";
                }
              } else {
                item[field] = record[field];
              }
              // item[field] =
              //   typeof record[field] == "object"
              //     ? JSON.stringify(record[field])
              //     : record[field]; // works fine!
            } else if ((field as string).indexOf("/") > 0) {
              let lookUpField: string = (field as string).split("/")[0];
              let lookUpValueColumn: string = (field as string).split("/")[1];
              if (typeof record[lookUpField] == "object") {
                item[field] = record[lookUpField][lookUpValueColumn];
              }
            }
          });
          items.push(item);
        }
      });
      UtilityService.exportJsonAsExcelSheet(
        items,
        this.props.downloadFileName || "export"
      );
    } catch {}
    this.setState({ downloading: false });
  };

  initiateDownload = async () => {
    var list = this.props.list;
    var data: T[] = [];
    let items: any[] = [];
    await list
      .getItems(this.props.filterQuery, this.props.orderBy, 5000)
      .then((results: any) => {
        data = results;
        this.setState({ isItIndexedDownload: false });
      })
      .catch(async (err: any) => {
        data = await list.getItemsIndexed(
          this.props.filterQuery,
          this.props.orderBy,
          5000
        );
        this.setState({ isItIndexedDownload: true });
      });
    await this.setState({
      exportData: data,
      hasNextPageToDownload:
        (list._nextPageLink != undefined && list._nextPageLink.length > 0) ||
        list.lowerId > 0,
    });
    if (this.state.hasNextPageToDownload === true) {
      await this.continueDownload(list);
    }
  };

  continueDownload = async (list: IList<T>) => {
    try {
      var data: T[] = [];
      if (this.state.isItIndexedDownload) data = await list.loadMoreIndexed();
      else {
        data = await list.loadMore();
      }
      await this.setState({
        exportData: [...this.state.exportData, ...data],
        hasNextPageToDownload:
          (list._nextPageLink != undefined && list._nextPageLink.length > 0) ||
          list.lowerId > 0,
      });
      if (this.state.hasNextPageToDownload === true) {
        await this.continueDownload(list);
      }
    } catch {}
  };

  public render() {
    return (
      <div className="ms-Grid" dir="ltr">
        {/* <div style={{ display: "none" }}>{this.props.refreshedOn}</div> */}
        {this.props.hasExport == true &&
          this.state.showingItems != undefined &&
          this.state.showingItems.length > 0 && (
            <div className="ms-Grid-row">
              <div className="ms-Grid-col ms-sm12 ms-md12 ms-lg12">
                <CommandBar
                  farItems={[
                    {
                      key: "grid",
                      text: "",
                      title: "Grid View",
                      disabled: this.state.displayMode === DisplayMode.Grid,
                      iconProps: { iconName: "Table" },
                      onClick: () => {
                        this.setState({ displayMode: DisplayMode.Grid });
                      },
                    },
                    {
                      key: "tile",
                      text: "",
                      title: "Tile View",
                      disabled: this.state.displayMode === DisplayMode.Tile,
                      iconProps: { iconName: "Tiles" },
                      onClick: () => {
                        this.setState({ displayMode: DisplayMode.Tile });
                      },
                    },
                    {
                      key: "export",
                      text: "",
                      title: "Download",
                      iconProps: { iconName: "Download" },
                      onClick: () => {
                        this.downloadData();
                      },
                    },
                  ]}
                  items={[
                    {
                      key: "total",
                      text: `Showing ${this.state.items.length} records`,
                      title: `Showing ${this.state.items.length} records`,
                      onClick: () => {},
                    },
                    // {
                    //   key: "loadMore",
                    //   text: "Try to get more older records",
                    //   title: "Try to get more older records",
                    //   iconProps: { iconName: "DoubleChevronDown" },
                    //   onClick: () => {
                    //     if (this.state.processing != true)
                    //       this.setState({ processing: true }, async () => {
                    //         await this.loadMore();
                    //         this.setState({ processing: false });
                    //       });
                    //   },
                    // },
                  ]}
                />
                {this.state.downloading && <Spinner label="Please wait..." />}
              </div>
            </div>
          )}

        {(this.state.showingItems == undefined ||
          this.state.showingItems.length > 0) && (
          <div className="ms-Grid-row">
            <div className="ms-Grid-col ms-sm12 ms-md12 ms-lg12">
              {this.state.displayMode === DisplayMode.Grid && (
                <div className={this.props.outerGridClass || ""}>
                  <div className={this.props.fixedGridWidthCss || ""}>
                    <DetailsList
                      items={
                        this.state.showingItems != undefined
                          ? this.state.showingItems
                          : []
                      }
                      setKey={"Id"}
                      columns={this.state.columns}
                      onRenderItemColumn={this._renderItemColumn}
                      onColumnHeaderClick={this._onColumnClick}
                      onItemInvoked={this._onItemInvoked}
                      onShouldVirtualize={() => false}
                      onColumnHeaderContextMenu={
                        this._onColumnHeaderContextMenu
                      }
                      onActiveItemChanged={(item: any, index?: number) => {
                        this.itemSelected(item, index);
                      }}
                      selectionMode={
                        this.props.selectionMode || SelectionMode.none
                      }
                      selection={this._selection}
                    />
                  </div>
                </div>
              )}
              {this.state.displayMode === DisplayMode.Tile &&
                this.state.showingItems != undefined &&
                this.state.showingItems.length > 0 &&
                this.state.showingItems.map((item: any) => {
                  return this.props.tileComponent != undefined ? (
                    this.props.tileComponent(item)
                  ) : (
                    <></>
                  );
                })}
            </div>
          </div>
        )}
        {(this.state.showingItems == undefined ||
          this.state.showingItems.length == 0) && (
          <div className="ms-Grid-row">
            <div
              className={`ms-Grid-col ms-sm12 ms-md12 ms-lg12 centralizeContent  ${
                this.props.noRecordsMessageCssClass || ""
              }`}
            >
              {this.props.noRecordsMessage || "No records found"}
            </div>
          </div>
        )}
        <div className="ms-Grid-row">
          <div
            className={`ms-Grid-col ms-sm12 ms-md12 ms-lg12 centralizeContent loadMoreGrid  ${
              this.props.noRecordsMessageCssClass || ""
            }`}
          >
            {this.state.processing !== true && this.state.hasNextPage && (
              <ActionButton
                iconProps={{ iconName: "DoubleChevronDown" }}
                onClick={() => {
                  this.setState({ processing: true }, async () => {
                    await this.loadMore();
                    this.setState({ processing: false });
                  });
                }}
              >
                Try to get more older records
              </ActionButton>
            )}
            {this.state.processing && <Spinner label="Please wait..." />}
          </div>
        </div>
      </div>
    );
  }

  private itemSelected = (item: any, index?: number) => {};

  private _buildColumns = (items: any[]) => {
    var columns: IColumn[] = [];
    if (items != null && items.length > 0) {
      columns = buildColumns(items);
    } else if (
      this.state.items != null &&
      this.state.items != undefined &&
      this.state.items.length > 0
    ) {
      columns = buildColumns(this.state.items);
    }
    var idIndex = 0;
    columns.map((raw, i) => {
      raw.name = raw.name.split("_").join(" ");
      raw.name = raw.name.replace(/\b\w/g, (l) => {
        return l.toUpperCase();
      });
      if (raw.name == "Id") {
        idIndex = i;
      }
    });
    // columns = columns.filter(matchedColumns => matchedColumns.name != "Id" && matchedColumns.name !="ID");
    if (
      this.props.modifyColumns != undefined &&
      this.props.modifyColumns != null
    ) {
      columns = this.props.modifyColumns(columns);
    }
    return columns;
  };

  private _renderItemColumn = (
    item?: any,
    index?: number,
    column?: IColumn
  ) => {
    if (
      this.state.showingRawData != null &&
      this.state.showingRawData != undefined &&
      this.state.showingRawData.length > 0
    ) {
      if (column != undefined && column.fieldName != undefined) {
        const fieldContent = item[column.fieldName];
        if (
          this.props.renderCustomColumns != undefined &&
          this.props.renderCustomColumns != undefined
        ) {
          return this.props.renderCustomColumns(
            fieldContent,
            column.key,
            this.state.showingRawData[index || 0]
          );
        } else {
          return <span>{fieldContent}</span>;
        }
      }
    }
  };

  private _onColumnClick = (
    event?: React.MouseEvent<HTMLElement>,
    column?: IColumn
  ): void => {
    const { columns } = this.state;
    let { items } = this.state;

    if (column != undefined && column.fieldName != undefined) {
      let isSortedDescending = column.isSortedDescending;

      // If we've sorted this column, flip it.
      if (column.isSorted) {
        isSortedDescending = !isSortedDescending;
      }

      // Sort the items.
      items = items!.concat([]).sort((a, b) => {
        const firstValue =
          column.fieldName != undefined ? a[column.fieldName] : undefined;
        const secondValue =
          column.fieldName != undefined ? b[column.fieldName] : undefined;

        if (isSortedDescending) {
          return firstValue > secondValue ? -1 : 1;
        } else {
          return firstValue > secondValue ? 1 : -1;
        }
      });

      // Reset the items and columns to match the state.
      this.setState({
        items: items,
        columns: columns!.map((col) => {
          col.isSorted = col.key === column.key;

          if (col.isSorted) {
            col.isSortedDescending = isSortedDescending;
          }

          return col;
        }),
      });
    }
  };

  private _onColumnHeaderContextMenu(
    column: IColumn | undefined,
    ev: React.MouseEvent<HTMLElement> | undefined
  ): void {
    // console.log(`column ${column!.key} contextmenu opened.`);
  }

  private _onItemInvoked = (item: any, index: number | undefined): void => {
    if (
      this.props.onItemInvoked != undefined &&
      this.props.onItemInvoked != null
    ) {
      this.props.onItemInvoked(item, index);
    }
  };
}
