<template>
  <div v-if="items.length" class="vm-crud-search">
    <div class="vm-crud-search__filters">
      <template v-for="(item, idx) in items" :key="`${item.prop}-${idx}`">
        <component
          :is="resolveField(item)"
          v-if="resolveField(item)"
          class="vm-crud-search__control"
          :class="{ 'is-range': isRange(item) }"
          v-bind="fieldProps(item)"
          :model-value="form[item.prop]"
          @update:model-value="(v: unknown) => (form[item.prop] = v)"
        />
        <Select
          v-else-if="item.type === 'select' && !item.multiple"
          :model-value="String(form[item.prop] ?? '')"
          @update:model-value="(v) => (form[item.prop] = coerceSelect(item, v))"
        >
          <SelectTrigger class="vm-crud-search__control">
            <SelectValue :placeholder="itemPlaceholder(item)" />
          </SelectTrigger>
          <SelectContent>
            <SelectItem
              v-for="opt in optionsOf(item)"
              :key="String(opt.value)"
              :value="String(opt.value)"
            >
              {{ opt.label }}
            </SelectItem>
          </SelectContent>
        </Select>
        <Input
          v-else
          class="vm-crud-search__control"
          :model-value="String(form[item.prop] ?? '')"
          :placeholder="itemPlaceholder(item)"
          @update:model-value="(v) => (form[item.prop] = v)"
          @keydown.enter.prevent="search"
        />
      </template>
    </div>
    <div v-if="inlineActions" class="vm-crud-search__actions">
      <button type="button" class="vm-crud-search__btn is-primary" @click="search">
        {{ crudLabel('search', crud.dict.label) }}
      </button>
      <button type="button" class="vm-crud-search__btn is-ghost" @click="reset">
        {{ crudLabel('reset', crud.dict.label) }}
      </button>
      <slot />
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, watch, reactive, onMounted, toValue } from 'vue'
import { onBeforeUnmount } from 'vue'
import { useCrud } from './useCrud'
import { injectSearchOptions } from './key'
import { buildAutoSearchItems, type TablePlugin } from './plugins'
import {
  getCrudStyle,
  crudLocaleRev,
  crudLabel,
  resolveSearchPlaceholder,
  resolveRangePlaceholders,
} from './style'
import { resolveCrudComponent } from './registry'
import { mapDictOptions } from './auto-fields'
import type { CrudSearchItem } from '../../../typings/admin/comm/crud'
defineOptions({ name: 'vm-search' })

const props = withDefaults(
  defineProps<{
    items?: CrudSearchItem[]
    /** 为 true 时在筛选器同行显示搜索/重置（默认由 vm-toolbar 展示） */
    inlineActions?: boolean
  }>(),
  { inlineActions: false },
)

const crud = useCrud()
const searchOpts = injectSearchOptions()
const autoItems = ref<CrudSearchItem[]>([])

const manualItems = computed(
  () => props.items?.length ? props.items : searchOpts?.items?.length ? searchOpts.items : [],
)

/** 自动项在前；手写项同 prop 覆盖自动项，避免多控件共绑 form[prop] */
function mergeSearchItems(auto: CrudSearchItem[], manual: CrudSearchItem[]) {
  const manualProps = new Set(manual.map((i) => i.prop).filter(Boolean))
  const merged = [
    ...auto.filter((i) => i.prop && !manualProps.has(i.prop)),
    ...manual.filter((i) => i.prop),
  ]
  return merged
}

const items = computed(() => {
  void crudLocaleRev.value
  return mergeSearchItems(autoItems.value, manualItems.value)
})

function itemPlaceholder(item: CrudSearchItem) {
  return resolveSearchPlaceholder(item)
}

const form = reactive<Record<string, unknown>>({})

function isRange(item: CrudSearchItem) {
  return item.type === 'number-range' || item.type === 'daterange' || Boolean(item.range)
}

/** 多选：explicit multiple，或组件声明为 vm-multi-select（宿主常漏写 multiple） */
function isMultiValue(item: CrudSearchItem) {
  if (item.multiple) return true
  const name = item.component?.name
  return typeof name === 'string' && /multi-select/i.test(name)
}

function emptyValue(item: CrudSearchItem) {
  if (isRange(item)) return [null, null]
  if (isMultiValue(item)) return []
  return ''
}

/** 浅拷贝初始化值（数组避免共用引用） */
function cloneInit(v: unknown) {
  if (Array.isArray(v)) return [...v]
  return v
}

/** item.value 优先；未设则 emptyValue。重置也走此规则。 */
function initialValue(item: CrudSearchItem) {
  if (item.value !== undefined) {
    const v = cloneInit(item.value)
    if (isMultiValue(item) && !Array.isArray(v)) {
      return v == null || v === '' ? [] : [v]
    }
    if (isRange(item) && !Array.isArray(v)) return [null, null]
    return v
  }
  return emptyValue(item)
}

/** 已有 form 值与控件类型不一致时纠正（避免 '' 绑到 multi-select） */
function coerceFormValue(item: CrudSearchItem, current: unknown) {
  if (isMultiValue(item)) {
    if (Array.isArray(current)) return current
    if (current == null || current === '') return []
    return [current]
  }
  if (isRange(item)) {
    if (Array.isArray(current) && current.length >= 2) return current
    return [null, null]
  }
  return current
}

function coerceSelect(item: CrudSearchItem, v: unknown) {
  const s = v == null ? '' : String(v)
  if (!s) return ''
  const hit = optionsOf(item).find((o) => String(o.value) === s)
  return hit ? hit.value : s
}

/** options 支持 Ref / ComputedRef / getter（与 vm-upsert 一致） */
function resolveOptions(
  options: CrudSearchItem['options'],
): Array<{ label: string; value: string | number | boolean }> {
  const list = options == null ? [] : toValue(options)
  return Array.isArray(list) ? list : []
}

/** 筛选项 options：优先 EPS 本地一级字典，禁止请求 /data */
function optionsOf(item: CrudSearchItem) {
  if (item.dict) return mapDictOptions(item.dict)
  return resolveOptions(item.options)
}

function rebuildAutoItems() {
  const plugins = getCrudStyle().search.plugins as TablePlugin[]
  const auto = plugins?.find((p) => p?.__plugin === 'setAuto')
  if (!auto) {
    autoItems.value = []
    return
  }
  const svc = crud.service as { namespace?: string; search?: Record<string, unknown> }
  autoItems.value = buildAutoSearchItems(svc, {
    hideLabel: auto.hideLabel !== false,
    ignoreFields: auto.ignoreFields as string[] | undefined,
  })
}

onMounted(rebuildAutoItems)

// EPS 热更 / createEps 重建 leaf 后补一次（仅 onMounted 会漏）
watch(
  () => {
    const svc = crud.service as { namespace?: string; search?: unknown }
    return [svc?.namespace, svc?.search, getCrudStyle().search.plugins]
  },
  rebuildAutoItems,
  { deep: true },
)

watch(
  items,
  (list) => {
    for (const it of list) {
      if (!(it.prop in form)) {
        form[it.prop] = initialValue(it)
      } else {
        form[it.prop] = coerceFormValue(it, form[it.prop])
      }
    }
    const active = list.length > 0
    crud.hasSearchFilters.value = active
    crud.searchActions.value = active ? { search, reset } : null
    // 先写入 form + params（有 value 则带条件，无则不带），不在这里 refresh；
    // 等页面 useCrud 回调 / immediate 只拉一次，避免空参无效请求。
    if (active) applyFormToParams(false)
  },
  { immediate: true, deep: true },
)

function resolveField(item: CrudSearchItem) {
  const name = item.component?.name
  if (typeof name === 'string') {
    const comp = resolveCrudComponent(name)
    if (comp) return comp
  } else if (name) {
    return name
  }
  if (item.type === 'select' && item.multiple) {
    return resolveCrudComponent('vm-multi-select')
  }
  if (item.type === 'select' && isMultiValue(item)) {
    return resolveCrudComponent('vm-multi-select')
  }
  if (item.type === 'daterange') {
    return resolveCrudComponent('vm-date-range')
  }
  if (item.type === 'date') {
    return resolveCrudComponent('vm-date-picker')
  }
  if (item.type === 'number-range') {
    return resolveCrudComponent('vm-number-range')
  }
  if (item.type === 'select') return undefined
  return undefined
}

function fieldProps(item: CrudSearchItem) {
  const ph = itemPlaceholder(item)
  const rangePlaceholders =
    item.type === 'number-range' || item.type === 'daterange' || item.phMode === 'range'
      ? resolveRangePlaceholders(item)
      : {}
  return {
    options: optionsOf(item),
    refreshOnChange: false,
    precision: item.range?.rangeType,
    mode:
      item.range?.rangeType === 'float' || item.range?.rangeType === 'int'
        ? item.range.rangeType
        : undefined,
    ...(item.component?.props || {}),
    placeholder: ph,
    ...rangePlaceholders,
  }
}

function buildSearchPayload() {
  const payload: Record<string, unknown> = {}
  for (const it of items.value) {
    const v = form[it.prop]
    if (it.range) {
      const pair = Array.isArray(v) ? v : [null, null]
      if (pair[0] != null && pair[0] !== '') payload[it.range.min] = pair[0]
      if (pair[1] != null && pair[1] !== '') payload[it.range.max] = pair[1]
      continue
    }
    if (it.multiple || isMultiValue(it)) {
      if (Array.isArray(v) && v.length) payload[it.prop] = v
      continue
    }
    if (v !== '' && v != null) payload[it.prop] = v
  }
  return payload
}

/** 用当前 form 覆盖本组筛选项对应 params；doRefresh=false 仅赋值供首屏一次查询 */
function applyFormToParams(doRefresh = true) {
  const payload = buildSearchPayload()
  const next = { ...crud.getParams() }
  for (const it of items.value) {
    delete next[it.prop]
    if (it.range) {
      delete next[it.range.min]
      delete next[it.range.max]
    }
  }
  Object.assign(next, payload)
  crud.params.value = next
  if (!doRefresh) return
  crud.pagination.value = { ...crud.pagination.value, page: 1 }
  void crud.refresh()
}

function search() {
  applyFormToParams(true)
}

function reset() {
  for (const it of items.value) {
    form[it.prop] = initialValue(it)
  }
  // 回到 props.params 底稿后，再按当前 form（含 value 初始）提交一次
  crud.resetParams(false)
  search()
}

onBeforeUnmount(() => {
  crud.hasSearchFilters.value = false
  if (crud.searchActions.value?.search === search) {
    crud.searchActions.value = null
  }
})

defineExpose({ form, search, reset })
</script>

<style lang="scss" scoped>
.vm-crud-search {
  --vm-search-gap: 12px;
  container-type: inline-size;
  container-name: vm-crud-search;

  display: flex;
  width: 100%;
  min-width: 0;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--vm-search-gap);
  margin-bottom: 6px;
}

.vm-crud-search__filters {
  display: grid;
  flex: 1 1 auto;
  /* 默认 2 列；随容器变宽升到 8 列，单列始终 ≥140 */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--vm-search-gap);
  min-width: 0;
  align-items: center;
}

/* n * 140 + (n-1) * 12 = 152n - 12 */
@container vm-crud-search (min-width: 444px) {
  .vm-crud-search__filters {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@container vm-crud-search (min-width: 596px) {
  .vm-crud-search__filters {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

@container vm-crud-search (min-width: 748px) {
  .vm-crud-search__filters {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }
}

@container vm-crud-search (min-width: 900px) {
  .vm-crud-search__filters {
    grid-template-columns: repeat(6, minmax(0, 1fr));
  }
}

@container vm-crud-search (min-width: 1052px) {
  .vm-crud-search__filters {
    grid-template-columns: repeat(7, minmax(0, 1fr));
  }
}

@container vm-crud-search (min-width: 1204px) {
  .vm-crud-search__filters {
    grid-template-columns: repeat(8, minmax(0, 1fr));
  }
}

.vm-crud-search__actions {
  display: flex;
  flex: 0 0 auto;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--vm-search-gap);
}

:deep(.vm-crud-search__control) {
  box-sizing: border-box;
  width: 100% !important;
  min-width: 0 !important;
  max-width: none !important;
  height: var(--vm-control-height) !important;
  border-radius: 12px !important;
  box-shadow: none !important;

  &:not(.is-range) {
    border: 1px solid transparent !important;
    background: var(--muted) !important;

    &:focus,
    &:focus-visible {
      border-color: var(--focus-border) !important;
      box-shadow: none !important;
    }
  }

  /* 区间占两列：2 * 列宽 + 12px */
  &.is-range {
    width: 100% !important;
    min-width: 0 !important;
    max-width: none !important;
    height: var(--vm-control-height) !important;
    grid-column: span 2;
    padding: 0 10px !important;
    border: 1px solid transparent !important;
    border-radius: 12px !important;
    background: var(--muted) !important;
  }
}

.vm-crud-search__btn {
  height: var(--vm-control-height);
  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;

  &.is-primary {
    background: var(--brand);
    color: #fff;

    &:hover {
      background: var(--brand-deep);
    }
  }

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

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