<script setup lang="ts">
import { ref, computed } from 'vue';
import {
  type PartitionTable, type PartitionEntry,
  PartitionType, PartitionFlags,
  TYPE_NAMES, NAME_TO_TYPE,
  getSubtypeName, subtypeFromName,
  AppSubtype, DataSubtype,
  APP_SUBTYPE_NAMES, DATA_SUBTYPE_NAMES,
  parseBinary, serializeBinary,
  parseCsv, serializeCsv,
  validateTable,
  NAME_FIELD_SIZE,
} from '../../lib/partition-table';

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

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

const table = ref<PartitionTable>({ entries: [], md5Valid: false });
const statusMessage = ref('');
const statusType = ref<'success' | 'error' | 'info'>('info');

// Add dialog
const showAddDialog = ref(false);
const newEntry = ref<PartitionEntry>({
  name: '',
  type: PartitionType.DATA,
  subtype: DataSubtype.NVS,
  offset: 0,
  size: 0x1000,
  flags: PartitionFlags.NONE,
});

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

const subtypeOptions = computed(() => {
  return newEntry.value.type === PartitionType.APP
    ? Object.entries(APP_SUBTYPE_NAMES)
    : Object.entries(DATA_SUBTYPE_NAMES);
});

function getSubtypeOptionsForType(type: PartitionType) {
  return type === PartitionType.APP
    ? Object.entries(APP_SUBTYPE_NAMES)
    : Object.entries(DATA_SUBTYPE_NAMES);
}

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

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

function formatHex(val: number): string {
  return '0x' + val.toString(16).toUpperCase();
}

function formatSize(size: number): string {
  if (size >= 1024 * 1024 && size % (1024 * 1024) === 0) {
    return `${size / (1024 * 1024)} MB`;
  }
  if (size >= 1024 && size % 1024 === 0) {
    return `${size / 1024} KB`;
  }
  return `${size} B`;
}

function parseHexOrDec(str: string): number {
  str = str.trim();
  if (str.startsWith('0x') || str.startsWith('0X')) {
    if (!/^0x[0-9a-f]+$/i.test(str)) throw new Error(`"${str}"`);
    const v = parseInt(str, 16);
    if (isNaN(v) || v < 0 || v > 0xFFFF_FFFF) throw new Error(`"${str}"`);
    return v;
  }
  // Support K/M suffixes
  const match = str.match(/^(\d+(?:\.\d+)?)\s*([KkMm])?$/);
  if (match) {
    const num = parseFloat(match[1]);
    const unit = (match[2] || '').toUpperCase();
    let result: number;
    if (unit === 'K') result = Math.floor(num * 1024);
    else if (unit === 'M') result = Math.floor(num * 1024 * 1024);
    else result = Math.floor(num);
    if (result > 0xFFFF_FFFF) throw new Error(`"${str}" (超出 32 位范围)`);
    return result;
  }
  throw new Error(`"${str}"`);
}

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);
}

// ── Actions ────────────────────────────────────────────────────────

function handleAddEntry() {
  if (!newEntry.value.name) return;
  table.value = {
    ...table.value,
    entries: [...table.value.entries, { ...newEntry.value }],
  };
  showAddDialog.value = false;
  newEntry.value = {
    name: '',
    type: PartitionType.DATA,
    subtype: DataSubtype.NVS,
    offset: 0,
    size: 0x1000,
    flags: PartitionFlags.NONE,
  };
  showStatus('已添加分区', 'success');
}

function handleDeleteEntry(index: number) {
  const entries = [...table.value.entries];
  entries.splice(index, 1);
  table.value = { ...table.value, entries };
}

function handleMoveUp(index: number) {
  if (index <= 0) return;
  const entries = [...table.value.entries];
  [entries[index - 1], entries[index]] = [entries[index], entries[index - 1]];
  table.value = { ...table.value, entries };
}

function handleMoveDown(index: number) {
  if (index >= table.value.entries.length - 1) return;
  const entries = [...table.value.entries];
  [entries[index], entries[index + 1]] = [entries[index + 1], entries[index]];
  table.value = { ...table.value, entries };
}

function handleUpdateName(index: number, val: string) {
  const entries = [...table.value.entries];
  entries[index] = { ...entries[index], name: val.substring(0, NAME_FIELD_SIZE - 1) };
  table.value = { ...table.value, entries };
}

function handleUpdateType(index: number, val: PartitionType) {
  const entries = [...table.value.entries];
  // Reset subtype when type changes
  const defaultSubtype = val === PartitionType.APP ? AppSubtype.FACTORY : DataSubtype.NVS;
  entries[index] = { ...entries[index], type: val, subtype: defaultSubtype };
  table.value = { ...table.value, entries };
}

function handleUpdateSubtype(index: number, val: number) {
  const entries = [...table.value.entries];
  entries[index] = { ...entries[index], subtype: val };
  table.value = { ...table.value, entries };
}

function handleUpdateOffset(index: number, val: string) {
  try {
    const entries = [...table.value.entries];
    entries[index] = { ...entries[index], offset: parseHexOrDec(val) };
    table.value = { ...table.value, entries };
  } catch (e: any) {
    showStatus(`无效的偏移量: ${e.message}`, 'error');
  }
}

function handleUpdateSize(index: number, val: string) {
  try {
    const entries = [...table.value.entries];
    entries[index] = { ...entries[index], size: parseHexOrDec(val) };
    table.value = { ...table.value, entries };
  } catch (e: any) {
    showStatus(`无效的大小: ${e.message}`, 'error');
  }
}

function handleUpdateFlags(index: number, encrypted: boolean) {
  const entries = [...table.value.entries];
  const cur = entries[index].flags;
  entries[index] = { ...entries[index], flags: encrypted
    ? cur | PartitionFlags.ENCRYPTED
    : cur & ~PartitionFlags.ENCRYPTED };
  table.value = { ...table.value, entries };
}

function handleClear() {
  table.value = { entries: [], md5Valid: false };
  showStatus('已清空分区表', 'info');
}

// ── File I/O ───────────────────────────────────────────────────────

async function handleOpenBinary(file: File): Promise<false> {
  try {
    const buffer = await file.arrayBuffer();
    table.value = parseBinary(new Uint8Array(buffer));
    const md5Status = table.value.md5Valid ? '有效' : '无效/缺失';
    const corruptedNote = table.value.corrupted ? ', 检测到二进制损坏' : '';
    showStatus(
      `已加载 ${file.name} (${table.value.entries.length} 分区, MD5: ${md5Status}${corruptedNote})`,
      table.value.corrupted ? 'error' : 'success',
    );
  } catch (e: any) {
    showStatus(`加载失败: ${e.message}`, 'error');
  }
  return false;
}

function handleExportBinary() {
  const errors = validateTable(table.value);
  if (errors.length > 0) {
    const suffix = errors.length > 1 ? ` 等 ${errors.length} 个问题` : '';
    showStatus(`导出取消: ${errors[0].message}${suffix}`, 'error');
    return;
  }
  try {
    const data = serializeBinary(table.value);
    downloadBlob(new Blob([data]), 'partitions.bin');
    showStatus('已导出 partitions.bin', 'success');
  } catch (e: any) {
    showStatus(`导出失败: ${e.message}`, 'error');
  }
}

async function handleOpenCsv(file: File): Promise<false> {
  try {
    const text = await file.text();
    const warnings: string[] = [];
    table.value = parseCsv(text, (line, msg) => warnings.push(`行 ${line}: ${msg}`));
    if (warnings.length > 0) {
      const preview = warnings.slice(0, 2).join('; ') + (warnings.length > 2 ? '…' : '');
      showStatus(`已加载 ${file.name} (${table.value.entries.length} 分区，${warnings.length} 个警告: ${preview})`, 'error');
    } else {
      showStatus(`已加载 ${file.name} (${table.value.entries.length} 分区)`, 'success');
    }
  } catch (e: any) {
    showStatus(`加载失败: ${e.message}`, 'error');
  }
  return false;
}

function handleExportCsv() {
  try {
    const text = serializeCsv(table.value);
    downloadBlob(new Blob([text], { type: 'text/csv;charset=utf-8' }), 'partitions.csv');
    showStatus('已导出 partitions.csv', 'success');
  } catch (e: any) {
    showStatus(`导出失败: ${e.message}`, 'error');
  }
}
</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>

    <!-- ── Toolbar ── -->
    <div class="flex flex-wrap items-center gap-2 mb-3">
      <el-button type="primary" @click="showAddDialog = true">添加分区</el-button>
      <el-button type="danger" plain @click="handleClear">清空</el-button>
      <el-divider direction="vertical" />
      <el-text size="small">{{ table.entries.length }} 个分区</el-text>
    </div>

    <!-- ── Data table ── -->
    <el-table
      :data="table.entries"
      border
      stripe
      size="small"
      empty-text="暂无分区，请添加或导入"
      max-height="500"
    >
      <el-table-column label="名称" width="160">
        <template #default="{ row, $index }">
          <el-input
            :model-value="row.name"
            size="small"
            :maxlength="15"
            @change="(val: string) => handleUpdateName($index, val)"
          />
        </template>
      </el-table-column>

      <el-table-column label="类型" width="110">
        <template #default="{ row, $index }">
          <el-select
            :model-value="row.type"
            size="small"
            @change="(val: PartitionType) => handleUpdateType($index, val)"
          >
            <el-option label="app" :value="PartitionType.APP" />
            <el-option label="data" :value="PartitionType.DATA" />
          </el-select>
        </template>
      </el-table-column>

      <el-table-column label="子类型" width="140">
        <template #default="{ row, $index }">
          <el-select
            :model-value="row.subtype"
            size="small"
            @change="(val: number) => handleUpdateSubtype($index, val)"
          >
            <el-option
              v-for="[val, name] in getSubtypeOptionsForType(row.type)"
              :key="val"
              :label="name"
              :value="Number(val)"
            />
          </el-select>
        </template>
      </el-table-column>

      <el-table-column label="偏移量" width="140">
        <template #default="{ row, $index }">
          <el-input
            :model-value="formatHex(row.offset)"
            size="small"
            @change="(val: string) => handleUpdateOffset($index, val)"
          />
        </template>
      </el-table-column>

      <el-table-column label="大小" width="140">
        <template #default="{ row, $index }">
          <el-input
            :model-value="formatHex(row.size)"
            size="small"
            @change="(val: string) => handleUpdateSize($index, val)"
          >
            <template #append>
              <span class="text-xs">{{ formatSize(row.size) }}</span>
            </template>
          </el-input>
        </template>
      </el-table-column>

      <el-table-column label="加密" width="70" align="center">
        <template #default="{ row, $index }">
          <el-checkbox
            :model-value="(row.flags & 0x01) !== 0"
            @change="(val: boolean) => handleUpdateFlags($index, val)"
          />
        </template>
      </el-table-column>

      <el-table-column label="操作" width="130" fixed="right">
        <template #default="{ $index }">
          <el-button size="small" text @click="handleMoveUp($index)" :disabled="$index === 0">上</el-button>
          <el-button size="small" text @click="handleMoveDown($index)" :disabled="$index === table.entries.length - 1">下</el-button>
          <el-popconfirm title="确定删除?" @confirm="handleDeleteEntry($index)">
            <template #reference>
              <el-button size="small" text type="danger">删除</el-button>
            </template>
          </el-popconfirm>
        </template>
      </el-table-column>
    </el-table>

    <!-- ── Import/Export section ── -->
    <el-divider />
    <div class="flex flex-wrap gap-4">
      <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>
        </div>
      </div>

      <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>
        </div>
      </div>
    </div>

    <!-- ── Add partition dialog ── -->
    <el-dialog v-model="showAddDialog" title="添加分区" width="450px">
      <el-form label-width="80px">
        <el-form-item label="名称">
          <el-input v-model="newEntry.name" :maxlength="15" placeholder="partition name" />
        </el-form-item>
        <el-form-item label="类型">
          <el-select v-model="newEntry.type">
            <el-option label="app" :value="PartitionType.APP" />
            <el-option label="data" :value="PartitionType.DATA" />
          </el-select>
        </el-form-item>
        <el-form-item label="子类型">
          <el-select v-model="newEntry.subtype">
            <el-option
              v-for="[val, name] in subtypeOptions"
              :key="val"
              :label="name"
              :value="Number(val)"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="偏移量">
          <el-input
            :model-value="formatHex(newEntry.offset)"
            @change="(val: string) => { try { newEntry.offset = parseHexOrDec(val) } catch { showStatus('无效的偏移量', 'error') } }"
            placeholder="0x9000"
          />
        </el-form-item>
        <el-form-item label="大小">
          <el-input
            :model-value="formatHex(newEntry.size)"
            @change="(val: string) => { try { newEntry.size = parseHexOrDec(val) } catch { showStatus('无效的大小', 'error') } }"
            placeholder="0x6000 or 24K"
          />
        </el-form-item>
        <el-form-item label="加密">
          <el-checkbox
            :model-value="(newEntry.flags & PartitionFlags.ENCRYPTED) !== 0"
            @change="(val: boolean) => newEntry.flags = val
              ? newEntry.flags | PartitionFlags.ENCRYPTED
              : newEntry.flags & ~PartitionFlags.ENCRYPTED"
          />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="showAddDialog = false">取消</el-button>
        <el-button type="primary" @click="handleAddEntry" :disabled="!newEntry.name">添加</el-button>
      </template>
    </el-dialog>
  </div>
</template>
