<template>
  <button
    v-if="visible"
    type="button"
    class="vm-crud-toolbar__btn is-ghost"
    :disabled="disabled || submitting"
    @click="onExport"
  >
    <i class="ri-download-2-line" />
    {{ buttonText }}
  </button>
</template>

<script setup lang="ts">
import { computed, inject, ref } from 'vue'
import { toast } from 'vue-sonner'
import {
  exportCrudExcel,
  flattenExportRows,
  pickExportColumns,
} from '../lib/export-excel'
import {
  injectTableOptions,
  TABLE_API_KEY,
  type TableApi,
} from './key'
import { fetchExportRowsByPage } from './export-fetch'
import { useCrud } from './useCrud'
import { crudFormat, crudLabel } from './style'
import type {
  CrudColumn,
  CrudPermission,
} from '../../../typings/admin/comm/crud'

defineOptions({ name: 'vm-export-btn' })

const props = withDefaults(
  defineProps<{
    /** 列配置；默认取 Table.getColumns() 或 useTable columns */
    columns?: CrudColumn[]
    /** 自定义数据源；默认按当前筛选 POST page 自动翻页拉全量 */
    data?:
      | Record<string, unknown>[]
      | ((params: Record<string, unknown>) => Promise<Record<string, unknown>[]>)
    filename?: string | (() => string)
    maxExportLimit?: number
    disabled?: boolean
    text?: string
    /** 权限键，默认 page */
    permission?: keyof CrudPermission
  }>(),
  {
    filename: undefined,
    maxExportLimit: 5000,
    disabled: false,
    permission: 'page',
  },
)

const crud = useCrud()
const tableOpts = injectTableOptions()
const tableApi = inject(TABLE_API_KEY, null as TableApi | null)
const submitting = ref(false)

/** 导出长请求：不走默认 5s 超时 */
const EXPORT_REQUEST_OPTS = { timeoutMs: 0 } as const

const visible = computed(() => Boolean(crud.getPermission(props.permission)))

const buttonText = computed(
  () => props.text || crudLabel('export', crud.dict.label),
)

const exportColumns = computed(() => {
  const raw =
    props.columns?.length
      ? props.columns
      : tableApi?.getColumns() || tableOpts?.columns || []
  return pickExportColumns(raw)
})

function resolveFilename() {
  const raw = props.filename
  const name = typeof raw === 'function' ? raw() : raw
  return name || crudLabel('export')
}

async function fetchRowsByPage(): Promise<Record<string, unknown>[]> {
  const svc = crud.service
  if (!svc.page) {
    return flattenExportRows(crud.list.value, tableOpts?.tree)
  }

  const pageKey = crud.dict.pagination.page
  const sizeKey = crud.dict.pagination.size

  return fetchExportRowsByPage({
    buildQuery: (extra) => crud.buildListQuery(extra),
    pageKey,
    sizeKey,
    page: (body, opts) => crud.requestPage(body, opts),
    maxExportLimit: props.maxExportLimit,
    totalHint: crud.pagination.value.total,
    requestOpts: EXPORT_REQUEST_OPTS,
  })
}

async function fetchRows(): Promise<Record<string, unknown>[]> {
  if (props.data) {
    if (typeof props.data === 'function') {
      return props.data(crud.buildExportQuery())
    }
    return props.data
  }

  return fetchRowsByPage()
}

async function onExport() {
  if (submitting.value || props.disabled) return
  if (!exportColumns.value.length) {
    toast.error(crudLabel('exportNoCols'))
    return
  }

  submitting.value = true
  try {
    const rows = await fetchRows()
    if (!rows.length) {
      toast.warning(crudLabel('exportNoData'))
      return
    }
    const flat = flattenExportRows(rows, tableOpts?.tree)
    exportCrudExcel(exportColumns.value, flat, resolveFilename())
    toast.success(crudFormat(crudLabel('exportSuccess'), flat.length))
  } catch (e) {
    toast.error(e instanceof Error ? e.message : crudLabel('exportFail'))
  } finally {
    submitting.value = false
  }
}
</script>

<style lang="scss" scoped>
.vm-crud-toolbar__btn {
  display: inline-flex;
  height: 36px;
  align-items: center;
  gap: 4px;
  padding: 0 14px;
  border: none;
  border-radius: 12px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, opacity 0.15s ease;

  &:disabled {
    cursor: not-allowed;
    opacity: 0.45;
  }

  &.is-ghost {
    background: var(--muted);
    color: var(--foreground);

    &:hover:not(:disabled) {
      background: color-mix(in srgb, var(--muted) 70%, var(--foreground) 8%);
    }
  }
}
</style>
