<template>
  <div class="vm-json-kv" :class="{ 'is-nested': nested }">
    <div
      v-for="(row, index) in modelValue"
      :key="row.uid"
      class="vm-json-kv__item"
    >
      <div class="vm-json-kv__line">
        <input
          class="vm-json-kv__key"
          :value="row.key"
          placeholder="key"
          @input="
            patchRow(index, {
              key: ($event.target as HTMLInputElement).value,
            })
          "
        />
        <vm-select
          class="vm-json-kv__kind"
          :model-value="row.valueKind"
          :options="kindOptions"
          label-key="label"
          value-key="value"
          :filterable="false"
          :refresh-on-change="false"
          width="110px"
          :placeholder="crudLabel('jsonType')"
          @update:model-value="(v) => onKindChange(index, String(v))"
        />
        <input
          v-if="row.valueKind === 'string' || row.valueKind === 'number'"
          class="vm-json-kv__value"
          :value="row.stringValue"
          :placeholder="row.valueKind === 'number' ? crudLabel('jsonNumber') : 'value'"
          :type="row.valueKind === 'number' ? 'number' : 'text'"
          @input="
            patchRow(index, {
              stringValue: ($event.target as HTMLInputElement).value,
            })
          "
        />
        <vm-select
          v-else-if="row.valueKind === 'boolean'"
          class="vm-json-kv__value"
          :model-value="row.stringValue === 'true' ? 'true' : 'false'"
          :options="booleanOptions"
          :filterable="false"
          :refresh-on-change="false"
          width="110px"
          @update:model-value="(v) => patchRow(index, { stringValue: String(v) })"
        />
        <div v-else class="vm-json-kv__value-spacer" />
        <div class="vm-json-kv__app" :title="crudLabel('openToApp')">
          <vm-switch
            :model-value="row.openToApp"
            :active-value="true"
            :inactive-value="false"
            @update:model-value="(v) => patchRow(index, { openToApp: v === true })"
          />
        </div>
        <vm-icon-btn
          v-if="row.valueKind === 'object'"
          icon="ri-add-line"
          :title="crudLabel('addChildKv')"
          size="sm"
          @click="addChild(index)"
        />
        <vm-icon-btn
          icon="ri-delete-bin-line"
          :title="crudLabel('delete')"
          size="sm"
          variant="danger"
          @click="removeRow(index)"
        />
      </div>
      <vm-json-kv-editor
        v-if="row.valueKind === 'object'"
        class="vm-json-kv__child"
        nested
        :model-value="row.children"
        :kind-options="kindOptions"
        @update:model-value="(v) => patchRow(index, { children: v })"
      />
    </div>
  </div>
</template>

<script setup lang="ts">
import { crudLabel } from '../style'
import { computed, toValue, type MaybeRefOrGetter } from 'vue'
import type { JsonKvNode, JsonValueKind } from '../json-kv'
import VmSelect from './vm-select.vue'
import VmSwitch from './vm-switch.vue'
import VmIconBtn from '../../components/vm-icon-btn.vue'

defineOptions({ name: 'vm-json-kv-editor' })

const props = withDefaults(
  defineProps<{
    modelValue: JsonKvNode[]
    nested?: boolean
    /** 字典 base_json_value_kind；可直接传 dict.options(...)（ComputedRef） */
    kindOptions?: MaybeRefOrGetter<Array<Record<string, unknown>> | undefined>
  }>(),
  { nested: false },
)

const emit = defineEmits<{
  'update:modelValue': [JsonKvNode[]]
}>()

const defaultKindOptions = computed(() => [
  { label: crudLabel('jsonText'), value: 'string' },
  { label: crudLabel('jsonNumber'), value: 'number' },
  { label: crudLabel('jsonBoolean'), value: 'boolean' },
  { label: crudLabel('jsonObject'), value: 'object' },
])

/** 平铺为 label/value；不走索引搜索（filterable=false） */
const kindOptions = computed(() => {
  const list = toValue(props.kindOptions)
  const raw = list?.length ? list : defaultKindOptions.value
  return raw.map((opt) => ({
    label: String(opt.label ?? opt.name ?? opt.value ?? ''),
    value: opt.value as string | number,
  }))
})

const booleanOptions = [
  { label: 'true', value: 'true' },
  { label: 'false', value: 'false' },
]

function newUid() {
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`
}

function emptyNode(): JsonKvNode {
  return {
    uid: newUid(),
    key: '',
    valueKind: 'string',
    stringValue: '',
    openToApp: false,
    children: [],
  }
}

function addChild(index: number) {
  const row = props.modelValue[index]
  if (!row || row.valueKind !== 'object') return
  patchRow(index, { children: [...row.children, emptyNode()] })
}

function removeRow(index: number) {
  emit(
    'update:modelValue',
    props.modelValue.filter((_, i) => i !== index),
  )
}

function patchRow(index: number, patch: Partial<JsonKvNode>) {
  emit(
    'update:modelValue',
    props.modelValue.map((row, i) =>
      i === index ? { ...row, ...patch } : row,
    ),
  )
}

function onKindChange(index: number, kind: string) {
  const valueKind: JsonValueKind =
    kind === 'object'
      ? 'object'
      : kind === 'number'
        ? 'number'
        : kind === 'boolean'
          ? 'boolean'
          : 'string'
  const row = props.modelValue[index]
  if (!row) return
  if (valueKind === 'object') {
    patchRow(index, {
      valueKind,
      children: row.children.length ? row.children : [emptyNode()],
    })
    return
  }
  const next: Partial<JsonKvNode> = {
    valueKind,
    children: [],
  }
  if (valueKind === 'boolean') {
    next.stringValue =
      row.stringValue === 'true' || row.stringValue === '1' ? 'true' : 'false'
  } else if (valueKind === 'number') {
    const n = Number(row.stringValue)
    next.stringValue = Number.isFinite(n) ? String(n) : '0'
  }
  patchRow(index, next)
}
</script>

<style lang="scss" scoped>
.vm-json-kv {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
  min-width: 0;
}

.vm-json-kv.is-nested {
  margin-top: 8px;
  padding: 10px;
  border: 1px dashed var(--border);
  border-radius: var(--radius, 8px);
  background: color-mix(in srgb, var(--muted) 35%, transparent);
}

.vm-json-kv__item {
  display: flex;
  flex-direction: column;
  gap: 0;
  min-width: 0;
}

.vm-json-kv__line {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.vm-json-kv__key,
.vm-json-kv__value {
  height: var(--vm-control-height, 36px);
  padding: 0 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius, 8px);
  background: var(--background);
  color: var(--foreground);
  font-size: 13px;
  outline: none;
}

.vm-json-kv__key:focus,
.vm-json-kv__value:focus {
  border-color: var(--ring, var(--brand));
}

.vm-json-kv__key {
  flex: 1 1 120px;
  min-width: 100px;
  max-width: 200px;
}

.vm-json-kv__kind {
  flex: 0 0 auto;
}

.vm-json-kv__value {
  flex: 1 1 140px;
  min-width: 100px;
}

.vm-json-kv__value-spacer {
  flex: 1 1 140px;
  min-width: 0;
}

.vm-json-kv__app {
  display: flex;
  flex-shrink: 0;
  align-items: center;
}

.vm-json-kv__child {
  margin-left: 12px;
}

@media (max-width: 767px) {
  .vm-json-kv__key,
  .vm-json-kv__value,
  .vm-json-kv__value-spacer {
    flex: 1 1 100%;
    max-width: none;
  }

  .vm-json-kv__child {
    margin-left: 0;
  }
}
</style>
