// data.jsx — sample data + formatting helpers + scoring engine for Zei JP Admin
// Schema mirrors what the production crawler (susumi_deal_os_v9) emits per product,
// plus everything the V3 admin needs to display/score/route.

/* ============ FX / PRICE CONFIG ============ */
const PRICE_CONFIG = {
  rate: 196,            // JPY -> VND per yen (mirrors susumi config.json)
  shipPerKg: 190000,    // VND per kg
  defaultWeightKg: 0.5, // default product weight
  margin: 0,            // VND added on top
};
// FX kept as legacy alias for older sample data
const FX = PRICE_CONFIG.rate;

/* ============ BRANDS ============ */
const BRANDS = [
  { key: 'uniqlo', name: 'Uniqlo', jp: 'ユニクロ', safe: 'U.ni.q.lo', color: '#d8232a', hosts: ['uniqlo.com', 'uniqlo.com/jp'], kind: 'json_api',  weight: 9, enabled: true,  interval: 720,  lastScrape: '2 phút trước',  rate24: 95,  rateN: '19/20', sale: 'https://www.uniqlo.com/jp/sale',         pages: 4, cookies: false, fallback: null,            disabledReason: null },
  { key: 'gu',     name: 'GU',     jp: 'ジーユー',  safe: 'G.U',       color: '#e4002b', hosts: ['gu-global.com', 'gu.jp'],     kind: 'json_api',  weight: 8, enabled: true,  interval: 720,  lastScrape: '5 phút trước',  rate24: 100, rateN: '8/8',   sale: 'https://www.gu-global.com/jp/sale',     pages: 3, cookies: false, fallback: null,            disabledReason: null },
  { key: 'nike',   name: 'Nike',   jp: 'ナイキ',    safe: 'N.ke',      color: '#111111', hosts: ['nike.com', 'nike.com/jp'],    kind: 'html_parse',weight: 7, enabled: true,  interval: 1440, lastScrape: '38 phút trước', rate24: 88,  rateN: '14/16', sale: 'https://www.nike.com/jp/w/sale',         pages: 2, cookies: false, fallback: 'rakuten_nike',  disabledReason: null },
  { key: 'adidas', name: 'Adidas', jp: 'アディダス', safe: 'A.di.das',  color: '#1a1a1a', hosts: ['adidas.jp', 'adidas.com'],    kind: 'html_parse',weight: 7, enabled: false, interval: 1440, lastScrape: '—',             rate24: 0,   rateN: '0/0',   sale: 'https://shop.adidas.jp/sale',           pages: 2, cookies: true,  fallback: 'rakuten_adidas',disabledReason: 'Blocked HTTP 403 anti-bot. Dùng rakuten_adidas thay thế.' },
  { key: 'gap',    name: 'GAP',    jp: 'ギャップ',  safe: 'G.AP',      color: '#002868', hosts: ['gap.co.jp'],                  kind: 'html_parse',weight: 6, enabled: true,  interval: 1440, lastScrape: '1 giờ trước',   rate24: 78,  rateN: '11/14', sale: 'https://www.gap.co.jp/gap/sale',         pages: 2, cookies: false, fallback: null,            disabledReason: null },
  { key: 'lacoste',name: 'Lacoste',jp: 'ラコステ',  safe: 'L.cost',    color: '#004a32', hosts: ['lacoste.jp'],                 kind: 'html_parse',weight: 6, enabled: true,  interval: 1440, lastScrape: '2 giờ trước',   rate24: 82,  rateN: '9/11',  sale: 'https://www.lacoste.jp/sale',            pages: 2, cookies: false, fallback: null,            disabledReason: null },
  { key: 'amazonjp',name:'Amazon JP',jp:'アマゾン', safe: 'A.ma.zon',  color: '#ff9900', hosts: ['amazon.co.jp'],               kind: 'html_parse',weight: 4, enabled: false, interval: 2880, lastScrape: '4 giờ trước',   rate24: 62,  rateN: '8/13',  sale: '',                                       pages: 1, cookies: false, fallback: null,            disabledReason: 'High anti-bot + price logic phức tạp. Tạm tắt.' },
  { key: 'muji',   name: 'Muji',   jp: '無印良品',  safe: 'Mu.ji',     color: '#7a2226', hosts: ['muji.com', 'muji.net'],       kind: 'json_api',  weight: 5, enabled: true,  interval: 1440, lastScrape: '2 giờ trước',   rate24: 91,  rateN: '10/11', sale: 'https://www.muji.com/jp/sale',           pages: 2, cookies: false, fallback: null,            disabledReason: null },
  { key: 'beams',  name: 'Beams',  jp: 'ビームス',  safe: 'Be.ams',    color: '#e60012', hosts: ['beams.co.jp'],                kind: 'manual',    weight: 4, enabled: false, interval: 1440, lastScrape: '3 ngày trước',  rate24: 0,   rateN: '0/0',   sale: '',                                       pages: 1, cookies: false, fallback: null,            disabledReason: null },
  // Rakuten aggregator brands — used as fallback for blocked official sites
  { key: 'rakuten_nike',  name: 'Rakuten · Nike',  jp: '楽天 NIKE', safe: 'Rkt.N',  color: '#bf0000', hosts: ['search.rakuten.co.jp'], kind: 'html_parse', weight: 5, enabled: true, interval: 1440, lastScrape: '50 phút trước', rate24: 84, rateN: '12/14', sale: 'https://search.rakuten.co.jp/search/mall/NIKE+SALE/',   pages: 3, cookies: false, fallback: null, disabledReason: null, isFallback: true },
  { key: 'rakuten_adidas',name: 'Rakuten · Adidas',jp: '楽天 ADIDAS',safe: 'Rkt.A', color: '#bf0000', hosts: ['search.rakuten.co.jp'], kind: 'html_parse', weight: 5, enabled: true, interval: 1440, lastScrape: '55 phút trước', rate24: 80, rateN: '10/12', sale: 'https://search.rakuten.co.jp/search/mall/adidas+%E3%82%BB%E3%83%BC%E3%83%AB/', pages: 3, cookies: false, fallback: null, disabledReason: null, isFallback: true },
  { key: 'rakuten_reebok',name: 'Rakuten · Reebok',jp: '楽天 REEBOK',safe: 'Rkt.R', color: '#bf0000', hosts: ['search.rakuten.co.jp'], kind: 'html_parse', weight: 4, enabled: true, interval: 1440, lastScrape: '1 giờ trước', rate24: 76, rateN: '8/11', sale: 'https://search.rakuten.co.jp/search/mall/reebok+%E3%82%BB%E3%83%BC%E3%83%AB/', pages: 3, cookies: false, fallback: null, disabledReason: null, isFallback: true },
];
const brandOf = (k) => BRANDS.find(b => b.key === k) || BRANDS[0];

/* ============ PRODUCT STATUS (state of the catalog item itself) ============ */
const PRODUCT_STATUS = {
  pending:  { label: 'Chờ review',     cls: 'badge-blue',    hint: 'Mới scrape, chờ admin xem' },
  eligible: { label: 'Đủ điều kiện',   cls: 'badge-green',   hint: 'Đã duyệt, có thể tạo bài' },
  invalid:  { label: 'Không hợp lệ',   cls: 'badge-red',     hint: 'Thiếu data bắt buộc' },
  skipped:  { label: 'Đã bỏ qua',      cls: 'badge-neutral', hint: 'Admin đánh dấu không đăng' },
  archived: { label: 'Lưu trữ',        cls: 'badge-neutral', hint: 'Sản phẩm cũ trên 30 ngày' },
};

/* ============ POST STATUS (state of one content draft / scheduled post) ============ */
const POST_STATUS = {
  draft:         { label: 'Nháp',             cls: 'badge-neutral' },
  text_pending:  { label: 'Chờ duyệt text',   cls: 'badge-yellow' },
  image_pending: { label: 'Chờ duyệt ảnh',    cls: 'badge-yellow' },
  approved:      { label: 'Đã duyệt',         cls: 'badge-green' },
  scheduled:     { label: 'Đã đặt lịch',      cls: 'badge-violet' },
  posted:        { label: 'Đã đăng',          cls: 'badge-green' },
  failed:        { label: 'Lỗi đăng',         cls: 'badge-red' },
};

// Legacy STATUS alias kept so older screens (analytics, queue) keep rendering
// while we migrate per-screen wiring. Combines both dicts.
const STATUS = {
  ...PRODUCT_STATUS,
  ...POST_STATUS,
  ready:          PRODUCT_STATUS.pending,
  generating:     POST_STATUS.draft,
  text_ready:     POST_STATUS.text_pending,
  text_approved:  POST_STATUS.image_pending,
  image_ready:    POST_STATUS.image_pending,
  image_approved: POST_STATUS.approved,
  awaiting:       POST_STATUS.text_pending,
  reviewed_skip:  PRODUCT_STATUS.skipped,
  failed:         POST_STATUS.failed,
};

const SOURCE_ICON = { cron: 'rotate', web: 'globe', telegram: 'telegram', zalo: 'send', watchlist: 'star' };

/* ============ FORMATTERS ============ */
const fmtVND = (n) => new Intl.NumberFormat('vi-VN').format(Math.round(n || 0)) + 'đ';
const fmtJPY = (n) => '¥' + new Intl.NumberFormat('ja-JP').format(Math.round(n || 0));
const fmtPct = (n) => (n >= 0 ? '+' : '') + Math.round(n) + '%';

/* ============ CONFIDENCE BUCKETS ============ */
const REQUIRED_FIELDS = ['titleVn', 'jpy', 'sizes', 'mainImage', 'productCode'];
const NICE_FIELDS = ['description', 'material', 'color', 'category'];

function confidenceOf(o) {
  const missing = [];
  REQUIRED_FIELDS.forEach(f => { if (!o[f] && o[f] !== 0) missing.push(f); });
  const niceMissing = NICE_FIELDS.filter(f => !o[f] && o[f] !== 0);
  let conf;
  if (missing.length === 0 && niceMissing.length <= 1) conf = 'high';
  else if (missing.length === 0) conf = 'medium';
  else conf = 'low';
  return { conf, missing: [...missing, ...niceMissing] };
}

/* ============ VND CALC ============ */
function calcVnd({ yen, rate = PRICE_CONFIG.rate, weightKg = PRICE_CONFIG.defaultWeightKg, shipPerKg = PRICE_CONFIG.shipPerKg, margin = PRICE_CONFIG.margin }) {
  const product = yen * rate;
  const ship = weightKg * shipPerKg;
  const total = product + ship + margin;
  return { product, ship, margin, total, weightKg, rate, shipPerKg };
}

/* ============ SCORING CONFIG ============ */
// Tier table: maps discount % range → bonus points.
// User can edit this in Settings → Deal Score (Batch 3).
const SCORING_CONFIG = {
  // Tier 1: discount-based bonus
  discountTiers: [
    { min: 50, score: 30, label: 'Mega deal' },
    { min: 35, score: 24, label: 'Strong sale' },
    { min: 20, score: 15, label: 'Sale ổn' },
    { min: 10, score: 8,  label: 'Sale nhẹ' },
    { min: 0,  score: 0,  label: 'No discount' },
  ],
  // Tier 2: VND friendliness (lower = better for VN proxy market)
  priceVndTiers: [
    { max: 500000,   score: 20, label: 'Dưới 500k · best impulse buy' },
    { max: 1000000,  score: 16, label: 'Dưới 1tr · mass market' },
    { max: 2000000,  score: 10, label: 'Dưới 2tr · trung cấp' },
    { max: 5000000,  score: 4,  label: 'Dưới 5tr · premium' },
    { max: Infinity, score: 0,  label: '5tr+ · luxury' },
  ],
  // Coupon contribution: couponPercent * couponMultiplier
  couponMultiplier: 0.6,
  // Brand quality weight: 1-10. score += brandWeight * brandWeightFactor
  brandWeightFactor: 2,
  // Bonuses
  watchlistBonus: 15,
  sizeAvailFullBonus: 5,
  sizeAvailHalfBonus: 2,
  confidenceBonus: { high: 10, medium: 5, low: 0 },
  // Auto-routing threshold: score >= this auto-creates content draft
  autoRouteThreshold: 60,
  // Min score to be eligible for posting
  minPostableScore: 40,
};

/* ============ SCORING ENGINE ============ */
function calcScore(p, cfg = SCORING_CONFIG) {
  const breakdown = {};
  // 1. Brand weight
  const b = brandOf(p.brand);
  breakdown.brand = (b.weight || 5) * cfg.brandWeightFactor;
  // 2. Discount tier
  const tier = cfg.discountTiers.find(t => p.disc >= t.min);
  breakdown.discount = tier ? tier.score : 0;
  breakdown.discountLabel = tier ? tier.label : '';
  // 3. Coupon
  breakdown.coupon = Math.round((p.couponPercent || 0) * cfg.couponMultiplier);
  // 4. VND friendliness
  const finalVnd = p.priceVnd || p.vnd || 0;
  const vndTier = cfg.priceVndTiers.find(t => finalVnd <= t.max);
  breakdown.priceFriendly = vndTier ? vndTier.score : 0;
  breakdown.priceFriendlyLabel = vndTier ? vndTier.label : '';
  // 5. Watchlist match
  breakdown.watchlist = p.watchlistMatch ? cfg.watchlistBonus : 0;
  // 6. Size availability
  const sizeRatio = p.sizeTotal > 0 ? (p.sizeAvail || 0) / p.sizeTotal : 0;
  if (sizeRatio >= 0.8) breakdown.size = cfg.sizeAvailFullBonus;
  else if (sizeRatio >= 0.5) breakdown.size = cfg.sizeAvailHalfBonus;
  else breakdown.size = 0;
  // 7. Confidence bonus
  breakdown.confidence = cfg.confidenceBonus[p.confidence || 'medium'] || 0;
  // Total
  breakdown.total = Math.min(100, Math.round(
    breakdown.brand + breakdown.discount + breakdown.coupon + breakdown.priceFriendly +
    breakdown.watchlist + breakdown.size + breakdown.confidence
  ));
  return breakdown;
}

/* ============ WATCHLIST ============ */
const WATCHLIST = [
  { id: 'wl1', keyword: 'Air Max',     brand: 'nike',   maxPriceYen: 18000, active: true,  lastHit: '2 ngày trước',  hitCount: 4 },
  { id: 'wl2', keyword: 'AF1',         brand: 'nike',   maxPriceYen: 15000, active: true,  lastHit: '12 giờ trước',  hitCount: 7 },
  { id: 'wl3', keyword: 'Heattech',    brand: 'uniqlo', maxPriceYen: 2500,  active: true,  lastHit: '4 giờ trước',   hitCount: 9 },
  { id: 'wl4', keyword: 'AIRism',      brand: 'uniqlo', maxPriceYen: 2000,  active: true,  lastHit: '30 phút trước', hitCount: 14 },
  { id: 'wl5', keyword: 'Samba OG',    brand: 'adidas', maxPriceYen: 18000, active: true,  lastHit: '5 giờ trước',   hitCount: 3 },
  { id: 'wl6', keyword: 'Dunk Low',    brand: 'nike',   maxPriceYen: 16000, active: true,  lastHit: '1 ngày trước',  hitCount: 5 },
  { id: 'wl7', keyword: 'Light Down',  brand: 'gu',     maxPriceYen: 4500,  active: true,  lastHit: '28 phút trước', hitCount: 6 },
  { id: 'wl8', keyword: 'Trefoil',     brand: 'adidas', maxPriceYen: 10000, active: false, lastHit: '1 tuần trước',  hitCount: 1 },
];

function matchWatchlist(p) {
  return WATCHLIST.find(w => w.active && w.brand === p.brand &&
    (p.titleVn || '').toLowerCase().includes(w.keyword.toLowerCase()) &&
    p.jpy <= w.maxPriceYen) || null;
}

/* ============ PRODUCT FACTORY ============ */
let _pid = 1000;
function P(o) {
  _pid++;
  const jpy = o.jpy, jpyOld = o.jpyOld || null;
  const disc = jpyOld ? Math.round((1 - jpy / jpyOld) * 100) : 0;
  const couponPercent = o.couponPercent || 0;
  const couponText = couponPercent > 0 ? `クーポン ${couponPercent}% OFF` : '';
  const priceAfterCoupon = couponPercent > 0 ? Math.round(jpy * (1 - couponPercent / 100)) : jpy;
  const effectivePrice = priceAfterCoupon;
  const weightKg = o.weightKg || PRICE_CONFIG.defaultWeightKg;
  const vndBreakdown = calcVnd({ yen: effectivePrice, weightKg });
  const sizes = o.sizes || ['S','M','L','XL'];
  const sizeTotal = sizes.length;
  const sizeAvail = o.sizeAvail !== undefined ? o.sizeAvail : Math.max(1, Math.floor(sizeTotal * (0.4 + Math.random() * 0.5)));
  const id = 'p' + _pid;

  const draft = {
    id, sku: o.sku, brand: o.brand,
    titleVn: o.vn, titleJp: o.jp,
    jpy, jpyOld, disc,
    couponPercent, couponText,
    priceAfterCoupon, effectivePrice,
    vnd: effectivePrice * PRICE_CONFIG.rate, // legacy: yen × rate only, no ship
    vndOld: jpyOld ? jpyOld * PRICE_CONFIG.rate : null,
    priceVnd: vndBreakdown.total, // new: includes ship + margin
    vndBreakdown,
    weightKg,
    status: o.status, source: o.source || 'cron',
    created: o.created,
    sizes, sizeTotal, sizeAvail,
    colors: o.colors || 3,
    color: o.color || '',
    material: o.material || '',
    category: o.category || '',
    productCode: o.productCode || o.sku,
    mainImage: o.mainImage || '',
    desc: o.desc || 'Sản phẩm thời trang Nhật, chất liệu cao cấp, form chuẩn Á Đông. Đã dịch tự động từ mô tả gốc tiếng Nhật.',
    description: o.desc || '',
    url: o.url, label: o.label,
    campaignId: o.campaignId || null,
    crawledAt: o.crawledAt || '2026-05-30T08:00:00+09:00',
    validityReason: o.validityReason || null,
    isValid: o.validityReason ? false : true,
  };
  // Confidence + watchlist + score
  const { conf, missing } = confidenceOf(draft);
  draft.confidence = conf;
  draft.missingFields = missing;
  draft.watchlistMatch = matchWatchlist(draft);
  draft.scoreBreakdown = calcScore(draft);
  draft.score = o.score !== undefined ? o.score : draft.scoreBreakdown.total;
  return draft;
}

/* ============ SAMPLE PRODUCTS ============ */
// Product status only — no post-lifecycle states. Post tracking lives in POSTS.
const PRODUCTS = [
  P({ sku: '465185', brand: 'uniqlo', vn: 'Áo thun nam AIRism Cotton cổ tròn ngắn tay', jp: 'メンズ エアリズムコットンクルーネックT', jpy: 1500, jpyOld: 1990, status: 'pending', source: 'cron', created: '12 phút trước', url: 'uniqlo.com/jp/465185', label: 'AIRism Tee · navy', colors: 6, campaignId: 'c1', material: 'Cotton 100%', category: 'T-shirt', color: 'Navy/White/Gray', mainImage: 'uniqlo-airism.jpg' }),
  P({ sku: '462160', brand: 'uniqlo', vn: 'Quần Smart Ankle nam co giãn 2 chiều',       jp: 'メンズ スマートアンクルパンツ',           jpy: 2990, jpyOld: 3990, status: 'pending', source: 'cron', created: '20 phút trước', url: 'uniqlo.com/jp/462160', label: 'Ankle Pants · charcoal', campaignId: 'c1', material: 'Polyester 65% Rayon 35%', category: 'Pants', color: 'Charcoal' }),
  P({ sku: 'GU-3391', brand: 'gu',    vn: 'Áo khoác phao siêu nhẹ nữ có mũ',            jp: 'ウルトラライトダウンパーカー',              jpy: 3990, jpyOld: 5990, status: 'pending', source: 'cron', created: '28 phút trước', url: 'gu-global.com/jp/3391',  label: 'Light Down · beige', colors: 4, campaignId: 'c2', material: 'Nylon ripstop', category: 'Outerwear', color: 'Beige', couponPercent: 10 }),
  P({ sku: 'GU-2210', brand: 'gu',    vn: 'Chân váy xếp ly dài nữ',                     jp: 'プリーツロングスカート',                   jpy: 1990, jpyOld: 2490, status: 'pending', source: 'web',  created: '41 phút trước', url: 'gu-global.com/jp/2210',  label: 'Pleated Skirt · olive', campaignId: 'c2', material: 'Polyester 100%', category: 'Skirt', color: 'Olive' }),
  P({ sku: 'DV9-553', brand: 'nike',  vn: 'Giày Nike Air Force 1 ’07 nam trắng',         jp: 'ナイキ エアフォース1 07',                   jpy: 14300, jpyOld: 16500, status: 'eligible', source: 'watchlist', created: '1 giờ trước', url: 'nike.com/jp/dv9553', label: 'AF1 ’07 · triple white', sizes: ['25','26','27','28','29'], colors: 2, material: 'Leather', category: 'Sneakers', color: 'Triple White', productCode: 'DV9553-100' }),
  P({ sku: 'IG-8472', brand: 'rakuten_adidas', vn: 'Áo hoodie Trefoil Essentials unisex', jp: 'トレフォイル フーディー',                jpy: 5500, jpyOld: 8800, status: 'eligible', source: 'cron', created: '1 giờ trước', url: 'rakuten.co.jp/ig8472', label: 'Trefoil Hoodie · black', material: 'Cotton/Polyester', category: 'Hoodie', color: 'Black', couponPercent: 15 }),
  P({ sku: 'B0CX-21', brand: 'amazonjp', vn: 'Tất cổ ngắn nam cotton (set 5 đôi)',       jp: 'メンズ ソックス 5足セット',                 jpy: 1280, jpyOld: 1680, status: 'invalid', source: 'telegram', created: '2 giờ trước', url: 'amazon.co.jp/B0CX21', label: 'Sock set ×5', sizes: ['25-27cm'], colors: 1, material: 'Cotton', category: 'Socks', color: 'Black', validityReason: 'Tên sản phẩm quá ngắn + thiếu ảnh chính (1.8KB chip detected)' }),
  P({ sku: 'MJ-0098', brand: 'muji',  vn: 'Áo sơ mi vải Oxford nam dài tay',             jp: 'オックスフォードシャツ',                   jpy: 2990, jpyOld: null, status: 'pending', source: 'cron', created: '2 giờ trước', url: 'muji.com/jp/0098', label: 'Oxford Shirt · white', material: 'Cotton 100%', category: 'Shirt', color: 'White' }),
  P({ sku: '463210', brand: 'uniqlo', vn: 'Áo len cổ lọ nữ Heattech',                    jp: 'ヒートテックタートルネックセーター',         jpy: 1990, jpyOld: 2990, status: 'eligible', source: 'watchlist', created: '3 giờ trước', url: 'uniqlo.com/jp/463210', label: 'Heattech knit · cream', colors: 5, material: 'Acrylic/Wool', category: 'Knitwear', color: 'Cream' }),
  P({ sku: 'CW-2288', brand: 'nike',  vn: 'Quần short Dri-FIT nam tập gym',              jp: 'ドライフィット ショートパンツ',              jpy: 2750, jpyOld: 3500, status: 'pending', source: 'cron', created: '3 giờ trước', url: 'nike.com/jp/cw2288', label: 'Dri-FIT short', material: 'Polyester', category: 'Shorts', color: 'Black' }),
  P({ sku: 'GU-7741', brand: 'gu',    vn: 'Áo blazer nữ form rộng oversize',             jp: 'オーバーサイズテーラードジャケット',        jpy: 4990, jpyOld: 5990, status: 'eligible', source: 'cron', created: '5 giờ trước', url: 'gu-global.com/jp/7741',  label: 'Oversize Blazer · grey', material: 'Polyester blend', category: 'Blazer', color: 'Grey' }),
  P({ sku: 'HP-6612', brand: 'rakuten_adidas', vn: 'Giày Samba OG cổ điển nâu',          jp: 'サンバ OG',                              jpy: 16500, jpyOld: null, status: 'eligible', source: 'watchlist', created: '5 giờ trước', url: 'rakuten.co.jp/hp6612', label: 'Samba OG · brown gum', sizes: ['25','26','27','28'], colors: 1, material: 'Suede/Leather', category: 'Sneakers', color: 'Brown Gum', couponPercent: 12, productCode: 'HP6612' }),
  P({ sku: '461887', brand: 'uniqlo', vn: 'Quần jeans nam Slim Fit co giãn',             jp: 'スリムフィットジーンズ',                   jpy: 3990, jpyOld: 4990, status: 'eligible', source: 'cron', created: '8 giờ trước', url: 'uniqlo.com/jp/461887', label: 'Slim Jeans · indigo', material: 'Denim cotton', category: 'Jeans', color: 'Indigo' }),
  P({ sku: 'MJ-3320', brand: 'muji',  vn: 'Túi tote vải canvas dày',                     jp: 'コットンキャンバストートバッグ',           jpy: 1490, jpyOld: null, status: 'pending', source: 'web',  created: '9 giờ trước', url: 'muji.com/jp/3320', label: 'Canvas Tote · natural', material: 'Cotton canvas', category: 'Bag', color: 'Natural' }),
  P({ sku: 'GU-1102', brand: 'gu',    vn: 'Áo thun nữ in hình graphic oversize',         jp: 'グラフィックT オーバーサイズ',             jpy: 990, jpyOld: 1490, status: 'skipped', source: 'cron', created: '11 giờ trước', url: 'gu-global.com/jp/1102',  label: 'Graphic Tee', material: 'Cotton', category: 'T-shirt', color: 'White/Print' }),
  P({ sku: 'DZ-5485', brand: 'nike',  vn: 'Giày Nike Dunk Low nữ panda',                 jp: 'ダンク ロー',                            jpy: 13200, jpyOld: 15400, status: 'eligible', source: 'watchlist', created: '1 ngày trước', url: 'nike.com/jp/dz5485', label: 'Dunk Low · panda', sizes: ['23','24','25','26'], colors: 1, material: 'Leather', category: 'Sneakers', color: 'Black/White', productCode: 'DZ5485-100' }),
];

/* ============ CAMPAIGNS ============ */
const CAMPAIGNS = [
  { id: 'c1', brand: 'uniqlo', title: 'Sale cuối tuần Uniqlo: -50% basic essentials', titleJp: '週末セール ベーシック半額',
    desc: 'Giảm 30-50% toàn bộ dòng AIRism, Heattech, Smart Ankle. Hot deal cho mùa hè.',
    discount: 50, startAt: '2026-05-29T00:00:00+09:00', endAt: '2026-06-01T23:59:59+09:00',
    productCount: 47, postedCount: 3, bannerLabel: 'Weekend Sale · -50%',
    status: 'active', sourceUrl: 'uniqlo.com/jp/sale-weekend', lastScraped: '32 phút trước', color: '#d8232a' },
  { id: 'c2', brand: 'gu', title: 'GU mùa hè: outerwear & dress giảm sốc', titleJp: 'GU 夏のセール',
    desc: 'Áo khoác phao, váy, blazer giảm 20-40%. Stock có hạn, hết hàng nhanh.',
    discount: 40, startAt: '2026-05-28T00:00:00+09:00', endAt: '2026-06-04T23:59:59+09:00',
    productCount: 28, postedCount: 1, bannerLabel: 'Summer Sale · -40%',
    status: 'active', sourceUrl: 'gu-global.com/jp/sale', lastScraped: '1 giờ trước', color: '#e4002b' },
  { id: 'c3', brand: 'nike', title: 'Nike Sneaker Week: AF1 + Dunk + Samba', titleJp: 'スニーカーウィーク',
    desc: 'Tuần lễ sneaker icon: AF1, Dunk Low, Samba giảm 10-25% chính hãng Nhật.',
    discount: 25, startAt: '2026-06-02T00:00:00+09:00', endAt: '2026-06-09T23:59:59+09:00',
    productCount: 18, postedCount: 0, bannerLabel: 'Sneaker Week · -25%',
    status: 'upcoming', sourceUrl: 'nike.com/jp/sneaker-week', lastScraped: '3 giờ trước', color: '#111111' },
  { id: 'c4', brand: 'muji', title: 'Muji No-Brand Sale: đồ gia dụng + thời trang', titleJp: '無印週間',
    desc: 'Thành viên Muji giảm thêm 10% khi mua đồ basic. Áo Oxford, túi canvas, đồ dùng.',
    discount: 10, startAt: '2026-05-25T00:00:00+09:00', endAt: '2026-05-30T23:59:59+09:00',
    productCount: 12, postedCount: 2, bannerLabel: 'Muji Week · -10%',
    status: 'active', sourceUrl: 'muji.com/jp/week', lastScraped: '2 giờ trước', color: '#7a2226' },
  { id: 'c5', brand: 'rakuten_adidas', title: 'Adidas Originals: Trefoil collection', titleJp: 'オリジナルス トレフォイル',
    desc: 'Bộ sưu tập Trefoil Essentials và Samba OG, giá thành viên ưu đãi.',
    discount: 15, startAt: '2026-05-15T00:00:00+09:00', endAt: '2026-05-27T23:59:59+09:00',
    productCount: 9, postedCount: 4, bannerLabel: 'Originals · -15%',
    status: 'ended', sourceUrl: 'rakuten.co.jp/originals', lastScraped: '1 ngày trước', color: '#1a1a1a' },
];
const campaignOf = (id) => CAMPAIGNS.find(c => c.id === id);
const productsInCampaign = (cid) => PRODUCTS.filter(p => p.campaignId === cid);

/* ============ POSTS ============ */
// Explicit post records. Each post references one or more products (productIds).
// Post status is independent of product status — a product remains "eligible"
// while many posts get created, scheduled, and published from it.
function scheduleFuture(i) { const d = new Date('2026-05-29T20:00:00+07:00'); d.setHours(d.getHours() + i * 5); return d.toISOString(); }
function schedulePast(i)   { const d = new Date('2026-05-28T09:00:00+07:00'); d.setHours(d.getHours() + i * 3); return d.toISOString(); }

const POSTS = [
  // Đang chờ duyệt text — vừa AI viết caption, chờ admin review chữ
  { id: 'post200', productIds: ['p1005'], productId: 'p1005',
    channel: 'fb', format: 'single', status: 'text_pending',
    textApproved: false, imageApproved: false,
    scheduledAt: null, postUrl: null, publishedAt: null,
    genBy: 'GPT-4o + Banana Pro 1.5', genAt: '14:30', regen: 0,
    campaignId: null, tone: 'streetwear',
    captionRef: 'auto' },
  { id: 'post204', productIds: ['p1012'], productId: 'p1012',
    channel: 'fb', format: 'single', status: 'text_pending',
    textApproved: false, imageApproved: false,
    scheduledAt: null, postUrl: null, publishedAt: null,
    genBy: 'GPT-4o + Banana Pro 1.5', genAt: '14:34', regen: 1,
    campaignId: null, tone: 'streetwear', captionRef: 'auto' },

  // Đang chờ duyệt ảnh — text đã pass, ảnh AI đang chờ admin xem
  { id: 'post201', productIds: ['p1006'], productId: 'p1006',
    channel: 'fb', format: 'single', status: 'image_pending',
    textApproved: true, imageApproved: false,
    scheduledAt: null, postUrl: null, publishedAt: null,
    genBy: 'GPT-4o + Banana Pro 1.5', genAt: '14:31', regen: 2,
    campaignId: null, tone: 'zei_youth', captionRef: 'auto' },

  // Đã đặt lịch — chờ cron-publisher fire
  { id: 'post202', productIds: ['p1009'], productId: 'p1009',
    channel: 'fb', format: 'single', status: 'scheduled',
    textApproved: true, imageApproved: true,
    scheduledAt: scheduleFuture(0), postUrl: null, publishedAt: null,
    genBy: 'GPT-4o + Banana Pro 1.5', genAt: '14:32', regen: 0,
    campaignId: null, tone: 'zei_youth', captionRef: 'auto' },
  { id: 'post206', productIds: ['p1016'], productId: 'p1016',
    channel: 'fb', format: 'single', status: 'scheduled',
    textApproved: true, imageApproved: true,
    scheduledAt: scheduleFuture(2), postUrl: null, publishedAt: null,
    genBy: 'GPT-4o + Banana Pro 1.5', genAt: '14:36', regen: 0,
    campaignId: null, tone: 'streetwear', captionRef: 'auto' },

  // Đã đăng lên kênh thành công
  { id: 'post203', productIds: ['p1011'], productId: 'p1011',
    channel: 'fb', format: 'single', status: 'posted',
    textApproved: true, imageApproved: true,
    scheduledAt: schedulePast(0), publishedAt: schedulePast(0),
    postUrl: 'facebook.com/376516022211741/posts/8807',
    platformPostId: '376516022211741_8807',
    genBy: 'GPT-4o + Banana Pro 1.5', genAt: '14:33', regen: 0,
    campaignId: 'c2', tone: 'lifestyle', captionRef: 'auto',
    metrics: { reach: 4820, likes: 312, shares: 24, comments: 41, saves: 18 } },
  { id: 'post205', productIds: ['p1013'], productId: 'p1013',
    channel: 'fb', format: 'single', status: 'posted',
    textApproved: true, imageApproved: true,
    scheduledAt: schedulePast(2), publishedAt: schedulePast(2),
    postUrl: 'facebook.com/376516022211741/posts/8809',
    platformPostId: '376516022211741_8809',
    genBy: 'GPT-4o + Banana Pro 1.5', genAt: '14:35', regen: 1,
    campaignId: null, tone: 'zei_youth', captionRef: 'auto',
    metrics: { reach: 6240, likes: 487, shares: 38, comments: 62, saves: 27 } },

  // Multi-product carousel / composite
  { id: 'post-multi-1', productIds: ['p1001','p1002','p1009'], productId: 'p1001',
    channel: 'fb', format: 'carousel', status: 'image_pending',
    textApproved: true, imageApproved: false,
    scheduledAt: null, postUrl: null, publishedAt: null,
    caption: '🔥 Combo Uniqlo cuối tuần: AIRism Tee + Smart Ankle + Heattech knit\n\nMix-match 3 items basic Nhật cho cả tuần. Giá lẻ hơn 200k, mua combo này tiết kiệm 40%!\n\n📦 Đặt mua hộ → ship 7-10 ngày.\n\n#ZeiJP #combo #UniqloVN #thoitrangNhat',
    genBy: 'GPT-4o + Banana Pro 1.5', genAt: '14:42', regen: 0,
    campaignId: 'c1', tone: 'zei_youth' },
  { id: 'post-multi-2', productIds: ['p1003','p1011'], productId: 'p1003',
    channel: 'fb', format: 'composite', status: 'text_pending',
    textApproved: false, imageApproved: false,
    scheduledAt: null, postUrl: null, publishedAt: null,
    caption: '✨ GU Summer Lookbook: Light Down + Oversize Blazer\n\n2 items must-have cho thời tiết chuyển mùa. AI ghép lookbook outfit chuẩn Nhật.\n\nGiá Zei JP: từ 700.000đ. Inbox để đặt nhé!\n\n#ZeiJP #GUVN #lookbook',
    genBy: 'GPT-4o + Banana Pro 1.5', genAt: '15:08', regen: 1,
    campaignId: 'c2', tone: 'lifestyle' },
];

// Resolve caption text on read — single-product posts use makeCaption(product),
// multi-product posts use the literal caption field.
function captionOf(post) {
  if (post.caption) return post.caption;
  const p = PRODUCTS.find(x => x.id === post.productId);
  return p ? makeCaption(p) : '';
}

// Backfill resolved caption onto each post for convenience
POSTS.forEach(post => { if (!post.caption) post.caption = captionOf(post); });

function makeCaption(p) {
  const b = brandOf(p.brand);
  const disc = p.disc ? `🔥 SALE ${p.disc}% — ` : '';
  return `${disc}${p.titleVn} (${b.safe} Nhật chính hãng) 🇯🇵\n\nGiá Zei JP: ${fmtVND(p.vnd)}${p.vndOld ? ` (gốc ${fmtVND(p.vndOld)})` : ''}\nSize: ${p.sizes.join(' / ')} · ${p.colors} màu\n\n📦 Đặt mua hộ → ship tận nhà 7-10 ngày. Inbox shop để chốt đơn nhé cả nhà ơi!\n\n#ZeiJP #${b.safe.replace(/\./g,'')}Nhat #thoitrangNhat #muahoNhatBan`;
}

/* ============ POST HELPERS (per-product tracking) ============ */
const postsForProduct  = (pid) => POSTS.filter(po => (po.productIds || [po.productId]).includes(pid));
const postCountFor     = (pid) => postsForProduct(pid).length;
const postedCountFor   = (pid) => postsForProduct(pid).filter(p => p.status === 'posted').length;
const hasActiveDraft   = (pid) => postsForProduct(pid).some(p => ['draft','text_pending','image_pending','approved','scheduled'].includes(p.status));
function lastPostedAt(pid) {
  const posted = postsForProduct(pid).filter(p => p.status === 'posted' && p.publishedAt);
  if (!posted.length) return null;
  posted.sort((a, b) => new Date(b.publishedAt) - new Date(a.publishedAt));
  return posted[0].publishedAt;
}

/* ============ ACTIVITY FEED ============ */
const ACTIVITY = [
  { icon: 'check', who: 'admin@zei.com', what: 'duyệt nội dung text', target: 'Giày Samba OG cổ điển nâu', time: '3 phút trước', color: 'var(--green)' },
  { icon: 'sparkle', who: 'Hệ thống', what: 'tạo xong content draft', target: 'Áo khoác phao siêu nhẹ', time: '6 phút trước', color: 'var(--accent)' },
  { icon: 'image', who: 'admin@zei.com', what: 'duyệt ảnh AI', target: 'Áo hoodie Trefoil Essentials', time: '12 phút trước', color: 'var(--blue)' },
  { icon: 'upload', who: 'Hệ thống', what: 'đăng FB thành công qua API-Hub', target: 'Áo blazer nữ oversize', time: '24 phút trước', color: 'var(--green)' },
  { icon: 'rotate', who: 'Cron', what: 'scrape 12 sản phẩm mới', target: 'Uniqlo sale page', time: '38 phút trước', color: 'var(--blue)' },
  { icon: 'star',   who: 'Watchlist', what: 'phát hiện match keyword', target: 'AIRism Tee 1.500¥ < 2.000¥', time: '40 phút trước', color: 'var(--yellow)' },
  { icon: 'megaphone', who: 'Cron', what: 'phát hiện chương trình KM mới', target: 'GU mùa hè -40%', time: '52 phút trước', color: 'var(--accent)' },
  { icon: 'edit', who: 'admin@zei.com', what: 'sửa caption', target: 'Giày Dunk Low panda', time: '1 giờ trước', color: 'var(--text-2)' },
  { icon: 'alert', who: 'Hệ thống', what: 'adapter fail 5 lần', target: 'Amazon JP', time: '1 giờ trước', color: 'var(--red)' },
  { icon: 'skip', who: 'admin@zei.com', what: 'bỏ qua sản phẩm', target: 'Áo thun graphic oversize', time: '2 giờ trước', color: 'var(--text-3)' },
];

/* ============ SOCIAL CHANNELS ============ */
// 9 nền tảng đăng bài. Bật/tắt độc lập từng bài.
const SOCIAL_CHANNELS = [
  { key: 'facebook',     label: 'Facebook',        icon: 'facebook',  color: '#1877f2', enabled: true,  handle: '@zeijp.fashion', profileNote: 'FB Page · main publishing target' },
  { key: 'instagram',    label: 'Instagram',       icon: 'instagram', color: '#e4405f', enabled: true,  handle: '@zeijp.jp',      profileNote: 'IG · feed + reels' },
  { key: 'tiktok',       label: 'TikTok',          icon: 'video',     color: '#000000', enabled: true,  handle: '@zeijp.jp',      profileNote: 'TikTok · short video discovery' },
  { key: 'threads',      label: 'Threads',         icon: 'inbox',     color: '#000000', enabled: false, handle: '@zeijp.jp',      profileNote: 'Threads · text-first audience' },
  { key: 'x',            label: 'X / Twitter',     icon: 'send',      color: '#000000', enabled: false, handle: '@zeijp_jp',      profileNote: 'X · drop alerts + thread teasers' },
  { key: 'linkedin',     label: 'LinkedIn',        icon: 'user',      color: '#0a66c2', enabled: false, handle: 'Zei JP Org',     profileNote: 'LI · brand awareness (B2C low priority)' },
  { key: 'reddit',       label: 'Reddit',          icon: 'globe',     color: '#ff4500', enabled: false, handle: 'u/zeijp_jp',     profileNote: 'Reddit · niche subs (r/japancirclejerk, r/streetwear)' },
  { key: 'youtube',      label: 'YouTube',         icon: 'play',      color: '#ff0000', enabled: false, handle: '@zeijp_jp',      profileNote: 'YT · long-form unbox + try-on' },
  { key: 'google_biz',   label: 'Google Business', icon: 'store',     color: '#4285f4', enabled: false, handle: 'Zei JP Shop',    profileNote: 'GBP · local discovery + reviews' },
];

/* ============ SOCIAL STATS (per channel, 28 days) ============ */
const SOCIAL_STATS = {
  facebook: {
    label: 'Facebook', icon: 'facebook', color: '#1877f2',
    handle: '@zeijp.fashion', followers: 14820, followersDelta: 235,
    reach28d: 142300, reachDelta: 18.5,
    engagement28d: 8640, engagementDelta: 12.4, er: 6.07,
    posts28d: 96, postsDelta: 12,
    sparkline: [12, 18, 22, 19, 27, 32, 28, 24, 31, 35, 41, 38, 44, 47, 42, 38, 44, 52, 49, 55, 51, 58, 62, 57, 64, 71, 68, 76],
    topPosts: [
      { id: 't1', title: 'Combo Uniqlo cuối tuần',     postId: 'post-multi-1', reach: 22400, likes: 1240, shares: 89, comments: 156, saves: 0,   publishedAt: '2026-05-28T14:00:00+07:00' },
      { id: 't2', title: 'GU Light Down lookbook',     postId: 'post203',      reach: 18900, likes: 980,  shares: 67, comments: 124, saves: 0,   publishedAt: '2026-05-28T09:00:00+07:00' },
      { id: 't3', title: 'Nike AF1 ’07 triple white',  postId: 'post205',      reach: 15600, likes: 870,  shares: 52, comments: 98,  saves: 0,   publishedAt: '2026-05-28T15:00:00+07:00' },
    ],
  },
  instagram: {
    label: 'Instagram', icon: 'instagram', color: '#e4405f',
    handle: '@zeijp.jp', followers: 8240, followersDelta: 412,
    reach28d: 89500, reachDelta: 24.8,
    engagement28d: 6720, engagementDelta: 19.2, er: 7.51,
    posts28d: 64, postsDelta: 8,
    sparkline: [8, 10, 14, 12, 16, 19, 22, 20, 24, 27, 31, 28, 33, 38, 36, 41, 44, 48, 45, 50, 54, 51, 57, 61, 58, 64, 67, 72],
    topPosts: [
      { id: 't4', title: 'Samba OG brown gum reels',   postId: null, reach: 18200, likes: 1430, shares: 76, comments: 211, saves: 312, publishedAt: '2026-05-26T19:30:00+07:00' },
      { id: 't5', title: 'AIRism Tee styling tips',    postId: null, reach: 13400, likes: 920,  shares: 58, comments: 142, saves: 198, publishedAt: '2026-05-25T11:00:00+07:00' },
      { id: 't6', title: 'Muji Oxford shirt OOTD',     postId: null, reach: 9800,  likes: 680,  shares: 34, comments: 89,  saves: 142, publishedAt: '2026-05-24T08:30:00+07:00' },
    ],
  },
  tiktok: {
    label: 'TikTok', icon: 'video', color: '#000000',
    handle: '@zeijp.jp', followers: 5140, followersDelta: 680,
    reach28d: 312000, reachDelta: 64.2,
    engagement28d: 21400, engagementDelta: 88.5, er: 6.86,
    posts28d: 32, postsDelta: 18,
    sparkline: [5, 6, 8, 7, 11, 14, 19, 23, 28, 35, 42, 48, 55, 62, 71, 78, 84, 92, 88, 95, 104, 112, 108, 124, 132, 145, 158, 172],
    topPosts: [
      { id: 't7', title: 'Unbox Heattech mùa lạnh',    postId: null, reach: 89200, likes: 6840, shares: 425, comments: 312, saves: 0, publishedAt: '2026-05-22T20:00:00+07:00' },
      { id: 't8', title: 'Try-on Light Down GU',       postId: null, reach: 52400, likes: 4120, shares: 287, comments: 198, saves: 0, publishedAt: '2026-05-24T18:45:00+07:00' },
      { id: 't9', title: 'Compare AF1 fake vs real',   postId: null, reach: 41800, likes: 3650, shares: 234, comments: 421, saves: 0, publishedAt: '2026-05-25T21:15:00+07:00' },
    ],
  },
};

/* ============ AD CAMPAIGNS (Meta Marketing API + TikTok Marketing API mirror) ============ */
// Data shape mirrors Meta Marketing API /act_{id}/insights and TikTok /report/integrated/get/.
// Fields: campaign_name, objective, status, daily_budget, spend, reach, impressions, clicks, conversions, cpm, cpc, ctr, roas.
const ADS_CAMPAIGNS = [
  { id: 'adfb1', platform: 'facebook',  name: 'Sale cuối tuần Uniqlo · CBO',     objective: 'CONVERSIONS', status: 'active',
    startAt: '2026-05-27', endAt: '2026-06-01', dailyBudget: 500000, lifetimeSpend: 1840000,
    reach: 48200, impressions: 96800, clicks: 1820, ctr: 1.88, cpm: 19000, cpc: 1011,
    conversions: 124, cpa: 14839, roas: 3.4, currency: 'VND',
    linkedPostId: 'post-multi-1' },
  { id: 'adfb2', platform: 'facebook',  name: 'GU Summer · awareness',           objective: 'REACH',       status: 'active',
    startAt: '2026-05-25', endAt: '2026-06-04', dailyBudget: 300000, lifetimeSpend: 1620000,
    reach: 72400, impressions: 142800, clicks: 1180, ctr: 0.83, cpm: 11342, cpc: 1373,
    conversions: 38, cpa: 42632, roas: 1.8, currency: 'VND',
    linkedPostId: 'post203' },
  { id: 'adfb3', platform: 'facebook',  name: 'Retarget · cart abandon 7d',      objective: 'CONVERSIONS', status: 'paused',
    startAt: '2026-05-20', endAt: '2026-05-27', dailyBudget: 200000, lifetimeSpend: 1280000,
    reach: 8420, impressions: 24100, clicks: 612, ctr: 2.54, cpm: 53112, cpc: 2092,
    conversions: 89, cpa: 14382, roas: 4.2, currency: 'VND',
    linkedPostId: null },
  { id: 'adig1', platform: 'instagram', name: 'IG Reels boost · Samba OG',       objective: 'VIDEO_VIEWS', status: 'active',
    startAt: '2026-05-26', endAt: '2026-06-02', dailyBudget: 250000, lifetimeSpend: 1180000,
    reach: 54200, impressions: 124000, clicks: 980, ctr: 0.79, cpm: 9516,  cpc: 1204,
    conversions: 42, cpa: 28095, roas: 2.6, currency: 'VND',
    linkedPostId: null },
  { id: 'adig2', platform: 'instagram', name: 'IG carousel · GU Lookbook',       objective: 'TRAFFIC',     status: 'active',
    startAt: '2026-05-28', endAt: '2026-06-05', dailyBudget: 180000, lifetimeSpend: 620000,
    reach: 18900, impressions: 41800, clicks: 458, ctr: 1.10, cpm: 14833, cpc: 1354,
    conversions: 18, cpa: 34444, roas: 2.1, currency: 'VND',
    linkedPostId: null },
  { id: 'adtt1', platform: 'tiktok',    name: 'TikTok Spark Ads · Heattech',     objective: 'VIDEO_VIEWS', status: 'active',
    startAt: '2026-05-24', endAt: '2026-06-01', dailyBudget: 400000, lifetimeSpend: 2680000,
    reach: 184200, impressions: 412800, clicks: 5240, ctr: 1.27, cpm: 6492, cpc: 511,
    conversions: 78, cpa: 34359, roas: 2.9, currency: 'VND',
    linkedPostId: null },
  { id: 'adtt2', platform: 'tiktok',    name: 'TikTok TopView · brand intro',    objective: 'AWARENESS',   status: 'ended',
    startAt: '2026-05-15', endAt: '2026-05-22', dailyBudget: 600000, lifetimeSpend: 4200000,
    reach: 312800, impressions: 624000, clicks: 4180, ctr: 0.67, cpm: 6731, cpc: 1005,
    conversions: 52, cpa: 80769, roas: 1.4, currency: 'VND',
    linkedPostId: null },
];

/* ============ INBOX STATS (Messenger Platform + IG Messaging + Zalo OA Open API) ============ */
// TikTok DMs NOT in this list — TikTok Business API does not expose DM endpoints publicly.
const INBOX_STATS = {
  facebook: {
    label: 'Facebook Messenger', icon: 'facebook', color: '#1877f2',
    unreadCount: 12, totalConversations: 184, awaitingReplyCount: 8,
    avgResponseMin: 14, slaTarget: 30,
    last7d: [8, 14, 11, 19, 22, 16, 18],
    recent: [
      { id: 'm1', from: 'Hà Linh', preview: 'Shop ơi AIRism size M còn hàng không ạ?',  unread: true,  receivedAt: '2026-05-30T08:42:00+07:00' },
      { id: 'm2', from: 'Quang Anh', preview: 'Em đặt giày Samba bao lâu về tới ạ',     unread: true,  receivedAt: '2026-05-30T08:18:00+07:00' },
      { id: 'm3', from: 'Mai Anh', preview: 'Đã nhận hàng, cảm ơn shop!',               unread: false, receivedAt: '2026-05-30T07:55:00+07:00' },
      { id: 'm4', from: 'Đức Huy', preview: 'AF1 còn size 27 không shop',                unread: true,  receivedAt: '2026-05-30T07:32:00+07:00' },
    ],
  },
  instagram: {
    label: 'Instagram DM', icon: 'instagram', color: '#e4405f',
    unreadCount: 6, totalConversations: 92, awaitingReplyCount: 4,
    avgResponseMin: 24, slaTarget: 60,
    last7d: [4, 8, 6, 11, 14, 9, 12],
    recent: [
      { id: 'i1', from: 'tee.minhanh', preview: 'Áo Hoodie Trefoil còn không ạ',         unread: true,  receivedAt: '2026-05-30T08:32:00+07:00' },
      { id: 'i2', from: 'phuongnguyen.jp', preview: 'Shop ship Hà Nội mất bao lâu',       unread: true,  receivedAt: '2026-05-30T08:05:00+07:00' },
      { id: 'i3', from: 'huyle.streetwear', preview: 'Dunk Low panda restock chưa',       unread: false, receivedAt: '2026-05-30T07:45:00+07:00' },
    ],
  },
  zalo: {
    label: 'Zalo OA', icon: 'send', color: '#0068ff',
    unreadCount: 18, totalConversations: 268, awaitingReplyCount: 14,
    avgResponseMin: 9, slaTarget: 15,
    last7d: [14, 22, 18, 28, 31, 24, 26],
    recent: [
      { id: 'z1', from: '+84 901 234 567', preview: 'AIRism nam size M ạ',                 unread: true,  receivedAt: '2026-05-30T08:48:00+07:00' },
      { id: 'z2', from: '+84 912 345 678', preview: 'Cho em xin link AF1 với',             unread: true,  receivedAt: '2026-05-30T08:36:00+07:00' },
      { id: 'z3', from: '+84 988 765 432', preview: 'Em mua combo Uniqlo, khi nào giao',   unread: false, receivedAt: '2026-05-30T07:58:00+07:00' },
    ],
  },
};

/* ============ TONE & VOICE PRESETS ============ */
const TONE_PRESETS = [
  { id: 'zei_youth',     name: 'Zei trẻ trung',     persona: 'Gen-Z VN, friendly, energetic', emoji: '🔥✨💯',
    do: ['Dùng "shop", "cả nhà ơi"', 'Hashtag #ZeiJP #thoitrangNhat', 'Câu CTA ngắn'],
    dont: ['Không xưng "quý khách"', 'Tránh từ Hán-Việt nặng'],
    hashtagSet: ['#ZeiJP', '#thoitrangNhat', '#muahoNhatBan'],
    sample: '🔥 SALE 40% — Áo AIRism cool dữ thần. Giá Zei JP chỉ 380k. Inbox shop chốt liền nha!' },
  { id: 'lifestyle',     name: 'Lifestyle styling', persona: 'Fashion editor, OOTD-focused',  emoji: '🌿☕📷',
    do: ['Storytelling ngắn', 'Mix outfit gợi ý', 'Hashtag lifestyle'],
    dont: ['Không spam emoji', 'Không CTA dồn dập'],
    hashtagSet: ['#ZeiJPlookbook', '#muahoNhat', '#OOTDjapan'],
    sample: 'Trời chuyển mùa, một chiếc Light Down GU + jeans + sneaker basic là combo gọn nhẹ cho ngày dài… Đặt qua Zei JP, ship 7-10 ngày.' },
  { id: 'premium',       name: 'Sang trọng',        persona: 'Luxury proxy, refined tone',     emoji: '✨🇯🇵',
    do: ['Nhấn chất liệu, xuất xứ Nhật', 'Câu dài đủ ngữ pháp'],
    dont: ['Không emoji rẻ tiền', 'Không viết tắt'],
    hashtagSet: ['#ZeiJP', '#authentic', '#madeinJapan'],
    sample: 'Áo blazer GU oversize, chất liệu polyester pha cao cấp, cắt may chuẩn Nhật. Mức giá Zei JP: 980.000đ — hợp lý cho một item statement.' },
  { id: 'humor',         name: 'Hài hước',          persona: 'Funny meme-style, gen-z humor',  emoji: '🤣💀',
    do: ['Đùa nhẹ, gắn meme', 'Câu chốt twist'],
    dont: ['Không tự đùa quá đà', 'Tránh chính trị'],
    hashtagSet: ['#ZeiJP', '#funnyfashion'],
    sample: 'Mua áo Heattech rồi mới biết: hóa ra mình chỉ rét vì… ví hết tiền. 990k còn 590k thôi đó cả nhà 😭' },
  { id: 'inbox_cta',     name: 'Inbox CTA mạnh',    persona: 'Conversion-focused, direct DM',  emoji: '📩💬',
    do: ['1 câu mô tả + 1 câu CTA inbox', 'Nhấn "còn hàng"'],
    dont: ['Không storytelling dài'],
    hashtagSet: ['#ZeiJP'],
    sample: 'Nike Dunk Low Panda size 25-26 còn hàng. Inbox shop ngay để chốt đơn — ship 7-10 ngày, giá Zei JP 2.5tr!' },
  { id: 'streetwear',    name: 'Streetwear',        persona: 'Sneakerhead/streetwear hype',    emoji: '🔥👟',
    do: ['Nhắc model, colorway chuẩn', 'Hashtag #sneakerhead'],
    dont: ['Không gọi giày là "đồ"', 'Tránh viết "siêu rẻ"'],
    hashtagSet: ['#ZeiJP', '#sneakerheadvn', '#kicksonfire'],
    sample: 'AF1 \'07 triple white — silhouette never miss. Size 26-29 chính hãng Nhật, Zei JP giá 2.8tr.' },
];

/* ============ FIELD DETECTION RULES (regex from susumi v9) ============ */
const DETECTION_RULES = {
  category: [
    { pattern: 'パンツ|pants|trouser|jean', label: 'Pants' },
    { pattern: 'シャツ|shirt|blouse',       label: 'Shirt' },
    { pattern: 'tee|t-?shirt|tシャツ',     label: 'T-shirt' },
    { pattern: 'sneaker|スニーカー|シューズ', label: 'Sneakers' },
    { pattern: 'jacket|hoodie|outerwear|フーディ|パーカー|ジャケット', label: 'Outerwear' },
    { pattern: 'skirt|スカート|dress|ワンピ', label: 'Skirt/Dress' },
    { pattern: 'bag|tote|backpack|バッグ',   label: 'Bag' },
    { pattern: 'sock|タイツ|ストッキング',   label: 'Socks' },
  ],
  material: [
    { pattern: 'cotton\\s*100|綿\\s*100', label: 'Cotton 100%' },
    { pattern: 'polyester|ポリエステル',   label: 'Polyester' },
    { pattern: 'wool|ウール',              label: 'Wool' },
    { pattern: 'nylon|ナイロン',            label: 'Nylon' },
    { pattern: 'leather|レザー|革',         label: 'Leather' },
    { pattern: 'denim|デニム',              label: 'Denim' },
  ],
  productCode: [
    { pattern: '\\b[A-Z]{2,4}-?\\d{3,5}\\b', label: 'Style code (SKU)' },
    { pattern: '\\b\\d{6}\\b',                label: 'Numeric SKU 6-digit' },
  ],
  color: [
    { pattern: '黒|ブラック|black',          label: 'Black' },
    { pattern: '白|ホワイト|white',          label: 'White' },
    { pattern: 'ネイビー|navy',              label: 'Navy' },
    { pattern: 'ベージュ|beige',             label: 'Beige' },
    { pattern: 'グレー|gray|grey|チャコール',label: 'Gray/Charcoal' },
    { pattern: 'カーキ|オリーブ|olive|khaki',label: 'Olive/Khaki' },
    { pattern: 'ブラウン|brown|キャメル',    label: 'Brown' },
  ],
  badName: [
    { pattern: '^(画像|写真|サイズ|ガイド|brand|brands|logo|logos)$', label: 'Generic name → reject' },
    { pattern: '^.{1,4}$', label: 'Name too short (<5 chars) → reject' },
  ],
  logoRow: [
    { pattern: '\\b(brands?|logos?)\\b', label: 'Brand logo grid → skip card' },
  ],
};

/* ============ PHASES ============ */
const PHASES = [
  { id: 'teaser',     name: 'Teaser',     daysBefore: 7, color: '#7c3aed', contentType: 'mood image + tagline', sample: 'Một thứ Nhật rất xinh sắp về Zei JP… 🇯🇵' },
  { id: 'reveal',     name: 'Reveal',     daysBefore: 5, color: '#2563eb', contentType: 'product showcase',     sample: 'Đây rồi! AIRism Tee mới về, sale 40%.' },
  { id: 'countdown',  name: 'Countdown',  daysBefore: 1, color: '#f59e0b', contentType: 'urgency post',         sample: '24h cuối! Còn 8 size sẵn.' },
  { id: 'live',       name: 'Live',       daysBefore: 0, color: '#ef4444', contentType: 'main CTA post',        sample: '🔥 LIVE NOW — Inbox để chốt đơn ngay!' },
  { id: 'afterparty', name: 'Afterparty', daysBefore: -3,color: '#10b981', contentType: 'UGC + thank you',      sample: 'Cảm ơn cả nhà! Đợt tới tiếp tục nhé.' },
];

/* ============ CRAWL JOBS (sample) ============ */
const CRAWL_JOBS = [
  { id: 'j1', brand: 'uniqlo', status: 'ok',      startedAt: '2026-05-30T07:55:00+09:00', durationS: 84,  productsFound: 47, error: null, artifactUrl: null,                              pages: 3 },
  { id: 'j2', brand: 'gu',     status: 'ok',      startedAt: '2026-05-30T07:48:00+09:00', durationS: 62,  productsFound: 28, error: null, artifactUrl: null,                              pages: 2 },
  { id: 'j3', brand: 'nike',   status: 'partial', startedAt: '2026-05-30T07:20:00+09:00', durationS: 145, productsFound: 14, error: 'Adapter timeout trên page 2/2; retry rakuten_nike OK',artifactUrl: '/artifacts/nike_2026-05-30_p2.png', pages: 2 },
  { id: 'j4', brand: 'adidas', status: 'failed',  startedAt: '2026-05-30T06:30:00+09:00', durationS: 12,  productsFound: 0,  error: 'HTTP 403 (block). Fallback rakuten_adidas đã chạy.', artifactUrl: '/artifacts/adidas_2026-05-30.html', pages: 0 },
  { id: 'j5', brand: 'rakuten_adidas', status: 'ok', startedAt: '2026-05-30T06:32:00+09:00', durationS: 98, productsFound: 19, error: null, artifactUrl: null, pages: 3, triggeredBy: 'fallback' },
  { id: 'j6', brand: 'muji',   status: 'ok',      startedAt: '2026-05-30T05:55:00+09:00', durationS: 71,  productsFound: 12, error: null, artifactUrl: null,                              pages: 2 },
];

/* ============ PUBLISH LOG (sample) ============ */
const PUBLISH_LOG = [
  { id: 'pl1', postId: 'post200', channel: 'facebook', status: 'ok',     publishedAt: '2026-05-30T09:15:00+07:00', latencyMs: 1840, platformPostId: '376516022211741_8810', canonicalUrl: 'facebook.com/376516022211741/posts/8810', error: null },
  { id: 'pl2', postId: 'post201', channel: 'facebook', status: 'ok',     publishedAt: '2026-05-30T08:32:00+07:00', latencyMs: 2110, platformPostId: '376516022211741_8809', canonicalUrl: 'facebook.com/376516022211741/posts/8809', error: null },
  { id: 'pl3', postId: 'post202', channel: 'facebook', status: 'retry',  publishedAt: '2026-05-30T07:50:00+07:00', latencyMs: 0,    platformPostId: null, canonicalUrl: null, error: 'Rate-limited 25/24h; auto-retry sau 4h' },
  { id: 'pl4', postId: 'post-multi-1', channel: 'instagram', status: 'pending', publishedAt: null,                  latencyMs: 0,    platformPostId: null, canonicalUrl: null, error: 'Đang chờ approve ảnh' },
  { id: 'pl5', postId: 'post203', channel: 'facebook', status: 'ok',     publishedAt: '2026-05-29T22:14:00+07:00', latencyMs: 1620, platformPostId: '376516022211741_8807', canonicalUrl: 'facebook.com/376516022211741/posts/8807', error: null },
];

/* ============ FILTER PRESETS (sample, user can add more in Batch 5) ============ */
const FILTER_PRESETS = [
  { id: 'fp1', name: 'High deal hôm nay',     query: { minScore: 70, maxAgeH: 6 },           icon: 'zap',   color: 'var(--accent)' },
  { id: 'fp2', name: 'Coupon ≥ 10%',          query: { minCouponPercent: 10 },               icon: 'tag',   color: 'var(--yellow)' },
  { id: 'fp3', name: 'Watchlist match',       query: { watchlistOnly: true },                icon: 'star',  color: 'var(--yellow)' },
  { id: 'fp4', name: 'Confidence cao',        query: { confidence: 'high' },                 icon: 'check', color: 'var(--green)' },
  { id: 'fp5', name: 'Cần admin xem (low)',   query: { confidence: 'low' },                  icon: 'alert', color: 'var(--red)' },
  { id: 'fp6', name: 'Dưới 500k',             query: { maxPriceVnd: 500000 },                icon: 'yen',   color: 'var(--blue)' },
];

/* ============ SETTINGS (FX history + legacy scoring config keep for compat) ============ */
const SETTINGS = {
  fx: { rate: PRICE_CONFIG.rate, lockedAgo: '2 ngày trước', live: 198.4, drift: 1.2 },
  fxHistory: [
    { date: '27/05/2026', rate: 196 }, { date: '25/05/2026', rate: 195.2 },
    { date: '22/05/2026', rate: 197.1 }, { date: '18/05/2026', rate: 194.9 },
    { date: '15/05/2026', rate: 195.5 },
  ],
  channel: 'ZEIJP', fb: true, ig: false, tiktok: false,
  scoring: { discount: 45, stock: 25, image: 30 },
  telegram: { token: '78••••••••••••••••:AAH••••••', uids: ['@zeijp_owner', '@zeijp_assist'] },
};

Object.assign(window, {
  PRICE_CONFIG, FX,
  BRANDS, brandOf,
  PRODUCT_STATUS, POST_STATUS, STATUS, SOURCE_ICON,
  REQUIRED_FIELDS, NICE_FIELDS, confidenceOf,
  calcVnd,
  SCORING_CONFIG, calcScore,
  WATCHLIST, matchWatchlist,
  PRODUCTS, POSTS, ACTIVITY, SETTINGS,
  CAMPAIGNS, campaignOf, productsInCampaign,
  SOCIAL_CHANNELS, SOCIAL_STATS,
  ADS_CAMPAIGNS, INBOX_STATS,
  TONE_PRESETS, DETECTION_RULES, PHASES,
  CRAWL_JOBS, PUBLISH_LOG, FILTER_PRESETS,
  postsForProduct, postCountFor, postedCountFor, hasActiveDraft, lastPostedAt,
  captionOf,
  fmtVND, fmtJPY, fmtPct, makeCaption,
});
