import { UITypes } from 'nocodb-sdk'
import type { ColumnType, TableType } from 'nocodb-sdk'
import type { Ref } from 'vue'
import { message } from 'ant-design-vue'
import dayjs from 'dayjs'
import { useI18n } from 'vue-i18n'
import {
  NOCO,
  extractPkFromRow,
  extractSdkResponseErrorMsg,
  getHTMLEncodedText,
  useApi,
  useInjectionState,
  useNuxtApp,
  useProject,
  useProvideSmartsheetRowStore,
} from '#imports'
import type { Row } from '~/composables'

const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((meta: Ref<TableType>, row: Ref<Row>) => {
  const { $e, $state, $api } = useNuxtApp()
  const { api, isLoading: isCommentsLoading, error: commentsError } = useApi()
  const { t } = useI18n()

  const commentsOnly = ref(false)
  const commentsAndLogs = ref<any[]>([])
  const comment = ref('')
  const commentsDrawer = ref(false)
  const changedColumns = ref(new Set<string>())
  const { project } = useProject()
  const rowStore = useProvideSmartsheetRowStore(meta, row)
  // todo
  // const activeView = inject(ActiveViewInj)

  // const { updateOrSaveRow, insertRow } = useViewData(meta, activeView as any)

  // getters
  const primaryValue = computed(() => {
    if (row?.value?.row) {
      const col = meta?.value?.columns?.find((c) => c.pv)
      if (!col) {
        return
      }
      const value = row.value.row?.[col.title as string]
      const uidt = col.uidt
      if (uidt === UITypes.Date) {
        return (/^\d+$/.test(value) ? dayjs(+value) : dayjs(value)).format('YYYY-MM-DD')
      } else if (uidt === UITypes.DateTime) {
        return (/^\d+$/.test(value) ? dayjs(+value) : dayjs(value)).format('YYYY-MM-DD HH:mm')
      } else if (uidt === UITypes.Time) {
        let dateTime = dayjs(value)
        if (!dateTime.isValid()) {
          dateTime = dayjs(value, 'HH:mm:ss')
        }
        if (!dateTime.isValid()) {
          dateTime = dayjs(`1999-01-01 ${value}`)
        }
        if (!dateTime.isValid()) {
          return value
        }
        return dateTime.format('HH:mm:ss')
      }
      return value
    }
  })

  // actions
  const loadCommentsAndLogs = async () => {
    if (!row.value) return
    const rowId = extractPkFromRow(row.value.row, meta.value.columns as ColumnType[])
    if (!rowId) return
    commentsAndLogs.value =
      (
        await api.utils.commentList({
          row_id: rowId,
          fk_model_id: meta.value.id as string,
          comments_only: commentsOnly.value,
        })
      )?.reverse?.() || []
  }

  const isYou = (email: string) => {
    return $state.user?.value?.email === email
  }

  const saveComment = async () => {
    try {
      if (!row.value || !comment.value) return
      const rowId = extractPkFromRow(row.value.row, meta.value.columns as ColumnType[])
      if (!rowId) return

      await api.utils.commentRow({
        fk_model_id: meta.value?.id as string,
        row_id: rowId,
        // todo: description missing from argument type
        description: comment.value,
      } as any)

      comment.value = ''

      await loadCommentsAndLogs()
    } catch (e: any) {
      message.error(e.message)
    }

    $e('a:row-expand:comment')
  }

  const save = async () => {
    let data
    try {
      // todo:
      // if (this.presetValues) {
      //   // cater presetValues
      //   for (const k in this.presetValues) {
      //     this.$set(this.changedColumns, k, true);
      //   }
      // }

      const updateOrInsertObj = [...changedColumns.value].reduce((obj, col) => {
        obj[col] = row.value.row[col]
        return obj
      }, {} as Record<string, any>)

      if (row.value.rowMeta?.new) {
        data = await $api.dbTableRow.create('noco', project.value.title as string, meta.value.title, updateOrInsertObj)

        Object.assign(row.value, {
          row: data,
          rowMeta: {},
          oldRow: { ...data },
        })
      } else if (Object.keys(updateOrInsertObj).length) {
        const id = extractPkFromRow(row.value.row, meta.value.columns as ColumnType[])

        if (!id) {
          return message.info("Update not allowed for table which doesn't have primary Key")
        }
        await $api.dbTableRow.update(NOCO, project.value.title as string, meta.value.title, id, updateOrInsertObj)
        for (const key of Object.keys(updateOrInsertObj)) {
          // audit
          $api.utils
            .auditRowUpdate(id, {
              fk_model_id: meta.value.id,
              column_name: key,
              row_id: id,
              value: getHTMLEncodedText(updateOrInsertObj[key]),
              prev_value: getHTMLEncodedText(row.value.oldRow[key]),
            })
            .then(async () => {
              /** load latest comments/audit if right drawer is open */
              if (commentsDrawer.value) {
                await loadCommentsAndLogs()
              }
            })
        }
      } else {
        // No columns to update
        return message.info(t('msg.info.noColumnsToUpdate'))
      }

      // this.$emit('update:oldRow', { ...this.localState });
      // this.changedColumns = {};
      // this.$emit('input', this.localState);
      // this.$emit('update:isNew', false);

      message.success(`${primaryValue.value || 'Row'} updated successfully.`)

      changedColumns.value = new Set()
    } catch (e: any) {
      message.error(`${t('msg.error.rowUpdateFailed')}: ${await extractSdkResponseErrorMsg(e)}`)
    }
    $e('a:row-expand:add')
    return data
  }

  return {
    ...rowStore,
    commentsOnly,
    loadCommentsAndLogs,
    commentsAndLogs,
    isCommentsLoading,
    commentsError,
    saveComment,
    comment,
    isYou,
    commentsDrawer,
    row,
    primaryValue,
    save,
    changedColumns,
  }
}, 'expanded-form-store')

export { useProvideExpandedFormStore }

export function useExpandedFormStoreOrThrow() {
  const expandedFormStore = useExpandedFormStore()
  if (expandedFormStore == null) throw new Error('Please call `useExpandedFormStore` on the appropriate parent component')
  return expandedFormStore
}
