/* ===== Shared Brand Memory Store ===== */

(function initBrandMemoryStore() {
  if (window.BrandMemoryStore) return;

  const DB_NAME = 'aurelia-longform-agent';
  const DB_VERSION = 1;
  const KV_STORE = 'kv';
  const PROFILE_KEY = 'brandProfiles';
  const SCHEMA_VERSION = 2;

  const nowStamp = () => new Date().toLocaleString('zh-CN', { hour12: false });
  const listFromValue = (value) => {
    if (Array.isArray(value)) return value.map(item => String(item || '').trim()).filter(Boolean);
    if (typeof value !== 'string') return [];
    return value.split(/[、,，;；\n]/).map(item => item.trim()).filter(Boolean);
  };
  const uniq = (items = []) => Array.from(new Set(items.filter(Boolean)));
  const mergeMemoryItems = (seedItems = [], currentItems = []) => {
    const map = new Map();
    [...seedItems, ...currentItems].forEach((item, index) => {
      if (!item) return;
      const key = item.id || `${item.title || item.label || item.name || 'item'}-${index}`;
      map.set(key, { ...(map.get(key) || {}), ...item, id: key });
    });
    return Array.from(map.values());
  };
  const mergeColorTokens = (seedTokens = [], currentTokens = []) => {
    const seen = new Set();
    return [...seedTokens, ...currentTokens].filter(token => {
      const key = `${String(token.hex || '').toUpperCase()}|${token.alpha || ''}`;
      if (seen.has(key)) return false;
      seen.add(key);
      return true;
    });
  };
  const socialNumber = (value) => Number(value || 0);
  const socialInteraction = (post = {}) => socialNumber(post.likes) + socialNumber(post.comments) + socialNumber(post.shares) + socialNumber(post.saves);
  const getSocialPostTime = (post = {}) => {
    const raw = String(post.published_at || post.publish_time || post.created_at || '').trim();
    if (!raw) return 0;
    const parsed = new Date(raw.replace(/年|月/g, '/').replace(/日/g, '').replace(/\./g, '/'));
    return Number.isNaN(parsed.getTime()) ? 0 : parsed.getTime();
  };
  const isExternalSocialPost = (post = {}) => {
    const type = `${post.source_type || ''} ${post.asset_type || ''} ${post.relationship_status || ''}`.toLowerCase();
    return post.is_official_post === false || type.includes('earned') || type.includes('ugc') || type.includes('kol') || type.includes('external');
  };
  const isExternalSocialAccount = (account = {}) => {
    const type = `${account.source_type || ''} ${account.account_type || ''} ${account.relationship_status || ''} ${account.positioning || ''}`.toLowerCase();
    return account.is_official_account === false || type.includes('earned') || type.includes('ugc') || type.includes('kol') || type.includes('external');
  };

  const normalizeProductNarrative = (narrative = {}, product = {}) => ({
    positioning: narrative.positioning || product.positioning || '',
    coreCapabilities: listFromValue(narrative.coreCapabilities),
    userScenarios: listFromValue(narrative.userScenarios),
    proofPoints: listFromValue(narrative.proofPoints),
    comparisonBoundaries: listFromValue(narrative.comparisonBoundaries),
    requiredMentions: listFromValue(narrative.requiredMentions || product.requiredClaims),
    forbiddenClaims: listFromValue(narrative.forbiddenClaims || product.forbiddenClaims),
  });

  const normalizeProductProfile = (product = {}, index = 0) => ({
    id: product.id || `product-${Date.now()}-${index}`,
    name: product.name || `Product ${index + 1}`,
    aliases: listFromValue(product.aliases),
    positioning: product.positioning || '',
    audience: listFromValue(product.audience),
    tone: listFromValue(product.tone),
    requiredClaims: listFromValue(product.requiredClaims),
    forbiddenClaims: listFromValue(product.forbiddenClaims),
    narrative: normalizeProductNarrative(product.narrative, product),
    source: product.source || 'brand-product-profile',
    factStatus: product.factStatus || 'confirmed',
    provenance: product.provenance || 'documented',
    sourceEvidenceId: product.sourceEvidenceId || '',
    sourceRefs: listFromValue(product.sourceRefs),
    updatedAt: product.updatedAt || product.createdAt || 'seed',
  });

  const normalizeBrandNarrative = (narrative = {}) => ({
    positioning: narrative.positioning || '',
    worldview: narrative.worldview || '',
    valueProposition: narrative.valueProposition || '',
    voice: listFromValue(narrative.voice),
    requiredThemes: listFromValue(narrative.requiredThemes),
    forbiddenAngles: listFromValue(narrative.forbiddenAngles),
    proofTypes: listFromValue(narrative.proofTypes),
  });

  const normalizeConstraint = (constraint = {}, index = 0) => ({
    id: constraint.id || `guardrail-${Date.now()}-${index}`,
    label: constraint.label || constraint.name || `护栏 ${index + 1}`,
    category: constraint.category || 'brand',
    scope: listFromValue(constraint.scope).length ? listFromValue(constraint.scope) : ['strategy', 'brief', 'content', 'longform', 'monitor', 'reports', 'reply'],
    severity: constraint.severity || 'required',
    source: constraint.source || 'brand-memory',
    factStatus: constraint.factStatus || (constraint.status === 'pending' ? 'pending-confirmation' : 'confirmed'),
    provenance: constraint.provenance || 'documented',
    sourceEvidenceId: constraint.sourceEvidenceId || '',
    sourceRefs: listFromValue(constraint.sourceRefs),
    description: constraint.description || constraint.rule || '',
    enabled: constraint.enabled !== false,
    defaultLocked: Boolean(constraint.defaultLocked),
    forbiddenTerms: listFromValue(constraint.forbiddenTerms),
    requiredTerms: listFromValue(constraint.requiredTerms),
    rules: constraint.rules || {},
    updatedAt: constraint.updatedAt || constraint.createdAt || 'seed',
  });

  const inferTraceStatus = (item = {}, fallbackStatus = 'confirmed') => {
    const raw = item.factStatus || item.traceStatus || item.status || fallbackStatus;
    if (raw === 'active') return 'confirmed';
    if (raw === 'pending') return 'pending-confirmation';
    return raw || fallbackStatus;
  };

  const normalizeTraceItem = (item = {}, fallback = {}) => ({
    ...item,
    factStatus: inferTraceStatus(item, fallback.factStatus || 'confirmed'),
    provenance: item.provenance || fallback.provenance || 'documented',
    source: item.source || fallback.source || 'brand-memory',
    sourceEvidenceId: item.sourceEvidenceId || fallback.sourceEvidenceId || '',
    sourceRefs: listFromValue(item.sourceRefs).length ? listFromValue(item.sourceRefs) : listFromValue(fallback.sourceRefs),
    conflicts: listFromValue(item.conflicts).length ? listFromValue(item.conflicts) : listFromValue(fallback.conflicts),
  });

  const normalizeOperatingStage = (stage = {}) => {
    const traced = normalizeTraceItem(stage, { source: stage.source || stage.sourceEvidenceId || 'brand-memory' });
    return {
      ...traced,
      sourceRefs: listFromValue(stage.sourceRefs),
      operatingPreferences: Array.isArray(stage.operatingPreferences)
        ? stage.operatingPreferences.map(item => normalizeTraceItem(item, {
          factStatus: stage.factStatus || 'confirmed',
          provenance: stage.provenance || 'documented',
          source: stage.source || stage.sourceEvidenceId || 'brand-memory',
          sourceRefs: stage.sourceRefs || [],
          conflicts: stage.conflicts || [],
        }))
        : [],
    };
  };

  const normalizeVisualSystem = (visualSystem = {}) => ({
    ...visualSystem,
    colorTokens: Array.isArray(visualSystem.colorTokens)
      ? visualSystem.colorTokens.map(item => normalizeTraceItem(item, { source: item.source || 'MONTX_GUIDELINES_V1.1.pptx', sourceEvidenceId: 'source-vi-guidelines-v11' }))
      : [],
    viGuidelines: Array.isArray(visualSystem.viGuidelines)
      ? visualSystem.viGuidelines.map(item => normalizeTraceItem(item, { source: item.file || item.source || 'MONTX_GUIDELINES_V1.1.pptx', sourceEvidenceId: 'source-vi-guidelines-v11' }))
      : [],
    logoAssets: Array.isArray(visualSystem.logoAssets)
      ? visualSystem.logoAssets.map(item => normalizeTraceItem(item, { source: item.source || 'brand-logos', sourceEvidenceId: 'source-vi-guidelines-v11' }))
      : [],
    logoLockups: Array.isArray(visualSystem.logoLockups)
      ? visualSystem.logoLockups.map(item => normalizeTraceItem(item, { source: 'MONTX_GUIDELINES_V1.1.pptx', sourceEvidenceId: 'source-vi-guidelines-v11' }))
      : [],
    typography: Array.isArray(visualSystem.typography)
      ? visualSystem.typography.map(item => normalizeTraceItem(item, { source: 'MONTX_GUIDELINES_V1.1.pptx', sourceEvidenceId: 'source-vi-guidelines-v11' }))
      : [],
    applicationRules: Array.isArray(visualSystem.applicationRules)
      ? visualSystem.applicationRules.map(item => normalizeTraceItem(item, { source: 'MONTX_GUIDELINES_V1.1.pptx', sourceEvidenceId: 'source-vi-guidelines-v11' }))
      : [],
  });

  const MONTX_PRODUCT_PROFILES = [
    {
      id: 'montx-p10',
      name: 'P10',
      aliases: ['MONTX P10', 'P10', 'Pickup 01', '未来先锋皮卡', '全域移动地空母舰'],
      positioning: 'MONTX P10 是概念阶段的未来先锋皮卡，以“全域移动地空母舰”承载地空双栖、全域适应与机能美学叙事。',
      audience: ['跨界多用途尝鲜型用户', '户外探索者', '科考/摄影/露营/指挥等复合场景用户'],
      tone: ['开拓', '机能', '先锋', '克制', '概念边界清晰'],
      requiredClaims: ['标注概念车叙事', '强调全域适应/全域智能/全域美学', '把载人飞行器等表达限定为概念愿景'],
      forbiddenClaims: ['量产承诺', '完全自动驾驶', '无人驾驶', '绝对安全', '已上市/可交付', '确定配置/价格/交付时间'],
      narrative: {
        positioning: 'P10 是征服地表与天空的“全域移动地空母舰”，将皮卡从单一装载工具升维为支持立体出行的未来装备。',
        coreCapabilities: ['地空双栖概念', '四轮分布式驱动电机', '智能底盘与底盘扭矩矢量控制', '车顶/车斗模块化', '战机灰机能涂装与雨林线'],
        userScenarios: ['越野穿越', '摄影/科考', '露营', '指挥车', '无人区探索', '空中侦察/补给概念'],
        proofPoints: ['地空一体视觉张力', '全域机能美学', '主动生态伴随', '从工具到平台的升维'],
        comparisonBoundaries: ['不做竞品压制', '不把概念车能力写成量产承诺', '不暗示自动驾驶替代驾驶员'],
        requiredMentions: ['概念车', '全域移动地空母舰', '全域适应', '全域智能', '全域美学'],
        forbiddenClaims: ['量产承诺', '完全自动驾驶', '无人驾驶', '绝对安全', '已上市/可交付', '确定配置/价格/交付时间'],
      },
      source: 'gac-brand-assets-2026-04',
    },
    {
      id: 'montx-v10',
      name: 'V10',
      aliases: ['MONTX V10', 'V10', '概念 VAN', '全域移动整备舱', '数字游民大本营'],
      positioning: 'MONTX V10 是概念阶段的全域移动整备舱，面向数字游民、户外探索与移动办公/旅居复合场景。',
      audience: ['数字游民', '户外探索者', '移动办公用户', '长周期旅居用户', '跨界多用途尝鲜型用户'],
      tone: ['理性浪漫', '工业秩序感', '克制', '温度感', '概念边界清晰'],
      requiredClaims: ['标注概念车叙事', '说明移动办公/旅居第三空间', '强调模块化空间与文明秩序感'],
      forbiddenClaims: ['量产承诺', '完全自动驾驶', '替代驾驶员', '绝对安全', '已上市/可交付', '确定配置/价格/交付时间'],
      narrative: {
        positioning: 'V10 是能在任意场景下切换工作、生活与旅行的“全域移动整备舱”，是数字游民的大本营和户外探险移动的家。',
        coreCapabilities: ['线控滑移方向盘概念', '智能双舱', '模块化座舱', '工作/社交/睡眠场景切换', '洗漱系统/模块化收纳/基础卫浴功能', '时间感知自动进化概念'],
        userScenarios: ['移动办公', '长周期旅居', '荒野驻留', '社交沙龙', '户外探险', '跨市场左右舵切换概念'],
        proofPoints: ['工业硬装与户外软装融合', '四开式尾门展开', '自循环基地', '隐形管家', '文明秩序与尊严'],
        comparisonBoundaries: ['不把自动切换、无感调节写成量产承诺', '不暗示无人驾驶或替代驾驶员', '不使用夸张竞品对比'],
        requiredMentions: ['概念车', '全域移动整备舱', '数字游民', '移动办公/旅居第三空间', '工业秩序感'],
        forbiddenClaims: ['量产承诺', '完全自动驾驶', '替代驾驶员', '绝对安全', '已上市/可交付', '确定配置/价格/交付时间'],
      },
      source: 'gac-brand-assets-2026-04',
    },
  ];

  const DEFAULT_BRAND_PROFILES = [
    {
      id: 'montx',
      name: 'MONTX',
      version: SCHEMA_VERSION,
      activeSkillId: 'montx-digital-editorial',
      products: MONTX_PRODUCT_PROFILES,
      narrative: {
        positioning: 'MONTX / 领越是广汽领程面向全球市场的“全域跨界新物种”，服务希望一部车满足商业、生活与探索多重需求的用户。',
        worldview: '移动出行世界中，场景、功能与用途的边界正在融合和重塑；MONTX 以跨界开拓精神，让用户在事业与生活之间获得更自由的全域场景体验。',
        valueProposition: '以一车多能、自由切换、全域适应和共创生态，帮助用户拓展事业与生活的边界。',
        voice: ['开拓', '全域', '机能', '先锋', '共创', '克制', '概念边界清晰'],
        requiredThemes: ['全域跨界新物种', 'New Breed. No Boundaries.', '一车多能', '自由切换', '全域智能', '全域适应', '全域美学', '共创更自由美好的事业与生活'],
        forbiddenAngles: ['把概念车写成量产承诺', '完全自动驾驶承诺', '无人驾驶暗示', '绝对安全', '过度玩梗', '轻浮自嘲', '竞品碾压', '财富自由夸大承诺'],
        proofTypes: ['品牌核心讯息', '产品概念车叙事', '真实用户场景', '共创机制', '视觉规范', '领导讲话/新闻稿'],
      },
      visualSystem: {
        colorTokens: [
          { id: 'montx-white', name: 'White', hex: '#FFFFFF', rgb: '255, 255, 255', cmyk: 'C0 M0 Y0 K0', pms: 'Bright White', role: 'default-logo-typography', usage: '黑白体系默认色，用于白底黑标、黑底白标和关键资产。', source: 'MONTX_GUIDELINES_V1.1.pptx' },
          { id: 'montx-black', name: 'Black', hex: '#000000', rgb: '0, 0, 0', cmyk: 'C0 M0 Y0 K100', pms: 'Black 6C', role: 'default-logo-typography', usage: '黑白体系默认色，用于 Logo、字体和高对比文字。', source: 'MONTX_GUIDELINES_V1.1.pptx' },
          { id: 'montx-earth-red', name: 'Earth Red', hex: '#580018', rgb: '88, 0, 24', cmyk: 'C0 M100 Y73 K65', pms: '7421C', role: 'accent-exception', usage: '品牌差异化强调色；仅在需要强品牌识别或重点信息时使用。', source: 'MONTX_GUIDELINES_V1.1.pptx' },
          { id: 'montx-limestone-grey', name: 'Limestone Grey', hex: '#E1E0D8', rgb: '225, 224, 216', cmyk: 'C0 M0 Y4 K12', pms: 'Cool Gray 1C', role: 'accent-exception-background', usage: '天然地形感浅底色；用于背景、留白区、内容承载底色。', source: 'MONTX_GUIDELINES_V1.1.pptx' },
          { id: 'montx-earth-red-watermark-10', name: 'Earth Red 10% 水印', hex: '#580018', rgb: '88, 0, 24', alpha: 0.1, role: 'watermark', usage: '巨大数字水印、低干扰背景编号；不得影响正文可读性。', source: 'MONTX_DEFAULT_SKILL.md' },
        ],
        semanticColors: {
          background: '#E1E0D8',
          accent: '#580018',
          text: '#000000',
          inverseText: '#FFFFFF',
          watermark: 'rgba(88, 0, 24, 0.1)',
        },
        colorRules: [
          '主视觉系统采用骨白、酒红、黑、白的高对比组合。',
          '酒红用于结构性模块和装饰线，不作为渐变或柔化光效。',
          '黑字仅用于骨白底；白字仅用于酒红底，避免低对比叠加。',
          '水印使用酒红 10% 透明度，不能影响正文可读性。',
          '禁止新增渐变、毛玻璃、阴影等柔化色彩效果。',
        ],
        viGuidelines: [
          { id: 'identity-guidelines-v1-2026-04', name: 'MONTX Identity Guidelines V1.0 April 2026', file: 'MONTX_GUIDELINES_V1.1.pptx', pages: 30, source: 'gac-brand-assets-2026-04' },
        ],
        logoAssets: [
          { id: 'social-avatar-brandmark', name: 'Social Avatar Brandmark', type: 'brandmark', src: 'assets/brand-logos/montx-social-avatar.png', background: '#580018', usage: '官方社媒头像与方形品牌触点，适合品牌记忆主档直接展示。', source: 'MONTX社媒平台账号主页品牌头像.png' },
          { id: 'wordmark-ink', name: 'MONTX Wordmark Ink', type: 'wordmark', src: 'assets/brand-logos/montx-wordmark-ai-04-ink.png', background: '#FFFFFF', usage: '浅底或白底主品牌横向露出。', source: 'brand-logos' },
          { id: 'wordmark-white', name: 'MONTX Wordmark White', type: 'wordmark', src: 'assets/brand-logos/montx-wordmark-ai-04-white.png', background: '#000000', usage: '黑底、深色图像或视频尾帧主品牌横向露出。', source: 'brand-logos' },
          { id: 'wordmark-default', name: 'MONTX Wordmark', type: 'wordmark', src: 'assets/brand-logos/montx-wordmark.png', background: '#E1E0D8', usage: '骨白底传播物料与品牌资产预览。', source: 'brand-logos' },
        ],
        logoLockups: [
          { id: 'brandmark', name: 'Brandmark', usage: '独立图形标识，用于头像、车标、图标化场景和强识别触点。', isolation: '四周保留 50% X 安全区。' },
          { id: 'horizontal-lockup', name: 'Horizontal Lockup', usage: 'Brandmark + MONTX wordmark 横向组合，用于主品牌露出。', construction: 'Wordmark 与 brandmark 按视觉中心对齐，保持固定比例与间距。', isolation: '横向组合四周保留规范安全区。' },
          { id: 'vertical-lockup', name: 'Vertical Lockup', usage: '上下组合，用于空间受限或居中陈列。', construction: 'Wordmark 高度为 brandmark 的 33%，间距为 brandmark 的 66%，整体数学居中。', isolation: '四周保留规范安全区。' },
          { id: 'horizontal-bilingual', name: 'Horizontal Bilingual', usage: '中英横向组合，仅用于中文市场。', construction: '中文“领越”与 MONTX 横向并列；wordmark 高度为 brandmark 的 75%。', isolation: '横向中英组合安全区为 150% X。' },
          { id: 'vertical-bilingual', name: 'Vertical Bilingual', usage: '中英上下组合，用于中文市场的居中品牌露出。', construction: 'Brandmark、MONTX、领越垂直排列，保持固定比例和中心轴。', isolation: '四周保留规范安全区。' },
        ],
        logoRules: [
          'Brandmark 四周保留 50% X 安全区。',
          'Horizontal Bilingual 仅用于中文市场，横向中英组合安全区为 150% X。',
          'Logo 常规使用黑/白；Earth Red 与 Limestone Grey 仅作为例外使用，适合独立露出并需要更强品牌识别的场景。',
          'Logo 不得拉伸、压缩、旋转、重绘、改变比例或随意改变组合关系。',
          'Logo 放置可使用预设位置系统：左上、右上、左下、右下、居中等，不得贴边或侵入安全区。',
        ],
        typography: [
          { id: '5ceta-mono', name: '5Ceta Mono', role: 'primary-latin-headline', usage: '主拉丁字体，用于标题、主信息和关键传播元素。' },
          { id: 'helvetica-neue-roman', name: 'Helvetica Neue Roman', role: 'secondary-body-copy', usage: '辅助字体，用于副标题、正文和说明文字。' },
          { id: 'source-han-sans-cn-bold', name: 'SourceHanSansCN-Bold', role: 'chinese-digital-editorial', usage: '长图文中文标题与重点模块，可与 Helvetica 体系搭配。' },
        ],
        layoutRules: ['1080px 画布', '图片全通栏', '图文分离', '禁止圆角拼图'],
        imageRules: ['社媒头像使用 1080 x 1080 PNG/JPG', '产品 Logo 可使用 brushed metal / chrome / internal illuminated 工艺表达'],
        applicationRules: [
          { id: 'rich-logo-pages', name: 'Rich Logo Pages', usage: '整页展示 brandmark、wordmark 和中英组合时，保持足够留白和固定组合比例。' },
          { id: 'product-logo-placement', name: 'Product Logo Placement', usage: '车头、车尾、轮毂、储物/尾门等产品触点按结构中心或预设区域放置，不破坏车身线条。' },
          { id: 'product-logo-finishes', name: 'Product Logo Finishes', usage: '产品标识可使用 Brushed metal、Chrome、Internal illuminated 等工艺。' },
          { id: 'website-layout', name: 'Website Layout', usage: '官网首屏采用大图场景、克制顶部导航和清晰 Logo 位置；避免复杂装饰。' },
          { id: 'social-layout', name: 'Social Layout', usage: '社媒主页头像、封面和内容流保持 Logo 清晰，头像优先使用 brandmark。' },
          { id: 'online-promotion', name: 'Online Promotion Layout', usage: '移动端推广保留 Logo 安全区，文字不压 Logo，不破坏主视觉。' },
          { id: 'car-show-booth', name: 'Car Show Booth Display', usage: '展台招牌、门头、立面和车区使用大比例 MONTX/领越标识，保持远距可识别。' },
          { id: 'dealership-stationery', name: 'Dealership Stationery', usage: '经销商物料包含 letterhead、envelope、business card、folder、tag、flag 等版式。' },
          { id: 'video-end-frame', name: 'Video End Frame / CTA', usage: '视频结尾使用黑底 Logo lockup，可搭配网址或 CTA 文案。' },
        ],
      },
      campaigns: [
        { id: 'niuma-ye-xiaoyao', name: '牛马野逍遥共创计划', status: 'active', allowedContexts: ['共创计划', '活动复盘', 'KOL 联合发起人语境'], forbiddenContexts: ['品牌常规语气', '用户自嘲', '官方硬广标题'], source: 'gac-brand-assets-2026-04' },
        { id: 'hyper-teaser', name: 'Hyper 预热视频', status: 'active', allowedContexts: ['Hyper Performance', 'Hyper Intelligence', 'Hyper Design', 'Hyper Capability', 'Hyper Safety'], forbiddenContexts: ['脱离产品证据的性能夸张'], source: 'gac-brand-assets-2026-04' },
      ],
      preferences: [
        { id: 'scene-first-branding', type: 'content-style', polarity: 'prefer', stability: 'medium', label: '用场景定义品牌', description: '优先从商业、生活、探索等真实场景进入品牌叙事，而不是先堆配置或参数。', source: 'gac-brand-assets-2026-04', status: 'confirmed' },
        { id: 'boundary-aware-concept', type: 'claim-style', polarity: 'prefer', stability: 'long-term', label: '概念边界清晰', description: 'P10/V10 的愿景能力可以讲，但必须用概念车、愿景、探索方向等语气标注边界。', source: '新闻稿 / 讲话稿', status: 'confirmed' },
        { id: 'co-creation-context', type: 'campaign-style', polarity: 'prefer', stability: 'medium', label: '用户共创优先', description: '涉及牛马野逍遥时强调真开源、真共创、真定制，避免把用户共创写成普通活动噱头。', source: '牛马野逍遥-0415.pptx', status: 'confirmed' },
        { id: 'avoid-light-meme', type: 'tone-boundary', polarity: 'avoid', stability: 'long-term', label: '避免轻浮玩梗', description: '品牌可以先锋、开拓、机能，但不能用轻浮自嘲、过度玩梗或竞品碾压来换短期互动。', source: 'brand-policy', status: 'confirmed' },
      ],
      decisions: [
        { id: 'decision-concept-boundary', status: 'confirmed', type: 'brand-boundary', title: 'P10/V10 统一按概念车叙事处理', rationale: '载人飞行器、线控滑移方向盘、时间感知自动进化等属于愿景和概念能力，不能写成量产承诺。', impact: ['strategy', 'brief', 'content', 'longform', 'monitor'], source: '新闻稿 / 讲话稿', decidedAt: '2026-04' },
        { id: 'decision-niuma-context', status: 'confirmed', type: 'campaign-boundary', title: '“牛马”只保留在共创项目语境', rationale: '该表达属于“牛马野逍遥共创计划”的活动命名，不进入品牌常规口吻，也不替用户自嘲。', impact: ['strategy', 'brief', 'content', 'reply'], source: '牛马野逍遥-0415.pptx', decidedAt: '2026-04' },
        { id: 'decision-official-social-only', status: 'confirmed', type: 'memory-governance', title: '品牌记忆只沉淀官方社媒内容', rationale: '外部达人和 earned media 是市场感知/传播复盘证据，不代表品牌自有表达，不能直接写入品牌记忆。', impact: ['memory', 'monitor', 'reports'], source: '人工确认', decidedAt: nowStamp() },
      ],
      evidenceSources: [
        { id: 'source-core-message-0420', title: '领越品牌核心讯息-0420-V1.pdf', type: 'brand-core', stability: 'long-term', status: 'confirmed', confidence: 'high', facts: ['品牌定位', '品牌口号', '品牌愿景', '核心价值主张'], sourcePath: '/Users/J/Downloads/广汽品牌资产/领越品牌核心讯息-0420-V1.pdf' },
        { id: 'source-brand-strategy-0418', title: '广汽领程新品牌品牌策略方案-0418_final.pptx', type: 'brand-strategy', stability: 'long-term', status: 'confirmed', confidence: 'high', facts: ['目标用户', '品牌世界观', '场景边界', '传播主张'], sourcePath: '/Users/J/Downloads/广汽品牌资产/广汽领程新品牌品牌策略方案-0418_final.pptx' },
        { id: 'source-vi-guidelines-v11', title: 'MONTX_GUIDELINES_V1.1.pptx', type: 'visual-system', stability: 'long-term', status: 'confirmed', confidence: 'high', facts: ['Logo 规范', '色彩规范', '字体规范', '应用规范'], sourcePath: '/Users/J/Downloads/广汽品牌资产/MONTX_GUIDELINES_V1.1.pptx' },
        { id: 'source-news-product-speech', title: '新闻稿 / 讲话稿 / 产品稿', type: 'product-narrative', stability: 'medium', status: 'confirmed', confidence: 'medium', facts: ['P10 叙事', 'V10 叙事', '概念车边界', '共创表述'], sourcePath: '/Users/J/Downloads/广汽品牌资产/【4月23日无更新】15-新闻稿' },
        { id: 'source-official-social', title: '官方社媒账号与已发布内容', type: 'approved-social-content', stability: 'short-term', status: 'pending', confidence: 'medium', facts: ['官方账号', '客户认可内容', '内容表现'], sourcePath: 'social-sync-state.json' },
        { id: 'source-social-account-opening-0410', title: '广汽领程社媒账号开通建议0410final(1).pdf', type: 'social-platform-strategy', stability: 'medium-term', status: 'superseded-draft', confidence: 'medium', facts: ['平台分阶段布局', '账号开通流程', '8周推进路径', '平台淘汰/待定规则'], sourcePath: '/Users/J/Downloads/广汽品牌资产/广汽领程社媒账号开通建议0410final(1).pdf' },
      ],
      memoryConflicts: [
        {
          id: 'conflict-youtube-account-opening-0410',
          status: 'pending',
          severity: 'needs-confirmation',
          title: 'YouTube 开通节奏与当前执行冲突',
          message: '0410 初稿把 YouTube 归入 2027 蓄水期/海外增量盘，但当前品牌同步数据里已有官方 YouTube 账号 MONTX Tech。',
          sourceEvidenceId: 'source-social-account-opening-0410',
          draftClaim: 'YouTube 2027 蓄水期启动，偏静态评测、设计解读、技术前瞻、概念车展示、工厂探秘。',
          currentFact: 'YouTube · MONTX Tech 已作为海外视频官方账号存在。',
          recommendedResolution: 'accept_current',
          options: ['accept_current', 'keep_as_draft', 'needs_more_review'],
          upstream: ['0410 社媒账号开通建议初稿', '官方账号同步数据'],
          downstream: ['strategy', 'brief', 'content', 'monitor', 'reports'],
          createdAt: '2026-05-02',
        },
      ],
      projectOperatingStages: [
        {
          id: 'stage-brand-launch-2026',
          name: '品牌发布期',
          period: '2026 年起',
          factStatus: 'confirmed',
          provenance: 'documented',
          sourceEvidenceId: 'source-social-account-opening-0410',
          source: '广汽领程社媒账号开通建议0410final(1).pdf',
          sourceRefs: ['PAGE 3', 'PAGE 8', 'PAGE 11', 'PAGE 12'],
          stageGoals: ['大规模用户触达', '构建品牌粉丝基盘', '传递品牌形象', '建立专业形象与用户心智'],
          stageObjectives: ['账号搭建', '基础曝光', '内容方向验证', '评论反馈收集'],
          brandStrategyBoundaries: ['内容不依赖量产车', '强调品牌故事、技术长文、视觉资产和场景种草', '概念车能力不写成量产承诺'],
          operatingPreferences: [
            { id: 'brand-launch-fb-ig', label: '海外发布期首批优先 Facebook + Instagram', factStatus: 'confirmed', provenance: 'documented', sourceRefs: ['PAGE 11', 'PAGE 12'] },
            { id: 'brand-launch-ig-reels', label: 'Instagram 内容结构可参考图片 60% + Reels 30% + UGC 互动 10%', factStatus: 'confirmed', provenance: 'documented', sourceRefs: ['PAGE 8'] },
            { id: 'brand-launch-weekly-frequency', label: '每周 2-3 条暂不能从现有资料直接确认，需要后续运营资料补证', factStatus: 'pending-confirmation', provenance: 'inferred-gap', sourceRefs: ['PAGE 23'] },
          ],
          platformPlan: {
            overseas: ['Facebook', 'Instagram'],
            domestic: ['微信生态', '小红书'],
          },
          downstream: ['strategy', 'brief', 'content', 'monitor', 'reports'],
        },
        {
          id: 'stage-auto-show-2026',
          name: '车展传播期',
          period: '2026 北京车展前后',
          factStatus: 'inferred',
          provenance: 'inferred-from-approved-content',
          sourceEvidenceId: 'source-news-product-speech',
          source: '新闻稿 / 讲话稿 / 官方社媒内容',
          sourceRefs: ['source-news-product-speech', 'source-official-social'],
          stageGoals: ['首秀曝光', '解释 P10/V10 概念边界', '收集评论反馈', '识别用户误解和高兴趣点'],
          stageObjectives: ['基础曝光', '评论反馈收集', '概念误解纠偏', '高互动素材复用'],
          brandStrategyBoundaries: ['强调全域跨界新物种', '不自称房车', '不把概念能力写成量产承诺', '慎用末世感和逃离现实语气'],
          operatingPreferences: [
            { id: 'auto-show-short-video', label: '短视频优先承接首秀、产品细节和场景解释', factStatus: 'inferred', provenance: 'inferred-from-approved-content', sourceRefs: ['source-official-social'] },
            { id: 'auto-show-youtube-current', label: 'YouTube 已按当前执行事实前置进入海外视频阵地，但与 0410 初稿存在冲突待确认', factStatus: 'conflict-adjusted', provenance: 'conflict-adjusted', sourceRefs: ['conflict-youtube-account-opening-0410'] },
          ],
          platformPlan: {
            overseas: ['Instagram', 'YouTube'],
            domestic: ['视频号', '微信公众号', '小红书'],
          },
          conflicts: ['conflict-youtube-account-opening-0410'],
          downstream: ['strategy', 'brief', 'content', 'monitor', 'reports'],
        },
        {
          id: 'stage-account-startup-8w',
          name: '账号启动期',
          period: '8 周推进路径',
          factStatus: 'confirmed',
          provenance: 'documented',
          sourceEvidenceId: 'source-social-account-opening-0410',
          source: '广汽领程社媒账号开通建议0410final(1).pdf',
          sourceRefs: ['PAGE 22', 'PAGE 23'],
          stageGoals: ['账号可对外展示', '内容节奏稳定', '消息与留资入口打通', '首轮投放数据可复盘'],
          stageObjectives: ['命名规范', '资料补齐', '权限与验证', '四大内容支柱上线', '首批内容 6-8 条上线', '小预算测试素材', '输出月度复盘'],
          brandStrategyBoundaries: ['准备材料包含 Logo、封面、头像、品牌简介、Slogan、官网链接', '法务合规需覆盖商标授权、版权确认、对外表述边界、隐私政策'],
          operatingPreferences: [
            { id: 'startup-global-first', label: '先全球主账号，再看重点市场是否拆区域号', factStatus: 'confirmed', provenance: 'documented', sourceRefs: ['PAGE 22'] },
            { id: 'startup-8-week-gate', label: '以账号展示、内容节奏、消息/留资、投放复盘作为阶段判断标准', factStatus: 'confirmed', provenance: 'documented', sourceRefs: ['PAGE 23'] },
          ],
          platformPlan: {
            overseas: ['Facebook', 'Instagram', 'YouTube（当前执行事实，待冲突确认）'],
            domestic: ['微信生态', '小红书'],
          },
          downstream: ['memory', 'strategy', 'content', 'monitor', 'reports'],
        },
        {
          id: 'stage-growth-2027',
          name: '品牌成长期',
          period: '2027 年起',
          factStatus: 'confirmed',
          provenance: 'documented-draft',
          sourceEvidenceId: 'source-social-account-opening-0410',
          source: '广汽领程社媒账号开通建议0410final(1).pdf',
          sourceRefs: ['PAGE 3', 'PAGE 12', 'PAGE 13'],
          stageGoals: ['迎合市场触媒偏好', '触达更多目标市场', '进一步提升品牌认知度', '赋能开网、合作伙伴和供应链业务拓展'],
          stageObjectives: ['流量爆发', '深度种草', '区域阵地验证', '私域沉淀'],
          brandStrategyBoundaries: ['TikTok/Snapchat 属区域增量或验证阵地，不直接当全球统一阵地', 'YouTube 在初稿中属于蓄水期，但当前已前置执行，需保留冲突标记'],
          operatingPreferences: [
            { id: 'growth-platform-experiments', label: 'TikTok、Snapchat 结合区域验证择机启动', factStatus: 'confirmed', provenance: 'documented-draft', sourceRefs: ['PAGE 12'] },
            { id: 'growth-youtube-draft', label: 'YouTube 初稿建议 2027 做设计解读、技术前瞻、概念车展示等蓄水内容', factStatus: 'conflict-adjusted', provenance: 'conflict-adjusted', sourceRefs: ['PAGE 8', 'PAGE 12', 'conflict-youtube-account-opening-0410'] },
          ],
          platformPlan: {
            overseas: ['YouTube', 'TikTok', 'LinkedIn', 'Snapchat'],
            domestic: ['抖音', '微博', 'B站', '微信生态'],
          },
          conflicts: ['conflict-youtube-account-opening-0410'],
          downstream: ['strategy', 'brief', 'content', 'monitor', 'reports'],
        },
      ],
      constraints: [
        { id: 'concept-boundary', label: '概念车能力必须标注边界', category: 'claim', scope: ['strategy', 'brief', 'content', 'longform', 'monitor', 'reports'], severity: 'required', source: 'gac-brand-assets-2026-04', description: 'P10/V10 的载人飞行器、线控滑移方向盘、时间感知自动进化等表达必须标注为概念车/愿景叙事，不能写成量产承诺。', defaultLocked: true, forbiddenTerms: ['已量产', '即将交付', '确定上市', '标配', '价格'] },
        { id: 'no-autonomous-overclaim', label: '禁止自动驾驶过度承诺', category: 'claim', scope: ['strategy', 'brief', 'content', 'longform', 'monitor', 'reports', 'reply'], severity: 'required', source: 'gac-brand-assets-2026-04', description: '涉及自动识别、自动接管、主动调整、无人区侦察等表达时，不得暗示完全自动驾驶、无人驾驶、替代驾驶员或绝对安全。', defaultLocked: true, forbiddenTerms: ['完全自动驾驶', '无人驾驶', '替代驾驶员', '绝对安全', '解放双手'] },
        { id: 'niuma-context-only', label: '“牛马”仅限共创项目语境', category: 'campaign', scope: ['strategy', 'brief', 'content', 'longform', 'monitor', 'reply'], severity: 'required', source: 'gac-brand-assets-2026-04', description: '“牛马”只允许出现在“牛马野逍遥”共创计划相关语境，不进入品牌常规口吻，不替用户自嘲。', defaultLocked: true, forbiddenTerms: ['牛马'] },
        { id: 'wealth-freedom-caution', label: '慎用财富自由', category: 'claim', scope: ['strategy', 'brief', 'content', 'longform', 'monitor', 'reports', 'reply'], severity: 'required', source: 'gac-brand-assets-2026-04', description: '“财富自由”只能作为讲话稿历史素材引用，常规生成中需避免金融收益、成本收益或商业成功的夸大承诺。', defaultLocked: true, forbiddenTerms: ['财富自由'] },
        { id: 'no-gradient', label: '禁止渐变 / 毛玻璃 / 阴影', category: 'visual', scope: ['longform', 'content'], severity: 'required', source: 'brand-policy', description: '保持硬核对比和纯色块，不使用柔化视觉效果。', defaultLocked: true, forbiddenTerms: ['渐变', '毛玻璃', '阴影'] },
        { id: 'no-image-text-overlay', label: '禁止图片压字', category: 'visual', scope: ['longform', 'content'], severity: 'required', source: 'brand-policy', description: '图片必须独立呈现，文字区块与图片分离。', defaultLocked: true, forbiddenTerms: ['图片压字', '文字覆盖图片'] },
        { id: 'full-bleed-image', label: '图片必须全通栏', category: 'visual', scope: ['longform'], severity: 'required', source: 'brand-policy', description: '图片模块以 1080px 宽度铺满，不做拼图和局部圆角。', defaultLocked: true },
        { id: 'no-rounded-collage', label: '禁止圆角与拼图', category: 'visual', scope: ['longform'], severity: 'required', source: 'brand-policy', description: 'Image Block 必须 w-full block，不加圆角、边距、拼接。', defaultLocked: true, forbiddenTerms: ['圆角拼图', '拼图'] },
        { id: 'normal-body-weight', label: '正文禁止单字加粗', category: 'typography', scope: ['longform'], severity: 'required', source: 'brand-policy', description: '除标题外，段落文本强制 normal，不做局部加粗强调。', defaultLocked: true },
        { id: 'canvas-1080', label: '画布宽度固定 1080px', category: 'visual', scope: ['longform'], severity: 'required', source: 'brand-policy', description: 'HTML 预览与 PNG 导出目标均为 1080px 宽。', defaultLocked: true },
        { id: 'single-file-html', label: '单文件 HTML', category: 'delivery', scope: ['longform'], severity: 'required', source: 'brand-policy', description: '所有样式集成在导出的 HTML 中，便于后续转图。', defaultLocked: true },
        { id: 'no-interaction', label: '无交互动画', category: 'delivery', scope: ['longform'], severity: 'required', source: 'brand-policy', description: '长图图文为静态交付，移除 hover、动画和滚动效果。', defaultLocked: true },
      ],
      learnings: [],
      sources: ['领越品牌核心讯息-0420-V1.pdf', '广汽领程新品牌品牌策略方案-0418_final.pptx', 'MONTX_GUIDELINES_V1.1.pptx', '新闻稿 / 讲话稿 / 牛马野逍遥 / TVC'],
      updatedAt: 'seed',
    },
    {
      id: 'aurelia',
      name: 'Aurelia Motors',
      version: SCHEMA_VERSION,
      activeSkillId: 'aurelia-editorial-reserved',
      products: [
        { id: 'aurelia-ax7', name: 'AX-7', aliases: ['Aurelia AX-7', 'AX-7'], positioning: 'Aurelia Motors 旗舰纯电轿跑 SUV 首发产品，承载静奢、东方家庭和高端智能出行叙事。', audience: ['中高端家庭用户', '纯电 SUV 关注者', '品牌首发关注者'], tone: ['静奢', '克制', '高级', '家庭场景'], requiredClaims: ['旗舰首发', '静奢气质', '家庭第二台车场景'], forbiddenClaims: ['促销秒杀', '赛道碾压', '暴力加速'], narrative: { positioning: 'AX-7 是 Aurelia Motors 的旗舰首发产品，承担品牌进入中高端纯电轿跑 SUV 市场的叙事任务。', coreCapabilities: ['静奢设计', '家庭场景舒适性', '高端纯电体验'], userScenarios: ['家庭第二台车', '城市周末出行', '品牌首发认知建立'], proofPoints: ['设计语言', '空间与舒适性', '纯电体验', '品牌调性'], comparisonBoundaries: ['不以赛道和暴力加速作为主叙事', '不做低价促销化表达'], requiredMentions: ['AX-7 旗舰首发', '静奢', '家庭场景'], forbiddenClaims: ['促销秒杀', '碾压竞品', '暴力加速'] }, source: 'workspace-seed' },
        { id: 'aurelia-ax5', name: 'AX-5', aliases: ['Aurelia AX-5', 'AX-5'], positioning: 'Aurelia Motors 城市家庭产品线。', audience: ['城市家庭用户'], tone: ['克制', '实用', '温和'], requiredClaims: ['城市家庭', '日常使用'], forbiddenClaims: ['赛道化表达'], narrative: { positioning: 'AX-5 面向城市家庭日常使用。' }, source: 'workspace-seed' },
        { id: 'aurelia-concept-s', name: 'Concept-S', aliases: ['Aurelia Concept-S', 'Concept-S'], positioning: 'Aurelia Motors 概念产品线，用于未来设计方向表达。', audience: ['品牌关注者', '设计媒体'], tone: ['前瞻', '克制', '设计感'], requiredClaims: ['未来设计方向'], forbiddenClaims: ['量产承诺'], narrative: { positioning: 'Concept-S 用于表达 Aurelia Motors 的未来设计方向。' }, source: 'workspace-seed' },
      ],
      narrative: {
        positioning: 'Aurelia Motors 是中高端纯电出行品牌，强调静奢、克制和东方家庭场景。',
        worldview: '电动豪华不应依赖喧闹性能叙事，而应通过克制设计、家庭场景和长期信任建立心智。',
        valueProposition: '以静奢设计和可靠体验服务高品质家庭出行。',
        voice: ['克制', '静奢', '高级', '温和'],
        requiredThemes: ['静奢', '东方家庭', '纯电体验', '长期信任'],
        forbiddenAngles: ['促销化表达', '赛道暴力叙事', '竞品碾压'],
        proofTypes: ['设计语言', '家庭场景', '用户洞察', '产品体验证据'],
      },
      constraints: [
        { id: 'aurelia-tone', label: '克制 / 静奢 / 非促销', category: 'tone', scope: ['strategy', 'brief', 'content', 'longform', 'monitor', 'reports', 'reply'], severity: 'required', source: 'brand-policy', description: '避免硬广、秒杀、碾压等转化型表达。', defaultLocked: true, forbiddenTerms: ['秒杀', '硬广', '碾压'] },
        { id: 'aurelia-no-racing', label: '回避赛车对比', category: 'claim', scope: ['strategy', 'brief', 'content', 'longform'], severity: 'required', source: 'brand-policy', description: '不以赛道、暴力加速、竞品压制作为主叙事。', defaultLocked: true, forbiddenTerms: ['赛道碾压', '暴力加速'] },
      ],
      learnings: [],
      decisions: [],
      sources: ['workspace-seed'],
      updatedAt: 'seed',
    },
  ];

  const normalizeBrandProfile = (brand = {}, index = 0) => ({
    ...brand,
    id: brand.id || `brand-${index + 1}`,
    name: brand.name || `Brand ${index + 1}`,
    version: Math.max(Number(brand.version || 1), SCHEMA_VERSION),
    narrative: normalizeBrandNarrative(brand.narrative),
    products: (Array.isArray(brand.products) ? brand.products : []).map(normalizeProductProfile),
    constraints: (brand.constraints || []).map((constraint, itemIndex) => normalizeTraceItem(normalizeConstraint(constraint, itemIndex), { source: constraint.source || 'brand-policy' })),
    campaigns: Array.isArray(brand.campaigns) ? brand.campaigns.map(item => normalizeTraceItem(item, { source: 'gac-brand-assets-2026-04' })) : [],
    preferences: Array.isArray(brand.preferences) ? brand.preferences.map(item => normalizeTraceItem(item, { source: item.source || 'brand-memory' })) : [],
    projectOperatingStages: Array.isArray(brand.projectOperatingStages) ? brand.projectOperatingStages.map(normalizeOperatingStage) : [],
    visualSystem: normalizeVisualSystem(brand.visualSystem || {}),
    socialAssets: brand.socialAssets || { accounts: [], approvedPosts: [], commentsSummary: {}, updatedAt: '' },
    learnings: Array.isArray(brand.learnings) ? brand.learnings.map(item => normalizeTraceItem(item, { factStatus: item.status === 'confirmed' ? 'confirmed' : 'pending-confirmation', provenance: 'pending-writeback', source: item.source || 'brand-memory' })) : [],
    decisions: Array.isArray(brand.decisions) ? brand.decisions.map(item => normalizeTraceItem(item, { source: item.source || 'manual' })) : [],
    evidenceSources: Array.isArray(brand.evidenceSources) ? brand.evidenceSources.map(item => normalizeTraceItem(item, { factStatus: item.status || 'confirmed', provenance: item.status === 'superseded-draft' ? 'documented-draft' : 'documented', source: item.sourcePath || item.title || 'source' })) : [],
    memoryConflicts: Array.isArray(brand.memoryConflicts) ? brand.memoryConflicts.map(item => normalizeTraceItem(item, { factStatus: 'pending-confirmation', provenance: 'conflict-detected', source: item.sourceEvidenceId || 'conflict-source', conflicts: [item.id] })) : [],
    sources: Array.isArray(brand.sources) ? brand.sources : [],
    updatedAt: brand.updatedAt || 'seed',
  });

  const normalizeBrandProfiles = (brands = DEFAULT_BRAND_PROFILES) => brands.map(normalizeBrandProfile);

  const mergeSeedDefaults = (saved = []) => {
    const seed = normalizeBrandProfiles(DEFAULT_BRAND_PROFILES);
    const savedProfiles = normalizeBrandProfiles(saved?.length ? saved : seed);
    const merged = seed.map(seedBrand => {
      const current = savedProfiles.find(brand => brand.id === seedBrand.id);
      if (!current) return seedBrand;
      const constraints = [
        ...current.constraints,
        ...seedBrand.constraints.filter(seedRule => !current.constraints.some(rule => rule.id === seedRule.id)),
      ];
      const products = [
        ...current.products,
        ...seedBrand.products.filter(seedProduct => !current.products.some(product => product.id === seedProduct.id)),
      ];
      return normalizeBrandProfile({
        ...seedBrand,
        ...current,
        version: Math.max(Number(current.version || 1), SCHEMA_VERSION),
        visualSystem: {
          ...(seedBrand.visualSystem || {}),
          ...(current.visualSystem || {}),
          colorTokens: mergeColorTokens(seedBrand.visualSystem?.colorTokens || [], current.visualSystem?.colorTokens || []),
          semanticColors: {
            ...(seedBrand.visualSystem?.semanticColors || {}),
            ...(current.visualSystem?.semanticColors || {}),
          },
          colorRules: uniq([...(seedBrand.visualSystem?.colorRules || []), ...(current.visualSystem?.colorRules || [])]),
          viGuidelines: current.visualSystem?.viGuidelines?.length ? current.visualSystem.viGuidelines : (seedBrand.visualSystem?.viGuidelines || []),
          logoAssets: current.visualSystem?.logoAssets?.length ? current.visualSystem.logoAssets : (seedBrand.visualSystem?.logoAssets || []),
          logoLockups: current.visualSystem?.logoLockups?.length ? current.visualSystem.logoLockups : (seedBrand.visualSystem?.logoLockups || []),
          logoRules: uniq([...(seedBrand.visualSystem?.logoRules || []), ...(current.visualSystem?.logoRules || [])]),
          typography: current.visualSystem?.typography?.length ? current.visualSystem.typography : (seedBrand.visualSystem?.typography || []),
          layoutRules: uniq([...(seedBrand.visualSystem?.layoutRules || []), ...(current.visualSystem?.layoutRules || [])]),
          imageRules: uniq([...(seedBrand.visualSystem?.imageRules || []), ...(current.visualSystem?.imageRules || [])]),
          applicationRules: current.visualSystem?.applicationRules?.length ? current.visualSystem.applicationRules : (seedBrand.visualSystem?.applicationRules || []),
        },
        constraints,
        products,
        campaigns: mergeMemoryItems(seedBrand.campaigns || [], current.campaigns || []),
        preferences: mergeMemoryItems(seedBrand.preferences || [], current.preferences || []),
        projectOperatingStages: mergeMemoryItems(seedBrand.projectOperatingStages || [], current.projectOperatingStages || []),
        decisions: mergeMemoryItems(seedBrand.decisions || [], current.decisions || []),
        evidenceSources: mergeMemoryItems(seedBrand.evidenceSources || [], current.evidenceSources || []),
        memoryConflicts: mergeMemoryItems(seedBrand.memoryConflicts || [], current.memoryConflicts || []),
        learnings: mergeMemoryItems(seedBrand.learnings || [], current.learnings || []),
        sources: uniq([...(seedBrand.sources || []), ...(current.sources || [])]),
      });
    });
    return [
      ...merged,
      ...savedProfiles.filter(brand => !seed.some(seedBrand => seedBrand.id === brand.id)),
    ];
  };

  const BrandMemoryDB = {
    open() {
      return new Promise((resolve, reject) => {
        const req = indexedDB.open(DB_NAME, DB_VERSION);
        req.onupgradeneeded = () => {
          if (!req.result.objectStoreNames.contains(KV_STORE)) req.result.createObjectStore(KV_STORE);
        };
        req.onsuccess = () => resolve(req.result);
        req.onerror = () => reject(req.error);
      });
    },
    async get(key) {
      try {
        const db = await BrandMemoryDB.open();
        return await new Promise((resolve, reject) => {
          const req = db.transaction(KV_STORE, 'readonly').objectStore(KV_STORE).get(key);
          req.onsuccess = () => resolve(req.result);
          req.onerror = () => reject(req.error);
        });
      } catch (error) {
        console.warn('BrandMemory IndexedDB read failed', error);
        return null;
      }
    },
    async set(key, value) {
      try {
        const db = await BrandMemoryDB.open();
        await new Promise((resolve, reject) => {
          const req = db.transaction(KV_STORE, 'readwrite').objectStore(KV_STORE).put(value, key);
          req.onsuccess = () => resolve();
          req.onerror = () => reject(req.error);
        });
      } catch (error) {
        console.warn('BrandMemory IndexedDB write failed', error);
      }
    },
  };

  const loadProfiles = async () => mergeSeedDefaults(await BrandMemoryDB.get(PROFILE_KEY));
  const saveProfiles = async (profiles) => BrandMemoryDB.set(PROFILE_KEY, normalizeBrandProfiles(profiles));
  const getBrandProfile = (brandId, brands = DEFAULT_BRAND_PROFILES) => {
    const profiles = normalizeBrandProfiles(brands);
    return profiles.find(brand => brand.id === brandId) || profiles[0] || null;
  };
  const getProductProfile = (brand, productId) => {
    const products = brand?.products || [];
    return products.find(product => product.id === productId) || products[0] || null;
  };
  const getActiveConstraints = (brand, scope) => (brand?.constraints || []).filter(rule => {
    if (rule.enabled === false) return false;
    if (!scope) return true;
    const scopes = listFromValue(rule.scope);
    return !scopes.length || scopes.includes(scope);
  });

  const updateProfileList = (profiles, brandId, updater) => normalizeBrandProfiles(profiles).map(brand => {
    if (brand.id !== brandId) return brand;
    const next = typeof updater === 'function' ? updater(brand) : updater;
    return normalizeBrandProfile({ ...brand, ...next, updatedAt: nowStamp() });
  });

  const saveBrandProfile = async (brandId, updater) => {
    const profiles = await loadProfiles();
    const next = updateProfileList(profiles, brandId, updater);
    await saveProfiles(next);
    return getBrandProfile(brandId, next);
  };

  const termAllowedByContext = (term, context = {}) => {
    if (term !== '牛马') return false;
    const campaignId = String(context.campaignId || '').toLowerCase();
    const campaignName = String(context.campaignName || context.campaign || '');
    return campaignId.includes('niuma') || campaignName.includes('牛马野逍遥');
  };

  const runBrandGuardrails = (input = '', context = {}) => {
    const text = typeof input === 'string' ? input : JSON.stringify(input || {});
    const brand = context.brand || getBrandProfile(context.brandId, context.brands);
    const constraints = getActiveConstraints(brand, context.scope);
    const violations = [];
    const warnings = [];
    const requiredFixes = [];

    constraints.forEach(rule => {
      (rule.forbiddenTerms || []).forEach(term => {
        if (!term || !text.includes(term) || termAllowedByContext(term, context)) return;
        const item = {
          ruleId: rule.id,
          severity: rule.severity || 'required',
          category: rule.category || 'brand',
          term,
          message: `${rule.label}: 命中禁用表达“${term}”。`,
          suggestion: rule.description || '请按品牌记忆护栏改写。',
        };
        violations.push(item);
        if (item.severity === 'required') requiredFixes.push(item);
        else warnings.push(item);
      });
      (rule.requiredTerms || []).forEach(term => {
        if (!term || text.includes(term)) return;
        warnings.push({
          ruleId: rule.id,
          severity: rule.severity || 'required',
          category: rule.category || 'brand',
          term,
          message: `${rule.label}: 建议补充“${term}”。`,
          suggestion: rule.description || '请补充必要品牌表达。',
        });
      });
    });

    return {
      passed: violations.length === 0,
      brandId: brand?.id || context.brandId || '',
      scope: context.scope || 'all',
      version: brand?.version || SCHEMA_VERSION,
      checkedRules: constraints.length,
      violations,
      warnings,
      requiredFixes,
      constraintSnapshot: constraints.map(({ id, label, category, scope, severity, updatedAt }) => ({ id, label, category, scope, severity, updatedAt })),
    };
  };

  const recordBrandLearning = (brandId, learning) => saveBrandProfile(brandId, brand => ({
    ...brand,
    learnings: [...(brand.learnings || []), { id: `learning-${Date.now()}`, status: 'pending', createdAt: nowStamp(), ...learning }],
  }));

  const recordBrandDecision = (brandId, decision) => saveBrandProfile(brandId, brand => ({
    ...brand,
    decisions: [...(brand.decisions || []), { id: `decision-${Date.now()}`, createdAt: nowStamp(), ...decision }],
  }));

  const mergeRowsById = (rows = [], idKey) => {
    const map = new Map();
    rows.forEach(row => {
      if (!row?.[idKey]) return;
      map.set(row[idKey], { ...(map.get(row[idKey]) || {}), ...row });
    });
    return Array.from(map.values());
  };

  const buildSocialMemorySnapshot = ({ brandId = 'montx', accounts = [], posts = [], comments = [], lastSync = '' } = {}) => {
    const mergedPosts = mergeRowsById(posts, 'post_id');
    const officialPosts = mergedPosts
      .filter(post => !isExternalSocialPost(post))
      .sort((a, b) => socialInteraction(b) - socialInteraction(a) || getSocialPostTime(b) - getSocialPostTime(a));
    const officialPostAccountIds = new Set(officialPosts.map(post => post.account_id).filter(Boolean));
    const scopedAccounts = mergeRowsById(accounts, 'account_id')
      .filter(account => !isExternalSocialAccount(account))
      .filter(account => account.brand_id === brandId || officialPostAccountIds.has(account.account_id))
      .map(account => ({
        account_id: account.account_id,
        platform: account.platform || '',
        account_name: account.account_name || account.handle || '',
        handle: account.handle || '',
        homepage_url: account.homepage_url || '',
        owner: account.owner || '',
        positioning: account.positioning || '',
        status: account.status || 'active',
        follower_delta_period: socialNumber(account.follower_delta_period),
        source: account.provider || account.source || 'social-sync',
      }));
    const accountIds = new Set(scopedAccounts.map(account => account.account_id));
    const scopedOfficialPosts = officialPosts
      .filter(post => post.brand_id === brandId || accountIds.has(post.account_id))
      .sort((a, b) => socialInteraction(b) - socialInteraction(a) || getSocialPostTime(b) - getSocialPostTime(a));
    const postIds = new Set(scopedOfficialPosts.map(post => post.post_id));
    const scopedComments = mergeRowsById(comments, 'comment_id').filter(comment => postIds.has(comment.post_id));
    const approvedPosts = scopedOfficialPosts.slice(0, 12).map(post => ({
      post_id: post.post_id,
      account_id: post.account_id || '',
      platform: post.platform || '',
      title: post.corrected_title || post.display_title || post.title || post.manual_title || post.raw?.display_title || '未命名内容',
      fullText: post.title && post.title !== (post.corrected_title || post.display_title) ? post.title : '',
      url: post.url || post.manual_metrics?.url || post.raw?.share_url || '',
      content_type: post.content_type || '',
      campaign: post.campaign || '',
      product: post.product || '',
      published_at: post.published_at || post.publish_time || '',
      cover_url: post.cached_cover_url || post.media_url || post.raw?.image?.thumbnail || '',
      metrics: {
        views: socialNumber(post.views || post.plays || post.reads || post.organic_impressions),
        likes: socialNumber(post.likes),
        comments: socialNumber(post.comments),
        shares: socialNumber(post.shares),
        saves: socialNumber(post.saves),
        interaction: socialInteraction(post),
      },
      source: post.provider || post.data_channel || post.source_type || 'social-sync',
      memoryRole: 'approved-content-evidence',
    }));
    const positive = scopedComments.filter(comment => socialNumber(comment.sentiment) > 0.15).length;
    const risk = scopedComments.filter(comment => comment.risk_tag || socialNumber(comment.sentiment) < -0.15).length;
    return {
      source: 'social-sync',
      brandId,
      accounts: scopedAccounts,
      approvedPosts,
      commentsSummary: {
        total: scopedComments.length,
        positive,
        risk,
        neutral: Math.max(0, scopedComments.length - positive - risk),
      },
      updatedAt: lastSync || nowStamp(),
      learningId: `social-memory-${brandId}-${approvedPosts.map(post => post.post_id).join('|').slice(0, 80) || 'empty'}`,
    };
  };

  const applySocialMemorySnapshot = (brand = {}, snapshot = {}) => {
    const learning = {
      id: snapshot.learningId || `social-memory-${Date.now()}`,
      status: 'pending',
      type: 'approved-social-content',
      source: 'social-sync',
      title: '官方社媒已发布内容归档',
      text: `已归档 ${snapshot.accounts?.length || 0} 个官方账号、${snapshot.approvedPosts?.length || 0} 条已发布内容，作为客户认可表达与内容证据池。`,
      createdAt: nowStamp(),
      evidencePostIds: (snapshot.approvedPosts || []).map(post => post.post_id),
    };
    return normalizeBrandProfile({
      ...brand,
      socialAssets: {
        ...(brand.socialAssets || {}),
        accounts: snapshot.accounts || [],
        approvedPosts: snapshot.approvedPosts || [],
        commentsSummary: snapshot.commentsSummary || {},
        updatedAt: snapshot.updatedAt || nowStamp(),
      },
      learnings: [
        ...(brand.learnings || []).filter(item => item.id !== learning.id),
        learning,
      ],
      updatedAt: nowStamp(),
    });
  };

  const applyMemoryConflictResolution = (brand = {}, conflictId, resolution = 'accept_current', note = '') => {
    const stampedAt = nowStamp();
    const conflicts = brand.memoryConflicts || [];
    const conflict = conflicts.find(item => item.id === conflictId);
    if (!conflict) return normalizeBrandProfile(brand);
    const status = resolution === 'needs_more_review' ? 'pending' : resolution === 'keep_as_draft' ? 'archived' : 'resolved';
    const nextConflicts = conflicts.map(item => item.id === conflictId ? {
      ...item,
      status,
      resolution,
      resolutionNote: note,
      resolvedAt: status === 'resolved' || status === 'archived' ? stampedAt : item.resolvedAt,
    } : item);
    const resolutionText = {
      accept_current: '以当前执行事实为准，初稿仅作为策略演进证据。',
      keep_as_draft: '仅保留为历史初稿，不进入当前平台策略。',
      needs_more_review: '保留待确认状态，需要用户补充新版资料或人工判断。',
    }[resolution] || resolution;
    const nextDecisions = resolution === 'needs_more_review' ? (brand.decisions || []) : mergeMemoryItems(brand.decisions || [], [{
      id: `decision-${conflict.id}-${resolution}`,
      status: 'confirmed',
      type: 'conflict-resolution',
      title: `冲突处理：${conflict.title}`,
      rationale: `${resolutionText}${note ? ` 备注：${note}` : ''}`,
      impact: conflict.downstream || ['memory'],
      source: '人工确认',
      decidedAt: stampedAt,
    }]);
    const nextEvidenceSources = (brand.evidenceSources || []).map(source => (
      source.id === conflict.sourceEvidenceId && resolution === 'keep_as_draft'
        ? { ...source, status: 'superseded-draft', updatedAt: stampedAt }
        : source
    ));
    return normalizeBrandProfile({
      ...brand,
      memoryConflicts: nextConflicts,
      decisions: nextDecisions,
      evidenceSources: nextEvidenceSources,
      updatedAt: stampedAt,
    });
  };

  const useBrandMemoryProfiles = () => {
    const [brands, setBrands] = React.useState(() => normalizeBrandProfiles(DEFAULT_BRAND_PROFILES));
    const [ready, setReady] = React.useState(false);
    React.useEffect(() => {
      let mounted = true;
      loadProfiles().then(profiles => {
        if (!mounted) return;
        setBrands(profiles);
        setReady(true);
      });
      return () => { mounted = false; };
    }, []);
    React.useEffect(() => {
      if (ready) saveProfiles(brands);
    }, [ready, brands]);
    return { brands, setBrands, ready };
  };

  const useBrandMemory = (brandId) => {
    const state = useBrandMemoryProfiles();
    const brand = getBrandProfile(brandId, state.brands);
    const updateBrand = (nextBrandOrUpdater) => {
      if (!brand) return;
      state.setBrands(prev => updateProfileList(prev, brand.id, nextBrandOrUpdater));
    };
    return {
      ...state,
      brand,
      updateBrand,
      activeConstraints: getActiveConstraints(brand),
      runGuardrails: (input, context = {}) => runBrandGuardrails(input, { ...context, brand, brandId: brand?.id || brandId }),
    };
  };

  window.BrandMemoryStore = {
    SCHEMA_VERSION,
    DEFAULT_BRAND_PROFILES,
    MONTX_PRODUCT_PROFILES,
    normalizeBrandProfiles,
    normalizeBrandProfile,
    normalizeConstraint,
    loadProfiles,
    saveProfiles,
    getBrandProfile,
    getProductProfile,
    getActiveConstraints,
    saveBrandProfile,
    recordBrandLearning,
    recordBrandDecision,
    buildSocialMemorySnapshot,
    applySocialMemorySnapshot,
    applyMemoryConflictResolution,
    runBrandGuardrails,
    useBrandMemoryProfiles,
    useBrandMemory,
  };
})();
