<script setup lang="ts">
import { ref, computed, reactive } from 'vue';
import {
  type NvsPartition, type NvsEntry, type NvsEncoding, type NvsFlashStats,
  NvsType, NvsVersion,
  ENCODING_OPTIONS, ENCODING_TO_TYPE, TYPE_TO_ENCODING,
  isPrimitiveType,
  createEmptyPartition, addEntry, removeEntry, updateEntry,
  duplicateEntry, mergePartitions, calculateFlashStats,
  validatePartition, sortEntries, generateEntryId,
  parseBinary, serializeBinary, parseCsv, serializeCsv,
  MAX_KEY_LENGTH, PAGE_SIZE,
} from '../../lib/nvs';

const props = defineProps<{
  isDark?: boolean;
}>();

// ── Core state ─────────────────────────────────────────────────────

const partition = ref<NvsPartition>(createEmptyPartition());
const targetSize = ref(0x4000); // 16KB default

// ── UI state ───────────────────────────────────────────────────────

const namespaceFilter = ref('');
const keySearch = ref('');
const mergeMode = ref<'overwrite' | 'skip'>('overwrite');
const statusMessage = ref('');
const statusType = ref<'success' | 'error' | 'info'>('info');

// Add entry dialog
const showAddDialog = ref(false);
const newEntry = reactive({
  namespace: '',
  key: '',
  encoding: 'u8' as NvsEncoding,
  value: '',
});

// Add namespace dialog
const showNsDialog = ref(false);
const newNamespace = ref('');

// ── Computed ───────────────────────────────────────────────────────

const flashStats = computed<NvsFlashStats>(() =>
  calculateFlashStats(partition.value, targetSize.value),
);

const errors = computed(() => validatePartition(partition.value));

const filteredEntries = computed(() => {
  let entries = partition.value.entries;
  if (namespaceFilter.value) {
    entries = entries.filter(e => e.namespace === namespaceFilter.value);
  }
  if (keySearch.value) {
    entries = entries.filter(e => e.key.includes(keySearch.value));
  }
  return entries;
});

const progressColor = computed(() => {
  const pct = flashStats.value.usagePercent;
  if (pct >= 85) return '#F56C6C';
  if (pct >= 60) return '#E6A23C';
  return '#67C23A';
});

const sizeOptions = [
  { label: '12 KB (3页)', value: 0x3000 },
  { label: '16 KB (4页)', value: 0x4000 },
  { label: '20 KB (5页)', value: 0x5000 },
  { label: '24 KB (6页)', value: 0x6000 },
  { label: '32 KB (8页)', value: 0x8000 },
  { label: '64 KB (16页)', value: 0x10000 },
  { label: '128 KB (32页)', value: 0x20000 },
  { label: '256 KB (64页)', value: 0x40000 },
];

// ── Helpers ────────────────────────────────────────────────────────

function showStatus(msg: string, type: 'success' | 'error' | 'info' = 'info') {
  statusMessage.value = msg;
  statusType.value = type;
  setTimeout(() => { statusMessage.value = ''; }, 4000);
}

function getEncodingForType(type: NvsType): NvsEncoding {
  return TYPE_TO_ENCODING[type] ?? 'u8';
}

function formatValue(entry: NvsEntry): string {
  if (entry.value instanceof Uint8Array) {
    if (entry.value.length <= 32) {
      return Array.from(entry.value).map(b => b.toString(16).padStart(2, '0')).join(' ');
    }
    return Array.from(entry.value.subarray(0, 32))
      .map(b => b.toString(16).padStart(2, '0')).join(' ') +
      ` ... (${entry.value.length} 字节)`;
  }
  return String(entry.value);
}

function downloadBlob(blob: Blob, filename: string) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  setTimeout(() => URL.revokeObjectURL(url), 10000);
}

/** Parse a value string based on encoding */
function parseValueInput(encoding: NvsEncoding, raw: string): number | bigint | string | Uint8Array {
  switch (encoding) {
    case 'u8': case 'u16': case 'u32':
    case 'i8': case 'i16': case 'i32': {
      const str = raw.trim();
      if (str.startsWith('0x') || str.startsWith('0X')) {
        if (!/^-?0[xX][0-9a-fA-F]+$/.test(str)) throw new Error(`无效的整数值: "${str}"`);
        return parseInt(str, 16);
      }
      if (!/^-?\d+$/.test(str)) throw new Error(`无效的整数值: "${str}"`);
      return parseInt(str, 10);
    }
    case 'u64': case 'i64': {
      const str64 = raw.trim();
      if (str64.startsWith('-0x') || str64.startsWith('-0X')) {
        if (!/^-0[xX][0-9a-fA-F]+$/.test(str64)) throw new Error(`无效的整数值: "${str64}"`);
        return -BigInt(str64.slice(1));
      }
      try { return BigInt(str64); } catch { throw new Error(`无效的整数值: "${str64}"`); }
    }
    case 'string':
      return raw;
    case 'blob':
    case 'binary': {
      const hex = raw.replace(/\s/g, '');
      if (hex.length === 0) return new Uint8Array(0);
      if (hex.length % 2 !== 0) throw new Error(`十六进制字符串长度必须为偶数`);
      if (!/^[0-9a-fA-F]+$/.test(hex)) throw new Error(`十六进制字符串包含无效字符`);
      const bytes = new Uint8Array(hex.length / 2);
      for (let i = 0; i < bytes.length; i++) {
        bytes[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
      }
      return bytes;
    }
    default:
      return raw;
  }
}

// ── Actions: CRUD ──────────────────────────────────────────────────

function handleAddEntry() {
  if (!newEntry.namespace || !newEntry.key) return;

  const type = ENCODING_TO_TYPE[newEntry.encoding];
  let value: ReturnType<typeof parseValueInput>;
  try {
    value = parseValueInput(newEntry.encoding, newEntry.value);
  } catch (e: any) {
    showStatus(e.message ?? '值格式错误', 'error');
    return;
  }

  partition.value = addEntry(partition.value, {
    namespace: newEntry.namespace,
    key: newEntry.key,
    type,
    value,
  });

  showAddDialog.value = false;
  newEntry.key = '';
  newEntry.value = '';
  showStatus('已添加记录', 'success');
}

function handleAddNamespace() {
  const ns = newNamespace.value.trim();
  if (!ns) return;
  if (partition.value.namespaces.includes(ns)) {
    showStatus('命名空间已存在', 'error');
    return;
  }
  partition.value = {
    ...partition.value,
    namespaces: [...partition.value.namespaces, ns],
  };
  showNsDialog.value = false;
  newNamespace.value = '';
  // Auto-select new namespace in add dialog
  newEntry.namespace = ns;
  showStatus(`已添加命名空间 "${ns}"`, 'success');
}

function handleDeleteEntry(entryId: string) {
  partition.value = removeEntry(partition.value, entryId);
}

function handleDuplicateEntry(entryId: string) {
  partition.value = duplicateEntry(partition.value, entryId);
  showStatus('已复制记录', 'success');
}

function handleSort() {
  partition.value = sortEntries(partition.value);
}

function handleClear() {
  partition.value = createEmptyPartition(partition.value.version);
  showStatus('已清空所有记录', 'info');
}

// ── Actions: Inline edit ───────────────────────────────────────────

function handleUpdateKey(entryId: string, newKey: string) {
  partition.value = updateEntry(partition.value, entryId, { key: newKey });
}

function handleUpdateNamespace(entryId: string, ns: string) {
  partition.value = updateEntry(partition.value, entryId, { namespace: ns });
}

function handleUpdateEncoding(entryId: string, encoding: NvsEncoding) {
  const type = ENCODING_TO_TYPE[encoding];
  // Reset value to sensible default when type changes
  let value: NvsEntry['value'];
  if (isPrimitiveType(type)) value = 0;
  else if (type === NvsType.SZ) value = '';
  else value = new Uint8Array(0);

  partition.value = updateEntry(partition.value, entryId, { type, value });
}

function handleUpdateValue(entryId: string, encoding: NvsEncoding, raw: string) {
  let value: ReturnType<typeof parseValueInput>;
  try {
    value = parseValueInput(encoding, raw);
  } catch (e: any) {
    showStatus(e.message ?? '值格式错误', 'error');
    return;
  }
  partition.value = updateEntry(partition.value, entryId, { value });
}

// ── Actions: File I/O ──────────────────────────────────────────────

async function handleOpenBinary(file: File): Promise<false> {
  try {
    const buffer = await file.arrayBuffer();
    const data = new Uint8Array(buffer);
    partition.value = parseBinary(data);
    // Auto-set target size to match file
    targetSize.value = data.byteLength;
    showStatus(`已加载 ${file.name} (${data.byteLength} 字节)`, 'success');
  } catch (e: any) {
    showStatus(`加载失败: ${e.message}`, 'error');
  }
  return false; // prevent el-upload auto-upload
}

function handleExportBinary() {
  try {
    const errs = validatePartition(partition.value);
    if (errs.length > 0) {
      showStatus(`验证错误: ${errs[0]}`, 'error');
      return;
    }
    const data = serializeBinary(partition.value, targetSize.value);
    downloadBlob(new Blob([data]), 'nvs.bin');
    showStatus('已导出 nvs.bin', 'success');
  } catch (e: any) {
    showStatus(`导出失败: ${e.message}`, 'error');
  }
}

async function handleMergeBinary(file: File): Promise<false> {
  try {
    const buffer = await file.arrayBuffer();
    const incoming = parseBinary(new Uint8Array(buffer));
    partition.value = mergePartitions(partition.value, incoming, mergeMode.value);
    showStatus(`已合并 ${file.name} (${incoming.entries.length} 条记录)`, 'success');
  } catch (e: any) {
    showStatus(`合并失败: ${e.message}`, 'error');
  }
  return false;
}

async function handleOpenCsv(file: File): Promise<false> {
  try {
    const text = await file.text();
    partition.value = parseCsv(text);
    showStatus(`已加载 ${file.name}`, 'success');
  } catch (e: any) {
    showStatus(`加载失败: ${e.message}`, 'error');
  }
  return false;
}

function handleExportCsv() {
  try {
    const text = serializeCsv(partition.value);
    downloadBlob(new Blob([text], { type: 'text/csv;charset=utf-8' }), 'nvs.csv');
    showStatus('已导出 nvs.csv', 'success');
  } catch (e: any) {
    showStatus(`导出失败: ${e.message}`, 'error');
  }
}

async function handleMergeCsv(file: File): Promise<false> {
  try {
    const text = await file.text();
    const incoming = parseCsv(text);
    partition.value = mergePartitions(partition.value, incoming, mergeMode.value);
    showStatus(`已合并 ${file.name} (${incoming.entries.length} 条记录)`, 'success');
  } catch (e: any) {
    showStatus(`合并失败: ${e.message}`, 'error');
  }
  return false;
}

async function handleBlobFileUpload(entryId: string, file: File): Promise<false> {
  try {
    const buffer = await file.arrayBuffer();
    const data = new Uint8Array(buffer);
    partition.value = updateEntry(partition.value, entryId, { value: data });
    showStatus(`已上传 ${file.name} (${data.length} 字节)`, 'success');
  } catch (e: any) {
    showStatus(`上传失败: ${e.message}`, 'error');
  }
  return false;
}
</script>

<template>
  <div>
    <!-- Status message -->
    <transition name="el-fade-in">
      <el-alert
        v-if="statusMessage"
        :title="statusMessage"
        :type="statusType"
        show-icon
        closable
        class="mb-3"
        @close="statusMessage = ''"
      />
    </transition>

    <!-- Validation errors -->
    <el-alert
      v-if="errors.length > 0"
      type="warning"
      show-icon
      class="mb-3"
      :closable="false"
    >
      <template #title>
        验证问题 ({{ errors.length }})
      </template>
      <div v-for="(err, i) in errors" :key="i" class="text-xs">{{ err }}</div>
    </el-alert>

    <!-- ── Toolbar ── -->
    <div class="flex flex-wrap items-center gap-2 mb-3">
      <el-button type="primary" @click="showAddDialog = true">
        添加记录
      </el-button>
      <el-button @click="showNsDialog = true">
        添加命名空间
      </el-button>
      <el-button @click="handleSort">排序</el-button>
      <el-button type="danger" plain @click="handleClear">清空</el-button>

      <el-divider direction="vertical" />

      <span class="text-sm">分区大小:</span>
      <el-select v-model="targetSize" style="width: 160px;">
        <el-option
          v-for="opt in sizeOptions"
          :key="opt.value"
          :label="opt.label"
          :value="opt.value"
        />
      </el-select>

      <el-divider direction="vertical" />

      <div class="flex items-center gap-2 min-w-[200px]">
        <el-progress
          :percentage="flashStats.usagePercent"
          :color="progressColor"
          :stroke-width="14"
          :show-text="false"
          style="flex: 1;"
        />
        <el-text size="small">
          {{ flashStats.usedEntries }} / {{ flashStats.maxEntries }} 条目
        </el-text>
      </div>
    </div>

    <!-- ── Filter row ── -->
    <div class="flex flex-wrap items-center gap-2 mb-3">
      <el-select
        v-model="namespaceFilter"
        placeholder="全部命名空间"
        clearable
        style="width: 180px;"
      >
        <el-option
          v-for="ns in partition.namespaces"
          :key="ns"
          :label="ns"
          :value="ns"
        />
      </el-select>
      <el-input
        v-model="keySearch"
        placeholder="搜索键名..."
        clearable
        style="width: 200px;"
      />
    </div>

    <!-- ── Data table ── -->
    <el-table
      :data="filteredEntries"
      border
      stripe
      size="small"
      row-key="id"
      empty-text="暂无记录，请添加或导入数据"
      max-height="500"
    >
      <el-table-column label="命名空间" width="150">
        <template #default="{ row }">
          <el-select
            :model-value="row.namespace"
            size="small"
            @change="(val: string) => handleUpdateNamespace(row.id, val)"
          >
            <el-option
              v-for="ns in partition.namespaces"
              :key="ns"
              :label="ns"
              :value="ns"
            />
          </el-select>
        </template>
      </el-table-column>

      <el-table-column label="键名" width="180">
        <template #default="{ row }">
          <el-input
            :model-value="row.key"
            size="small"
            :maxlength="MAX_KEY_LENGTH"
            @change="(val: string) => handleUpdateKey(row.id, val)"
          />
        </template>
      </el-table-column>

      <el-table-column label="类型" width="120">
        <template #default="{ row }">
          <el-select
            :model-value="getEncodingForType(row.type)"
            size="small"
            @change="(val: NvsEncoding) => handleUpdateEncoding(row.id, val)"
          >
            <el-option
              v-for="enc in ENCODING_OPTIONS"
              :key="enc"
              :label="enc"
              :value="enc"
            />
          </el-select>
        </template>
      </el-table-column>

      <el-table-column label="值" min-width="250">
        <template #default="{ row }">
          <!-- Primitive types: input -->
          <el-input
            v-if="isPrimitiveType(row.type)"
            :model-value="String(row.value)"
            size="small"
            @change="(val: string) => handleUpdateValue(row.id, getEncodingForType(row.type), val)"
          />
          <!-- String type -->
          <el-input
            v-else-if="row.type === NvsType.SZ"
            :model-value="row.value as string"
            size="small"
            type="textarea"
            :autosize="{ minRows: 1, maxRows: 3 }"
            @change="(val: string) => handleUpdateValue(row.id, 'string', val)"
          />
          <!-- Blob types -->
          <div v-else class="flex items-center gap-1">
            <el-text size="small" class="font-mono" truncated>
              {{ formatValue(row) }}
            </el-text>
            <el-upload
              :before-upload="(file: File) => handleBlobFileUpload(row.id, file)"
              :show-file-list="false"
              accept="*/*"
            >
              <el-button size="small" type="info" plain>上传文件</el-button>
            </el-upload>
          </div>
        </template>
      </el-table-column>

      <el-table-column label="操作" width="120" fixed="right">
        <template #default="{ row }">
          <el-button
            size="small"
            text
            @click="handleDuplicateEntry(row.id)"
            title="复制"
          >
            复制
          </el-button>
          <el-popconfirm
            title="确定删除?"
            @confirm="handleDeleteEntry(row.id)"
          >
            <template #reference>
              <el-button size="small" text type="danger" title="删除">
                删除
              </el-button>
            </template>
          </el-popconfirm>
        </template>
      </el-table-column>
    </el-table>

    <!-- ── Import/Export section ── -->
    <el-divider />
    <div class="flex flex-wrap gap-4">
      <!-- Binary -->
      <div>
        <el-text tag="b" class="block mb-2">二进制文件 (.bin)</el-text>
        <div class="flex flex-wrap gap-2">
          <el-upload :before-upload="handleOpenBinary" :show-file-list="false" accept=".bin">
            <el-button>打开</el-button>
          </el-upload>
          <el-button type="primary" @click="handleExportBinary">导出</el-button>
          <el-upload :before-upload="handleMergeBinary" :show-file-list="false" accept=".bin">
            <el-button>合并</el-button>
          </el-upload>
        </div>
      </div>

      <!-- CSV -->
      <div>
        <el-text tag="b" class="block mb-2">CSV文件 (.csv)</el-text>
        <div class="flex flex-wrap gap-2">
          <el-upload :before-upload="handleOpenCsv" :show-file-list="false" accept=".csv">
            <el-button>打开</el-button>
          </el-upload>
          <el-button type="primary" @click="handleExportCsv">导出</el-button>
          <el-upload :before-upload="handleMergeCsv" :show-file-list="false" accept=".csv">
            <el-button>合并</el-button>
          </el-upload>
        </div>
      </div>

      <!-- Merge options -->
      <div>
        <el-text tag="b" class="block mb-2">合并选项</el-text>
        <el-radio-group v-model="mergeMode">
          <el-radio value="overwrite">覆盖同名键</el-radio>
          <el-radio value="skip">跳过同名键</el-radio>
        </el-radio-group>
      </div>
    </div>

    <!-- ── Add entry dialog ── -->
    <el-dialog v-model="showAddDialog" title="添加记录" width="450px">
      <el-form label-width="80px">
        <el-form-item label="命名空间">
          <el-select v-model="newEntry.namespace" placeholder="选择命名空间">
            <el-option
              v-for="ns in partition.namespaces"
              :key="ns"
              :label="ns"
              :value="ns"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="键名">
          <el-input v-model="newEntry.key" :maxlength="MAX_KEY_LENGTH" placeholder="key name" />
        </el-form-item>
        <el-form-item label="类型">
          <el-select v-model="newEntry.encoding">
            <el-option v-for="enc in ENCODING_OPTIONS" :key="enc" :label="enc" :value="enc" />
          </el-select>
        </el-form-item>
        <el-form-item label="值">
          <el-input
            v-model="newEntry.value"
            :type="newEntry.encoding === 'string' ? 'textarea' : 'text'"
            placeholder="value"
          />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="showAddDialog = false">取消</el-button>
        <el-button type="primary" @click="handleAddEntry" :disabled="!newEntry.namespace || !newEntry.key">
          添加
        </el-button>
      </template>
    </el-dialog>

    <!-- ── Add namespace dialog ── -->
    <el-dialog v-model="showNsDialog" title="添加命名空间" width="400px">
      <el-form label-width="80px">
        <el-form-item label="名称">
          <el-input
            v-model="newNamespace"
            :maxlength="MAX_KEY_LENGTH"
            placeholder="namespace name"
            @keyup.enter="handleAddNamespace"
          />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="showNsDialog = false">取消</el-button>
        <el-button type="primary" @click="handleAddNamespace" :disabled="!newNamespace.trim()">
          添加
        </el-button>
      </template>
    </el-dialog>
  </div>
</template>

<style scoped>
.font-mono {
  font-family: 'Courier New', Courier, monospace;
}
</style>
