<template>
  <div>
    <q-table
      id="table"
      v-model:selected="internalSelected"
      class="w-data-table no-shadow"
      :columns="columnsDetailed"
      :rows="filteredRows"
      :filter="tableFilter"
      :filter-method="filter"
      row-key="id"
      :pagination="controlledPagination"
      :rows-per-page-options="controlledPagination.options"
      :loading="loading"
      :no-data-label="displayLabels.noData"
      :no-results-label="displayLabels.noResults"
      :loading-label="displayLabels.loading"
      :rows-per-page-label="displayLabels.rowsPerPage"
      :visible-columns="displayedColumns"
      :selection="selection"
      @request="onRequest"
      @update:pagination="onUpdatePagination"
    >
      <template #top="props">
        <div class="column col q-gutter-y-sm">
          <div class="row col reverse justify-between q-gutter-y-xs">
            <div class="row col-xs-12 col-md-auto justify-end items-center q-col-gutter-xs">
              <w-btn-dropdown
                v-if="!hideColumnOptions"
                id="columns"
                class="col-auto non-hoverable"
                flat
                no-ripple
                :label="columnGroupOptions.length > 0 ? 'Column groups' : 'Columns'"
                no-caps
              >
                <q-list separator>
                  <q-item class="row no-wrap q-col-gutter-x-xs items-center">
                    <div class="col-auto">
                      Select
                    </div>
                    <div class="col-auto">
                      <w-btn
                        id="selectAllBtn"
                        class="non-hoverable"
                        flat
                        dense
                        no-ripple
                        color="primary"
                        label="All"
                        :disable="selectAllDisabled"
                        @click="selectAll"
                      />
                    </div>
                    <div class="col-auto">
                      /
                    </div>
                    <div class="col-auto">
                      <w-btn
                        id="selectNoneBtn"
                        class="non-hoverable"
                        flat
                        dense
                        no-ripple
                        color="primary"
                        label="None"
                        :disable="selectNoneDisabled"
                        @click="selectNone"
                      />
                    </div>
                  </q-item>
                  <q-item>
                    <q-option-group v-if="columnGroupOptions.length > 0" v-model="columnGroups" type="toggle" :options="groupToggleOptions" />
                    <q-option-group v-else v-model="visibleColumns" type="toggle" :options="columnSingleOptions" />
                  </q-item>
                </q-list>
              </w-btn-dropdown>
              <div v-if="showLimit" class="col-auto">
                <w-select v-model="selectedLimit" class="limit-rows-select" label="Limit rows" :options="limitOptions" emit-value map-options>
                  <q-tooltip anchor="bottom middle" self="top middle">
                    Show limit
                  </q-tooltip>
                </w-select>
              </div>
              <div v-if="showExportToCsv" class="col-auto">
                <w-btn
                  id="exportToCSV"
                  flat
                  round
                  dense
                  icon="fal fa-file-csv"
                  color="grey-6"
                  @click="exportToCsv"
                >
                  <q-tooltip anchor="bottom middle" self="top middle">
                    Export to csv
                  </q-tooltip>
                </w-btn>
              </div>
              <div class="col-auto">
                <w-btn
                  id="maximise-page"
                  flat
                  round
                  dense
                  :icon="props.inFullscreen ? 'fal fa-compress' : 'fal fa-expand'"
                  color="grey-6"
                  @click="props.toggleFullscreen"
                >
                  <q-tooltip anchor="bottom middle" self="top middle">
                    Full screen
                  </q-tooltip>
                </w-btn>
              </div>
              <div class="col-auto">
                <w-btn
                  id="refresh-table"
                  flat
                  round
                  dense
                  :icon="`${limitHasChanged ? 'fa' : 'fal'} fa-sync`"
                  color="grey-6"
                  :class="{ 'animate-bounce': limitHasChanged }"
                  @click="loadData"
                >
                  <q-tooltip anchor="bottom middle" self="top middle">
                    Refresh data
                  </q-tooltip>
                </w-btn>
              </div>
              <div v-if="allowDelete && internalSelected.length" class="col-auto">
                <w-btn
                  flat
                  round
                  dense
                  icon="fal fa-trash"
                  color="faded"
                  :class="`animate-scale ${deleteButtonClass}`"
                  @click="deleteRow"
                >
                  <q-tooltip anchor="bottom middle" self="top middle">
                    Delete
                  </q-tooltip>
                </w-btn>
              </div>
              <div v-if="internalSelected.length" class="col-auto">
                <slot name="tableActions" />
              </div>
              <div v-if="allowAddBulk" class="col-auto">
                <w-btn id="add-record-bulk" round dense icon="fal fa-upload" color="orange-9" :class="bulkAddButtonClass " @click="$emit('addRowBulk')">
                  <q-tooltip anchor="bottom middle" self="top middle">
                    Bulk upload
                  </q-tooltip>
                </w-btn>
              </div>
              <div v-if="allowAdd" class="col-auto">
                <w-btn id="add-record" round dense icon="fal fa-plus" color="orange-9" :class="addButtonClass" @click="$emit('addRow')">
                  <q-tooltip anchor="bottom middle" self="top middle">
                    Add new
                  </q-tooltip>
                </w-btn>
              </div>
            </div>
            <div class="gt-xs q-pa-md" />
            <div v-if="search" class="col-xs-12 col-md">
              <w-search-input id="search" v-model="tableFilter" inverted-light />
            </div>
          </div>
          <w-collapsible v-show="filterColumns && advancedFiltering" class="col" label="Filters" icon="fal fa-search" expand-icon="fal fa-search">
            <table-filter :columns="filterColumns" :exclude-column-keys="excludeFilterColumnKeys" :initial-filters="initialFilters" :location-search="locationSearch" @filters-updated="updateFilters" @search="loadData" />
          </w-collapsible>
        </div>
      </template>
      <template #body="props">
        <q-tr class="cursor-pointer" @click="$emit('rowclick', props.row)">
          <q-td v-if="selection === 'multiple'" class="auto-width">
            <q-checkbox v-model="props.selected" color="primary" />
          </q-td>
          <q-td v-for="col in props.cols" :key="col.name" :props="props">
            <slot :name="'cell-' + col.name" :row="props.row">
              {{ col.value }}
            </slot>
          </q-td>
        </q-tr>
      </template>
      <template v-if="!!$slots['no-data']" #no-data="props">
        <slot name="no-data" v-bind="props" />
      </template>
    </q-table>
    <w-dialog v-model="showDeleteDialog" title="Confirm">
      <span>Are you sure you want to delete {{ internalSelected.length }} {{ displayLabels.name }}(s)?</span>
      <template #actions>
        <w-btn v-close-popup label="Cancel" color="primary" flat />
        <w-btn label="Delete" color="negative" @click="$emit('deleteRow', internalSelected); internalSelected =[]; showDeleteDialog=false" />
      </template>
    </w-dialog>
  </div>
</template>

<script>
const standardFilter = (rows, terms, cols, cellValue) => {
  const filteringTerms = terms ? terms.toLowerCase().split(" ") : [];
  const hasMatched = (row, filter) => cols.some(col => (cellValue(col, row) + "").toLowerCase().includes(filter));
  return rows.filter(row => filteringTerms.some(filter => hasMatched(row, filter)));
};

import TableFilter from "./TableFilter";

export default {
  name: "WDataTable",
  components: {
    TableFilter,
  },
  props: {
    loading: {
      type: Boolean,
      default: false,
    },
    hideColumnOptions: {
      type: Boolean,
      default: false,
    },
    locationSearch: {
      type: Function,
      default: () => [],
    },
    excludeFilterColumnKeys: {
      type: Array,
      default: () => null,
    },
    filterColumns: {
      type: Array,
      default: () => [],
    },
    initialVisibleColumns: {
      type: Array,
      default: () => [],
    },
    columnGroupOptions: {
      type: Array,
      default: () => [],
    },
    search: {
      type: Boolean,
      default: false,
    },
    allowAdd: {
      type: Boolean,
      default: false,
    },
    addButtonClass: {
      type: String,
      default: "",
    },
    allowAddBulk: {
      type: Boolean,
      default: false,
    },
    bulkAddButtonClass: {
      type: String,
      default: "",
    },
    allowDelete: {
      type: Boolean,
      default: false,
    },
    deleteButtonClass: {
      type: String,
      default: "",
    },
    showExportToCsv: {
      type: Boolean,
      default: false,
    },
    selection: {
      type: String,
      default: "none",
    },
    columns: {
      type: Array,
      required: true,
    },
    rows: {
      type: Array,
      default: () => [],
    },
    dense: {
      type: Boolean,
      default: false,
    },
    pagination: {
      type: Object,
      default: null
    },
    filter: {
      type: Function,
      default: standardFilter,
    },
    labels: {
      type: Object,
      default: () => ({}),
    },
    advancedFiltering: {
      type: Boolean,
      default: false,
    },
    showLimit: {
      type: Boolean,
      default: false,
    },
    limit: {
      type: Number,
      default: -1,
    },
    getUrl: {
      type: Function,
      default: null,
    },
    initialFilters: {
      type: Array,
      default: () => null,
    },
    selected: {
      type: Array,
      default: () => [],
    },
    keepSelectedAfterSearch: {
      type: Boolean,
      default: false,
    },
  },
  emits: [
    "update:selected",
    "rowclick",
    "deleteRow",
    "addRow",
    "addRowBulk",
    "request",
    "search",
    "update:pagination",
  ],
  data() {
    return {
      tableFilter: "",
      defaultLabels: {
        noData: "No data available",
        noResults: "No matching records found",
        loading: "Loading...",
        rowsPerPage: "Rows displayed:",
        searchDefault: "Search",
        title: "",
        name: "",
      },
      displayLabels: {},
      showColumnToggles: false,
      columnGroups: [],
      columnSingleOptions: [],
      groupToggleOptions: [],
      fixedColumns: [],
      optionalColumns: [],
      visibleColumns: [],
      internalSelected: [],
      selectedLimit: this.limit,
      limitOptions: [
        {
          label: "50",
          value: 50,
        },
        {
          label: "200",
          value: 200,
        },
        {
          label: "500",
          value: 500,
        },
        {
          label: "1,000",
          value: 1000,
        },
        {
          label: "Don't limit",
          value: -1,
        },
      ],
      currentFilter: null,
      showDeleteDialog: false,
      limitHasChanged: false,
      defaultPagination: {
        rowsPerPage: 10,
        page: 1,
        options: [5, 10, 20, 50],
        sortBy: null,
        descending: false,
      },
      controlledPagination: null,
    };
  },
  computed: {
    selectAllDisabled() {
      return this.groupToggleOptions.length
        ? this.columnGroups.length === this.groupToggleOptions.length
        : this.visibleColumns.length === this.columnSingleOptions.length;
    },
    selectNoneDisabled() {
      return !this.visibleColumns.length;
    },
    getColumnGroups() {
      return this.columnGroups;
    },
    columnsDetailed() {
      return this.columns.map(x => {
        x.name = x.name || x.field;
        x.align = x.align || "left";
        return x;
      });
    },
    displayedColumns() {
      if (this.columnGroupOptions.length > 0) {
        return this.initialVisibleColumns.concat(...this.visibleColumns);
      } else {
        return this.fixedColumns.concat(...this.visibleColumns);
      }
    },
    filterableColumns() {
      return this.columns.filter(column => column.field);
    },
    filteredRows() {
      if (!this.tableFilter) {
        return this.rows;
      } else {
        const getCellValue = (col, row) => {
          const val = typeof col.field === "function" ? col.field(row) : row[col.field];
          return col.format ? col.format(val, row) : val;
        };
        return this.filter(this.rows, this.tableFilter, this.filterableColumns, getCellValue);
      }
    },
  },
  watch: {
    columns() {
      this.createDataColumns();
    },
    getColumnGroups(columnLabels) {
      this.visibleColumns = this.columnGroupOptions.filter(x => columnLabels.includes(x.label)).map(x => x.value);
    },
    selectedLimit() {
      this.limitHasChanged = true;
    },
    internalSelected() {
      this.$emit("update:selected", this.internalSelected);
    },
    selected() {
      this.internalSelected = this.selected;
    },
    filteredData() {
      this.defaultPagination.rowsNumber = this.filteredRows.length;
    },
    pagination(value) {
      this.controlledPagination = value;
    },
  },
  created() {
    this.controlledPagination = this.pagination ?? this.defaultPagination;
  },
  mounted() {
    for (const label in this.defaultLabels) {
      this.displayLabels[label] = this.labels[label] || this.defaultLabels[label];
    }
    this.createDataColumns();
    this.visibleColumns = this.initialVisibleColumns.filter(val => !this.fixedColumns.includes(val));
  },
  methods: {
    onUpdatePagination(pagination) {
      // frontend sorting triggers onUpdate pagination event
      if (!pagination.rowsNumber && this.pagination) {
        return;
      }

      this.controlledPagination = pagination;
      this.$emit("update:pagination", pagination);
    },
    onRequest(ev) {
      // backend sorting triggers onRequest pagination event
      this.$emit("request", ev);
    },
    selectAll() {
      if (this.columnGroupOptions.length) {
        this.visibleColumns = [].concat(...this.columnGroupOptions.map(x => x.value));
        this.columnGroups = this.columnGroupOptions.map(x => x.label);
      } else {
        this.visibleColumns = [].concat(...this.optionalColumns);
      }
    },
    selectNone() {
      this.columnGroups = [];
      this.visibleColumns = [];
    },
    createDataColumns() {
      this.columnSingleOptions = [];
      this.fixedColumns = [];
      this.optionalColumns = [];
      this.groupToggleOptions = [];

      for (const column of this.columns) {
        if (column.allowHide) {
          this.columnSingleOptions.push({ label: column.label, value: column.name });
          this.optionalColumns.push(column.name);
        } else {
          this.fixedColumns.push(column.name);
        }
      }
      this.columnGroupOptions.forEach(column => {
        this.groupToggleOptions.push({ label: column.label, value: column.label });
      });
    },
    loadData() {
      if (!this.keepSelectedAfterSearch) {
        this.internalSelected = [];
      }
      this.controlledPagination.page = 1;
      this.$emit("search", this.filterColumns && this.advancedFiltering ? this.currentFilter || [] : null, this.selectedLimit);
      this.limitHasChanged = false;
    },
    updateFilters(filters) {
      this.currentFilter = filters;
    },
    gotoLastPage() {
      this.controlledPagination.page = Math.ceil(this.rows.length / this.controlledPagination.rowsPerPage);
    },
    deleteRow() {
      if (this.internalSelected.length === 0) {
        this.$q.notify({
          message: "No row is selected",
          icon: "fal fa-ban",
          type: "negative",
        });
      } else {
        this.showDeleteDialog = true;
      }
    },
    async exportToCsv() {
      if (!this.getUrl) {
        return;
      }
      const url = this.getUrl({
        filters: this.currentFilter,
        export: JSON.stringify(this.columns.filter(x => x.key && this.displayedColumns.includes(x.name)).map(c => ({ label: c.label, value: c.key }))),
      });
      const response = await this.$axios.get(url);

      if (response) {
        const blob = new Blob([response.data], { type: "application/csv" });
        const link = document.createElement("a");
        let filename = "";
        const disposition = response.headers["content-disposition"];
        if (disposition && disposition.indexOf("attachment") !== -1) {
          const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
          const matches = filenameRegex.exec(disposition);
          if (matches != null && matches[1]) {
            filename = matches[1].replace(/['"]/g, "");
          }
        }
        if (window.navigator.msSaveOrOpenBlob) {
          // IE11 & Edge
          window.navigator.msSaveOrOpenBlob(blob, "results.csv");
        } else {
          link.href = window.URL.createObjectURL(blob);
          document.body.appendChild(link);
          link.download = filename;
          link.click();
          link.remove();
        }
      }
    },
  },
};
</script>
