import React, { useState, useEffect, useMemo } from 'react';
import {
  Search, Plus, X, Mail, Edit2, Trash2, Check, Clock,
  Settings as SettingsIcon, Copy, ChevronRight,
  Building2, Globe, Phone, Save, RefreshCw, LayoutDashboard, Users,
  Package, Layers, Zap, Tag, Inbox, Paperclip,
  Menu, LogOut, GripVertical, BookOpen, FileText, FolderTree, Lock, Crown, Sparkles
} from 'lucide-react';
import {
  storageGet, storageSet, uploadFile, deleteFile,
  authCheck, authLogin, authLogout, authChangePassword,
  getAiConfig, saveAiConfig, deleteAiConfig, testAiConfig, generateAiDraft,
} from './storage';

/* ----------------------------- constants ----------------------------- */

const REGIONS = [
  { id: 'africa', label: 'Africa / 非洲' },
  { id: 'middle_east', label: 'Middle East / 中东' },
  { id: 'australia', label: 'Australia / 大洋洲' },
  { id: 'europe', label: 'Europe / 欧洲' },
  { id: 'sea', label: 'SE Asia / 东南亚' },
  { id: 'other', label: 'Other / 其他' },
];

const PRODUCT_LINES = ['UPS不间断电源', '光伏逆变器', '储能电池', 'Other'];

const PRODUCT_CATEGORIES = [
  {
    id: 'ups', label: 'UPS不间断电源 / UPS', short: 'UPS不间断电源',
    subs: [
      { id: 'mini', label: '迷你UPS / Mini UPS', short: '迷你UPS' },
      { id: 'offline', label: '离线式UPS / Offline UPS', short: '离线式UPS' },
      { id: 'line_interactive', label: '在线互动式UPS / Line-Interactive UPS', short: '在线互动式UPS' },
      {
        id: 'online', label: '在线式UPS / Online UPS', short: '在线式UPS',
        subs: [
          { id: 'rack', label: '机架式UPS / Rack-mount UPS', short: '机架式UPS' },
          { id: 'high_freq_tower', label: '高频塔式UPS / High-Frequency Tower UPS', short: '高频塔式UPS' },
          { id: 'low_freq', label: '工频UPS / Low-Frequency UPS', short: '工频UPS' },
          { id: 'modular', label: '模块化UPS / Modular UPS', short: '模块化UPS' },
        ],
      },
    ],
  },
  {
    id: 'inverter', label: '光伏逆变器 / Solar Inverter', short: '光伏逆变器',
    subs: [
      { id: 'hybrid', label: '太阳能混网逆变器 / Hybrid Solar Inverter', short: '太阳能混网逆变器' },
      { id: 'grid_off_grid', label: '并网/离网逆变器 / Grid-tied / Off-grid Inverter', short: '并网/离网逆变器' },
    ],
  },
  {
    id: 'battery', label: '储能电池 / Energy Storage Battery', short: '储能电池',
    subs: [
      { id: 'lead_acid', label: '铅酸电池 / Lead-acid Battery', short: '铅酸电池' },
      { id: 'lifepo4', label: '磷酸铁锂电池 / LiFePO4 Battery', short: '磷酸铁锂电池' },
    ],
  },
];

const DEFAULT_SETTINGS = { defaultFollowUpDays: 14, dueSoonWindow: 7 };

const CUSTOMER_SOURCES = ['Made-in-China', '官网', '展会', 'Other'];
const FILE_CATEGORIES = ['报价单', '合同', '聊天记录', '产品资料', '物流单据', 'Other'];

// 国家 -> 地区 / 电话国家代码 / 时区（以首都为准）映射表
// 用于：选择国家后自动匹配所属大洲、电话区号前缀、当地时间
const COUNTRIES = [
  // Africa / 非洲
  { name: '尼日利亚', en: 'Nigeria', region: 'africa', dial: '+234', tz: 'Africa/Lagos' },
  { name: '埃及', en: 'Egypt', region: 'africa', dial: '+20', tz: 'Africa/Cairo' },
  { name: '南非', en: 'South Africa', region: 'africa', dial: '+27', tz: 'Africa/Johannesburg' },
  { name: '肯尼亚', en: 'Kenya', region: 'africa', dial: '+254', tz: 'Africa/Nairobi' },
  { name: '加纳', en: 'Ghana', region: 'africa', dial: '+233', tz: 'Africa/Accra' },
  { name: '阿尔及利亚', en: 'Algeria', region: 'africa', dial: '+213', tz: 'Africa/Algiers' },
  { name: '摩洛哥', en: 'Morocco', region: 'africa', dial: '+212', tz: 'Africa/Casablanca' },
  { name: '突尼斯', en: 'Tunisia', region: 'africa', dial: '+216', tz: 'Africa/Tunis' },
  { name: '埃塞俄比亚', en: 'Ethiopia', region: 'africa', dial: '+251', tz: 'Africa/Addis_Ababa' },
  { name: '坦桑尼亚', en: 'Tanzania', region: 'africa', dial: '+255', tz: 'Africa/Dar_es_Salaam' },
  { name: '乌干达', en: 'Uganda', region: 'africa', dial: '+256', tz: 'Africa/Kampala' },
  { name: '安哥拉', en: 'Angola', region: 'africa', dial: '+244', tz: 'Africa/Luanda' },
  { name: '莫桑比克', en: 'Mozambique', region: 'africa', dial: '+258', tz: 'Africa/Maputo' },
  { name: '赞比亚', en: 'Zambia', region: 'africa', dial: '+260', tz: 'Africa/Lusaka' },
  { name: '津巴布韦', en: 'Zimbabwe', region: 'africa', dial: '+263', tz: 'Africa/Harare' },
  { name: '塞内加尔', en: 'Senegal', region: 'africa', dial: '+221', tz: 'Africa/Dakar' },
  { name: '科特迪瓦', en: "Cote d'Ivoire", region: 'africa', dial: '+225', tz: 'Africa/Abidjan' },
  { name: '喀麦隆', en: 'Cameroon', region: 'africa', dial: '+237', tz: 'Africa/Douala' },
  { name: '刚果(金)', en: 'DR Congo', region: 'africa', dial: '+243', tz: 'Africa/Kinshasa' },
  { name: '刚果(布)', en: 'Congo', region: 'africa', dial: '+242', tz: 'Africa/Brazzaville' },
  { name: '利比亚', en: 'Libya', region: 'africa', dial: '+218', tz: 'Africa/Tripoli' },
  { name: '苏丹', en: 'Sudan', region: 'africa', dial: '+249', tz: 'Africa/Khartoum' },
  { name: '卢旺达', en: 'Rwanda', region: 'africa', dial: '+250', tz: 'Africa/Kigali' },
  { name: '博茨瓦纳', en: 'Botswana', region: 'africa', dial: '+267', tz: 'Africa/Gaborone' },
  { name: '纳米比亚', en: 'Namibia', region: 'africa', dial: '+264', tz: 'Africa/Windhoek' },
  { name: '马里', en: 'Mali', region: 'africa', dial: '+223', tz: 'Africa/Bamako' },
  { name: '尼日尔', en: 'Niger', region: 'africa', dial: '+227', tz: 'Africa/Niamey' },

  // Middle East / 中东
  { name: '沙特阿拉伯', en: 'Saudi Arabia', region: 'middle_east', dial: '+966', tz: 'Asia/Riyadh' },
  { name: '阿联酋', en: 'UAE', region: 'middle_east', dial: '+971', tz: 'Asia/Dubai' },
  { name: '卡塔尔', en: 'Qatar', region: 'middle_east', dial: '+974', tz: 'Asia/Qatar' },
  { name: '科威特', en: 'Kuwait', region: 'middle_east', dial: '+965', tz: 'Asia/Kuwait' },
  { name: '巴林', en: 'Bahrain', region: 'middle_east', dial: '+973', tz: 'Asia/Bahrain' },
  { name: '阿曼', en: 'Oman', region: 'middle_east', dial: '+968', tz: 'Asia/Muscat' },
  { name: '伊拉克', en: 'Iraq', region: 'middle_east', dial: '+964', tz: 'Asia/Baghdad' },
  { name: '伊朗', en: 'Iran', region: 'middle_east', dial: '+98', tz: 'Asia/Tehran' },
  { name: '约旦', en: 'Jordan', region: 'middle_east', dial: '+962', tz: 'Asia/Amman' },
  { name: '黎巴嫩', en: 'Lebanon', region: 'middle_east', dial: '+961', tz: 'Asia/Beirut' },
  { name: '以色列', en: 'Israel', region: 'middle_east', dial: '+972', tz: 'Asia/Jerusalem' },
  { name: '也门', en: 'Yemen', region: 'middle_east', dial: '+967', tz: 'Asia/Aden' },
  { name: '叙利亚', en: 'Syria', region: 'middle_east', dial: '+963', tz: 'Asia/Damascus' },
  { name: '土耳其', en: 'Turkey', region: 'middle_east', dial: '+90', tz: 'Europe/Istanbul' },

  // Australia / 大洋洲
  { name: '澳大利亚', en: 'Australia', region: 'australia', dial: '+61', tz: 'Australia/Sydney' },
  { name: '新西兰', en: 'New Zealand', region: 'australia', dial: '+64', tz: 'Pacific/Auckland' },
  { name: '巴布亚新几内亚', en: 'Papua New Guinea', region: 'australia', dial: '+675', tz: 'Pacific/Port_Moresby' },
  { name: '斐济', en: 'Fiji', region: 'australia', dial: '+679', tz: 'Pacific/Fiji' },

  // Europe / 欧洲
  { name: '俄罗斯', en: 'Russia', region: 'europe', dial: '+7', tz: 'Europe/Moscow' },
  { name: '德国', en: 'Germany', region: 'europe', dial: '+49', tz: 'Europe/Berlin' },
  { name: '法国', en: 'France', region: 'europe', dial: '+33', tz: 'Europe/Paris' },
  { name: '英国', en: 'United Kingdom', region: 'europe', dial: '+44', tz: 'Europe/London' },
  { name: '意大利', en: 'Italy', region: 'europe', dial: '+39', tz: 'Europe/Rome' },
  { name: '西班牙', en: 'Spain', region: 'europe', dial: '+34', tz: 'Europe/Madrid' },
  { name: '波兰', en: 'Poland', region: 'europe', dial: '+48', tz: 'Europe/Warsaw' },
  { name: '乌克兰', en: 'Ukraine', region: 'europe', dial: '+380', tz: 'Europe/Kyiv' },
  { name: '荷兰', en: 'Netherlands', region: 'europe', dial: '+31', tz: 'Europe/Amsterdam' },
  { name: '比利时', en: 'Belgium', region: 'europe', dial: '+32', tz: 'Europe/Brussels' },
  { name: '瑞典', en: 'Sweden', region: 'europe', dial: '+46', tz: 'Europe/Stockholm' },
  { name: '挪威', en: 'Norway', region: 'europe', dial: '+47', tz: 'Europe/Oslo' },
  { name: '芬兰', en: 'Finland', region: 'europe', dial: '+358', tz: 'Europe/Helsinki' },
  { name: '丹麦', en: 'Denmark', region: 'europe', dial: '+45', tz: 'Europe/Copenhagen' },
  { name: '瑞士', en: 'Switzerland', region: 'europe', dial: '+41', tz: 'Europe/Zurich' },
  { name: '奥地利', en: 'Austria', region: 'europe', dial: '+43', tz: 'Europe/Vienna' },
  { name: '葡萄牙', en: 'Portugal', region: 'europe', dial: '+351', tz: 'Europe/Lisbon' },
  { name: '希腊', en: 'Greece', region: 'europe', dial: '+30', tz: 'Europe/Athens' },
  { name: '罗马尼亚', en: 'Romania', region: 'europe', dial: '+40', tz: 'Europe/Bucharest' },
  { name: '匈牙利', en: 'Hungary', region: 'europe', dial: '+36', tz: 'Europe/Budapest' },
  { name: '捷克', en: 'Czech Republic', region: 'europe', dial: '+420', tz: 'Europe/Prague' },
  { name: '塞尔维亚', en: 'Serbia', region: 'europe', dial: '+381', tz: 'Europe/Belgrade' },
  { name: '保加利亚', en: 'Bulgaria', region: 'europe', dial: '+359', tz: 'Europe/Sofia' },
  { name: '克罗地亚', en: 'Croatia', region: 'europe', dial: '+385', tz: 'Europe/Zagreb' },
  { name: '爱尔兰', en: 'Ireland', region: 'europe', dial: '+353', tz: 'Europe/Dublin' },

  // SE Asia / 东南亚
  { name: '印度尼西亚', en: 'Indonesia', region: 'sea', dial: '+62', tz: 'Asia/Jakarta' },
  { name: '马来西亚', en: 'Malaysia', region: 'sea', dial: '+60', tz: 'Asia/Kuala_Lumpur' },
  { name: '泰国', en: 'Thailand', region: 'sea', dial: '+66', tz: 'Asia/Bangkok' },
  { name: '越南', en: 'Vietnam', region: 'sea', dial: '+84', tz: 'Asia/Ho_Chi_Minh' },
  { name: '菲律宾', en: 'Philippines', region: 'sea', dial: '+63', tz: 'Asia/Manila' },
  { name: '新加坡', en: 'Singapore', region: 'sea', dial: '+65', tz: 'Asia/Singapore' },
  { name: '缅甸', en: 'Myanmar', region: 'sea', dial: '+95', tz: 'Asia/Yangon' },
  { name: '柬埔寨', en: 'Cambodia', region: 'sea', dial: '+855', tz: 'Asia/Phnom_Penh' },
  { name: '老挝', en: 'Laos', region: 'sea', dial: '+856', tz: 'Asia/Vientiane' },
  { name: '文莱', en: 'Brunei', region: 'sea', dial: '+673', tz: 'Asia/Brunei' },

  // Other / 其他
  { name: '美国', en: 'United States', region: 'other', dial: '+1', tz: 'America/New_York' },
  { name: '加拿大', en: 'Canada', region: 'other', dial: '+1', tz: 'America/Toronto' },
  { name: '巴西', en: 'Brazil', region: 'other', dial: '+55', tz: 'America/Sao_Paulo' },
  { name: '墨西哥', en: 'Mexico', region: 'other', dial: '+52', tz: 'America/Mexico_City' },
  { name: '阿根廷', en: 'Argentina', region: 'other', dial: '+54', tz: 'America/Argentina/Buenos_Aires' },
  { name: '印度', en: 'India', region: 'other', dial: '+91', tz: 'Asia/Kolkata' },
  { name: '巴基斯坦', en: 'Pakistan', region: 'other', dial: '+92', tz: 'Asia/Karachi' },
  { name: '孟加拉国', en: 'Bangladesh', region: 'other', dial: '+880', tz: 'Asia/Dhaka' },
  { name: '中国', en: 'China', region: 'other', dial: '+86', tz: 'Asia/Shanghai' },
  { name: '韩国', en: 'South Korea', region: 'other', dial: '+82', tz: 'Asia/Seoul' },
  { name: '日本', en: 'Japan', region: 'other', dial: '+81', tz: 'Asia/Tokyo' },
];

const COUNTRY_BY_NAME = Object.fromEntries(COUNTRIES.map(c => [c.name, c]));

const STORAGE_KEYS = { customers: 'crm:customers', products: 'crm:products', settings: 'crm:settings' };

const blankCustomer = () => ({
  id: 'c_' + Date.now() + '_' + Math.random().toString(36).slice(2, 7),
  company: '', contact: '', country: '', region: 'middle_east', email: '', phone: '',
  isVip: false,
  preferredContacts: [],
  website: '',
  productLines: [], productLinesNote: '',
  source: '',
  tags: '', notes: '', lastContact: '', followUpDays: '',
  warehouseAddress: '', freightContact: '', shippingMarkNote: '',
  attachments: [],
});

const blankProduct = () => ({
  id: 'p_' + Date.now() + '_' + Math.random().toString(36).slice(2, 7),
  name: '', brand: '', category: '', subCategory: '', subSubCategory: '',
  powerOrCapacity: '', voltage: '', certifications: '', targetRegions: [], notes: '',
});

/* ------------------------------- helpers ------------------------------ */

function todayStr() {
  const d = new Date();
  return d.toISOString().slice(0, 10);
}
function daysSince(dateStr) {
  if (!dateStr) return Infinity;
  const last = new Date(dateStr + 'T00:00:00');
  const today = new Date(); today.setHours(0, 0, 0, 0);
  return Math.floor((today - last) / 86400000);
}
function statusOf(customer, settings) {
  if (!customer.lastContact) return 'overdue';
  const d = daysSince(customer.lastContact);
  const interval = Number(customer.followUpDays) || settings.defaultFollowUpDays || 30;
  if (d >= interval) return 'overdue';
  if (d >= interval - (settings.dueSoonWindow || 7)) return 'due';
  return 'ok';
}
function statusLabel(s) {
  return s === 'overdue' ? '已逾期' : s === 'due' ? '即将到期' : '状态良好';
}
function regionLabel(id) {
  return (REGIONS.find(r => r.id === id) || {}).label || id;
}
function localTimeInfo(countryName, now) {
  const c = COUNTRY_BY_NAME[countryName];
  if (!c) return null;
  try {
    const time = new Intl.DateTimeFormat('zh-CN', { timeZone: c.tz, hour: '2-digit', minute: '2-digit', hour12: false }).format(now);
    return { time, en: c.en, tz: c.tz };
  } catch (e) {
    return null;
  }
}
function reorderArray(arr, fromIndex, toIndex) {
  const copy = [...arr];
  const [moved] = copy.splice(fromIndex, 1);
  copy.splice(toIndex, 0, moved);
  return copy;
}
function reorderWithinCategory(products, category, fromIndex, toIndex) {
  const catItems = products.filter(p => p.category === category);
  const reordered = reorderArray(catItems, fromIndex, toIndex);
  let i = 0;
  return products.map(p => (p.category === category ? reordered[i++] : p));
}
function fmtDate(d) {
  if (!d) return '—';
  return d;
}
function getTopCategory(id) { return PRODUCT_CATEGORIES.find(c => c.id === id); }
function categoryBreadcrumb(p) {
  const top = getTopCategory(p.category);
  if (!top) return '未分类';
  const sub = top.subs.find(s => s.id === p.subCategory);
  if (!sub) return top.short;
  const subsub = sub.subs && sub.subs.find(s => s.id === p.subSubCategory);
  return [top.short, sub.short, subsub && subsub.short].filter(Boolean).join(' › ');
}
function generateFollowUpEmail(customer) {
  const name = customer.contact || customer.company || 'there';
  const product = customer.productLines && customer.productLines.length ? customer.productLines.join(', ') : 'our products';
  const subject = `Following up – ${customer.company || name}`;
  const body = `Dear ${name},

I hope this message finds you well. It has been a little while since we last connected, and I wanted to check in regarding ${product}.

If you have any upcoming projects or requirements, I would be happy to share our latest pricing, lead times, and technical updates. Please let me know if there is anything I can help with.

Looking forward to hearing from you.

Best regards,
[Your name]`;
  return { subject, body };
}

/* -------------------------------- storage ------------------------------ */

async function loadAll() {
  let customers = [];
  let products = [];
  let settings = DEFAULT_SETTINGS;
  try {
    const c = await storageGet(STORAGE_KEYS.customers);
    if (c && c.value) customers = JSON.parse(c.value);
  } catch (e) {}
  try {
    const p = await storageGet(STORAGE_KEYS.products);
    if (p && p.value) products = JSON.parse(p.value);
  } catch (e) {}
  try {
    const s = await storageGet(STORAGE_KEYS.settings);
    if (s && s.value) settings = { ...DEFAULT_SETTINGS, ...JSON.parse(s.value) };
  } catch (e) {}
  return { customers, products, settings };
}

/* -------------------------------- styles -------------------------------- */

const Styles = () => (
  <style>{`
    @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=IBM+Plex+Mono:wght@400;500&family=Inter:wght@400;500;600&display=swap');

    .crm-root {
      --bg: #EDF1F4;
      --panel: #FFFFFF;
      --ink: #18233A;
      --ink-soft: #5B6B82;
      --line: #D9E1E8;
      --copper: #B5651D;
      --copper-soft: #F1E0CC;
      --blue: #3D6E99;
      --led-green: #2F8F5B;
      --led-amber: #C9842E;
      --led-red: #C2403A;
      --led-green-bg: #E4F3EA;
      --led-amber-bg: #FAEFDD;
      --led-red-bg: #FAE5E3;
      --led-blue-bg: #E4ECF2;
      font-family: 'Inter', system-ui, -apple-system, sans-serif;
      color: var(--ink);
      background:
        linear-gradient(var(--bg), var(--bg)),
        repeating-linear-gradient(0deg, rgba(61,110,153,0.05) 0 1px, transparent 1px 28px),
        repeating-linear-gradient(90deg, rgba(61,110,153,0.05) 0 1px, transparent 1px 28px);
      min-height: 100vh;
      width: 100%;
    }
    .crm-display { font-family: 'Space Grotesk', 'Inter', sans-serif; }
    .crm-mono { font-family: 'IBM Plex Mono', ui-monospace, monospace; }

    .crm-header {
      display: flex; align-items: center; justify-content: space-between;
      padding: 18px 24px; border-bottom: 1px solid var(--line);
      background: var(--panel);
    }
    .crm-title { font-size: 19px; font-weight: 700; letter-spacing: -0.01em; }
    .crm-subtitle { font-size: 12.5px; color: var(--ink-soft); margin-top: 2px; }

    .crm-tabs { display: flex; gap: 4px; padding: 10px 24px 0; background: var(--panel); border-bottom: 1px solid var(--line); }
    .crm-tab {
      display: flex; align-items: center; gap: 6px;
      padding: 9px 14px; font-size: 13.5px; font-weight: 600; color: var(--ink-soft);
      border: none; background: transparent; cursor: pointer;
      border-bottom: 2px solid transparent; margin-bottom: -1px;
    }
    .crm-tab.active { color: var(--copper); border-bottom-color: var(--copper); }
    .crm-tab:hover:not(.active) { color: var(--ink); }

    .crm-body { padding: 22px 24px 60px; max-width: 1180px; margin: 0 auto; }

    .stat-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 22px; }
    .stat-card {
      background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
      padding: 14px 16px; display: flex; align-items: center; gap: 12px;
    }
    .stat-num { font-family: 'IBM Plex Mono', monospace; font-size: 26px; font-weight: 500; line-height: 1; }
    .stat-label { font-size: 11.5px; color: var(--ink-soft); margin-top: 3px; }

    .led-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; display: inline-block; }
    .led-dot.green { background: var(--led-green); box-shadow: 0 0 0 3px var(--led-green-bg); }
    .led-dot.amber { background: var(--led-amber); box-shadow: 0 0 0 3px var(--led-amber-bg); }
    .led-dot.red { background: var(--led-red); box-shadow: 0 0 0 3px var(--led-red-bg); }
    .led-dot.blue { background: var(--blue); box-shadow: 0 0 0 3px var(--led-blue-bg); }
    @media (prefers-reduced-motion: no-preference) {
      .led-dot.red.pulse { animation: led-pulse 1.8s ease-in-out infinite; }
    }
    @keyframes led-pulse { 0%,100% { box-shadow: 0 0 0 3px var(--led-red-bg); } 50% { box-shadow: 0 0 0 5px var(--led-red-bg); } }

    .panel { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; }
    .panel-header { padding: 14px 18px; border-bottom: 1px solid var(--line); display: flex; align-items: center; justify-content: space-between; }
    .panel-title { font-size: 14px; font-weight: 700; display: flex; align-items: center; gap: 8px; }
    .panel-body { padding: 6px 0; }
    .section-gap { margin-bottom: 18px; }

    .row { display: flex; align-items: center; gap: 12px; padding: 12px 18px; border-bottom: 1px solid var(--line); }
    .row:last-child { border-bottom: none; }
    .row:hover { background: #FAFBFC; }
    .row-main { flex: 1; min-width: 0; }
    .row-title { font-size: 13.5px; font-weight: 600; }
    .row-sub { font-size: 12px; color: var(--ink-soft); margin-top: 2px; font-family: 'IBM Plex Mono', monospace; }

    .btn {
      display: inline-flex; align-items: center; gap: 6px;
      font-size: 12.5px; font-weight: 600; padding: 7px 12px; border-radius: 7px;
      border: 1px solid var(--line); background: var(--panel); color: var(--ink); cursor: pointer;
      white-space: nowrap;
    }
    .btn:hover { background: #F4F6F8; }
    .btn:disabled { opacity: 0.45; cursor: not-allowed; }
    .btn-primary { background: var(--copper); border-color: var(--copper); color: white; }
    .btn-primary:hover { background: #9c5418; }
    .btn-ghost { border-color: transparent; background: transparent; }
    .btn-danger { color: var(--led-red); }
    .btn-sm { padding: 5px 9px; font-size: 11.5px; }

    .empty-state { text-align: center; padding: 50px 20px; color: var(--ink-soft); }
    .empty-state svg { margin: 0 auto 10px; opacity: 0.5; }

    .field-label { font-size: 11.5px; font-weight: 600; color: var(--ink-soft); margin-bottom: 5px; display: block; text-transform: uppercase; letter-spacing: 0.03em; }
    .field { margin-bottom: 14px; }
    .input, .select, .textarea {
      width: 100%; font-size: 13.5px; padding: 8px 10px; border: 1px solid var(--line);
      border-radius: 7px; background: white; color: var(--ink); font-family: inherit;
    }
    .input:focus, .select:focus, .textarea:focus { outline: 2px solid var(--blue); outline-offset: 0; border-color: var(--blue); }
    .textarea { resize: vertical; min-height: 70px; font-family: 'IBM Plex Mono', monospace; font-size: 12.5px; line-height: 1.6; }
    .checkbox-row { display: flex; flex-wrap: wrap; gap: 10px; }
    .checkbox-pill {
      display: flex; align-items: center; gap: 6px; font-size: 12px; padding: 5px 10px;
      border: 1px solid var(--line); border-radius: 16px; cursor: pointer; user-select: none;
    }
    .checkbox-pill.checked { background: var(--copper-soft); border-color: var(--copper); }

    .grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
    .crm-layout { display: grid; grid-template-columns: 320px 1fr; gap: 16px; align-items: start; }
    @media (max-width: 760px) {
      .crm-layout { grid-template-columns: 1fr; }
      .stat-row { grid-template-columns: repeat(2, 1fr); }
      .grid-2 { grid-template-columns: 1fr; }
    }

    .search-box { display: flex; align-items: center; gap: 8px; padding: 9px 12px; border: 1px solid var(--line); border-radius: 8px; background: white; margin-bottom: 10px; }
    .search-box input { border: none; outline: none; font-size: 13px; flex: 1; }

    .modal-overlay {
      position: fixed; inset: 0; background: rgba(24,35,58,0.45);
      display: flex; align-items: center; justify-content: center; z-index: 50; padding: 20px;
    }
    .modal-box { background: white; border-radius: 12px; max-width: 520px; width: 100%; max-height: 86vh; overflow-y: auto; }
    .modal-head { padding: 16px 20px; border-bottom: 1px solid var(--line); display: flex; align-items: center; justify-content: space-between; }
    .modal-body { padding: 18px 20px; }
    .modal-foot { padding: 14px 20px; border-top: 1px solid var(--line); display: flex; justify-content: flex-end; gap: 8px; }

    .toast {
      position: fixed; bottom: 22px; left: 50%; transform: translateX(-50%);
      background: var(--ink); color: white; padding: 9px 16px; border-radius: 8px;
      font-size: 12.5px; z-index: 60; box-shadow: 0 6px 18px rgba(0,0,0,0.2);
    }
    .badge { font-size: 10.5px; padding: 2px 7px; border-radius: 10px; font-weight: 600; }
    .badge-region { background: #EAF1F6; color: var(--blue); }
    .disclaimer { font-size: 12px; color: var(--ink-soft); background: var(--copper-soft); border-radius: 8px; padding: 10px 12px; margin-bottom: 14px; }

    /* ---------- shell / sidebar ---------- */
    .crm-shell { display: flex; min-height: 100vh; }
    .crm-sidebar {
      width: 226px; flex-shrink: 0; background: var(--panel); border-right: 1px solid var(--line);
      display: flex; flex-direction: column;
    }
    .crm-sidebar-brand { padding: 18px 16px; border-bottom: 1px solid var(--line); }
    .crm-sidebar-scroll { flex: 1; overflow-y: auto; padding-bottom: 10px; }
    .crm-sidebar-group-label {
      font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;
      color: var(--ink-soft); padding: 16px 16px 6px;
    }
    .crm-sidebar-item {
      display: flex; align-items: center; gap: 9px; padding: 9px 16px; font-size: 13.5px; font-weight: 600;
      color: var(--ink-soft); cursor: pointer; border-left: 3px solid transparent; user-select: none;
    }
    .crm-sidebar-item.active { color: var(--copper); background: var(--copper-soft); border-left-color: var(--copper); }
    .crm-sidebar-item:hover:not(.active) { background: #F4F6F8; }
    .crm-sidebar-footer { border-top: 1px solid var(--line); padding: 12px 16px; }
    .crm-sidebar-user { font-size: 12px; color: var(--ink-soft); margin-bottom: 8px; display: flex; align-items: center; gap: 6px; }
    .crm-main { flex: 1; min-width: 0; }
    .crm-main-body { padding: 22px 24px 60px; max-width: 1180px; margin: 0 auto; }
    .crm-topbar-mobile { display: none; align-items: center; gap: 10px; padding: 12px 16px; background: var(--panel); border-bottom: 1px solid var(--line); position: sticky; top: 0; z-index: 20; }
    .crm-sidebar-overlay { display: none; }

    @media (max-width: 880px) {
      .crm-topbar-mobile { display: flex; }
      .crm-sidebar {
        position: fixed; top: 0; left: -260px; height: 100vh; width: 240px; z-index: 40;
        transition: left 0.2s ease; box-shadow: 4px 0 24px rgba(0,0,0,0.18);
      }
      .crm-sidebar.open { left: 0; }
      .crm-sidebar-overlay.show { display: block; position: fixed; inset: 0; background: rgba(18,25,40,0.4); z-index: 35; }
    }

    /* ---------- drag and drop ---------- */
    .drag-handle { cursor: grab; color: var(--ink-soft); flex-shrink: 0; touch-action: none; }
    .drag-handle:active { cursor: grabbing; }
    .draggable-row.drag-over { outline: 2px dashed var(--blue); outline-offset: 2px; background: #F0F6FA; }

    /* ---------- card grid (monitor / catalog) ---------- */
    .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 12px; }
    .monitor-card, .catalog-card {
      background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 14px;
      display: flex; flex-direction: column; gap: 8px;
    }
    .monitor-card-head, .catalog-card-head { display: flex; align-items: center; gap: 8px; }
    .monitor-card-title { font-size: 13.5px; font-weight: 700; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

    /* ---------- dropzone ---------- */
    .dropzone {
      border: 1.5px dashed var(--line); border-radius: 8px; padding: 14px; text-align: center;
      font-size: 12px; color: var(--ink-soft); flex: 1 1 160px; cursor: pointer; transition: border-color 0.15s, background 0.15s;
    }
    .dropzone.drag-active { border-color: var(--blue); background: #F0F6FA; color: var(--blue); }

    /* ---------- vip ---------- */
    .vip-toggle { cursor: pointer; color: var(--line); display: inline-flex; align-items: center; }
    .vip-toggle.active { color: #C9942E; }
    .vip-toggle:hover { color: #C9942E; opacity: 0.8; }
  `}</style>
);

/* -------------------------------- pieces -------------------------------- */

function StatCard({ num, label, tone }) {
  return (
    <div className="stat-card">
      <div className={`led-dot ${tone}`}></div>
      <div>
        <div className="stat-num">{num}</div>
        <div className="stat-label">{label}</div>
      </div>
    </div>
  );
}

function ProductPills({ value, onChange }) {
  const toggle = (p) => {
    const has = value.includes(p);
    onChange(has ? value.filter(x => x !== p) : [...value, p]);
  };
  return (
    <div className="checkbox-row">
      {PRODUCT_LINES.map(p => (
        <div key={p} className={`checkbox-pill ${value.includes(p) ? 'checked' : ''}`} onClick={() => toggle(p)}>
          {value.includes(p) && <Check size={11} />} {p}
        </div>
      ))}
    </div>
  );
}

function RegionPills({ value, onChange }) {
  const toggle = (id) => {
    const has = value.includes(id);
    onChange(has ? value.filter(x => x !== id) : [...value, id]);
  };
  return (
    <div className="checkbox-row">
      {REGIONS.map(r => (
        <div key={r.id} className={`checkbox-pill ${value.includes(r.id) ? 'checked' : ''}`} onClick={() => toggle(r.id)}>
          {value.includes(r.id) && <Check size={11} />} {r.label}
        </div>
      ))}
    </div>
  );
}

/* --------------------------------- app ---------------------------------- */

/* --------------------------------- nav --------------------------------- */

const NAV_GROUPS = [
  { label: '客户', items: [
    { id: 'monitor', label: '客户监控', icon: LayoutDashboard },
    { id: 'manage', label: '客户管理', icon: Users },
  ] },
  { label: '产品', items: [
    { id: 'groups', label: '产品分组', icon: FolderTree },
    { id: 'products', label: '产品管理', icon: Package },
    { id: 'catalog', label: '产品目录', icon: BookOpen },
  ] },
];
const STANDALONE_NAV = [
  { id: 'quotes', label: '报价单制作', icon: FileText },
  { id: 'settings', label: '设置', icon: SettingsIcon },
];
function viewTitle(view) {
  const all = [...NAV_GROUPS.flatMap(g => g.items), ...STANDALONE_NAV];
  const found = all.find(i => i.id === view);
  return found ? found.label : '客户档案系统';
}

/* ------------------------------- login screen ----------------------------- */

function LoginScreen({ onSuccess }) {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');
  const [loading, setLoading] = useState(false);

  async function handleSubmit(e) {
    e.preventDefault();
    if (loading) return;
    setError('');
    setLoading(true);
    try {
      const res = await authLogin(username, password);
      onSuccess(res.username || username);
    } catch (e) {
      setError('用户名或密码不正确');
    } finally {
      setLoading(false);
    }
  }

  return (
    <div className="crm-root" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', padding: 20 }}>
      <Styles />
      <form className="panel" style={{ width: 340, maxWidth: '100%', padding: 26 }} onSubmit={handleSubmit}>
        <div className="crm-display" style={{ fontSize: 19, fontWeight: 700 }}>客户档案系统</div>
        <div className="crm-subtitle" style={{ marginBottom: 18 }}>请登录后继续</div>
        <div className="field">
          <span className="field-label">用户名</span>
          <input className="input" value={username} onChange={e => setUsername(e.target.value)} autoFocus autoCapitalize="off" />
        </div>
        <div className="field">
          <span className="field-label">密码</span>
          <input className="input" type="password" value={password} onChange={e => setPassword(e.target.value)} />
        </div>
        {error && <div style={{ color: 'var(--led-red)', fontSize: 12.5, marginBottom: 10 }}>{error}</div>}
        <button className="btn btn-primary" type="submit" disabled={loading} style={{ width: '100%', justifyContent: 'center', marginTop: 4 }}>
          {loading ? '登录中…' : '登录'}
        </button>
        <div style={{ fontSize: 11.5, color: 'var(--ink-soft)', marginTop: 14 }}>
          首次使用？默认账号 admin / admin123，登录后请去"设置"里修改密码。
        </div>
      </form>
    </div>
  );
}

/* --------------------------------- root --------------------------------- */

export default function CustomerCRM() {
  const [authState, setAuthState] = useState('checking'); // checking | out | in
  const [username, setUsername] = useState(null);

  useEffect(() => {
    authCheck()
      .then(res => {
        if (res.authed) { setUsername(res.username); setAuthState('in'); }
        else setAuthState('out');
      })
      .catch(() => setAuthState('out'));
  }, []);

  function handleLogout() {
    authLogout().finally(() => {
      setUsername(null);
      setAuthState('out');
    });
  }

  if (authState === 'checking') {
    return (
      <div className="crm-root" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh' }}>
        <Styles />
        <div className="crm-mono" style={{ color: 'var(--ink-soft)', fontSize: 13 }}>加载中…</div>
      </div>
    );
  }
  if (authState === 'out') {
    return <LoginScreen onSuccess={(name) => { setUsername(name); setAuthState('in'); }} />;
  }
  return <MainApp username={username} onLogout={handleLogout} />;
}

export { LoginScreen, MainApp };

function MainApp({ username, onLogout }) {
  const [loaded, setLoaded] = useState(false);
  const [customers, setCustomers] = useState([]);
  const [products, setProducts] = useState([]);
  const [settings, setSettings] = useState(DEFAULT_SETTINGS);
  const [view, setView] = useState('monitor');
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);

  const [selectedId, setSelectedId] = useState(null);
  const [draft, setDraft] = useState(null);
  const [search, setSearch] = useState('');
  const [regionFilter, setRegionFilter] = useState('all');
  const [attachmentNote, setAttachmentNote] = useState('');
  const [attachmentCategory, setAttachmentCategory] = useState(FILE_CATEGORIES[0]);
  const [attachmentFile, setAttachmentFile] = useState(null);
  const [uploading, setUploading] = useState(false);
  const [fileInputKey, setFileInputKey] = useState(0);
  const [fileDragActive, setFileDragActive] = useState(false);

  const [selectedProductId, setSelectedProductId] = useState(null);
  const [productDraft, setProductDraft] = useState(null);
  const [productSearch, setProductSearch] = useState('');
  const [productCategoryFilter, setProductCategoryFilter] = useState('all');

  const [emailModal, setEmailModal] = useState(null);
  const [exportOpen, setExportOpen] = useState(false);
  const [toast, setToast] = useState('');
  const [now, setNow] = useState(() => new Date());

  const [dragCustomerIdx, setDragCustomerIdx] = useState(null);
  const [dragCatalogIdx, setDragCatalogIdx] = useState(null);
  const [dragGroupState, setDragGroupState] = useState(null); // { category, index }

  const [pwCurrent, setPwCurrent] = useState('');
  const [pwNew, setPwNew] = useState('');
  const [pwBusy, setPwBusy] = useState(false);

  const [aiConfigHasKey, setAiConfigHasKey] = useState(false);
  const [aiKeyInput, setAiKeyInput] = useState('');
  const [aiSaving, setAiSaving] = useState(false);
  const [aiTesting, setAiTesting] = useState(false);
  const [aiGenerating, setAiGenerating] = useState(false);
  const [aiDraftModal, setAiDraftModal] = useState(null); // { channel, customerId, subject, body }

  useEffect(() => {
    getAiConfig().then((res) => setAiConfigHasKey(!!res.hasKey)).catch(() => {});
  }, []);

  useEffect(() => {
    const id = setInterval(() => setNow(new Date()), 30000);
    return () => clearInterval(id);
  }, []);

  useEffect(() => {
    (async () => {
      const data = await loadAll();
      setCustomers(data.customers);
      setProducts(data.products);
      setSettings(data.settings);
      setLoaded(true);
    })();
  }, []);
  useEffect(() => { if (loaded) storageSet(STORAGE_KEYS.customers, JSON.stringify(customers)).catch(() => {}); }, [customers, loaded]);
  useEffect(() => { if (loaded) storageSet(STORAGE_KEYS.products, JSON.stringify(products)).catch(() => {}); }, [products, loaded]);
  useEffect(() => { if (loaded) storageSet(STORAGE_KEYS.settings, JSON.stringify(settings)).catch(() => {}); }, [settings, loaded]);

  function notify(msg) { setToast(msg); setTimeout(() => setToast(''), 2200); }

  function copyText(text) {
    navigator.clipboard.writeText(text).then(
      () => notify('已复制到剪贴板'),
      () => notify('复制失败，请手动选择文本复制')
    );
  }

  const selected = customers.find(c => c.id === selectedId) || null;
  const selectedProduct = products.find(p => p.id === selectedProductId) || null;

  const filteredCustomers = useMemo(() => {
    let list = customers;
    if (regionFilter !== 'all') list = list.filter(c => c.region === regionFilter);
    if (search.trim()) {
      const q = search.trim().toLowerCase();
      list = list.filter(c =>
        (c.company || '').toLowerCase().includes(q) ||
        (c.contact || '').toLowerCase().includes(q) ||
        (c.email || '').toLowerCase().includes(q) ||
        (c.country || '').toLowerCase().includes(q)
      );
    }
    const order = { overdue: 0, due: 1, ok: 2 };
    return [...list].sort((a, b) => order[statusOf(a, settings)] - order[statusOf(b, settings)] || (a.company || '').localeCompare(b.company || ''));
  }, [customers, search, regionFilter, settings]);

  const filteredProducts = useMemo(() => {
    let list = products;
    if (productCategoryFilter !== 'all') list = list.filter(p => p.category === productCategoryFilter);
    if (productSearch.trim()) {
      const q = productSearch.trim().toLowerCase();
      list = list.filter(p =>
        (p.name || '').toLowerCase().includes(q) ||
        (p.brand || '').toLowerCase().includes(q) ||
        categoryBreadcrumb(p).toLowerCase().includes(q)
      );
    }
    return [...list].sort((a, b) => categoryBreadcrumb(a).localeCompare(categoryBreadcrumb(b)) || (a.name || '').localeCompare(b.name || ''));
  }, [products, productSearch, productCategoryFilter]);

  const counts = useMemo(() => ({
    overdue: customers.filter(c => statusOf(c, settings) === 'overdue').length,
    due: customers.filter(c => statusOf(c, settings) === 'due').length,
    total: customers.length,
    products: products.length,
  }), [customers, settings, products]);

  function handleCustomerDrop(dropIdx) {
    if (dragCustomerIdx === null || dragCustomerIdx === dropIdx) { setDragCustomerIdx(null); return; }
    setCustomers(prev => reorderArray(prev, dragCustomerIdx, dropIdx));
    setDragCustomerIdx(null);
  }
  function handleCatalogDrop(dropIdx) {
    if (dragCatalogIdx === null || dragCatalogIdx === dropIdx) { setDragCatalogIdx(null); return; }
    setProducts(prev => reorderArray(prev, dragCatalogIdx, dropIdx));
    setDragCatalogIdx(null);
  }
  function handleGroupDrop(category, dropIdx) {
    if (!dragGroupState || dragGroupState.category !== category || dragGroupState.index === dropIdx) { setDragGroupState(null); return; }
    setProducts(prev => reorderWithinCategory(prev, category, dragGroupState.index, dropIdx));
    setDragGroupState(null);
  }

  async function handleChangePassword() {
    if (!pwCurrent || !pwNew) { notify('请填写当前密码和新密码'); return; }
    if (pwNew.length < 4) { notify('新密码至少4位'); return; }
    setPwBusy(true);
    try {
      await authChangePassword(pwCurrent, pwNew);
      setPwCurrent('');
      setPwNew('');
      notify('密码已修改');
    } catch (e) {
      notify('修改失败：当前密码不正确');
    } finally {
      setPwBusy(false);
    }
  }

  async function handleSaveAiKey() {
    if (!aiKeyInput.trim()) { notify('请输入API Key'); return; }
    setAiSaving(true);
    try {
      await saveAiConfig(aiKeyInput.trim());
      setAiConfigHasKey(true);
      setAiKeyInput('');
      notify('已保存');
    } catch (e) {
      notify('保存失败');
    } finally {
      setAiSaving(false);
    }
  }
  async function handleTestAiKey() {
    setAiTesting(true);
    try {
      await testAiConfig();
      notify('连接成功');
    } catch (e) {
      notify('连接失败，请检查API Key');
    } finally {
      setAiTesting(false);
    }
  }
  async function handleClearAiKey() {
    if (!window.confirm('确定清除已保存的DeepSeek API Key？')) return;
    await deleteAiConfig();
    setAiConfigHasKey(false);
    notify('已清除');
  }
  async function handleGenerateAi(customerId, channel) {
    if (!aiConfigHasKey) { notify('请先在设置中配置DeepSeek API Key'); return; }
    setAiGenerating(true);
    try {
      const result = await generateAiDraft(customerId, channel);
      setAiDraftModal({ channel, customerId, subject: result.subject || '', body: result.body || '' });
    } catch (e) {
      notify('生成失败：' + (e.message || '请检查API Key和网络'));
    } finally {
      setAiGenerating(false);
    }
  }

  function startNewCustomer() { setDraft(blankCustomer()); setSelectedId(null); }
  function startEditCustomer(c) { setDraft({ ...c, productLines: [...(c.productLines || [])] }); setSelectedId(c.id); }
  function cancelDraft() { setDraft(null); }
  function saveDraft() {
    setCustomers(prev => {
      const exists = prev.some(c => c.id === draft.id);
      return exists ? prev.map(c => (c.id === draft.id ? draft : c)) : [...prev, draft];
    });
    setSelectedId(draft.id);
    setDraft(null);
    notify('已保存');
  }
  function deleteCustomer(id) {
    if (!window.confirm('确定删除该客户档案？此操作不可恢复。')) return;
    setCustomers(prev => prev.filter(c => c.id !== id));
    if (selectedId === id) setSelectedId(null);
    notify('已删除');
  }
  function markContacted(id) {
    setCustomers(prev => prev.map(c => (c.id === id ? { ...c, lastContact: todayStr() } : c)));
    notify('已标记为今日已联系');
  }
  function togglePreferredContact(id, channel) {
    setCustomers(prev => prev.map(c => {
      if (c.id !== id) return c;
      const has = (c.preferredContacts || []).includes(channel);
      return { ...c, preferredContacts: has ? c.preferredContacts.filter(x => x !== channel) : [...(c.preferredContacts || []), channel] };
    }));
  }
  function toggleVip(id) {
    setCustomers(prev => prev.map(c => (c.id === id ? { ...c, isVip: !c.isVip } : c)));
  }
  function handleFileDragOver(e) { e.preventDefault(); setFileDragActive(true); }
  function handleFileDragLeave() { setFileDragActive(false); }
  function handleFileDrop(e) {
    e.preventDefault();
    setFileDragActive(false);
    const f = e.dataTransfer.files && e.dataTransfer.files[0];
    if (f) setAttachmentFile(f);
  }
  async function handleUploadAttachment(customerId) {
    if (!attachmentFile) { notify('请先选择文件'); return; }
    setUploading(true);
    try {
      const res = await uploadFile(customerId, attachmentFile);
      setCustomers(prev => prev.map(c => {
        if (c.id !== customerId) return c;
        const entry = {
          fileName: res.fileName,
          originalName: res.originalName,
          category: attachmentCategory,
          note: attachmentNote.trim(),
          date: todayStr(),
        };
        return { ...c, attachments: [entry, ...(c.attachments || [])] };
      }));
      setAttachmentFile(null);
      setAttachmentNote('');
      setAttachmentCategory(FILE_CATEGORIES[0]);
      setFileInputKey(k => k + 1);
      notify('已上传');
    } catch (e) {
      notify('上传失败，请重试');
    } finally {
      setUploading(false);
    }
  }
  async function handleDeleteAttachment(customerId, fileName) {
    if (!window.confirm('确定删除这个附件？此操作不可恢复。')) return;
    try {
      await deleteFile(customerId, fileName);
    } catch (e) {
      // 即使服务端删除失败（比如文件已经不存在），也继续把记录从客户档案里移除
    }
    setCustomers(prev => prev.map(c => (
      c.id === customerId ? { ...c, attachments: (c.attachments || []).filter(a => a.fileName !== fileName) } : c
    )));
  }
  function openFollowUpEmail(c) { setEmailModal({ ...generateFollowUpEmail(c), customerId: c.id }); }

  function startNewProduct() { setProductDraft(blankProduct()); setSelectedProductId(null); }
  function startEditProduct(p) { setProductDraft({ ...p, targetRegions: [...(p.targetRegions || [])] }); setSelectedProductId(p.id); }
  function cancelProductDraft() { setProductDraft(null); }
  function saveProductDraft() {
    if (!productDraft.name.trim()) { notify('请填写产品型号/名称'); return; }
    if (!productDraft.category) { notify('请选择产品大类'); return; }
    setProducts(prev => {
      const exists = prev.some(p => p.id === productDraft.id);
      return exists ? prev.map(p => (p.id === productDraft.id ? productDraft : p)) : [...prev, productDraft];
    });
    setSelectedProductId(productDraft.id);
    setProductDraft(null);
    notify('已保存');
  }
  function deleteProduct(id) {
    if (!window.confirm('确定删除该产品档案？此操作不可恢复。')) return;
    setProducts(prev => prev.filter(p => p.id !== id));
    if (selectedProductId === id) setSelectedProductId(null);
    notify('已删除');
  }

  if (!loaded) {
    return (
      <div className="crm-root" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: 320 }}>
        <div className="crm-mono" style={{ color: 'var(--ink-soft)', fontSize: 13 }}>加载中…</div>
      </div>
    );
  }

  return (
    <div className="crm-root">
      <Styles />
      <div className="crm-shell">

        <div className="crm-topbar-mobile">
          <button className="btn btn-ghost btn-sm" onClick={() => setMobileMenuOpen(true)}><Menu size={18} /></button>
          <span className="crm-display" style={{ fontWeight: 700, fontSize: 14 }}>{viewTitle(view)}</span>
        </div>
        {mobileMenuOpen && <div className="crm-sidebar-overlay show" onClick={() => setMobileMenuOpen(false)} />}

        <div className={`crm-sidebar ${mobileMenuOpen ? 'open' : ''}`}>
          <div className="crm-sidebar-brand">
            <div className="crm-display" style={{ fontWeight: 700, fontSize: 15 }}>客户档案系统</div>
            <div style={{ fontSize: 11, color: 'var(--ink-soft)', marginTop: 2 }}>Customer Console</div>
          </div>
          <div className="crm-sidebar-scroll">
            {NAV_GROUPS.map(group => (
              <div key={group.label}>
                <div className="crm-sidebar-group-label">{group.label}</div>
                {group.items.map(item => {
                  const Icon = item.icon;
                  return (
                    <div
                      key={item.id}
                      className={`crm-sidebar-item ${view === item.id ? 'active' : ''}`}
                      onClick={() => { setView(item.id); setMobileMenuOpen(false); }}
                    >
                      <Icon size={15} />{item.label}
                    </div>
                  );
                })}
              </div>
            ))}
            <div style={{ borderTop: '1px solid var(--line)', margin: '10px 16px' }} />
            {STANDALONE_NAV.map(item => {
              const Icon = item.icon;
              return (
                <div
                  key={item.id}
                  className={`crm-sidebar-item ${view === item.id ? 'active' : ''}`}
                  onClick={() => { setView(item.id); setMobileMenuOpen(false); }}
                >
                  <Icon size={15} />{item.label}
                </div>
              );
            })}
          </div>
          <div className="crm-sidebar-footer">
            <div className="crm-sidebar-user"><Users size={12} />{username}</div>
            <button className="btn btn-sm" style={{ width: '100%', justifyContent: 'center' }} onClick={onLogout}><LogOut size={12} />退出登录</button>
          </div>
        </div>

        <div className="crm-main">
          <div className="crm-main-body">

            {view === 'monitor' && (
              <>
                <div className="stat-row">
                  <StatCard num={counts.overdue} label="已逾期跟进" tone="red" />
                  <StatCard num={counts.due} label="即将到期" tone="amber" />
                  <StatCard num={counts.total} label="客户总数" tone="green" />
                  <StatCard num={counts.products} label="产品档案数量" tone="blue" />
                </div>

                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
                  <div className="panel-title">全部客户</div>
                  <span style={{ fontSize: 11.5, color: 'var(--ink-soft)' }}>拖动卡片可调整顺序</span>
                </div>
                {customers.length === 0 ? (
                  <div className="panel"><div className="empty-state"><Inbox size={28} />暂无客户，去"客户管理"添加第一个</div></div>
                ) : (
                  <div className="card-grid">
                    {customers.map((c, idx) => {
                      const s = statusOf(c, settings);
                      const lt = localTimeInfo(c.country, now);
                      return (
                        <div
                          key={c.id}
                          className="monitor-card"
                          style={{ opacity: dragCustomerIdx === idx ? 0.5 : 1 }}
                          draggable
                          onDragStart={() => setDragCustomerIdx(idx)}
                          onDragOver={(e) => e.preventDefault()}
                          onDrop={() => handleCustomerDrop(idx)}
                          onDragEnd={() => setDragCustomerIdx(null)}
                        >
                          <div className="monitor-card-head">
                            <GripVertical size={14} className="drag-handle" />
                            <div className={`led-dot ${s === 'overdue' ? 'red' : s === 'due' ? 'amber' : 'green'}`} />
                            <div className="monitor-card-title">{c.notes ? c.notes : (c.company || '(未命名)')}</div>
                            {c.isVip && <Crown size={14} fill="#C9942E" color="#C9942E" style={{ flexShrink: 0 }} />}
                          </div>
                          <div className="row-sub">{regionLabel(c.region)} · {c.country || '—'}</div>
                          {c.notes && c.company && <div className="row-sub">{c.company}</div>}
                          {lt && <div className="row-sub">当地时间 {lt.time}</div>}
                          <div className="row-sub">上次联系 {fmtDate(c.lastContact)}</div>
                          <div style={{ display: 'flex', gap: 6, marginTop: 4 }}>
                            <button className="btn btn-sm" style={{ flex: 1, justifyContent: 'center' }} onClick={() => { setView('manage'); setDraft(null); setSelectedId(c.id); }}>查看</button>
                            <button className="btn btn-sm" title="标记今日已联系" onClick={() => markContacted(c.id)}><Check size={12} /></button>
                          </div>
                        </div>
                      );
                    })}
                  </div>
                )}
              </>
            )}

            {view === 'manage' && (
              <div className="crm-layout">
                <div>
                  <div className="search-box"><Search size={14} color="var(--ink-soft)" /><input placeholder="搜索公司 / 联系人 / 邮箱 / 国家" value={search} onChange={e => setSearch(e.target.value)} /></div>
                  <div className="field">
                    <select className="select" value={regionFilter} onChange={e => setRegionFilter(e.target.value)}>
                      <option value="all">全部地区</option>
                      {REGIONS.map(r => <option key={r.id} value={r.id}>{r.label}</option>)}
                    </select>
                  </div>
                  <button className="btn btn-primary" style={{ width: '100%', justifyContent: 'center', marginBottom: 12 }} onClick={startNewCustomer}><Plus size={14} />新建客户档案</button>
                  <div className="panel">
                    <div className="panel-body">
                      {filteredCustomers.length === 0 ? (
                        <div className="empty-state"><Inbox size={26} />暂无客户，点击上方按钮添加第一个</div>
                      ) : filteredCustomers.map(c => {
                        const s = statusOf(c, settings);
                        return (
                          <div key={c.id} className="row" style={{ cursor: 'pointer', background: selectedId === c.id ? '#FAFBFC' : undefined }} onClick={() => { setDraft(null); setSelectedId(c.id); }}>
                            <div className={`led-dot ${s === 'overdue' ? 'red' : s === 'due' ? 'amber' : 'green'}`} />
                            <div className="row-main">
                              <div className="row-title">{c.isVip && <Crown size={12} fill="#C9942E" color="#C9942E" style={{ marginRight: 4, verticalAlign: -1 }} />}{c.company || '(未命名)'}</div>
                              <div className="row-sub">{regionLabel(c.region)} · {c.country || '—'}{localTimeInfo(c.country, now) && ` · 当地 ${localTimeInfo(c.country, now).time}`}</div>
                            </div>
                            <ChevronRight size={15} color="var(--ink-soft)" />
                          </div>
                        );
                      })}
                    </div>
                  </div>
                </div>

                <div>
                  {draft ? (
                    <CustomerForm draft={draft} setDraft={setDraft} onSave={saveDraft} onCancel={cancelDraft} />
                  ) : selected ? (
                    <CustomerDetail
                      customer={selected}
                      settings={settings}
                      now={now}
                      onEdit={() => startEditCustomer(selected)}
                      onDelete={() => deleteCustomer(selected.id)}
                      onMarkContacted={() => markContacted(selected.id)}
                      onEmail={() => openFollowUpEmail(selected)}
                      onTogglePreferred={(channel) => togglePreferredContact(selected.id, channel)}
                      onToggleVip={() => toggleVip(selected.id)}
                      attachmentNote={attachmentNote}
                      setAttachmentNote={setAttachmentNote}
                      attachmentCategory={attachmentCategory}
                      setAttachmentCategory={setAttachmentCategory}
                      attachmentFileName={attachmentFile ? attachmentFile.name : ''}
                      onPickAttachment={(f) => setAttachmentFile(f)}
                      uploading={uploading}
                      fileInputKey={fileInputKey}
                      onUploadAttachment={() => handleUploadAttachment(selected.id)}
                      onDeleteAttachment={(fileName) => handleDeleteAttachment(selected.id, fileName)}
                      fileDragActive={fileDragActive}
                      onFileDragOver={handleFileDragOver}
                      onFileDragLeave={handleFileDragLeave}
                      onFileDrop={handleFileDrop}
                      aiConfigHasKey={aiConfigHasKey}
                      aiGenerating={aiGenerating}
                      onGenerateAi={(channel) => handleGenerateAi(selected.id, channel)}
                    />
                  ) : (
                    <div className="panel"><div className="empty-state"><Users size={28} />选择左侧客户查看详情，或新建一个客户档案</div></div>
                  )}
                </div>
              </div>
            )}

            {view === 'groups' && (
              <>
                <div className="disclaimer">按产品大类分组浏览，每组内的产品可以拖拽调整顺序。</div>
                {PRODUCT_CATEGORIES.map(cat => {
                  const items = products.filter(p => p.category === cat.id);
                  return (
                    <div className="panel section-gap" key={cat.id}>
                      <div className="panel-header"><div className="panel-title"><Layers size={15} />{cat.short}（{items.length}）</div></div>
                      <div className="panel-body">
                        {items.length === 0 ? (
                          <div className="empty-state" style={{ padding: '20px 10px' }}>暂无产品</div>
                        ) : items.map((p, idx) => (
                          <div
                            key={p.id}
                            className="row"
                            style={{ cursor: 'grab', opacity: (dragGroupState && dragGroupState.category === cat.id && dragGroupState.index === idx) ? 0.5 : 1 }}
                            draggable
                            onDragStart={() => setDragGroupState({ category: cat.id, index: idx })}
                            onDragOver={(e) => e.preventDefault()}
                            onDrop={() => handleGroupDrop(cat.id, idx)}
                            onDragEnd={() => setDragGroupState(null)}
                          >
                            <GripVertical size={14} className="drag-handle" />
                            <div className="row-main">
                              <div className="row-title">{p.name || '(未命名)'}</div>
                              <div className="row-sub">{categoryBreadcrumb(p)}{p.brand ? ` · ${p.brand}` : ''}</div>
                            </div>
                            <button className="btn btn-sm" onClick={() => { setView('products'); setProductDraft(null); setSelectedProductId(p.id); }}>查看</button>
                          </div>
                        ))}
                      </div>
                    </div>
                  );
                })}
              </>
            )}

            {view === 'products' && (
              <div className="crm-layout">
                <div>
                  <div className="search-box"><Search size={14} color="var(--ink-soft)" /><input placeholder="搜索型号 / 品牌 / 类别" value={productSearch} onChange={e => setProductSearch(e.target.value)} /></div>
                  <div className="field">
                    <select className="select" value={productCategoryFilter} onChange={e => setProductCategoryFilter(e.target.value)}>
                      <option value="all">全部类别</option>
                      {PRODUCT_CATEGORIES.map(c => <option key={c.id} value={c.id}>{c.short}</option>)}
                    </select>
                  </div>
                  <button className="btn btn-primary" style={{ width: '100%', justifyContent: 'center', marginBottom: 12 }} onClick={startNewProduct}><Plus size={14} />新建产品档案</button>
                  <div className="panel">
                    <div className="panel-body">
                      {filteredProducts.length === 0 ? (
                        <div className="empty-state"><Package size={26} />暂无产品，点击上方按钮添加第一个</div>
                      ) : filteredProducts.map(p => (
                        <div key={p.id} className="row" style={{ cursor: 'pointer', background: selectedProductId === p.id ? '#FAFBFC' : undefined }} onClick={() => { setProductDraft(null); setSelectedProductId(p.id); }}>
                          <div className="led-dot blue" />
                          <div className="row-main">
                            <div className="row-title">{p.name || '(未命名)'}</div>
                            <div className="row-sub">{categoryBreadcrumb(p)}{p.brand ? ` · ${p.brand}` : ''}</div>
                          </div>
                          <ChevronRight size={15} color="var(--ink-soft)" />
                        </div>
                      ))}
                    </div>
                  </div>
                </div>

                <div>
                  {productDraft ? (
                    <ProductForm draft={productDraft} setDraft={setProductDraft} onSave={saveProductDraft} onCancel={cancelProductDraft} />
                  ) : selectedProduct ? (
                    <ProductDetail product={selectedProduct} onEdit={() => startEditProduct(selectedProduct)} onDelete={() => deleteProduct(selectedProduct.id)} />
                  ) : (
                    <div className="panel"><div className="empty-state"><Package size={28} />选择左侧产品查看详情，或新建一个产品档案</div></div>
                  )}
                </div>
              </div>
            )}

            {view === 'catalog' && (
              <>
                <div className="disclaimer">拖拽卡片调整展示顺序，方便整理成给客户看的产品介绍顺序。</div>
                {products.length === 0 ? (
                  <div className="panel"><div className="empty-state"><BookOpen size={28} />暂无产品，去"产品管理"添加第一个</div></div>
                ) : (
                  <div className="card-grid">
                    {products.map((p, idx) => {
                      const certs = (p.certifications || '').split(',').map(s => s.trim()).filter(Boolean);
                      return (
                        <div
                          key={p.id}
                          className="catalog-card"
                          style={{ opacity: dragCatalogIdx === idx ? 0.5 : 1 }}
                          draggable
                          onDragStart={() => setDragCatalogIdx(idx)}
                          onDragOver={(e) => e.preventDefault()}
                          onDrop={() => handleCatalogDrop(idx)}
                          onDragEnd={() => setDragCatalogIdx(null)}
                        >
                          <div className="catalog-card-head">
                            <GripVertical size={14} className="drag-handle" />
                            <div className="monitor-card-title">{p.name || '(未命名)'}</div>
                          </div>
                          <div className="row-sub">{categoryBreadcrumb(p)}</div>
                          {p.brand && <div style={{ fontSize: 12.5 }}>品牌：{p.brand}</div>}
                          {p.powerOrCapacity && <div style={{ fontSize: 12.5 }}>功率 / 容量：{p.powerOrCapacity}</div>}
                          {p.voltage && <div style={{ fontSize: 12.5 }}>电压：{p.voltage}</div>}
                          {certs.length > 0 && <div className="checkbox-row">{certs.map(cc => <span key={cc} className="badge badge-region">{cc}</span>)}</div>}
                        </div>
                      );
                    })}
                  </div>
                )}
              </>
            )}

            {view === 'quotes' && (
              <div className="panel">
                <div className="empty-state">
                  <FileText size={32} />
                  <div style={{ fontWeight: 700, fontSize: 14, marginTop: 8, color: 'var(--ink)' }}>报价单制作 · 下一步开发</div>
                  <div style={{ marginTop: 6 }}>计划：选客户 + 选产品 + 填价格，生成一份报价单。<br />这次先把导航位置占好，具体页面下一轮再做。</div>
                </div>
              </div>
            )}

            {view === 'settings' && (
              <>
                <div className="panel section-gap">
                  <div className="panel-header"><div className="panel-title">跟进与提醒设置</div></div>
                  <div className="panel-body" style={{ padding: 18 }}>
                    <div className="grid-2">
                      <div className="field">
                        <span className="field-label">默认跟进周期（天）</span>
                        <input type="number" min="1" className="input" value={settings.defaultFollowUpDays} onChange={e => setSettings(s => ({ ...s, defaultFollowUpDays: Number(e.target.value) || 1 }))} />
                      </div>
                      <div className="field">
                        <span className="field-label">"即将到期"提前提醒天数</span>
                        <input type="number" min="1" className="input" value={settings.dueSoonWindow} onChange={e => setSettings(s => ({ ...s, dueSoonWindow: Number(e.target.value) || 1 }))} />
                      </div>
                    </div>
                    <div style={{ display: 'flex', gap: 10, marginTop: 6 }}>
                      <button className="btn" onClick={() => setExportOpen(true)}><Save size={13} />导出数据备份</button>
                      <button className="btn btn-danger" onClick={() => {
                        if (window.confirm('将清空全部客户与产品档案数据，且不可恢复，确定继续？')) {
                          setCustomers([]); setProducts([]); setSettings(DEFAULT_SETTINGS);
                          notify('已清空');
                        }
                      }}><RefreshCw size={13} />清空所有数据</button>
                    </div>
                    <div className="disclaimer" style={{ marginTop: 18 }}>
                      说明：邮件功能用于<b>生成可复制的英文邮件文案</b>，需要你手动粘贴到自己的邮箱发送。报价单制作正在开发中，下一轮会上线。
                    </div>
                  </div>
                </div>

                <div className="panel section-gap">
                  <div className="panel-header"><div className="panel-title"><Lock size={15} />账号与密码</div></div>
                  <div className="panel-body" style={{ padding: 18 }}>
                    <div style={{ fontSize: 12.5, color: 'var(--ink-soft)', marginBottom: 12 }}>当前登录账号：{username}</div>
                    <div className="grid-2">
                      <div className="field"><span className="field-label">当前密码</span><input type="password" className="input" value={pwCurrent} onChange={e => setPwCurrent(e.target.value)} /></div>
                      <div className="field"><span className="field-label">新密码（至少4位）</span><input type="password" className="input" value={pwNew} onChange={e => setPwNew(e.target.value)} /></div>
                    </div>
                    <button className="btn btn-primary" disabled={pwBusy} onClick={handleChangePassword}><Lock size={13} />{pwBusy ? '修改中…' : '修改密码'}</button>
                    <div className="disclaimer" style={{ marginTop: 14 }}>
                      这个系统现在挂在公网上，定期更换密码、不要把密码告诉别人。
                    </div>
                  </div>
                </div>

                <div className="panel">
                  <div className="panel-header"><div className="panel-title"><Sparkles size={15} />AI 跟进文案（DeepSeek）</div></div>
                  <div className="panel-body" style={{ padding: 18 }}>
                    <div style={{ fontSize: 12.5, color: 'var(--ink-soft)', marginBottom: 12 }}>
                      状态：{aiConfigHasKey ? <span style={{ color: 'var(--led-green)', fontWeight: 600 }}>已配置</span> : <span>未配置</span>}
                    </div>
                    <div className="field">
                      <span className="field-label">DeepSeek API Key</span>
                      <input
                        type="password" className="input" value={aiKeyInput} onChange={e => setAiKeyInput(e.target.value)}
                        placeholder={aiConfigHasKey ? '已设置，重新填写可覆盖' : '在 platform.deepseek.com 申请'}
                      />
                    </div>
                    <div style={{ display: 'flex', gap: 8 }}>
                      <button className="btn btn-primary" disabled={aiSaving} onClick={handleSaveAiKey}><Save size={13} />保存</button>
                      <button className="btn" disabled={!aiConfigHasKey || aiTesting} onClick={handleTestAiKey}>{aiTesting ? '测试中…' : '测试连接'}</button>
                      {aiConfigHasKey && <button className="btn btn-danger" onClick={handleClearAiKey}>清除</button>}
                    </div>
                    <div className="disclaimer" style={{ marginTop: 14 }}>
                      用于客户详情页的"AI生成邮件 / AI生成WhatsApp话术"功能，调用DeepSeek官方API生成。成本很低（单次生成大概几分钱以内人民币，按你自己DeepSeek账户余额实际扣费），Key只会加密保存在服务器本地，不会显示给任何人。
                    </div>
                  </div>
                </div>
              </>
            )}

          </div>
        </div>
      </div>

      {aiDraftModal && (
        <div className="modal-overlay" onClick={() => setAiDraftModal(null)}>
          <div className="modal-box" onClick={e => e.stopPropagation()}>
            <div className="modal-head">
              <div className="panel-title"><Sparkles size={15} />{aiDraftModal.channel === 'email' ? 'AI生成邮件' : 'AI生成WhatsApp话术'}</div>
              <button className="btn btn-ghost btn-sm" onClick={() => setAiDraftModal(null)}><X size={14} /></button>
            </div>
            <div className="modal-body">
              {aiDraftModal.channel === 'email' && (
                <div className="field"><span className="field-label">主题 Subject</span><input className="input" value={aiDraftModal.subject} onChange={e => setAiDraftModal(m => ({ ...m, subject: e.target.value }))} /></div>
              )}
              <div className="field">
                <span className="field-label">{aiDraftModal.channel === 'email' ? '正文 Body' : '消息内容'}</span>
                <textarea
                  className="textarea" rows={aiDraftModal.channel === 'email' ? 10 : 6}
                  value={aiDraftModal.body} onChange={e => setAiDraftModal(m => ({ ...m, body: e.target.value }))}
                />
              </div>
            </div>
            <div className="modal-foot">
              {aiDraftModal.channel === 'email' && <button className="btn" onClick={() => copyText(aiDraftModal.subject)}><Copy size={12} />复制主题</button>}
              <button className="btn btn-primary" onClick={() => copyText(aiDraftModal.body)}><Copy size={12} />复制{aiDraftModal.channel === 'email' ? '正文' : '内容'}</button>
            </div>
          </div>
        </div>
      )}

      {emailModal && (
        <div className="modal-overlay" onClick={() => setEmailModal(null)}>
          <div className="modal-box" onClick={e => e.stopPropagation()}>
            <div className="modal-head"><div className="panel-title"><Mail size={15} />邮件文案</div><button className="btn btn-ghost btn-sm" onClick={() => setEmailModal(null)}><X size={14} /></button></div>
            <div className="modal-body">
              <div className="field"><span className="field-label">主题 Subject</span><input className="input" readOnly value={emailModal.subject} /></div>
              <div className="field"><span className="field-label">正文 Body</span><textarea className="textarea" rows={10} value={emailModal.body} onChange={e => setEmailModal(m => ({ ...m, body: e.target.value }))} /></div>
            </div>
            <div className="modal-foot">
              <button className="btn" onClick={() => copyText(emailModal.subject)}><Copy size={12} />复制主题</button>
              <button className="btn btn-primary" onClick={() => copyText(emailModal.body)}><Copy size={12} />复制正文</button>
            </div>
          </div>
        </div>
      )}

      {exportOpen && (
        <div className="modal-overlay" onClick={() => setExportOpen(false)}>
          <div className="modal-box" onClick={e => e.stopPropagation()}>
            <div className="modal-head"><div className="panel-title">数据备份（JSON）</div><button className="btn btn-ghost btn-sm" onClick={() => setExportOpen(false)}><X size={14} /></button></div>
            <div className="modal-body">
              <textarea className="textarea" rows={14} readOnly value={JSON.stringify({ customers, products, settings }, null, 2)} />
            </div>
            <div className="modal-foot">
              <button className="btn btn-primary" onClick={() => copyText(JSON.stringify({ customers, products, settings }, null, 2))}><Copy size={12} />复制全部</button>
            </div>
          </div>
        </div>
      )}

      {toast && <div className="toast">{toast}</div>}
    </div>
  );
}

/* ----------------------------- customer form / detail ----------------------------- */

function CustomerForm({ draft, setDraft, onSave, onCancel }) {
  const set = (patch) => setDraft(d => ({ ...d, ...patch }));

  function handleCountryChange(value) {
    const match = COUNTRY_BY_NAME[value];
    if (match) {
      setDraft(d => ({
        ...d,
        country: value,
        region: match.region,
        // 国家代码只在电话号码当前为空时自动填入，避免覆盖已经填好的号码
        phone: d.phone && d.phone.trim() ? d.phone : match.dial + ' ',
      }));
    } else {
      set({ country: value });
    }
  }

  return (
    <div className="panel">
      <div className="panel-header"><div className="panel-title"><Edit2 size={14} />{draft.company ? '编辑客户' : '新建客户'}</div></div>
      <div className="panel-body" style={{ padding: 18 }}>
        <div className="grid-2">
          <div className="field"><span className="field-label">公司名称</span><input className="input" value={draft.company} onChange={e => set({ company: e.target.value })} /></div>
          <div className="field"><span className="field-label">联系人</span><input className="input" value={draft.contact} onChange={e => set({ contact: e.target.value })} /></div>
          <div className="field">
            <span className="field-label">国家</span>
            <input
              className="input" list="country-options" value={draft.country}
              onChange={e => handleCountryChange(e.target.value)}
              placeholder="输入国家名称，会自动匹配地区和电话区号"
            />
            <datalist id="country-options">
              {COUNTRIES.map(c => <option key={c.name} value={c.name}>{c.en}</option>)}
            </datalist>
          </div>
          <div className="field">
            <span className="field-label">地区{COUNTRY_BY_NAME[draft.country] ? '（已自动匹配）' : ''}</span>
            <select className="select" value={draft.region} onChange={e => set({ region: e.target.value })}>
              {REGIONS.map(r => <option key={r.id} value={r.id}>{r.label}</option>)}
            </select>
          </div>
          <div className="field"><span className="field-label">邮箱</span><input className="input" value={draft.email} onChange={e => set({ email: e.target.value })} /></div>
          <div className="field">
            <span className="field-label">电话 / WhatsApp{COUNTRY_BY_NAME[draft.country] ? `（${COUNTRY_BY_NAME[draft.country].dial}）` : ''}</span>
            <input className="input" value={draft.phone} onChange={e => set({ phone: e.target.value })} />
          </div>
          <div className="field"><span className="field-label">网站</span><input className="input" value={draft.website} onChange={e => set({ website: e.target.value })} /></div>
          <div className="field">
            <span className="field-label">客户来源</span>
            <select className="select" value={draft.source} onChange={e => set({ source: e.target.value })}>
              <option value="">请选择</option>
              {CUSTOMER_SOURCES.map(s => <option key={s} value={s}>{s}</option>)}
            </select>
          </div>
        </div>

        <div className="field"><span className="field-label">关注产品线</span><ProductPills value={draft.productLines} onChange={v => set({ productLines: v })} /></div>
        {draft.productLines.length > 0 && (
          <div className="field">
            <span className="field-label">意向型号备注</span>
            <input
              className="input" value={draft.productLinesNote} onChange={e => set({ productLinesNote: e.target.value })}
            />
          </div>
        )}

        <div className="grid-2">
          <div className="field"><span className="field-label">上次联系日期</span><input type="date" className="input" value={draft.lastContact} onChange={e => set({ lastContact: e.target.value })} /></div>
          <div className="field"><span className="field-label">跟进周期（天，留空用默认值）</span><input type="number" min="1" className="input" value={draft.followUpDays} onChange={e => set({ followUpDays: e.target.value })} /></div>
        </div>
        <div className="field"><span className="field-label">标签（逗号分隔）</span><input className="input" value={draft.tags} onChange={e => set({ tags: e.target.value })} /></div>

        <CollapsibleSection title="货代信息" defaultOpen={!!(draft.warehouseAddress || draft.freightContact || draft.shippingMarkNote)}>
          <div className="grid-2">
            <div className="field"><span className="field-label">仓库地址</span><input className="input" value={draft.warehouseAddress} onChange={e => set({ warehouseAddress: e.target.value })} /></div>
            <div className="field"><span className="field-label">货代联系方式</span><input className="input" value={draft.freightContact} onChange={e => set({ freightContact: e.target.value })} /></div>
          </div>
          <div className="field"><span className="field-label">箱唛 / 备注</span><textarea className="textarea" style={{ fontFamily: 'inherit', fontSize: 13 }} value={draft.shippingMarkNote} onChange={e => set({ shippingMarkNote: e.target.value })} /></div>
        </CollapsibleSection>

        <div className="field"><span className="field-label">备注</span><textarea className="textarea" style={{ fontFamily: 'inherit', fontSize: 13 }} value={draft.notes} onChange={e => set({ notes: e.target.value })} /></div>
      </div>
      <div className="modal-foot"><button className="btn" onClick={onCancel}>取消</button><button className="btn btn-primary" onClick={onSave}><Save size={13} />保存</button></div>
    </div>
  );
}

function ContactInfo({ icon, label, value, active, onToggle }) {
  return (
    <div>
      <div
        className="field-label"
        style={{ display: 'flex', alignItems: 'center', gap: 5, cursor: 'pointer', color: active ? 'var(--led-green)' : undefined, userSelect: 'none' }}
        onClick={onToggle}
        title="点击标记 / 取消为常用联系方式"
      >
        {icon}{label}{active ? '（常用）' : ''}
      </div>
      <div style={{ fontSize: 13 }}>{value}</div>
    </div>
  );
}

function CollapsibleSection({ title, count, defaultOpen, children }) {
  const [open, setOpen] = useState(!!defaultOpen);
  return (
    <div className="field">
      <div
        style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', cursor: 'pointer', padding: '4px 0' }}
        onClick={() => setOpen(o => !o)}
      >
        <span className="field-label" style={{ marginBottom: 0 }}>{title}{typeof count === 'number' ? `（${count}）` : ''}</span>
        <ChevronRight size={14} color="var(--ink-soft)" style={{ transform: open ? 'rotate(90deg)' : 'none', transition: 'transform 0.15s' }} />
      </div>
      {open && <div style={{ marginTop: 6 }}>{children}</div>}
    </div>
  );
}

function CustomerDetail({
  customer: c, settings, now, onEdit, onDelete, onMarkContacted, onEmail,
  onTogglePreferred, onToggleVip,
  attachmentNote, setAttachmentNote, attachmentCategory, setAttachmentCategory,
  attachmentFileName, onPickAttachment, uploading, fileInputKey, onUploadAttachment, onDeleteAttachment,
  fileDragActive, onFileDragOver, onFileDragLeave, onFileDrop,
  aiConfigHasKey, aiGenerating, onGenerateAi,
}) {
  const s = statusOf(c, settings);
  const lt = localTimeInfo(c.country, now);
  const preferred = c.preferredContacts || [];
  const [fileSearch, setFileSearch] = useState('');
  const [fileCategoryFilter, setFileCategoryFilter] = useState('all');
  const filteredAttachments = (c.attachments || []).filter(a => {
    if (fileCategoryFilter !== 'all' && a.category !== fileCategoryFilter) return false;
    if (fileSearch.trim()) {
      const q = fileSearch.trim().toLowerCase();
      return (a.originalName || '').toLowerCase().includes(q) || (a.note || '').toLowerCase().includes(q);
    }
    return true;
  });
  return (
    <div className="panel">
      <div className="panel-header">
        <div className="panel-title">
          <div className={`led-dot ${s === 'overdue' ? 'red' : s === 'due' ? 'amber' : 'green'}`} />
          {c.company || '(未命名)'}
          <span className={`vip-toggle ${c.isVip ? 'active' : ''}`} onClick={onToggleVip} title={c.isVip ? '取消重点客户标记' : '标记为重点客户'}>
            <Crown size={15} fill={c.isVip ? '#C9942E' : 'none'} />
          </span>
        </div>
        <div style={{ display: 'flex', gap: 8 }}>
          <button className="btn btn-sm" onClick={onEdit}><Edit2 size={12} />编辑</button>
          <button className="btn btn-sm btn-ghost btn-danger" onClick={onDelete}><Trash2 size={12} /></button>
        </div>
      </div>
      <div className="panel-body" style={{ padding: 18 }}>
        <div className="grid-2" style={{ marginBottom: 16 }}>
          <Info icon={<Building2 size={13} />} label="联系人" value={c.contact || '—'} />
          <Info icon={<Globe size={13} />} label="地区 / 国家" value={`${regionLabel(c.region)} · ${c.country || '—'}`} />
          <ContactInfo icon={<Mail size={13} />} label="邮箱" value={c.email || '—'} active={preferred.includes('email')} onToggle={() => onTogglePreferred('email')} />
          <ContactInfo icon={<Phone size={13} />} label="电话" value={c.phone || '—'} active={preferred.includes('phone')} onToggle={() => onTogglePreferred('phone')} />
          <Info icon={<Clock size={13} />} label="当地时间" value={lt ? `${lt.time}（${lt.en}）` : '—'} />
          <Info icon={<Globe size={13} />} label="网站" value={c.website ? <a href={c.website} target="_blank" rel="noreferrer" style={{ color: 'var(--blue)' }}>{c.website}</a> : '—'} />
        </div>
        {c.source && <div className="field"><span className="field-label">客户来源</span><span className="badge badge-region">{c.source}</span></div>}
        {c.productLines && c.productLines.length > 0 && (
          <div className="field">
            <span className="field-label">关注产品线</span>
            <div className="checkbox-row">{c.productLines.map(p => <span key={p} className="badge badge-region">{p}</span>)}</div>
            {c.productLinesNote && <div style={{ fontSize: 12.5, marginTop: 6, color: 'var(--ink-soft)' }}>意向型号：{c.productLinesNote}</div>}
          </div>
        )}
        {c.tags && <div className="field"><span className="field-label">标签</span><div style={{ fontSize: 12.5 }}>{c.tags}</div></div>}

        <CollapsibleSection title="货代信息">
          {!(c.warehouseAddress || c.freightContact || c.shippingMarkNote) ? (
            <div className="row-sub" style={{ padding: '8px 0' }}>暂无货代信息</div>
          ) : (
            <>
              <div className="grid-2">
                {c.warehouseAddress && <Info label="仓库地址" value={c.warehouseAddress} />}
                {c.freightContact && <Info label="货代联系方式" value={c.freightContact} />}
              </div>
              {c.shippingMarkNote && <div style={{ fontSize: 12.5, marginTop: 6, whiteSpace: 'pre-wrap' }}>箱唛 / 备注：{c.shippingMarkNote}</div>}
            </>
          )}
        </CollapsibleSection>

        {c.notes && <div className="field"><span className="field-label">备注</span><div style={{ fontSize: 12.5, whiteSpace: 'pre-wrap' }}>{c.notes}</div></div>}

        <div style={{ display: 'flex', gap: 8, margin: '14px 0' }}>
          <span className="row-sub" style={{ flex: 1 }}>上次联系：{fmtDate(c.lastContact)} ｜ 状态：{statusLabel(s)}</span>
          <button className="btn btn-sm" onClick={onMarkContacted}><Check size={12} />标记今日已联系</button>
          <button className="btn btn-sm" disabled={!c.email} onClick={onEmail}><Mail size={12} />生成跟进邮件</button>
        </div>

        <div style={{ display: 'flex', gap: 8, alignItems: 'center', margin: '0 0 14px', flexWrap: 'wrap' }}>
          <button className="btn btn-sm" disabled={!aiConfigHasKey || aiGenerating} onClick={() => onGenerateAi('email')}>
            <Sparkles size={12} />{aiGenerating ? '生成中…' : 'AI生成邮件'}
          </button>
          <button className="btn btn-sm" disabled={!aiConfigHasKey || aiGenerating} onClick={() => onGenerateAi('whatsapp')}>
            <Sparkles size={12} />{aiGenerating ? '生成中…' : 'AI生成WhatsApp话术'}
          </button>
          {!aiConfigHasKey && <span className="row-sub">未配置DeepSeek，去"设置"里填API Key</span>}
        </div>

        <div className="field">
          <span className="field-label">客户文件管理</span>
          <div style={{ display: 'flex', gap: 8, alignItems: 'stretch', flexWrap: 'wrap', marginBottom: 8 }}>
            <label
              className={`dropzone ${fileDragActive ? 'drag-active' : ''}`}
              onDragOver={onFileDragOver} onDragLeave={onFileDragLeave} onDrop={onFileDrop}
            >
              <input
                key={fileInputKey} type="file"
                onChange={e => onPickAttachment(e.target.files && e.target.files[0])}
                style={{ display: 'none' }}
              />
              <Paperclip size={15} style={{ marginBottom: 4 }} />
              <div>{fileDragActive ? '松开鼠标完成选择' : attachmentFileName ? `已选择：${attachmentFileName}` : '点击选择文件，或把文件拖到这里'}</div>
            </label>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 8, flex: '1 1 160px' }}>
              <select className="select" value={attachmentCategory} onChange={e => setAttachmentCategory(e.target.value)}>
                {FILE_CATEGORIES.map(cat => <option key={cat} value={cat}>{cat}</option>)}
              </select>
              <input
                className="input" placeholder="备注（可选）"
                value={attachmentNote} onChange={e => setAttachmentNote(e.target.value)}
              />
              <button className="btn btn-sm" disabled={uploading} onClick={onUploadAttachment} style={{ justifyContent: 'center' }}>
                <Plus size={12} />{uploading ? '上传中…' : '上传'}
              </button>
            </div>
          </div>
        </div>
        <CollapsibleSection title="文件列表" count={(c.attachments || []).length} defaultOpen={(c.attachments || []).length > 0}>
          {(!c.attachments || c.attachments.length === 0) ? (
            <div className="row-sub" style={{ padding: '8px 0' }}>暂无文件</div>
          ) : (
            <>
              <div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
                <input
                  className="input" style={{ flex: 1 }} placeholder="搜索文件名 / 备注"
                  value={fileSearch} onChange={e => setFileSearch(e.target.value)}
                />
                <select className="select" style={{ flex: '0 1 120px' }} value={fileCategoryFilter} onChange={e => setFileCategoryFilter(e.target.value)}>
                  <option value="all">全部类别</option>
                  {FILE_CATEGORIES.map(cat => <option key={cat} value={cat}>{cat}</option>)}
                </select>
              </div>
              {filteredAttachments.length === 0 ? (
                <div className="row-sub" style={{ padding: '8px 0' }}>没有匹配的文件</div>
              ) : filteredAttachments.map((a, i) => (
                <div key={a.fileName + i} style={{ display: 'flex', gap: 10, padding: '8px 0', borderBottom: '1px solid var(--line)', alignItems: 'flex-start' }}>
                  <Paperclip size={13} color="var(--ink-soft)" style={{ marginTop: 2, flexShrink: 0 }} />
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <span className="crm-mono" style={{ fontSize: 11.5, color: 'var(--ink-soft)' }}>{a.date}</span>
                    {' '}<span className="badge badge-region">{a.category || 'Other'}</span>
                    <div style={{ fontSize: 12.5, wordBreak: 'break-word', marginTop: 2 }}>
                      <a href={`/uploads/${c.id}/${a.fileName}`} target="_blank" rel="noreferrer" style={{ color: 'var(--blue)' }}>{a.originalName}</a>
                      {a.note && <span style={{ color: 'var(--ink-soft)' }}> — {a.note}</span>}
                    </div>
                  </div>
                  <button className="btn btn-sm btn-ghost btn-danger" onClick={() => onDeleteAttachment(a.fileName)}><Trash2 size={12} /></button>
                </div>
              ))}
            </>
          )}
        </CollapsibleSection>
      </div>
    </div>
  );
}

/* ----------------------------- product form / detail ----------------------------- */

function ProductForm({ draft, setDraft, onSave, onCancel }) {
  const set = (patch) => setDraft(d => ({ ...d, ...patch }));
  const topCat = PRODUCT_CATEGORIES.find(c => c.id === draft.category);
  const subCat = topCat && topCat.subs.find(s => s.id === draft.subCategory);
  const hasSubSub = !!(subCat && subCat.subs && subCat.subs.length);
  return (
    <div className="panel">
      <div className="panel-header"><div className="panel-title"><Edit2 size={14} />{draft.name ? '编辑产品' : '新建产品'}</div></div>
      <div className="panel-body" style={{ padding: 18 }}>
        <div className="grid-2">
          <div className="field"><span className="field-label">型号 / 产品名称 *</span><input className="input" value={draft.name} onChange={e => set({ name: e.target.value })} /></div>
          <div className="field"><span className="field-label">品牌</span><input className="input" value={draft.brand} onChange={e => set({ brand: e.target.value })} placeholder="如：Eltek / 华为 / 伊顿 / SKE / Baace" /></div>
        </div>
        <div className="grid-2">
          <div className="field">
            <span className="field-label">产品大类 *</span>
            <select className="select" value={draft.category} onChange={e => set({ category: e.target.value, subCategory: '', subSubCategory: '' })}>
              <option value="">请选择</option>
              {PRODUCT_CATEGORIES.map(c => <option key={c.id} value={c.id}>{c.label}</option>)}
            </select>
          </div>
          {topCat && (
            <div className="field">
              <span className="field-label">细分品类</span>
              <select className="select" value={draft.subCategory} onChange={e => set({ subCategory: e.target.value, subSubCategory: '' })}>
                <option value="">请选择</option>
                {topCat.subs.map(s => <option key={s.id} value={s.id}>{s.label}</option>)}
              </select>
            </div>
          )}
        </div>
        {hasSubSub && (
          <div className="field">
            <span className="field-label">类型</span>
            <select className="select" value={draft.subSubCategory} onChange={e => set({ subSubCategory: e.target.value })}>
              <option value="">请选择</option>
              {subCat.subs.map(s => <option key={s.id} value={s.id}>{s.label}</option>)}
            </select>
          </div>
        )}
        <div className="grid-2">
          <div className="field"><span className="field-label">功率 / 容量</span><input className="input" value={draft.powerOrCapacity} onChange={e => set({ powerOrCapacity: e.target.value })} placeholder="如：10kVA / 100Ah / 5kWh" /></div>
          <div className="field"><span className="field-label">电压</span><input className="input" value={draft.voltage} onChange={e => set({ voltage: e.target.value })} placeholder="如：48V / 192V / 220V" /></div>
        </div>
        <div className="field"><span className="field-label">认证（逗号分隔）</span><input className="input" value={draft.certifications} onChange={e => set({ certifications: e.target.value })} placeholder="如：CE, FCC, UL, SASO" /></div>
        <div className="field"><span className="field-label">适用市场地区</span><RegionPills value={draft.targetRegions} onChange={v => set({ targetRegions: v })} /></div>
        <div className="field"><span className="field-label">备注 / 卖点说明</span><textarea className="textarea" style={{ fontFamily: 'inherit', fontSize: 13 }} value={draft.notes} onChange={e => set({ notes: e.target.value })} /></div>
      </div>
      <div className="modal-foot"><button className="btn" onClick={onCancel}>取消</button><button className="btn btn-primary" onClick={onSave}><Save size={13} />保存</button></div>
    </div>
  );
}

function ProductDetail({ product: p, onEdit, onDelete }) {
  const certs = (p.certifications || '').split(',').map(s => s.trim()).filter(Boolean);
  return (
    <div className="panel">
      <div className="panel-header">
        <div className="panel-title"><div className="led-dot blue" />{p.name || '(未命名)'}</div>
        <div style={{ display: 'flex', gap: 8 }}>
          <button className="btn btn-sm" onClick={onEdit}><Edit2 size={12} />编辑</button>
          <button className="btn btn-sm btn-ghost btn-danger" onClick={onDelete}><Trash2 size={12} /></button>
        </div>
      </div>
      <div className="panel-body" style={{ padding: 18 }}>
        <div className="grid-2" style={{ marginBottom: 16 }}>
          <Info icon={<Layers size={13} />} label="分类" value={categoryBreadcrumb(p)} />
          <Info icon={<Building2 size={13} />} label="品牌" value={p.brand || '—'} />
          <Info icon={<Zap size={13} />} label="功率 / 容量" value={p.powerOrCapacity || '—'} />
          <Info icon={<Zap size={13} />} label="电压" value={p.voltage || '—'} />
        </div>
        {certs.length > 0 && (
          <div className="field"><span className="field-label" style={{ display: 'flex', alignItems: 'center', gap: 5 }}><Tag size={12} />认证</span><div className="checkbox-row">{certs.map(c => <span key={c} className="badge badge-region">{c}</span>)}</div></div>
        )}
        {p.targetRegions && p.targetRegions.length > 0 && (
          <div className="field"><span className="field-label">适用市场地区</span><div className="checkbox-row">{p.targetRegions.map(r => <span key={r} className="badge badge-region">{regionLabel(r)}</span>)}</div></div>
        )}
        {p.notes && <div className="field"><span className="field-label">备注</span><div style={{ fontSize: 12.5, whiteSpace: 'pre-wrap' }}>{p.notes}</div></div>}
      </div>
    </div>
  );
}

function Info({ icon, label, value }) {
  return (
    <div>
      <div className="field-label" style={{ display: 'flex', alignItems: 'center', gap: 5 }}>{icon}{label}</div>
      <div style={{ fontSize: 13 }}>{value}</div>
    </div>
  );
}
