<template>
  <div class="fme-root">
    <div class="fme-header">
      <p class="fme-desc">{{ description }}</p>
      <el-descriptions v-if="pathInfos.length > 0" :column="1" size="small" border class="fme-paths">
        <el-descriptions-item v-for="info in pathInfos" :key="info.label" :label="info.label">
          <code v-if="info.value" class="fme-path">{{ info.value }}</code>
          <span v-else class="fme-path-empty">({{ info.emptyHint ?? '空' }})</span>
        </el-descriptions-item>
      </el-descriptions>

      <div class="fme-toolbar">
        <el-input
          v-model="filterText"
          placeholder="按中文/英文键过滤"
          size="small"
          clearable
          class="fme-search"
        >
          <template #prefix><el-icon><Search /></el-icon></template>
        </el-input>

        <el-button size="small" :loading="loading" @click="refresh">
          <el-icon><Refresh /></el-icon>
          <span class="fme-btn-text">刷新</span>
        </el-button>

        <el-button
          size="small"
          type="primary"
          :disabled="!hasDirty && !forceAllowSave"
          :loading="saving"
          @click="onSave"
        >
          <el-icon><Document /></el-icon>
          <span class="fme-btn-text">{{ saveButtonLabel }}{{ hasDirty ? `（${dirtyCount} 项未应用）` : '' }}</span>
        </el-button>

        <el-button v-if="hasDirty" size="small" link type="warning" @click="resetDirty">
          放弃未应用的改动
        </el-button>
      </div>
    </div>

    <el-card v-loading="loading" shadow="never" class="fme-table-card">
      <el-table
        :data="filteredRows"
        stripe
        border
        size="small"
        height="100%"
        class="fme-table"
        row-key="key"
      >
        <el-table-column label="中文说明" prop="label" min-width="240" show-overflow-tooltip />
        <el-table-column label="英文键" prop="key" width="240" show-overflow-tooltip />
        <el-table-column label="类型" prop="typeName" width="100" />
        <el-table-column label="当前值（可编辑）" min-width="260">
          <template #default="{ row }">
            <span v-if="row.locked" class="fme-locked">{{ row.value }}（🔒 锁定）</span>
            <el-switch
              v-else-if="isBool(row.typeName)"
              :model-value="boolOf(getEditing(row))"
              @update:model-value="(v: boolean | string | number) => setEditing(row, String(Boolean(v)))"
            />
            <el-input-number
              v-else-if="isInt(row.typeName)"
              :model-value="intOf(getEditing(row))"
              :controls="false"
              :step="1"
              :precision="0"
              size="small"
              class="fme-input"
              @update:model-value="(v: number | undefined) => setEditing(row, String(v ?? 0))"
            />
            <el-input-number
              v-else-if="isFloat(row.typeName)"
              :model-value="floatOf(getEditing(row))"
              :controls="false"
              :step="0.1"
              size="small"
              class="fme-input"
              @update:model-value="(v: number | undefined) => setEditing(row, String(v ?? 0))"
            />
            <el-input
              v-else
              :model-value="getEditing(row)"
              size="small"
              class="fme-input"
              @update:model-value="(v: string) => setEditing(row, v)"
            />
          </template>
        </el-table-column>
        <el-table-column label="状态" width="100" align="center">
          <template #default="{ row }">
            <el-tag v-if="row.locked" size="small" type="info" effect="plain">只读</el-tag>
            <el-tag v-else-if="isDirty(row)" size="small" type="warning" effect="dark">已修改</el-tag>
            <el-tag v-else size="small" type="success" effect="plain">同步</el-tag>
          </template>
        </el-table-column>
      </el-table>

      <el-empty v-if="!loading && rows.length === 0" :description="`未发现可编辑的 [FieldMember] 字段（target: ${targetLabel}）`" />
    </el-card>
  </div>
</template>

<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Document, Refresh, Search } from '@element-plus/icons-vue'
import type { ProjectPropertyRow } from '@/api/reflection'

export interface FieldMemberPathInfo {
  label: string
  value: string | null
  emptyHint?: string
}

export interface FieldMemberEditorLoadResult {
  fields: ProjectPropertyRow[]
  pathInfos?: FieldMemberPathInfo[]
  saveButtonLabel?: string
}

const props = defineProps<{
  targetLabel: string
  description: string
  loader: () => Promise<FieldMemberEditorLoadResult>
  onSet: (field: string, value: string) => Promise<unknown>
  onSave: () => Promise<{ path: string }>
}>()

const loading = ref(false)
const saving = ref(false)
const filterText = ref('')
const rows = ref<ProjectPropertyRow[]>([])
const pathInfos = ref<FieldMemberPathInfo[]>([])
const saveButtonLabel = ref('保存')
const editing = reactive<Record<string, string>>({})

// 即使无 dirty 也允许"保存"——例如桌面端修改了内存但平台仅想触发持久化。
// 现版本默认 false：保存按钮只在有 dirty 时可点；用户可后续按需放开。
const forceAllowSave = ref(false)

const filteredRows = computed<ProjectPropertyRow[]>(() => {
  const q = filterText.value.trim().toLowerCase()
  if (!q) return rows.value
  return rows.value.filter(
    (r) => r.key.toLowerCase().includes(q) || r.label.toLowerCase().includes(q)
  )
})

const dirtyCount = computed(() =>
  rows.value.reduce((n, f) => (f.locked ? n : editing[f.key] !== f.value ? n + 1 : n), 0)
)
const hasDirty = computed(() => dirtyCount.value > 0)

function isBool(t: string) {
  return t === 'Boolean'
}
function isInt(t: string) {
  return t === 'Int32' || t === 'Int64' || t === 'Int16' || t === 'Byte'
}
function isFloat(t: string) {
  return t === 'Single' || t === 'Double' || t === 'Decimal'
}
function boolOf(s: string): boolean {
  const v = (s ?? '').trim().toLowerCase()
  return v === 'true' || v === '1' || v === 'yes'
}
function intOf(s: string): number {
  const n = parseInt(s, 10)
  return Number.isFinite(n) ? n : 0
}
function floatOf(s: string): number {
  const n = parseFloat(s)
  return Number.isFinite(n) ? n : 0
}
function getEditing(row: ProjectPropertyRow): string {
  return editing[row.key] ?? row.value
}
function setEditing(row: ProjectPropertyRow, v: string) {
  editing[row.key] = v
}
function isDirty(row: ProjectPropertyRow): boolean {
  if (row.locked) return false
  const cur = editing[row.key]
  return cur !== undefined && cur !== row.value
}

async function refresh() {
  loading.value = true
  try {
    const r = await props.loader()
    rows.value = r.fields
    pathInfos.value = r.pathInfos ?? []
    if (r.saveButtonLabel) saveButtonLabel.value = r.saveButtonLabel
    for (const f of r.fields) editing[f.key] = f.value
    for (const k of Object.keys(editing)) {
      if (!r.fields.find((f) => f.key === k)) delete editing[k]
    }
  } catch (err) {
    ElMessage.error(`加载${props.targetLabel}失败：${(err as Error).message}`)
  } finally {
    loading.value = false
  }
}

function resetDirty() {
  for (const f of rows.value) editing[f.key] = f.value
}

async function onSave() {
  const dirtyFields = rows.value.filter((f) => !f.locked && editing[f.key] !== f.value)
  saving.value = true
  let okCount = 0
  const failed: Array<{ key: string; reason: string }> = []
  for (const f of dirtyFields) {
    try {
      await props.onSet(f.key, editing[f.key] ?? '')
      okCount += 1
    } catch (err) {
      failed.push({ key: f.key, reason: (err as Error).message })
    }
  }

  if (failed.length > 0) {
    saving.value = false
    try {
      await ElMessageBox.confirm(
        `共 ${dirtyFields.length} 项改动：成功 ${okCount}，失败 ${failed.length}（首项: ${failed[0].key} → ${failed[0].reason}）。\n是否仍要把已成功应用的改动持久化到磁盘？`,
        '部分字段写入失败',
        { confirmButtonText: '仍然保存', cancelButtonText: '取消', type: 'warning' }
      )
    } catch {
      ElMessage.warning(`已取消保存（${okCount} 项已写入内存，${failed.length} 项未生效）`)
      await refresh()
      return
    }
    saving.value = true
  }

  try {
    const r = await props.onSave()
    ElMessage.success(`已保存到：${r.path}（应用 ${okCount} 项改动${failed.length > 0 ? `；${failed.length} 项失败已忽略` : ''}）`)
    await refresh()
  } catch (err) {
    ElMessage.error(`保存失败：${(err as Error).message}`)
  } finally {
    saving.value = false
  }
}

watch(() => props.loader, () => { refresh() })

onMounted(refresh)

defineExpose({ refresh })
</script>

<style scoped>
.fme-root {
  height: 100%;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 12px;
  box-sizing: border-box;
}
.fme-header {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.fme-desc {
  margin: 0;
  color: var(--el-text-color-primary);
  font-size: 13px;
  line-height: 1.65;
  background: var(--el-fill-color-light);
  padding: 10px 12px;
  border-left: 3px solid var(--el-color-primary);
  border-radius: 4px;
}
.fme-paths code.fme-path {
  font-family: 'JetBrains Mono', Consolas, monospace;
  font-size: 12px;
  word-break: break-all;
  color: var(--el-text-color-primary);
}
.fme-path-empty {
  color: var(--el-text-color-secondary);
  font-size: 12px;
  font-style: italic;
}
.fme-toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.fme-search {
  width: 280px;
}
.fme-btn-text {
  margin-left: 4px;
}
.fme-table-card {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.fme-table-card :deep(.el-card__body) {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
}
.fme-table {
  flex: 1;
}
.fme-input {
  width: 100%;
}
.fme-locked {
  color: var(--el-text-color-secondary);
  font-style: italic;
}
</style>
