<template>
  <div class="ota-config">
    <div class="split">
      <!-- 左：车辆列表 -->
      <section class="pane left">
        <div class="pane-head">
          <el-input v-model="carSearch" size="small" clearable placeholder="名称 / IP" class="grow" />
          <el-button size="small" :icon="Refresh" :loading="carsLoading" @click="loadCars" />
        </div>
        <el-table
          v-loading="carsLoading"
          :data="filteredCars"
          size="small"
          height="520"
          highlight-current-row
          row-key="id"
          @current-change="onPickCar"
        >
          <el-table-column prop="name" label="名称" min-width="100" />
          <el-table-column prop="ip" label="IP" width="120">
            <template #default="{ row }"><span class="mono">{{ row.ip || '-' }}</span></template>
          </el-table-column>
        </el-table>
      </section>

      <!-- 右：JSON（对齐参考 OTA：选节点 → 选择 JSON 数据 → 修改 → 同步小车） -->
      <section class="pane right">
        <el-tabs v-model="app" @tab-change="onTabChange">
          <el-tab-pane label="Medulla 配置" name="medulla" />
          <el-tab-pane label="Detour 配置" name="detour" />
          <el-tab-pane label="Clumsy 配置" name="clumsy" />
        </el-tabs>

        <p class="flow-hint">
          流程：左侧点选车辆 → 树上点选要改的字段 →「选择 JSON 数据」编辑 →「同步小车」勾选目标车下发。也可直接「编辑全部配置」。
        </p>

        <div v-loading="jsonLoading" class="json-body">
          <el-empty v-if="!currentCar" description="请先在左侧点选一台有 IP 的车" :image-size="72" />
          <el-empty v-else-if="!jsonData" description="无数据（该车无 IP 或 WatchDog 不可达）" :image-size="72" />
          <template v-else>
            <div class="json-scroll">
              <el-tree
                :data="treeData"
                node-key="path"
                highlight-current
                :current-node-key="selectedPath || undefined"
                :default-expand-all="false"
                :default-expanded-keys="defaultExpanded"
                :expand-on-click-node="false"
                @node-click="onNodeClick"
              >
                <template #default="{ data }">
                  <span
                    class="tree-node"
                    :class="{
                      disabled: !isSelectable(data),
                      selected: data.path === selectedPath || data.path === selectedSourcePath
                    }"
                  >
                    <span class="radio" :class="{ on: data.path === selectedPath || data.path === selectedSourcePath }" />
                    <em>{{ data.label }}</em>
                    <span class="preview">{{ data.valuePreview }}</span>
                  </span>
                </template>
              </el-tree>
            </div>

            <!-- 固定底栏：不被长树顶出视口 -->
            <div class="json-actions">
              <div class="sel-block">
                <span class="label">已选路径</span>
                <code class="sel mono">{{ selectedPath || '（尚未选择）' }}</code>
              </div>
              <div class="btns">
                <el-button size="small" :disabled="!jsonData || !canWrite" @click="openEditAll">
                  编辑全部配置
                </el-button>
                <el-button
                  type="primary"
                  size="small"
                  :disabled="!selectedPath || !canWrite"
                  @click="openEdit"
                >
                  选择 JSON 数据
                </el-button>
              </div>
            </div>
          </template>
        </div>
      </section>
    </div>

    <!-- 修改 JSON -->
    <el-dialog v-model="editVisible" title="修改 JSON" width="640px" destroy-on-close align-center>
      <p class="dlg-hint">编辑下方 JSON 片段（WatchDog 会与车上配置深度合并）。改完后点「同步小车」选择下发目标。</p>
      <el-input v-model="editText" type="textarea" :rows="18" class="edit-area" />
      <template #footer>
        <el-button @click="editVisible = false">取消</el-button>
        <el-button type="primary" :disabled="!canWrite" @click="openCarSelect">同步小车</el-button>
      </template>
    </el-dialog>

    <!-- 选择车辆 -->
    <el-dialog
      v-model="carSelectVisible"
      title="选择车辆"
      width="560px"
      destroy-on-close
      align-center
      @opened="onCarSelectOpened"
    >
      <p class="dlg-hint">勾选要接收该参数的车辆（需有 IP）。默认已勾选当前查看的车。</p>
      <el-input v-model="pushSearch" size="small" clearable placeholder="筛选名称 / IP" class="mb8" />
      <el-table
        ref="pushTableRef"
        :data="pushFiltered"
        size="small"
        height="320"
        row-key="id"
        class="push-table"
        @selection-change="onPushSelection"
      >
        <el-table-column type="selection" width="48" />
        <el-table-column prop="name" label="名称" min-width="120" />
        <el-table-column prop="ip" label="IP" width="140">
          <template #default="{ row }"><span class="mono">{{ row.ip }}</span></template>
        </el-table-column>
      </el-table>
      <p class="sel-count">已选 {{ pushIds.length }} 台</p>
      <template #footer>
        <el-button @click="carSelectVisible = false">取消</el-button>
        <el-button type="primary" :loading="pushing" :disabled="!pushIds.length" @click="confirmPush">
          确定下发
        </el-button>
      </template>
    </el-dialog>
  </div>
</template>

<script setup lang="ts">
import { computed, nextTick, onMounted, ref } from 'vue'
import { Refresh } from '@element-plus/icons-vue'
import { ElMessage, type ElTable } from 'element-plus'
import { getOtaConfig, listOtaVehicles, pushOtaConfig } from '@/api/ota'
import type { OtaVehicleRow } from '@/types/ota'
import { resolvePathPartial, jsonToTree, type JsonTreeNode } from './jsonPath'

const props = defineProps<{ canWrite: boolean }>()
const emit = defineEmits<{ jobCreated: [] }>()

const cars = ref<OtaVehicleRow[]>([])
const carsLoading = ref(false)
const carSearch = ref('')
const currentCar = ref<OtaVehicleRow | null>(null)
const app = ref<'medulla' | 'detour' | 'clumsy'>('clumsy')
const jsonData = ref<any>(null)
const jsonLoading = ref(false)
const treeData = ref<JsonTreeNode[]>([])
const selectedPath = ref('')
const selectedSourcePath = ref('')
const defaultExpanded = ref<string[]>([])
const editVisible = ref(false)
const editText = ref('')
const editPartial = ref<Record<string, unknown>>({})
const carSelectVisible = ref(false)
const pushSearch = ref('')
const pushIds = ref<string[]>([])
const pushing = ref(false)
const pushTableRef = ref<InstanceType<typeof ElTable>>()

const filteredCars = computed(() => {
  const q = carSearch.value.trim().toLowerCase()
  if (!q) return cars.value
  return cars.value.filter((c) => [c.name, c.ip, c.id].some((x) => (x || '').toLowerCase().includes(q)))
})

const pushFiltered = computed(() => {
  const q = pushSearch.value.trim().toLowerCase()
  const withIp = cars.value.filter((c) => !!c.ip)
  if (!q) return withIp
  return withIp.filter((c) => [c.name, c.ip, c.id].some((x) => (x || '').toLowerCase().includes(q)))
})

function isSelectable(data: JsonTreeNode) {
  return data.selectable || /\[\d+\]$/.test(data.path)
}

async function loadCars() {
  carsLoading.value = true
  try {
    cars.value = await listOtaVehicles(false)
  } catch (e: any) {
    ElMessage.error(e?.message || '加载车辆失败')
  } finally {
    carsLoading.value = false
  }
}

async function loadJson() {
  if (!currentCar.value?.ip) {
    jsonData.value = null
    treeData.value = []
    selectedPath.value = ''
    selectedSourcePath.value = ''
    return
  }
  jsonLoading.value = true
  selectedPath.value = ''
  selectedSourcePath.value = ''
  try {
    const res = await getOtaConfig(currentCar.value.id, app.value)
    jsonData.value = typeof res.json === 'string' ? JSON.parse(res.json) : res.json
    treeData.value = jsonToTree(jsonData.value)
    // 默认展开第一层，避免整树铺满
    defaultExpanded.value = treeData.value.map((n) => n.path)
  } catch (e: any) {
    jsonData.value = null
    treeData.value = []
    ElMessage.error(e?.message || '拉取配置失败')
  } finally {
    jsonLoading.value = false
  }
}

function onPickCar(row: OtaVehicleRow | null) {
  currentCar.value = row
  void loadJson()
}

function onTabChange() {
  void loadJson()
}

/** 对齐参考 OTA：点到数组或其子项时改为选中整个数组字段，便于深度合并 */
function resolveSelectPath(data: JsonTreeNode): string | null {
  const path = data.path
  const arr = path.match(/^(.*?)\[\d+\]/)
  if (arr) return arr[1] || null
  if (!data.selectable) return null
  return path
}

function onNodeClick(data: JsonTreeNode) {
  const path = resolveSelectPath(data)
  if (!path) {
    selectedPath.value = ''
    selectedSourcePath.value = ''
    ElMessage.warning('请选择对象字段（或数组元素，将同步整个数组）')
    return
  }
  selectedSourcePath.value = data.path
  selectedPath.value = path
}

function openEdit() {
  if (!selectedPath.value || !jsonData.value) {
    ElMessage.warning('请先在树上点选要修改的配置字段')
    return
  }
  if (!props.canWrite) {
    ElMessage.warning('当前账号无写权限')
    return
  }
  const partial = resolvePathPartial(jsonData.value, selectedPath.value)
  editPartial.value = partial
  editText.value = JSON.stringify(partial, null, 2)
  editVisible.value = true
}

function openEditAll() {
  if (!jsonData.value) return
  if (!props.canWrite) {
    ElMessage.warning('当前账号无写权限')
    return
  }
  selectedPath.value = 'root'
  selectedSourcePath.value = 'root'
  editPartial.value = jsonData.value
  editText.value = JSON.stringify(jsonData.value, null, 2)
  editVisible.value = true
}

function openCarSelect() {
  try {
    editPartial.value = JSON.parse(editText.value)
  } catch {
    ElMessage.error('JSON 格式无效')
    return
  }
  pushIds.value = currentCar.value?.id ? [currentCar.value.id] : []
  carSelectVisible.value = true
}

function onPushSelection(rows: OtaVehicleRow[]) {
  pushIds.value = rows.map((r) => r.id)
}

async function onCarSelectOpened() {
  await nextTick()
  const table = pushTableRef.value
  if (!table) return
  table.clearSelection()
  const prefer = new Set(pushIds.value)
  for (const row of pushFiltered.value) {
    if (prefer.has(row.id)) table.toggleRowSelection(row, true)
  }
}

async function confirmPush() {
  if (!pushIds.value.length) {
    ElMessage.warning('请至少选择一台车')
    return
  }
  pushing.value = true
  try {
    await pushOtaConfig({
      carIds: pushIds.value,
      app: app.value,
      json: JSON.stringify(editPartial.value)
    })
    ElMessage.success('任务已创建，可在「任务进度」查看')
    carSelectVisible.value = false
    editVisible.value = false
    emit('jobCreated')
    void loadJson()
  } catch (e: any) {
    ElMessage.error(e?.message || '下发失败')
  } finally {
    pushing.value = false
  }
}

onMounted(() => void loadCars())
</script>

<style scoped>
.ota-config {
  color: rgb(var(--mg-text-tint-rgb));
}
.split {
  display: grid;
  grid-template-columns: minmax(260px, 1fr) minmax(360px, 1.35fr);
  gap: 12px;
  min-height: 560px;
}
.pane {
  border: 1px solid rgba(var(--mg-primary-rgb), 0.12);
  border-radius: 12px;
  background: #fff;
  padding: 10px;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.pane-head {
  display: flex;
  gap: 8px;
  margin-bottom: 8px;
}
.grow {
  flex: 1;
}
.flow-hint {
  margin: 0 0 8px;
  font-size: 12px;
  line-height: 1.45;
  color: rgba(var(--mg-text-hi-rgb), 0.62);
}
.json-body {
  flex: 1;
  min-height: 420px;
  display: flex;
  flex-direction: column;
  border: 1px solid rgba(var(--mg-primary-rgb), 0.08);
  border-radius: 10px;
  overflow: hidden;
}
.json-scroll {
  flex: 1;
  min-height: 280px;
  max-height: 420px;
  overflow: auto;
  padding: 8px 10px;
}
.tree-node {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  padding: 1px 0;
}
.tree-node .radio {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 1.5px solid rgba(var(--mg-primary-rgb), 0.45);
  flex-shrink: 0;
  box-sizing: border-box;
}
.tree-node .radio.on {
  border-color: var(--mg-primary);
  background: radial-gradient(circle at center, var(--mg-primary) 0 40%, transparent 42%);
}
.tree-node.selected em {
  color: var(--mg-primary);
}
.tree-node em {
  font-style: normal;
  font-weight: 600;
  color: rgb(var(--mg-text-tint-rgb));
}
.tree-node .preview {
  color: rgba(var(--mg-text-hi-rgb), 0.55);
  font-family: var(--mg-font-mono);
}
.tree-node.disabled {
  opacity: 0.5;
}
.json-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 10px 12px;
  border-top: 1px solid rgba(var(--mg-primary-rgb), 0.12);
  background: rgba(var(--mg-primary-rgb), 0.04);
}
.sel-block {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}
.sel-block .label {
  font-size: 11px;
  color: rgba(var(--mg-text-hi-rgb), 0.5);
}
.sel {
  font-size: 12px;
  color: var(--mg-primary);
  word-break: break-all;
}
.btns {
  display: flex;
  gap: 8px;
  flex-shrink: 0;
}
.mono {
  font-family: var(--mg-font-mono);
  font-size: 12px;
}
.edit-area :deep(textarea) {
  font-family: var(--mg-font-mono);
  font-size: 12px;
}
.dlg-hint {
  margin: 0 0 10px;
  font-size: 12px;
  color: rgba(var(--mg-text-hi-rgb), 0.65);
  line-height: 1.45;
}
.mb8 {
  margin-bottom: 8px;
}
.sel-count {
  margin: 8px 0 0;
  font-size: 12px;
  color: rgba(var(--mg-text-hi-rgb), 0.55);
}
.push-table :deep(.el-checkbox__inner) {
  width: 16px !important;
  height: 16px !important;
  border: 1.5px solid #7c3aed !important;
  background: #fff !important;
}
.push-table :deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
  background: #7c3aed !important;
  border-color: #7c3aed !important;
}
.push-table :deep(.el-checkbox__input.is-checked .el-checkbox__inner::after) {
  border-color: #fff !important;
}
@media (max-width: 960px) {
  .split {
    grid-template-columns: 1fr;
  }
}
</style>
