<script lang="ts" setup>
import 'blitzar/dist/style.css'
import { computed, markRaw, onMounted, PropType, reactive, ComputedRef, ref } from 'vue'
import { BlitzTable, BlitzFilters, SchemaField, FormContext, ROW_SELECTION_ID } from 'blitzar'
import { isArray } from 'is-what'
import CButton from '../CButton/CButton.vue'
import CModal from '../CModal/CModal.vue'
import CInput from '../CInput/CInput.vue'
import CPager from '../CPager/CPager.vue'
import CTh from '../CTh/CTh.vue'
import CCheckbox from '../CCheckbox/CCheckbox.vue'
import MDIcon from '../MDIcon/MDIcon.vue'
import { SortState, TableInfo } from './tableInfo'
import { showToast, showToastError } from './toasts'
import { t, lang } from './lang'
import { mergeMaps } from './utils'
import { sortRows } from './sort'

const props = defineProps({
  tableInfo: { type: Object as PropType<TableInfo<any>>, required: true },
  p_Title: { type: String as PropType<string>, default: '' },
  p_TitleAlign: { type: String as PropType<string>, default: 'left' },
  p_Addable: { type: Boolean as PropType<boolean>, default: true },
  p_SearchPosition: {
    type: String as PropType<'top-right' | 'top-left' | 'hide'>,
    default: 'top-right',
  },
  p_EditType: { type: String as PropType<'icon' | 'row-click'>, default: 'icon' },
  p_TableType: { type: String as PropType<'normal' | 'mini'>, default: 'normal' },
  p_ShowSelection: { type: Boolean, default: false },
  P_SortState: {
    type: Array as PropType<SortState>,
    default: () => [{ id: 'dateCreated', direction: 'desc' }],
  },
  p_ExtensibleLang: { type: Object as PropType<Record<string, string>>, default: () => ({}) },
  p_OpenModalOnRowClickMode: { type: Boolean, default: false },
  p_PagerPosition: {
    type: String as PropType<'bottom-left' | 'bottom-center' | 'bottom-right'>,
    default: 'bottom-right',
  },
})

const emit = defineEmits<{
  (e: 'updateCell', payload: { rowId: string; colId: string; value: any }): void
  (e: 'update:searchValue', searchValue: string): void
  (e: 'rowClick', targetRow: Record<string, any>): void
  (e: 'update:currentRowIndices', currentRowIndices: number[]): void
}>()
const cInput = markRaw(CInput)
const cPager = markRaw(CPager)
const mdIcon = markRaw(MDIcon)
const cTh = markRaw(CTh)
const cCheckbox = markRaw(CCheckbox)
const blitzFilters = markRaw(BlitzFilters)

const dbAsArray = computed(() =>
  isArray(props.tableInfo.db) ? props.tableInfo.db : [props.tableInfo.db]
)

const saveState = reactive({
  isBusy: false,
  showCheck: false,
  hasError: false,
  saved() {
    saveState.isBusy = false
    saveState.showCheck = true
    saveState.hasError = false
    showToast(t('all-changes-saved'))
  },
  error(error: any) {
    saveState.isBusy = false
    saveState.showCheck = false
    saveState.hasError = true
    showToastError(error)
  },
})

const editField: SchemaField = {
  mode: 'edit',
  component: mdIcon,
  name: 'more_horiz',
  class: '_detail_button',
  events: {
    click: (_e: any, context: FormContext) => {
      const { formData } = context
      const docId = formData.id
      if (!docId) {
        showToastError('ID not found!')
        return
      }
      /**
       * !デバッグ用console.log
       */
      console.log('docId→', docId)
      formModal.open(formData)
    },
  },
}

const checkboxField: SchemaField = {
  id: ROW_SELECTION_ID,
  component: cCheckbox,
  p_HoverEffect: false,
  p_CheckboxType: 'mini',
}

function onRowClick(rowData: Record<string, any>) {
  if (props.p_OpenModalOnRowClickMode) {
    const docId = rowData.id
    if (!docId) {
      showToastError('ID not found!')
      return
    }
    emit('rowClick', rowData)
    return
  }
  if (props.p_EditType !== 'row-click') return
  const docId = rowData.id
  if (!docId) {
    showToastError('ID not found!')
    return
  }
  /**
   * !デバッグ用console.log
   */
  console.log('docId→', docId)
  formModal.open(rowData)
}

const schemaColumns = computed<SchemaField[]>(() => {
  let schema: SchemaField[] = []

  const columns = props.tableInfo.schemaColumns

  if (!props.tableInfo.componentForm || props.p_EditType === 'row-click') {
    schema = columns
  } else {
    schema = [...columns, editField]
  }
  if (props.p_ShowSelection) return [checkboxField, ...schema]
  return schema
})

const formModal = reactive({
  shown: false,
  kind: 'ui' as 'ui' | 'data',
  data: {} as Record<string, any> | ComputedRef<Record<string, any>>,
  remountCounter: 0,
  open(data?: Record<string, any> | ComputedRef<Record<string, any>>) {
    this.data = data || {}
    this.remountCounter++
    this.shown = true
  },
  close() {
    this.shown = false
  },
})

/** For fetching extra stuff after the records were fetched */
const extraFetchState = reactive({
  status: 'none' as 'none' | 'fetching' | 'fetched' | 'error',
  recordCount: 0,
  ms: 0,
  async fetch(recordMaps?: Map<string, any>[]) {
    const recordMap = recordMaps
      ? mergeMaps(recordMaps)
      : mergeMaps(dbAsArray.value.map((db) => db.data))
    // do the after fetch
    if (props.tableInfo.afterFetch && recordMaps) {
      const startTime = performance.now()
      try {
        this.status = 'fetching'
        this.recordCount = await props.tableInfo.afterFetch(recordMap)
        this.status = 'fetched'
      } catch (error) {
        showToastError(error, t('error-after-fetch'))
        this.status = 'error'
      }
      const endTime = performance.now()
      this.ms = endTime - startTime
    }
  },
})

const fetchState = reactive({
  status: 'none' as 'none' | 'fetching' | 'fetched' | 'error',
  recordCount: 0,
  ms: 0,
  async fetch() {
    let recordMaps: Map<string, any>[] | undefined
    // do the initial fetch
    const startTime = performance.now()
    try {
      this.status = 'fetching'
      recordMaps = await Promise.all(dbAsArray.value.map((db) => db.fetch()))
      this.recordCount = recordMaps.reduce<number>((x, map) => x + map.size, 0)
      this.status = 'fetched'
    } catch (error) {
      showToastError(error, t('error-fetch'))
      this.status = 'error'
    }
    const endTime = performance.now()
    this.ms = endTime - startTime
    extraFetchState.fetch(recordMaps)
  },
})

onMounted(async () => {
  fetchState.fetch()
})

const rows = computed(() => {
  // const dataArr = [...props.tableInfo.db.data.values()]
  const dataMap = mergeMaps(dbAsArray.value.map((db) => db.data))

  const dataArr = sortRows(props.P_SortState, [...dataMap.values()])

  const mapRow = props.tableInfo.mapRow
  if (!mapRow || fetchState.status === 'fetching' || extraFetchState.status === 'fetching') {
    return dataArr
  }
  return dataArr.map((row) => mapRow(row))
})

const filtersField = computed(() => {
  const filterOptions = props.tableInfo.filterOptions
  if (!filterOptions) return undefined

  return { component: blitzFilters, filterOptions }
})

const CAdminTable = ref<any>(null)

const changePager = (newValue: number): void => {
  const adminTableDiv = CAdminTable.value?.$el
  if (!adminTableDiv) return
  const tableEl = adminTableDiv.querySelector('table')
  if (tableEl) tableEl.scrollTo(0, 0)
}

function onUpdateCell(payload: { rowId: string; colId: string; value: any }) {
  emit('updateCell', payload)
}

function onChangeSearvhValue(searchValue: string) {
  emit('update:searchValue', searchValue)
}
</script>

<template>
  <BlitzTable
    ref="CAdminTable"
    v-bind="$attrs"
    :class="[
      'admin-table',
      props.tableInfo.filterOptions === undefined
        ? ` _search-${p_SearchPosition} `
        : ` _search-${p_SearchPosition}-with-filters `,
      `  _tableType-${p_TableType}`,
      `_pager-${p_PagerPosition}`,
    ]"
    :lang="{ ...lang, ...props.p_ExtensibleLang }"
    :rows="rows"
    :schemaColumns="schemaColumns"
    :rowsPerPage="10"
    :filtersField="filtersField"
    :searchField="{ component: cInput, type: 'search', p_Debounce: 500 }"
    :paginationField="{ component: cPager }"
    :thField="{ component: cTh }"
    @update:pageNr="(newValue) => changePager(newValue)"
    @rowClick="(e, rowData) => onRowClick(rowData)"
    @updateCell="({ rowId, colId, value, origin }) => onUpdateCell({ rowId, colId, value })"
    @update:searchValue="(searchValue) => onChangeSearvhValue(searchValue)"
    @update:currentRowIndices="
      (currentRowIndices) => emit('update:currentRowIndices', currentRowIndices)
    "
  >
    <div class="_slot-content">
      <CButton
        v-if="tableInfo.componentForm && p_Addable"
        :disabled="!p_Addable"
        style="width: 180px; padding: 0 2rem"
        :p_Label="t('add new')"
        p_BtnType="primary"
        @click="() => formModal.open()"
      />
      <slot />
    </div>
    <template #noDataFound>
      <tr>
        <td :colspan="schemaColumns.length">
          <div class="_table_noData"><div>情報がありません</div></div>
        </td>
      </tr>
    </template>
  </BlitzTable>
  <CModal v-model="formModal.shown" :ssr="false" :p_Title="p_Title" :p_TitleAlign="p_TitleAlign">
    <component
      :is="tableInfo.componentForm"
      v-if="formModal.kind === 'ui' && tableInfo.componentForm"
      v-bind="tableInfo.componentProps?.(formModal.data) || {}"
      :key="formModal.remountCounter"
      :docId="formModal.data.id"
      :docData="formModal.data"
      class="admin-form"
      @hide="() => (formModal.shown = false)"
    />
  </CModal>
</template>

<style lang="scss">
.admin-table {
  &._search-top-right {
    grid-template-areas:
      'slot       search'
      'content    content'
      'pagination pagination';
    grid-template-columns: 1fr auto;
  }
  &._search-top-left {
    grid-template-areas:
      'search     slot'
      'content    content'
      'pagination pagination';
    grid-template-columns: auto 1fr;
  }
  &._search-hide {
    grid-template-areas:
      'slot     slot'
      'content    content'
      'pagination pagination';
    grid-template-columns: auto 1fr;
  }
  &._search-top-right-with-filters {
    grid-template-areas:
      'slot filters search'
      'content content content'
      'pagination pagination pagination';
    grid-template-columns: auto 1fr auto;
  }
  &._search-top-left-with-filters {
    grid-template-areas:
      'search filters slot'
      'content content content'
      'pagination pagination pagination';
    grid-template-columns: auto 1fr auto;
  }
  &._search-hide-with-filters {
    grid-template-areas:
      '. filters slot'
      'content content content'
      'pagination pagination pagination';
    grid-template-columns: auto 1fr auto;
  }
  &._pager-bottom-left {
    .blitz-table--pagination {
      margin-right: auto;
    }
  }
  &._pager-bottom-center {
    .blitz-table--pagination {
      margin: 0 auto;
    }
  }
  &._pager-bottom-right {
    .blitz-table--pagination {
      margin-left: auto;
    }
  }

  //tableの共通設定
  table .blitz-field__component {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    white-space: pre;
  }
  th {
    position: sticky;
    top: 0;
    background: #ffffff;
    z-index: 10;
  }

  .blitz-table--grid-toggle {
    justify-content: end;
  }
  .blitz-filters__field-label {
    color: lightslategrey;
    font-size: 0.6em;
  }
  .blitz-table--shown-rows-info {
    justify-content: end;
  }
  .blitz-table--grid-card {
    border: thin solid #dfe2e5;
    overflow: hidden;
  }
  .blitz-field__label {
    font-weight: 200;
    color: lightslategrey;
    font-size: 0.6em;
  }
  // .blitz-table--pagination {
  //   margin-left: auto;
  // }
}

// type normalの設定
._tableType-normal {
  table {
    display: block;
    border-collapse: collapse;
    margin: 1rem 0;
    overflow-x: auto;
    height: calc(100vh - 340px);
    min-height: 160px;
    width: 100%;
  }
  tr {
    border-top: none;
    height: 50px;
    &:nth-child(2n) {
      background-color: #efefef;
    }
  }

  th:not(:last-of-type),
  td:not(:last-of-type) {
    text-align: left;
    border: none;
    // min-width: calc(120px - 22px);
    padding: 10px 20px;
    white-space: nowrap;
  }
  th:last-of-type,
  td:last-of-type {
    padding: 10px 20px;
    width: 100%;
  }
  ._sort-arrows {
    margin-left: calc(120px - 22px);
  }
}

._tableType-mini {
  table {
    display: block;
    border-collapse: collapse;
    font-size: 12px;
    color: #707070;
    font-weight: normal;
    height: 270px;
    overflow: auto;
  }
  table th {
    text-align: left;
    font-weight: normal;
    height: 30px;
  }
  td {
    text-align: left;
    padding: 0 10px;
  }
  tr {
    border-bottom: solid 0.5px #707070;
    height: 30px;
    background-color: #ffffff;
    cursor: pointer;
    &:hover {
      background-color: #efefef;
    }
    &:active,
    &:focus {
      background-color: #e3e3e3;
    }
  }
}
._tableType-mini._no-data-found tbody {
  tr {
    border-bottom: none;
    cursor: auto;
    &:hover,
    &:active,
    &:focus {
      background-color: #ffffff;
    }
  }
}

.admin-form {
  padding: 1rem;
}

.admin-table._no-data-found {
  overflow-y: hidden;
}
._table_noData {
  height: 200px;
  display: flex;
  justify-content: center;
  align-items: center;
}

._slot-content {
  display: flex;
}
//詳細ボタン
._detail_button {
  margin-left: auto;
  font-size: 30px;
  width: 30px;
  height: 30px;
  background-color: var(--q-primary);
  border-radius: 50%;
  color: #ffffff;
}
._detail_button div {
  font-size: 30px;
}
._detail_button:hover {
  color: var(--q-primary);
  background-color: var(--q-positive);
  border-radius: 50%;
  opacity: 1;
}
</style>
