<template>
  <div class="vm-crud">
    <slot />
  </div>
</template>

<script setup lang="ts">
import { ref, computed, watch, shallowRef, provide, onMounted, onUnmounted } from 'vue'
import { toast } from 'vue-sonner'
import { deepTree } from '../lib/tree'
import { mergeDict, mergePermission } from './config'
import { invokeCrudPageRequest } from './crud-page-request'
import { vmConfirm } from './confirm'
import { CRUD_KEY, injectCrudOptions, injectTableOptions, injectUpsertOptions } from './key'
import { crudLabel } from './style'
import {
  canUseDataI18n,
  dataPackFromService,
  type DataI18nFieldMeta,
} from './data-i18n'
import { useVome } from '../hooks/useVome'
import { subscribeLocaleChange } from 'vome-core/client'
import type {
  CrudContext,
  CrudPermission,
  CrudService,
  CrudTrashMode,
} from '../../../typings/admin/comm/crud'
defineOptions({ name: 'vm-crud' })

const props = withDefaults(
  defineProps<{
    /** EPS service 对象，如 useCrud({ service: service.base.user }) */
    service?: CrudService
    params?: Record<string, unknown>
    immediate?: boolean
  }>(),
  {
    params: () => ({}),
    immediate: false,
  },
)

const hookOptions = injectCrudOptions()
const tableOpts = injectTableOptions()
const upsertOpts = injectUpsertOptions()
const searchActions = shallowRef<{ search: () => void; reset: () => void } | null>(null)
const hasSearchFilters = ref(false)

function pickService(): CrudService {
  const s = props.service ?? hookOptions?.service
  return (s || {}) as CrudService
}

function permFromService(s: CrudService): Partial<CrudPermission> | undefined {
  if (s._permission && typeof s._permission === 'object') {
    return s._permission as Partial<CrudPermission>
  }
  return undefined
}

const service = computed<CrudService>(() => pickService())

const dict = mergeDict(hookOptions?.dict)
const permission = ref(
  mergePermission(hookOptions?.permission, permFromService(service.value)),
)

const loading = ref(false)
const list = ref<Record<string, unknown>[]>([])
const selection = ref<Record<string, unknown>[]>([])
const pagination = ref({ page: 1, size: 20, total: 0 })
const params = ref<Record<string, unknown>>({ ...props.params })
const trashMode = ref<CrudTrashMode>('default')
const dataI18nLangCode = ref('en-US')
const dataI18nFields = ref<DataI18nFieldMeta[]>([])

const { service: hostService } = useVome()

const upsertVisible = ref(false)
const upsertMode = ref<'add' | 'update' | 'info'>('add')
const upsertForm = ref<Record<string, unknown>>({})

function getPermission(key?: keyof typeof permission.value) {
  if (!key) return { ...permission.value }
  return Boolean(permission.value[key])
}

function getParams() {
  return { ...params.value }
}

function applyListMeta(body: Record<string, unknown>) {
  const ds = tableOpts?.defaultSort
  if (ds?.prop && body[dict.sort.prop] == null) {
    body[dict.sort.prop] = ds.prop
    const o = ds.order
    body[dict.sort.order] =
      o === 'asc' || o === 'ascending' ? 'asc' : 'desc'
  }
  if (trashMode.value === 'onlyTrashed') body.onlyTrashed = true
  if (trashMode.value === 'withTrashed') body.withTrashed = true
  return body
}

/** 与 refresh 同源：筛选 params + 排序 + 列表/回收站视图；extra 可覆盖 page/size */
function buildListQuery(extra: Record<string, unknown> = {}) {
  const pageKey = dict.pagination.page
  const sizeKey = dict.pagination.size
  const body: Record<string, unknown> = {
    ...params.value,
    ...extra,
  }
  if (extra[pageKey] == null) body[pageKey] = pagination.value.page
  if (extra[sizeKey] == null) body[sizeKey] = pagination.value.size
  return applyListMeta(body)
}

/** 导出专用：筛选 params + 排序 + 列表/回收站视图；不含 page、size */
function buildExportQuery(extra: Record<string, unknown> = {}) {
  const pageKey = dict.pagination.page
  const sizeKey = dict.pagination.size
  const body: Record<string, unknown> = {
    ...params.value,
    ...extra,
  }
  delete body[pageKey]
  delete body[sizeKey]
  return applyListMeta(body)
}

async function rawFetchPage(
  req: Record<string, unknown>,
  opts?: { timeoutMs?: number },
) {
  const svc = service.value
  if (svc.page) {
    return await svc.page(req, opts)
  }
  const rows = (await svc.list!(req)) || []
  const pageKey = dict.pagination.page
  const sizeKey = dict.pagination.size
  return {
    list: rows,
    pagination: {
      page: Number(req[pageKey] ?? 1),
      size: Number(req[sizeKey] ?? (rows.length || 20)),
      total: rows.length,
    },
  }
}

/** 与 refresh 同源：含 onRefresh 注入的筛选条件 */
async function requestPage(
  req: Record<string, unknown>,
  opts?: { timeoutMs?: number },
) {
  return invokeCrudPageRequest({
    body: req,
    fetch: rawFetchPage,
    onRefresh: hookOptions?.onRefresh,
    requestOpts: opts,
  })
}

async function refresh(extra: Record<string, unknown> = {}) {
  if (trashMode.value === 'dataI18n') {
    await refreshDataI18nEntries(extra)
    return
  }

  const svc = service.value
  if (!svc.page && !svc.list) {
    toast.error(crudLabel('noPageList'))
    return
  }

  const body = buildListQuery(extra)

  loading.value = true

  const done = () => {
    loading.value = false
  }

  const render = (
    data:
      | Record<string, unknown>[]
      | { list: Record<string, unknown>[]; pagination?: unknown },
    pag?: { page?: number; size?: number; total?: number },
  ) => {
    let rows: Record<string, unknown>[]
    if (Array.isArray(data)) {
      rows = data
      if (pag) {
        pagination.value = {
          page: pag.page ?? pagination.value.page,
          size: pag.size ?? pagination.value.size,
          total: pag.total ?? data.length,
        }
      } else {
        pagination.value = { ...pagination.value, total: data.length }
      }
    } else {
      rows = data.list || []
      const p = (pag || data.pagination || {}) as {
        page?: number
        size?: number
        total?: number
      }
      pagination.value = {
        page: p.page ?? pagination.value.page,
        size: p.size ?? pagination.value.size,
        total: p.total ?? 0,
      }
    }

    // toTree：扁平 parentId → 树
    if (tableOpts?.tree) {
      const nested = rows.some((r) => Array.isArray(r.children) && r.children.length)
      list.value = nested ? rows : (deepTree(rows as never) as Record<string, unknown>[])
    } else {
      list.value = rows
    }

    selection.value = []
    done()
  }

  const next = async (req: Record<string, unknown>) => {
    try {
      const data = await rawFetchPage(req)
      render(data)
      return data
    } catch (e) {
      console.error(e)
      done()
      throw e
    }
  }

  try {
    if (hookOptions?.onRefresh) {
      await hookOptions.onRefresh(body, { next, done, render })
    } else {
      await next(body)
    }
  } catch {
    done()
  }
}

async function refreshDataI18nEntries(extra: Record<string, unknown> = {}) {
  const meta = canUseDataI18n(service.value, hostService)
  if (!meta.ok) {
    toast.error(crudLabel('noDataI18n'))
    trashMode.value = 'default'
    list.value = []
    dataI18nFields.value = []
    return
  }
  const dataPack = dataPackFromService(hostService)
  if (!dataPack?.entries) {
    toast.error(
      crudLabel('noDataPackEntries'),
    )
    list.value = []
    dataI18nFields.value = []
    pagination.value = { ...pagination.value, total: 0 }
    return
  }
  const lang = String(dataI18nLangCode.value || '').trim()
  if (!lang) {
    toast.error(crudLabel('pickLang'))
    list.value = []
    pagination.value = { ...pagination.value, total: 0 }
    return
  }

  loading.value = true
  try {
    const res = (await dataPack.entries({
      tableName: meta.tableName,
      langCode: lang,
    })) as {
      fields?: DataI18nFieldMeta[]
      list?: Array<{
        id: string
        values: Record<string, string>
        sources: Record<string, string>
      }>
    }
    const fields = Array.isArray(res?.fields) ? res.fields : []
    dataI18nFields.value = fields.length
      ? fields
      : (meta.entity?.dataI18nFields || []).map((name) => ({
          name,
          label: name,
        }))
    const all = (res?.list || []).map((r) => {
      const sources = r.sources || {}
      const srcFlat: Record<string, string> = {}
      for (const [k, v] of Object.entries(sources)) {
        srcFlat[`__src_${k}`] = String(v ?? '')
      }
      return {
        id: String(r.id),
        __rowKey: String(r.id),
        __sources: sources,
        ...srcFlat,
        ...(r.values || {}),
      }
    })
    const pageKey = dict.pagination.page
    const sizeKey = dict.pagination.size
    const page = Number(extra[pageKey] ?? pagination.value.page) || 1
    const size = Number(extra[sizeKey] ?? pagination.value.size) || 20
    const start = (page - 1) * size
    list.value = all.slice(start, start + size)
    pagination.value = {
      page,
      size,
      total: all.length,
    }
    selection.value = []
  } catch (e) {
    console.error(e)
    toast.error(e instanceof Error ? e.message : crudLabel('loadI18nFail'))
    list.value = []
    dataI18nFields.value = []
    pagination.value = { ...pagination.value, total: 0 }
  } finally {
    loading.value = false
  }
}

function setParams(p: Record<string, unknown>, doRefresh = true) {
  params.value = { ...params.value, ...p }
  if (doRefresh) {
    pagination.value = { ...pagination.value, page: 1 }
    void refresh()
  }
}

function resetParams(doRefresh = true) {
  params.value = { ...props.params }
  pagination.value = { ...pagination.value, page: 1 }
  if (doRefresh) void refresh()
}

function setSelection(rows: Record<string, unknown>[]) {
  selection.value = rows
}

/** 新增时收集 useUpsert items 的 value 作为底稿 */
function collectUpsertItemDefaults() {
  const out: Record<string, unknown> = {}
  for (const it of upsertOpts?.items || []) {
    if (it.value === undefined) continue
    out[it.prop] = Array.isArray(it.value) ? [...it.value] : it.value
  }
  return out
}

function rowAdd(defaults: Record<string, unknown> = {}) {
  if (!permission.value.add) return
  upsertMode.value = 'add'
  const merged = { ...collectUpsertItemDefaults(), ...defaults }
  upsertForm.value = merged
  upsertVisible.value = true
}

function rowAppend(data: Record<string, unknown> = {}) {
  rowAdd(data)
}

async function loadRowForm(row: Record<string, unknown>, mode: 'update' | 'info') {
  upsertMode.value = mode
  const svc = service.value

  const next = async (data?: Record<string, unknown>) => {
    if (svc.info && row[dict.primaryId] != null) {
      return await svc.info({
        id: row[dict.primaryId] as string | number,
        ...data,
      })
    }
    return { ...row, ...data }
  }

  const done = (data: Record<string, unknown>) => {
    upsertForm.value = data
    upsertVisible.value = true
  }

  const close = () => upsertClose()

  // 自定义拉详情
  if (upsertOpts?.onInfo) {
    await upsertOpts.onInfo({ ...row }, { next, done, close })
    return
  }

  // sync：直接用行数据打开，不调 info
  if (upsertOpts?.sync) {
    done({ ...row })
    return
  }

  try {
    done(await next())
  } catch {
    done({ ...row })
  }
}

async function rowEdit(row: Record<string, unknown>) {
  if (!permission.value.update) return
  await loadRowForm(row, 'update')
}

async function rowInfo(row: Record<string, unknown>) {
  if (!permission.value.info) return
  await loadRowForm(row, 'info')
}

function rowClose() {
  upsertClose()
}

/** 打开 upsert（不校验 add 权限；供转移部门等自定义场景） */
function openUpsert(
  form: Record<string, unknown> = {},
  mode: 'add' | 'update' | 'info' = 'add',
) {
  upsertMode.value = mode
  upsertForm.value =
    mode === 'add' ? { ...collectUpsertItemDefaults(), ...form } : { ...form }
  upsertVisible.value = true
}

async function doDelete(targets: Record<string, unknown>[], force = false) {
  const svc = service.value
  if (!svc.delete) {
    toast.error(crudLabel('noDeleteApi'))
    return
  }
  const ids = targets
    .map((r) => r[dict.primaryId] as string | number)
    .filter((id) => id != null)
  await svc.delete({ ids, force })
  toast.success(
    force ? crudLabel('forceDeleted', dict.label) : crudLabel('deleteSuccess', dict.label),
  )
  await refresh()
}

async function rowDelete(rows?: Record<string, unknown>[], force = false) {
  // 与后端一致：无 restore → 默认硬删；回收站内 / 显式 force 亦硬删
  const hard =
    force || trashMode.value === 'onlyTrashed' || !service.value.restore
  // 删除一律要 delete 权限（含无 restore 的硬删、回收站彻底删）
  if (!permission.value.delete) return
  const targets = rows?.length ? rows : selection.value
  if (!targets.length) {
    toast.warning(crudLabel('pleaseSelectData'))
    return
  }

  const next = async (payload?: Record<string, unknown>) => {
    if (payload?.ids) {
      const svc = service.value
      if (!svc.delete) throw new Error(crudLabel('noDeleteApi'))
      await svc.delete({
        ...(payload as { ids: Array<string | number> }),
        force: hard,
      })
      toast.success(
        hard ? crudLabel('forceDeleted', dict.label) : crudLabel('deleteSuccess', dict.label),
      )
      await refresh()
      return
    }
    await doDelete(targets, hard)
  }

  if (hookOptions?.onDelete && !hard) {
    await hookOptions.onDelete(targets, { next })
    return
  }

  const tip = hard
    ? crudLabel('forceDeleteConfirm', dict.label)
    : crudLabel('deleteConfirm', dict.label)
  const ok = await vmConfirm({
    title: hard ? crudLabel('forceDelete', dict.label) : crudLabel('tips', dict.label),
    message: tip,
    confirmText: crudLabel('confirm', dict.label),
    cancelText: crudLabel('close', dict.label),
  })
  if (!ok) return
  await next()
}

async function rowRestore(rows?: Record<string, unknown>[]) {
  if (!permission.value.restore) return
  const targets = rows?.length ? rows : selection.value
  if (!targets.length) {
    toast.warning(crudLabel('pleaseSelectData'))
    return
  }
  const svc = service.value
  if (!svc.restore) {
    toast.error(crudLabel('noRestoreApi'))
    return
  }
  if (!(await vmConfirm({ title: crudLabel('restoreConfirmTitle'), message: crudLabel('restoreConfirmMsg'), confirmText: crudLabel('confirm'), cancelText: crudLabel('close') }))) return
  const ids = targets
    .map((r) => r[dict.primaryId] as string | number)
    .filter((id) => id != null)
  await svc.restore({ ids })
  toast.success(crudLabel('restored'))
  await refresh()
}

async function upsertSubmit() {
  if (upsertMode.value === 'info') {
    upsertClose()
    return
  }
  // 系统时间字段由 BaseService.request 统一剥离，后端 Repository 再兜底
  const data = { ...upsertForm.value }
  const svc = service.value
  try {
    if (upsertMode.value === 'add') {
      if (!svc.add) throw new Error(crudLabel('noAddApi'))
      await svc.add(data)
      toast.success(crudLabel('saveSuccess', dict.label))
    } else {
      if (!svc.update) throw new Error(crudLabel('noUpdateConfig'))
      await svc.update(data)
      toast.success(crudLabel('saveSuccess', dict.label))
    }
    const after = async () => {
      upsertVisible.value = false
      await refresh()
    }
    if (upsertOpts?.onClose) {
      upsertOpts.onClose('submit', () => {
        void after()
      })
    } else {
      await after()
    }
  } catch (e) {
    console.error(e)
    const err = e as Error & { toasted?: boolean }
    const msg =
      e instanceof Error && e.message ? e.message : crudLabel('saveFailed')
    // request 已 toast 过则不再重复；本地校验/配置错误仍要提示
    if (!err.toasted) toast.error(msg.slice(0, 200))
  }
}

function upsertClose() {
  const finish = () => {
    upsertVisible.value = false
  }
  if (upsertOpts?.onClose) {
    upsertOpts.onClose('close', finish)
    return
  }
  finish()
}

/** 供页面可靠改表单（避免 template ref 解包 upsertForm 后误写） */
function patchUpsertForm(patch: Record<string, unknown>) {
  upsertForm.value = { ...upsertForm.value, ...patch }
}

function getUpsertForm() {
  return upsertForm.value
}

const ctx: CrudContext = {
  service: service.value,
  loading,
  list,
  selection,
  pagination,
  params,
  trashMode,
  dataI18nLangCode,
  dataI18nFields,
  permission,
  dict,
  refresh,
  setParams,
  resetParams,
  setSelection,
  getPermission: getPermission as CrudContext['getPermission'],
  getParams,
  buildListQuery,
  buildExportQuery,
  requestPage,
  rowAdd,
  rowAppend,
  rowEdit,
  rowInfo,
  rowClose,
  openUpsert,
  rowDelete,
  rowRestore,
  upsertVisible,
  upsertMode,
  upsertForm,
  upsertSubmit,
  upsertClose,
  patchUpsertForm,
  getUpsertForm,
  searchActions,
  hasSearchFilters,
}

provide(CRUD_KEY, ctx)

watch(
  () => props.params,
  (v) => {
    params.value = { ...v }
  },
)

watch(
  service,
  (s) => {
    ctx.service = s
    permission.value = mergePermission(
      hookOptions?.permission,
      permFromService(s),
    )
  },
  { immediate: true },
)

// 等子组件（如 vm-search）setup 写完初始 params 后再拉，避免空参多打一次
// 切语种始终 refresh（带新 X-Lang / dataI18n）；不依赖 immediate（多数页首拉走 useCrud 回调）
function onLocaleChange() {
  void refresh()
}

let offLocale: (() => void) | undefined

onMounted(() => {
  if (props.immediate) void refresh()
  offLocale = subscribeLocaleChange(() => onLocaleChange())
})

onUnmounted(() => {
  offLocale?.()
  offLocale = undefined
})

defineExpose({
  ...ctx,
  refresh,
  setParams,
  resetParams,
  rowAdd,
  rowAppend,
  rowEdit,
  rowInfo,
  rowClose,
  openUpsert,
  rowDelete,
  rowRestore,
  upsertForm,
  upsertClose,
  patchUpsertForm,
  getUpsertForm,
  selection,
  list,
  pagination,
  trashMode,
  dataI18nLangCode,
  dataI18nFields,
  permission,
  getPermission,
  getParams,
  buildListQuery,
  buildExportQuery,
  requestPage,
})
</script>

<style lang="scss" scoped>
.vm-crud {
  display: flex;
  width: 100%;
  min-width: 0;
  min-height: 0;
  flex: 1;
  flex-direction: column;
  gap: 8px;
  padding: 12px 16px;
  overflow: hidden;
  border-radius: 24px;
  background: var(--card);
  box-shadow: var(--shadow-soft);
}
</style>
