/* screen_marketing_briefings.jsx
   Submenu Briefings - lista, formulário 5 blocos, detalhe e aprovação.
   Blocos 1-3: AI Strategist. Blocos 4-5: Marketing Manager.
*/
function limparTravessao(v) {
  return window.semTravessaoConteudo ? window.semTravessaoConteudo(v) : v;
}

function textoPaginasKb(pages) {
  const nums = [...new Set((pages || []).map(n => Number(n)).filter(n => Number.isInteger(n) && n > 0))].sort((a, b) => a - b);
  if (!nums.length) return '';
  if (nums.length === 1) return `página ${nums[0]}`;
  return `páginas ${nums.slice(0, -1).join(', ')} e ${nums[nums.length - 1]}`;
}

function hrefFonteKb(s) {
  const u = String(s?.url || '').trim();
  if (/^https?:\/\//i.test(u)) return u;
  const nome = String(s?.file_name || '').trim();
  if (/^https?:\/\//i.test(nome)) return nome;
  if (s?.source === 'mimaki_com') {
    let path = nome.replace(/^europe:/i, '').replace(/^\/+/, '');
    if (/^products\//i.test(path)) return `https://www.mimakieurope.com/${path}`;
  }
  return '';
}

function fonteOficialKb(s) {
  const origem = String(s?.source || '');
  if (/^(mimaki_|biond_|decal|sensek_|alldecor_|netscreen_|digidelta_(pt|es|web))/i.test(origem)) return true;
  const href = hrefFonteKb(s);
  return /mimakieurope\.com|digidelta\.(pt|es)/i.test(href);
}

function confiancaKbApresentada(confidence, sources) {
  const lista = sources || [];
  if (lista.length && lista.every(fonteOficialKb) && confidence !== 'high') return 'high';
  return confidence || '';
}

// ─── Constantes ────────────────────────────────────────────────────────────────

const STATUS_CONFIG = {
  rascunho:     { label: 'Rascunho',      color: 'var(--text-muted, #4a5e7a)',  bg: 'var(--bg-sunken, #eef1f6)' },
  submetido:          { label: 'Para Manager',      color: 'var(--ai-500, #3859D0)', bg: 'color-mix(in srgb, #3859D0 10%, transparent)' },
  validado_fc:        { label: 'Para CEO',          color: 'var(--ai-500, #3859D0)', bg: 'color-mix(in srgb, #3859D0 10%, transparent)' },
  alteracoes_pedidas: { label: 'Alterações Pedidas', color: '#d97706',               bg: 'color-mix(in srgb, #d97706 10%, transparent)' },
  validado_ceo: { label: 'Para Chairman', color: 'var(--ai-500, #3859D0)',      bg: 'color-mix(in srgb, #3859D0 10%, transparent)' },
  aprovado:     { label: 'Aprovado',      color: 'var(--ai-500, #3859D0)',      bg: 'color-mix(in srgb, #3859D0 16%, transparent)' },
  rejeitado:    { label: 'Rejeitado',     color: 'var(--danger, #dc2626)',      bg: 'color-mix(in srgb, #dc2626 8%, transparent)' },
  em_campanha:  { label: 'Aprovado',      color: 'var(--ai-500, #3859D0)',      bg: 'color-mix(in srgb, #3859D0 16%, transparent)' },
};

function estadoBriefing(status) {
  return status === 'em_campanha' ? 'aprovado' : (status || '');
}

// ─── Configuração de aprovadores (espelho do servidor) ────────────────────────
// Fluxo: fc_dir (Marketing Mgr) → brand_dir (CEO) → rl (Chairman)
const APPROVER_CONFIG_FE = {
  'fabio.costa@digidelta.pt':  { roles: ['fc_dir', 'brand_dir', 'rl'], brands: 'all' },
  'joao.paulino@digidelta.pt': { roles: ['fc_dir', 'brand_dir', 'rl'], brands: 'all' },
  'rui.leitao@digidelta.pt':   { roles: ['rl'],           brands: 'all' },
  'armando.mota@digidelta.pt': { roles: ['brand_dir'],    brands: ['mimaki','biond','decal','alldecor','sensek'] },
  'bruno.rosa@digidelta.pt':   { roles: ['brand_dir'],    brands: ['netscreen'] },
};
// Ordem dos roles por marca (sequência de aprovação)
const BRAND_REQUIRED_ROLES_FE = { digidelta: ['fc_dir', 'rl'], _default: ['fc_dir', 'brand_dir', 'rl'] };
const BRAND_DIR_NAME_FE = {
  mimaki: 'Armando Mota', biond: 'Armando Mota', decal: 'Armando Mota',
  alldecor: 'Armando Mota', sensek: 'Armando Mota', netscreen: 'Bruno Rosa',
};

// Fase activa baseada no status do briefing
const getActiveRole = (briefingStatus, requiredRoles) => {
  if (briefingStatus === 'submetido')    return 'fc_dir';
  if (briefingStatus === 'validado_fc')  return requiredRoles.includes('brand_dir') ? 'brand_dir' : 'rl';
  if (briefingStatus === 'validado_ceo') return 'rl';
  return null;
};

const getRequiredApprovers = (brandSlug) => {
  const roles = BRAND_REQUIRED_ROLES_FE[brandSlug] || BRAND_REQUIRED_ROLES_FE._default;
  return roles.map(role => ({
    role,
    name: role === 'rl' ? 'Rui Leitão'
        : role === 'fc_dir' ? 'Fábio Costa'
        : (BRAND_DIR_NAME_FE[brandSlug] || 'Director de Marca'),
  }));
};

// A volta visível no fluxo. Pedir alterações ou rejeitar fecha a volta;
// quando o briefing volta a Para Manager, a barra mostra a volta nova.
function aprovacoesDaVolta(approvals, status) {
  const lista = approvals || [];
  const decis = lista.filter(a => a.action && a.action !== 'saved');
  const max = decis.reduce((m, a) => Math.max(m, Number(a.ciclo) || 1), 0);
  if (!max) return lista;
  const fechou = decis.some(a => (Number(a.ciclo) || 1) === max && (a.action === 'changes_requested' || a.action === 'rejected'));
  const volta = (fechou && status !== 'alteracoes_pedidas' && status !== 'rejeitado') ? max + 1 : max;
  return lista.filter(a => a.action === 'saved' || (Number(a.ciclo) || 1) === volta);
}

// Devolve os roles que este utilizador pode actuar AGORA (respeitando a fase activa)
const getMyPendingRoles = (email, brandSlug, approvals = [], briefingStatus = 'submetido') => {
  const cfg = APPROVER_CONFIG_FE[email?.toLowerCase()];
  if (!cfg) return [];
  const required = BRAND_REQUIRED_ROLES_FE[brandSlug] || BRAND_REQUIRED_ROLES_FE._default;
  const eligible = cfg.brands === 'all' || (cfg.brands || []).includes(brandSlug)
    ? cfg.roles : cfg.roles.filter(r => r === 'rl');
  const decided  = aprovacoesDaVolta(approvals, briefingStatus).filter(a => a.action !== 'saved').map(a => a.role);
  const activeRole = getActiveRole(briefingStatus, required);
  return eligible.filter(r => required.includes(r) && !decided.includes(r) && r === activeRole);
};
// ──────────────────────────────────────────────────────────────────────────────────────────────

const BRIEFING_TABS = [
  { id: 1, label: 'Produto', tutorial: 'fill_block1' },
  { id: 2, label: 'Cliente-alvo', tutorial: 'fill_block2_block3' },
  { id: 3, label: 'Mercado', tutorial: 'fill_block2_block3' },
  { id: 5, label: 'Restrições' },
  { id: 6, label: 'Resumo', tutorial: 'ai_analysis' },
];

function briefingParaGravar(form, opts = {}) {
  const block1 = { ...(form?.block1 || {}) };
  // A presença e a ficha só se gravam na criação. Numa edição o servidor mantém o que já está na base.
  if (!opts.incluirCampanha) delete block1.geo_markets;
  const next = { ...(form || {}), block1 };
  if (!opts.incluirCampanha) delete next.block4;
  return next;
}

const CAMPOS_LISTA_TEXTO = new Set([
  'usps', 'certifications', 'applications', 'geo_markets',
  'pain_points', 'motivators', 'objections',
  'competitors', 'differentiation_args', 'market_trends',
  'previous_campaigns',
  'prohibited_claims', 'off_brand_messages', 'competitors_not_name', 'out_of_scope_markets', 'other_restrictions',
]);

function textoEdicaoCampo(field, value) {
  if (field === 'kpis' && Array.isArray(value)) {
    return value.map(k => (k && typeof k === 'object') ? `${k.canal || 'negocio'}: ${k.kpi || ''}`.trim() : String(k || '').trim()).filter(Boolean).join('\n');
  }
  if (field === 'commercial_offer' && value && typeof value === 'object') {
    return [value.headline, value.condition, value.details].filter(Boolean).join('\n');
  }
  if (Array.isArray(value)) {
    return value.map(v => (v && typeof v === 'object') ? JSON.stringify(v) : String(v ?? '').trim()).filter(Boolean).join('\n');
  }
  if (value && typeof value === 'object') return JSON.stringify(value, null, 2);
  return String(value ?? '');
}

function valorEdicaoCampo(field, texto, actual) {
  const linhas = String(texto || '').split('\n').map(s => s.trim()).filter(Boolean);
  if (field === 'kpis') return linhas;
  if (field === 'channels') return texto;
  if (field === 'commercial_offer') {
    const base = actual && typeof actual === 'object' ? { ...actual } : {};
    const [headline, condition, ...resto] = String(texto || '').split('\n');
    return { ...base, headline: String(headline || '').trim(), condition: String(condition || '').trim(), details: resto.join('\n').trim() };
  }
  if (CAMPOS_LISTA_TEXTO.has(field)) return linhas;
  return String(texto || '').trim();
}

const EMPTY_FORM = {
  product_id: '',
  tipo_campanha: 'produto',
  block1: { commercial_name: '', product_family: '', elevator_pitch: '', usps: [''], certifications: [], applications: [], geo_markets: [] },
  block2: { decision_maker: '', end_user: '', pain_points: [''], motivators: [''], objections: [''], purchase_trigger: '' },
  block3: { competitors: [''], differentiation_args: [''], market_trends: [''], positioning_narrative: '', sensitive_info: '' },
  block4: { objective: '', kpis: [], channels: [], tone: '', key_message: '', timeline_start: '', timeline_end: '', previous_campaigns: [], commercial_offer: null, meta_ads_conversao: '' },
  block5: { prohibited_claims: [''], off_brand_messages: [''], competitors_not_name: [''], out_of_scope_markets: [''], other_restrictions: [] },
};

// Liga nome comercial / modelo sugerido a um produto do catálogo (importações e reopen).
function codigoModeloCatalogo(productName) {
  const raw = String(productName || '').toLowerCase();
  const m = raw.match(/\b((?:ucjv|ujv|ujf|uj|cjv|jv|jfx|txf|ts|cfl|cfx|cf2|cg|3duj|swj|tiger)[- ]?\d[\w-]*)\b/i);
  if (!m) return null;
  const codigo = m[1].replace(/\s+/g, '-').toLowerCase();
  const partes = codigo.match(/[a-z]+|\d+/g) || [];
  if (partes.length < 2) return null;
  const nums = partes.filter(p => /^\d+$/.test(p));
  return { partes, especifico: nums.length >= 2 };
}

function maquinaCompativelCatalogo(query, productName) {
  const q = codigoModeloCatalogo(query);
  const p = codigoModeloCatalogo(productName);
  if (!q) return null;
  if (!p) return false;
  const letras = info => info.partes.filter(x => /^[a-z]+$/.test(x)).join('');
  const nums = info => info.partes.filter(x => /^\d+$/.test(x));
  const qn = nums(q);
  const pn = nums(p);
  if (letras(q) !== letras(p) || qn[0] !== pn[0]) return false;
  if (q.especifico && pn[1] && qn[1] !== pn[1]) return false;
  return true;
}

const matchCatalogProduct = (products, hints = {}) => {
  const { productName, commercialName, family } = hints;
  const norm = (s) => String(s || '').toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[^a-z0-9]+/g, ' ').trim();
  const compact = (s) => norm(s).replace(/\s+/g, '');
  const stop = new Set(['para', 'com', 'sem', 'uma', 'the', 'and', 'oferta', 'compra', 'rolo', 'rolos', 'campanha', 'promocao']);
  let list = Array.isArray(products) ? products.slice() : [];
  if (family) {
    const scoped = list.filter(p => p.category === family || p.segment === family);
    if (scoped.length) list = scoped;
  }
  const queries = [productName, commercialName].map(norm).filter(Boolean);
  if (!queries.length || !list.length) return null;
  const identidade = codigoModeloCatalogo(productName) ? productName : (codigoModeloCatalogo(commercialName) ? commercialName : '');

  const scoreOne = (pname, q) => {
    const n = norm(pname);
    const nc = compact(pname);
    const qc = compact(q);
    if (!n || !q) return 0;
    if (n === q || nc === qc) return 1000;
    if (q.includes(n) && n.length >= 6) return 700 + Math.min(n.length, 80);
    if (n.includes(q) && q.length >= 6) return 650 + Math.min(q.length, 80);
    if (qc.includes(nc) && nc.length >= 6) return 680;
    if (nc.includes(qc) && qc.length >= 6) return 630;
    const qTokens = q.split(' ').filter(t => t.length > 2 && !stop.has(t));
    const nTokens = n.split(' ').filter(Boolean);
    if (!qTokens.length) return 0;
    let hits = 0;
    let orderedPrefix = 0;
    for (let i = 0; i < nTokens.length; i++) {
      const qt = qTokens[i];
      if (qt && (nTokens[i] === qt || nTokens[i].includes(qt) || qt.includes(nTokens[i]))) orderedPrefix++;
      else break;
    }
    for (const qt of qTokens) {
      if (nTokens.some(nt => nt === qt || nt.includes(qt) || qt.includes(nt))) hits++;
    }
    return hits * 60 + orderedPrefix * 40 + (hits >= 2 ? 50 : 0);
  };

  let best = null;
  let bestScore = 0;
  for (const p of list) {
    if (identidade && maquinaCompativelCatalogo(identidade, p.name) === false) continue;
    let s = 0;
    for (const q of queries) s = Math.max(s, scoreOne(p.name, q));
    if (identidade && maquinaCompativelCatalogo(identidade, p.name) === true) s += 250;
    if (s > bestScore || (s === bestScore && best && String(p.name).length < String(best.name).length)) {
      bestScore = s;
      best = p;
    }
  }
  return bestScore >= 150 ? best : null;
};

// ─── Placeholders dinâmicos por marca ─────────────────────────────────────────
const BRAND_PLACEHOLDERS = {
  mimaki: {
    commercial_name:       'ex: UCJV330-160',
    elevator_pitch:        'ex: A impressora UV híbrida que imprime em rolos e rígidos até 1600mm - substitui duas máquinas, duplica o portfólio de serviços',
    usps:                  'ex: UV LED de nova geração com 30% menos odor - prima Enter',
    certifications:        'ex: CE, IQ Certification Mimaki, RoHS - prima Enter',
    applications:          'ex: Sinalética exterior, Floor graphics, Janelas decorativas - prima Enter',
    geo_markets:           'ex: Portugal, Espanha, Itália - prima Enter',
    decision_maker:        'ex: Director de Produção, Proprietário de sign shop',
    end_user:              'ex: Operador de impressão, Técnico UV',
    pain_points:           'ex: Dois trabalhos em duas máquinas diferentes, baixa produtividade - prima Enter',
    motivators:            'ex: ROI em 18 meses, portfólio de aplicações mais alargado - prima Enter',
    objections:            'ex: Preço inicial elevado vs Roland - prima Enter',
    purchase_trigger:      'ex: Fim de vida da máquina actual, novo contrato de sinalética',
    competitors:           'ex: Roland DG - prima Enter',
    differentiation_args:  'ex: Única UV híbrida com IQ Certification e tinta SVHC-free - prima Enter',
    market_trends:         'ex: Crescimento floor graphics +22% pós-pandemia - prima Enter',
    positioning_narrative: 'ex: A marca japonesa que democratizou a impressão UV de grande formato em Portugal',
    sensitive_info:        'ex: Roland perdeu distribuidor Ibérico em 2024',
    tone:                  'ex: Técnico e assertivo, focado em ROI, sem jargão excessivo',
    key_message:           'ex: Uma máquina, múltiplas aplicações - o retorno que os teus clientes esperam',
    previous_campaigns:    'ex: FESPA 2025 - 47 leads, CPL €12 - prima Enter',
    prohibited_claims:     'ex: Não afirmar velocidades máximas sem especificar modo de impressão - prima Enter',
    off_brand_messages:    'ex: Não comparar directamente por preço com Roland - prima Enter',
    competitors_not_name:  'ex: Roland DG - prima Enter',
    out_of_scope_markets:  'ex: Impressão offset, serigrafia industrial - prima Enter',
    other_restrictions:    'ex: Não usar imagens de equipamentos de concorrentes - prima Enter',
  },
  biond: {
    commercial_name:       'ex: Bio-Print Film 120µm',
    elevator_pitch:        'ex: O único filme de impressão com certificação TÜV Biobased 87% - qualidade profissional com 87% de origem biológica, sem compromisso',
    usps:                  'ex: Certificação TÜV Rheinland Biobased 87% - prima Enter',
    certifications:        'ex: TÜV Rheinland OK Biobased, SGS FSC - prima Enter',
    applications:          'ex: Wrapping veicular, Protection film, Decoração interiores - prima Enter',
    geo_markets:           'ex: Portugal, Espanha, UK, Alemanha - prima Enter',
    decision_maker:        'ex: Responsável de compras, Distribuidor de materiais',
    end_user:              'ex: Aplicador de vinil, Wrapper profissional',
    pain_points:           'ex: Clientes exigem alternativas eco sem perder qualidade - prima Enter',
    motivators:            'ex: Diferenciação ecológica com certificação auditável - prima Enter',
    objections:            'ex: Preço premium vs filme convencional - prima Enter',
    purchase_trigger:      'ex: Regulação ambiental, briefing eco de cliente final, novo contrato',
    competitors:           'ex: Avery Dennison, 3M - prima Enter',
    differentiation_args:  'ex: Única certificação TÜV Biobased 87% em filmes de impressão - prima Enter',
    market_trends:         'ex: EU Plastics Directive 2025, crescimento eco-wrapping +34% - prima Enter',
    positioning_narrative: 'ex: A única alternativa sustentável que não exige compromisso na qualidade de impressão',
    sensitive_info:        'ex: Avery ainda sem certificação biobased equivalente no mercado PT/ES',
    tone:                  'ex: Ecológico e técnico, prova científica, sem greenwashing',
    key_message:           'ex: 87% biobased, 100% profissional - o futuro dos filmes de impressão já chegou',
    previous_campaigns:    'ex: FESPA Barcelona 2024 - 23 amostras, 4 distribuidores - prima Enter',
    prohibited_claims:     'ex: Não afirmar "100% biodegradável" - certificação é biobased - prima Enter',
    off_brand_messages:    'ex: Não usar claims ambientais sem fonte TÜV citada - prima Enter',
    competitors_not_name:  'ex: Avery Dennison, 3M - prima Enter',
    out_of_scope_markets:  'ex: Embalagem alimentar, dispositivos médicos - prima Enter',
    other_restrictions:    'ex: Claims ambientais sempre com fonte certificada visível - prima Enter',
  },
  decal: {
    commercial_name:       'ex: Bubble-Free Sign Graphics',
    elevator_pitch:        'ex: O vinil com canais de ar que elimina bolhas na aplicação - instalação perfeita em segundos, sem espátula, sem retrabalho',
    usps:                  'ex: Tecnologia Bubble-Free - aplicação sem bolhas - prima Enter',
    certifications:        'ex: REACH compliant, RoHS - prima Enter',
    applications:          'ex: Comunicação visual, Sinalética, Veículos, Point-of-sale - prima Enter',
    geo_markets:           'ex: Portugal, Espanha - prima Enter',
    decision_maker:        'ex: Responsável de compras em sign shop, Distribuidor de materiais',
    end_user:              'ex: Instalador de vinil, Signmaker',
    pain_points:           'ex: Bolhas e rugas que obrigam a refazer a aplicação - prima Enter',
    motivators:            'ex: Menos retrabalho, instalações mais rápidas, clientes satisfeitos - prima Enter',
    objections:            'ex: Preço por metro ligeiramente acima do standard - prima Enter',
    purchase_trigger:      'ex: Reclamação de cliente por bolhas, expansão de portfólio de serviços',
    competitors:           'ex: Orafol, 3M, Avery Dennison - prima Enter',
    differentiation_args:  'ex: Tecnologia Bubble-Free exclusiva - única sem necessidade de espátula - prima Enter',
    market_trends:         'ex: Crescimento aplicações removíveis +15% para sinalética temporária - prima Enter',
    positioning_narrative: 'ex: O material que torna qualquer instalador num profissional de excelência',
    sensitive_info:        'ex: Orafol com atrasos de entrega recorrentes em Q1 2025',
    tone:                  'ex: Prático e demonstrativo, foco na experiência de aplicação',
    key_message:           'ex: Sem bolhas, sem ferramentas, sem erros - Bubble-Free muda tudo',
    previous_campaigns:    'ex: Open Day Decal 2024 - 12 sign shops, 8 amostras pedidas - prima Enter',
    prohibited_claims:     'ex: Não afirmar "invisível" - vinil tem sempre espessura - prima Enter',
    off_brand_messages:    'ex: Não entrar em guerra de preços com 3M - prima Enter',
    competitors_not_name:  'ex: 3M, Orafol - prima Enter',
    out_of_scope_markets:  'ex: Embalagem industrial, uso exterior prolongado sem especificação - prima Enter',
    other_restrictions:    'ex: Durabilidade exterior sempre com especificação de superfície - prima Enter',
  },
  sensek: {
    commercial_name:       'ex: TS500-1800 AMF',
    elevator_pitch:        'ex: A impressora de sublimação de alta velocidade para produção têxtil - 105m²/h com qualidade fotográfica e custo por metro imbatível',
    usps:                  'ex: 105m²/h em modo produção, menor TCO do segmento - prima Enter',
    certifications:        'ex: Oeko-Tex Standard 100, CE - prima Enter',
    applications:          'ex: Moda, Sportswear, Home textile, Flags - prima Enter',
    geo_markets:           'ex: Portugal, Espanha, Turquia - prima Enter',
    decision_maker:        'ex: Director de operações, Responsável de compras industrial',
    end_user:              'ex: Operador de máquina têxtil, Técnico de sublimação',
    pain_points:           'ex: Custo por metro elevado, gargalos nos picos de produção - prima Enter',
    motivators:            'ex: Redução de custo por metro, internalização de produção têxtil - prima Enter',
    objections:            'ex: Investimento inicial elevado vs outsourcing - prima Enter',
    purchase_trigger:      'ex: Expansão de capacidade, internalização de produção têxtil',
    competitors:           'ex: Epson, MS Printing, Durst - prima Enter',
    differentiation_args:  'ex: Melhor ratio custo/velocidade no segmento mid-market têxtil - prima Enter',
    market_trends:         'ex: Nearshoring têxtil Europa +28%, crescimento sportswear personalizado - prima Enter',
    positioning_narrative: 'ex: A máquina que devolve a produção têxtil à Península Ibérica',
    sensitive_info:        'ex: Epson com problemas de cabeças em climas húmidos reportados em PT',
    tone:                  'ex: Industrial e preciso, foco em produtividade e TCO',
    key_message:           'ex: Mais metros por hora, menos custo por peça - a equação que muda a tua produção',
    previous_campaigns:    'ex: Texprocess 2024 - 8 leads qualificados, 2 demos agendadas - prima Enter',
    prohibited_claims:     'ex: Não garantir velocidade máxima sem especificar modo e substrato - prima Enter',
    off_brand_messages:    'ex: Não posicionar como substituto de impressão offset têxtil - prima Enter',
    competitors_not_name:  'ex: Epson, MS Printing - prima Enter',
    out_of_scope_markets:  'ex: Impressão de embalagem, papel industrial - prima Enter',
    other_restrictions:    'ex: Velocidade sempre com especificação de modo de impressão e qualidade - prima Enter',
  },
  alldecor: {
    commercial_name:       'ex: AllDecor Premium Walls',
    elevator_pitch:        'ex: A solução completa de decoração de interiores em impressão digital - do projecto à instalação, com materiais BIOND certificados e impressão Mimaki',
    usps:                  'ex: Serviço turnkey - projecto, impressão e instalação incluídos - prima Enter',
    certifications:        'ex: Materiais BIOND Biobased, Mimaki UV certified - prima Enter',
    applications:          'ex: Revestimentos de parede, Pavimentos decorativos, Tectos, Mobiliário - prima Enter',
    geo_markets:           'ex: Portugal, Espanha - prima Enter',
    decision_maker:        'ex: Arquitecto, Designer de interiores, Promotor imobiliário',
    end_user:              'ex: Cliente final residencial ou corporativo',
    pain_points:           'ex: Soluções de decoração personalizada demoram semanas e são inflexíveis - prima Enter',
    motivators:            'ex: Diferenciação do espaço, projecto único personalizado - prima Enter',
    objections:            'ex: Custo vs papel de parede standard ou vinílico - prima Enter',
    purchase_trigger:      'ex: Obra nova, renovação de escritório, projecto de arquitectura com cliente exigente',
    competitors:           'ex: HP Latex (decoração), Roland (impressão), fornecedores tradicionais - prima Enter',
    differentiation_args:  'ex: Único serviço end-to-end com materiais biobased em decoração digital - prima Enter',
    market_trends:         'ex: Crescimento personalização de espaços +40%, sustentabilidade em interiores - prima Enter',
    positioning_narrative: 'ex: Decoração digital personalizada que transforma qualquer espaço em experiência única',
    sensitive_info:        'ex: HP Latex sem parceiro de instalação com cobertura nacional em PT',
    tone:                  'ex: Premium e inspiracional, foco na experiência e personalização',
    key_message:           'ex: O teu espaço, o teu design - produzido em Portugal, entregue em 5 dias',
    previous_campaigns:    'ex: Architect@Work 2024 - 15 projectos iniciados - prima Enter',
    prohibited_claims:     'ex: Não garantir durabilidade sem especificar tipo de substrato e uso - prima Enter',
    off_brand_messages:    'ex: Não comparar com papéis de parede standard - produtos completamente diferentes - prima Enter',
    competitors_not_name:  'ex: HP Latex instaladores - prima Enter',
    out_of_scope_markets:  'ex: Impressão exterior UV directa sem laminação protectora - prima Enter',
    other_restrictions:    'ex: Materiais sempre apresentados com ficha técnica disponível - prima Enter',
  },
  netscreen: {
    commercial_name:       'ex: NetScreen P4 Indoor',
    elevator_pitch:        'ex: O display LED de alta densidade de pixels para ambientes interiores - 800cd/m², gestão cloud remota, instalação em 4 horas',
    usps:                  'ex: Gestão de conteúdo cloud sem software adicional - prima Enter',
    certifications:        'ex: CE, RoHS, UL - prima Enter',
    applications:          'ex: Publicidade retail, Eventos, Showrooms, Centros comerciais - prima Enter',
    geo_markets:           'ex: Portugal, Espanha - prima Enter',
    decision_maker:        'ex: Director de Marketing, Gestor de ponto de venda, Empresa de publicidade exterior',
    end_user:              'ex: Responsável de comunicação visual, Marketing manager',
    pain_points:           'ex: Conteúdos impressos desactualizados, custo de troca de materiais - prima Enter',
    motivators:            'ex: Actualização de conteúdo em tempo real, redução de custos de impressão - prima Enter',
    objections:            'ex: Investimento inicial elevado vs vinil/lona - prima Enter',
    purchase_trigger:      'ex: Renovação de ponto de venda, novo espaço comercial, substituição de totem',
    competitors:           'ex: Unilumin, Absen, Samsung Commercial - prima Enter',
    differentiation_args:  'ex: Suporte técnico local em Portugal com SLA 24h garantido - prima Enter',
    market_trends:         'ex: DOOH crescimento +18% anual, digitalização de retail acelerada - prima Enter',
    positioning_narrative: 'ex: A tecnologia LED que transforma pontos de venda em experiências digitais',
    sensitive_info:        'ex: Unilumin com atrasos logísticos recorrentes desde 2024',
    tone:                  'ex: Moderno e pragmático, foco em ROI e facilidade de gestão',
    key_message:           'ex: Actualiza a tua mensagem em segundos, não em semanas',
    previous_campaigns:    'ex: EuroShop 2024 - 6 projectos piloto, 3 instalações concluídas - prima Enter',
    prohibited_claims:     'ex: Não garantir brilho exterior sem especificar IP rating - prima Enter',
    off_brand_messages:    'ex: Não posicionar como substituto de toda a comunicação impressa - prima Enter',
    competitors_not_name:  'ex: Unilumin, Samsung Commercial - prima Enter',
    out_of_scope_markets:  'ex: Televisão consumer, sinalização de trânsito regulamentada - prima Enter',
    other_restrictions:    'ex: Sempre distinguir indoor vs outdoor nas especificações técnicas - prima Enter',
  },
  digidelta: {
    commercial_name:       'ex: DigiRent - Solução de Financiamento',
    elevator_pitch:        'ex: A solução financeira da Digidelta que permite aceder a equipamento profissional sem imobilizar capital - renda fixa, upgrades facilitados',
    usps:                  'ex: Aprovação em 48h, sem entrada inicial - prima Enter',
    certifications:        'ex: Parceria bancária certificada - prima Enter',
    applications:          'ex: Financiamento de equipamento, Locação operacional, Upgrade de máquina - prima Enter',
    geo_markets:           'ex: Portugal - prima Enter',
    decision_maker:        'ex: Director Financeiro, CEO, Sócio-gerente PME',
    end_user:              'ex: Director de produção, utilizador final do equipamento',
    pain_points:           'ex: Capital imobilizado em equipamento, aprovação bancária demorada - prima Enter',
    motivators:            'ex: Preservar liquidez, aceder a equipamento de topo imediatamente - prima Enter',
    objections:            'ex: Custo total de locação vs compra directa - prima Enter',
    purchase_trigger:      'ex: Necessidade urgente de equipamento, fim de contrato anterior, crescimento',
    competitors:           'ex: Financiamento bancário directo, leasing de terceiros - prima Enter',
    differentiation_args:  'ex: Conhecimento técnico do equipamento que nenhum banco tem - assessoria incluída - prima Enter',
    market_trends:         'ex: PMEs preferem OPEX a CAPEX, crescimento leasing operacional +12% - prima Enter',
    positioning_narrative: 'ex: A Digidelta não vende apenas equipamento - dá acesso ao equipamento certo, no momento certo',
    sensitive_info:        'ex: Banco X reduziu linha de crédito para PMEs de impressão em Q4 2024',
    tone:                  'ex: Confiante e consultivo, focado em benefício financeiro concreto',
    key_message:           'ex: O equipamento que precisas, a renda que consegues pagar - começa hoje',
    previous_campaigns:    'ex: DigiRent Launch 2024 - 18 contratos, €2.1M financiado - prima Enter',
    prohibited_claims:     'ex: Não garantir aprovação a todos os clientes - sujeito a análise - prima Enter',
    off_brand_messages:    'ex: Não posicionar DigiRent como crédito fácil - é locação operacional - prima Enter',
    competitors_not_name:  'ex: Leasing Millennium, BPI Empresas - prima Enter',
    out_of_scope_markets:  'ex: Particulares B2C, equipamento de terceiros - prima Enter',
    other_restrictions:    'ex: Sempre mencionar "sujeito a aprovação financeira" - prima Enter',
  },
};

const _PH_DEFAULT = {
  commercial_name:       'ex: Nome comercial do produto',
  elevator_pitch:        'ex: A única solução que combina X e Y numa passagem, reduzindo o tempo de produção em 40%',
  usps:                  'ex: Característica única que nenhum concorrente tem - prima Enter',
  certifications:        'ex: Certificação técnica relevante para o mercado - prima Enter',
  applications:          'ex: Aplicação principal, Aplicação secundária - prima Enter',
  geo_markets:           'ex: Portugal, Espanha, França - prima Enter',
  decision_maker:        'ex: Director de operações, Responsável de compras',
  end_user:              'ex: Operador técnico, utilizador final do produto',
  pain_points:           'ex: Problema que o produto resolve directamente - prima Enter',
  motivators:            'ex: Benefício concreto que motiva a decisão de compra - prima Enter',
  objections:            'ex: Objecção típica vs alternativa do mercado - prima Enter',
  purchase_trigger:      'ex: Evento que precipita a decisão de compra',
  competitors:           'ex: Concorrente principal do segmento - prima Enter',
  differentiation_args:  'ex: O que torna este produto único vs concorrência - prima Enter',
  market_trends:         'ex: Tendência de crescimento relevante para o produto - prima Enter',
  positioning_narrative: 'ex: A marca que define o padrão no seu segmento na Península Ibérica',
  sensitive_info:        'ex: Informação de concorrência para uso interno',
  tone:                  'ex: Tom de comunicação desejado - técnico, inspiracional, assertivo',
  key_message:           'ex: A mensagem central que todos os conteúdos devem transmitir',
  previous_campaigns:    'ex: Evento ou campanha anterior - N leads, CPL €X - prima Enter',
  prohibited_claims:     'ex: Afirmação que não pode ser usada por razões legais ou técnicas - prima Enter',
  off_brand_messages:    'ex: Mensagem que contradiz o posicionamento da marca - prima Enter',
  competitors_not_name:  'ex: Concorrente que não deve ser nomeado directamente - prima Enter',
  out_of_scope_markets:  'ex: Segmento ou mercado fora do âmbito desta campanha - prima Enter',
  other_restrictions:    'ex: Restrição adicional de conteúdo ou imagem - prima Enter',
};

function ph(field, brand) {
  return (BRAND_PLACEHOLDERS[brand] || {})[field] || _PH_DEFAULT[field] || '';
}

function calcProgress(form) {
  const hasText  = (v) => typeof v === 'string' && v.trim().length > 0;
  const hasItems = (v) => Array.isArray(v) && v.some(i => (typeof i === 'string' ? i.trim() : i));
  const score    = (arr) => arr.filter(Boolean).length / arr.length;

  const b1 = form.block1 || {};
  const b2 = form.block2 || {};
  const b3 = form.block3 || {};
  const b5 = form.block5 || {};

  const s1 = score([!!form.product_id, hasText(b1.commercial_name), hasText(b1.elevator_pitch), hasItems(b1.usps)]);
  const s2 = score([hasText(b2.decision_maker), hasText(b2.end_user), hasItems(b2.pain_points), hasText(b2.purchase_trigger)]);
  const s3 = score([hasItems(b3.competitors), hasText(b3.positioning_narrative), hasItems(b3.differentiation_args)]);
  // B5 pode legitimamente estar vazio - damos 0.5 base se tudo vazio
  const anyB5 = hasItems(b5.prohibited_claims) || hasItems(b5.off_brand_messages) || hasItems(b5.competitors_not_name) || hasItems(b5.out_of_scope_markets) || hasItems(b5.other_restrictions);
  const s5 = anyB5 ? 1 : 0.5;

  return Math.round(((s1 + s2 + s3 + s5) / 4) * 100);
}

const ProgressPill = ({ pct }) => {
  const color = pct >= 80 ? 'var(--ai-500, #3859D0)' : pct >= 40 ? 'var(--text-muted, #4a5e7a)' : 'var(--text-dim, #6b7fa3)';
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
      <div style={{ width: 80, height: 4, borderRadius: 2, background: 'var(--border)', overflow: 'hidden' }}>
        <div style={{ width: `${pct}%`, height: '100%', background: color, borderRadius: 2, transition: 'width 0.3s' }} />
      </div>
      <span style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color, fontWeight: 600 }}>{pct}%</span>
    </div>
  );
};

const CHANNELS_OPTS = [
  { id: 'meta_ads',           label: 'Meta Ads',           hint: 'Facebook + Instagram' },
  { id: 'linkedin_ads',       label: 'LinkedIn Ads',       hint: 'Pago' },
  { id: 'linkedin_organic',   label: 'LinkedIn',           hint: 'Orgânico' },
  { id: 'google_ads_search',  label: 'Google Search Ads',  hint: 'RSA' },
  { id: 'google_ads_display', label: 'Google Display',     hint: 'Banners' },
  { id: 'email',              label: 'Email Marketing',    hint: 'Brevo' },
  { id: 'whatsapp',           label: 'WhatsApp',           hint: '' },
  { id: 'website',            label: 'Website / Loja',     hint: 'Página da campanha' },
  { id: 'muppi_led',          label: 'Painéis LED',        hint: '' },
  { id: 'equipa_consumiveis',  label: 'Equipa Consumíveis',  hint: '' },
  { id: 'equipa_equipamentos', label: 'Equipa Equipamentos', hint: '' },
  { id: 'equipa_sdr',          label: 'Equipa SDR',          hint: '' },
  { id: 'distribuidores',      label: 'Distribuidores',      hint: '' },
];
const META_CONVERSAO_OPTS = [
  { value: 'formulario_nativo', label: 'Formulário nativo Meta (Lead Ads)' },
  { value: 'pagina_campanha', label: 'Página da campanha' },
  { value: 'click_whatsapp', label: 'Click to WhatsApp' },
  { value: 'pagina_ou_whatsapp', label: 'Página da campanha ou WhatsApp' },
  { value: 'landing_page', label: 'Landing page de pedido de proposta' },
];

// ─── Helpers de UI ─────────────────────────────────────────────────────────────

const StatusBadge = ({ status }) => {
  const cfg = STATUS_CONFIG[status] || STATUS_CONFIG.rascunho;
  return (
    <span style={{
      fontSize: 11, fontFamily: 'monospace', letterSpacing: '0.06em',
      padding: '3px 10px', borderRadius: 4, display: 'inline-block',
      fontWeight: 700,
      color: cfg.color, background: cfg.bg,
      border: '1px solid currentColor',
    }}>
      {cfg.label.toUpperCase()}
    </span>
  );
};

const Field = ({ label, hint, children, required, action, campo }) => (
  <div data-briefing-campo={campo || undefined} style={{ minWidth: 0 }}>
    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 6 }}>
      <label style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-dim)', letterSpacing: '0.04em' }}>
        {label}
        {required && <span style={{ color: 'var(--danger, #CF2E2E)', marginLeft: 3 }}>*</span>}
      </label>
      {action}
    </div>
    {children}
    {hint && <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 4, lineHeight: 1.4 }}>{hint}</div>}
  </div>
);

const FormCard = ({ title, children, campo }) => (
  <div data-briefing-campo={campo || undefined} style={{ background: 'var(--bg-elev, #ffffff)', border: '1px solid var(--border)', borderRadius: 10, padding: '20px 24px', marginBottom: 16, display: 'flex', flexDirection: 'column', gap: 20 }}>
    {title && (
      <div style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', color: 'var(--text-muted)', fontFamily: 'var(--font-mono)', paddingBottom: 8, borderBottom: '1px solid var(--border)' }}>
        {title}
      </div>
    )}
    {children}
  </div>
);

const TInput = ({ value, onChange, placeholder, multiline, rows = 3 }) => {
  const [focused, setFocused] = React.useState(false);
  const areaRef = React.useRef(null);
  React.useLayoutEffect(() => {
    const el = areaRef.current;
    if (!el) return;
    el.style.height = 'auto';
    el.style.height = `${el.scrollHeight}px`;
  }, [value]);
  const base = {
    width: '100%', minWidth: 0, padding: '9px 14px', boxSizing: 'border-box',
    background: 'var(--bg-elev, #ffffff)',
    border: `1px solid ${focused ? 'var(--ai-500)' : 'var(--border-strong, var(--border))'}`,
    borderRadius: 7, color: 'var(--text)', fontSize: 13,
    fontFamily: 'var(--font-body)', outline: 'none',
    boxShadow: focused ? '0 0 0 3px color-mix(in oklch, var(--ai-500) 12%, transparent)' : 'none',
    transition: 'border-color 0.15s, box-shadow 0.15s',
  };
  const events = { onFocus: () => setFocused(true), onBlur: () => setFocused(false) };
  return (
    <textarea ref={areaRef} value={value || ''} onChange={e => onChange(e.target.value)}
      placeholder={placeholder} rows={multiline ? rows : 1} {...events}
      style={{ ...base, resize: 'none', overflow: 'hidden', lineHeight: 1.5, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }} />
  );
};

const TDateInput = ({ value, onChange }) => {
  const [focused, setFocused] = React.useState(false);
  return (
    <input type="date" value={value || ''} onChange={e => onChange(e.target.value)}
      onFocus={() => setFocused(true)} onBlur={() => setFocused(false)}
      style={{
        width: '100%', padding: '9px 14px', boxSizing: 'border-box',
        background: 'var(--bg-elev, #ffffff)',
        border: `1px solid ${focused ? 'var(--ai-500)' : 'var(--border-strong, var(--border))'}`,
        borderRadius: 7, color: 'var(--text)', fontSize: 13,
        fontFamily: 'var(--font-body)', outline: 'none',
        boxShadow: focused ? '0 0 0 3px color-mix(in oklch, var(--ai-500) 12%, transparent)' : 'none',
        transition: 'border-color 0.15s, box-shadow 0.15s',
      }} />
  );
};

const TSelect = ({ value, onChange, options, placeholder }) => {
  const [open,    setOpen]    = React.useState(false);
  const [focused, setFocused] = React.useState(false);
  const [panelPos, setPanelPos] = React.useState({ top: 0, left: 0, width: 0 });
  const ref      = React.useRef(null);
  const triggerRef = React.useRef(null);
  const selected = options.find(o => o.value === value);

  React.useEffect(() => {
    if (!open) return;
    // Calcular posição via fixed - dropdown sempre abaixo, nunca sobrepõe o layout
    if (triggerRef.current) {
      const rect = triggerRef.current.getBoundingClientRect();
      setPanelPos({ top: rect.bottom, left: rect.left, width: rect.width });
    }
    const handler = (e) => { if (ref.current && !ref.current.contains(e.target)) { setOpen(false); setFocused(false); } };
    document.addEventListener('mousedown', handler);
    return () => document.removeEventListener('mousedown', handler);
  }, [open]);

  const handleKeyDown = (e) => {
    if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setOpen(o => !o); }
    if (e.key === 'Escape') { setOpen(false); setFocused(false); }
  };

  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <button type="button"
        onClick={() => { setOpen(o => !o); setFocused(true); }}
        onFocus={() => setFocused(true)}
        onBlur={(e) => { if (!ref.current?.contains(e.relatedTarget)) { setFocused(false); if (!open) {} } }}
        ref={triggerRef}
        onKeyDown={handleKeyDown}
        style={{
          width: '100%', minWidth: 0, padding: '9px 14px', boxSizing: 'border-box', textAlign: 'left',
          background: 'var(--bg-elev, #ffffff)',
          border: `1px solid ${(open || focused) ? 'var(--ai-500)' : 'var(--border-strong, var(--border))'}`,
          borderRadius: 7,
          color: selected ? 'var(--text)' : 'var(--text-muted)', fontSize: 13,
          fontFamily: 'var(--font-body)', cursor: 'pointer', outline: 'none',
          boxShadow: (open || focused) ? '0 0 0 3px color-mix(in oklch, var(--ai-500) 12%, transparent)' : 'none',
          transition: 'border-color 0.15s, box-shadow 0.15s',
          display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 8,
        }}>
        <span style={{ flex: 1, minWidth: 0, whiteSpace: 'normal', overflowWrap: 'anywhere', lineHeight: 1.45, textAlign: 'left' }}>
          {selected ? selected.label : (placeholder || 'Seleccionar...')}
        </span>
        <svg width="12" height="12" viewBox="0 0 12 12" fill="none" style={{ flexShrink: 0, transition: 'transform 0.2s', transform: open ? 'rotate(180deg)' : 'none', color: 'var(--text-muted)' }}>
          <path d="M2 4l4 4 4-4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
        </svg>
      </button>
      {open && (
        <div style={{
          position: 'fixed',
          top: panelPos.top, left: panelPos.left, width: panelPos.width,
          zIndex: 9999,
          background: 'var(--bg-elev, #ffffff)',
          border: '1px solid var(--ai-500)',
          borderRadius: 7,
          boxShadow: '0 12px 32px rgba(17,41,84,0.16), 0 2px 8px rgba(17,41,84,0.08)',
          maxHeight: 220, overflowY: 'auto',
          animation: 'tselect-in 0.12s ease-out',
        }}>
        <style>{`@keyframes tselect-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }`}</style>
          {(value !== '' && value !== null && value !== undefined) && (
            <button type="button" onClick={() => { onChange(''); setOpen(false); setFocused(false); }}
              style={{ display: 'block', width: '100%', textAlign: 'left', padding: '8px 14px', fontSize: 13, color: 'var(--text-muted)', background: 'transparent', border: 'none', cursor: 'pointer', fontFamily: 'var(--font-body)', fontStyle: 'italic' }}>
              {placeholder || 'Seleccionar...'}
            </button>
          )}
          {options.map(o => (
            <button type="button" key={o.value} onClick={() => { onChange(o.value); setOpen(false); setFocused(false); }}
              style={{
                display: 'block', width: '100%', textAlign: 'left', padding: '8px 14px', fontSize: 13,
                whiteSpace: 'normal', overflowWrap: 'anywhere', lineHeight: 1.45,
                color: o.value === value ? 'var(--ai-500)' : 'var(--text)',
                background: o.value === value ? 'color-mix(in oklch, var(--ai-500) 8%, transparent)' : 'transparent',
                border: 'none', cursor: 'pointer', fontFamily: 'var(--font-body)',
                fontWeight: o.value === value ? 600 : 400,
                transition: 'background 0.1s',
              }}
              onMouseEnter={e => { if (o.value !== value) e.currentTarget.style.background = 'var(--bg-sunken)'; }}
              onMouseLeave={e => { e.currentTarget.style.background = o.value === value ? 'color-mix(in oklch, var(--ai-500) 8%, transparent)' : 'transparent'; }}>
              {o.label}
            </button>
          ))}
        </div>
      )}
    </div>
  );
};

const ChipInput = ({ items, onChange, placeholder, maxItems, sortable }) => {
  const [inputVal, setInputVal]   = React.useState('');
  const [focused, setFocused]     = React.useState(false);
  const [dragIdx, setDragIdx]     = React.useState(null);
  const [dragOver, setDragOver]   = React.useState(null);
  const [editIdx, setEditIdx]     = React.useState(null);
  const [editVal, setEditVal]     = React.useState('');
  const filled = (items || []).filter(i => typeof i === 'string' ? i.trim() : i);
  const canAdd = !maxItems || filled.length < maxItems;

  const addChip = () => {
    const v = inputVal.trim();
    if (!v || !canAdd) return;
    onChange([...filled, v]);
    setInputVal('');
  };
  const del = (i) => { if (editIdx === i) setEditIdx(null); onChange(filled.filter((_, idx) => idx !== i)); };

  const startEdit = (i) => { setEditIdx(i); setEditVal(filled[i]); };
  const saveEdit  = () => {
    if (editIdx === null) return;
    const v = editVal.trim();
    if (v) { const next = [...filled]; next[editIdx] = v; onChange(next); }
    setEditIdx(null);
  };
  const cancelEdit = () => setEditIdx(null);

  const handleDrop = (targetIdx) => {
    if (dragIdx === null || dragIdx === targetIdx) return;
    const next = [...filled];
    const [moved] = next.splice(dragIdx, 1);
    next.splice(targetIdx, 0, moved);
    onChange(next);
    setDragIdx(null);
    setDragOver(null);
  };

  return (
    <div>
      {canAdd && (
        <div style={{ display: 'flex', gap: 6 }}>
          <input type="text" value={inputVal}
            onChange={e => setInputVal(e.target.value)}
            onFocus={() => setFocused(true)} onBlur={() => setFocused(false)}
            onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); addChip(); } }}
            placeholder={placeholder}
            style={{
              flex: 1, padding: '8px 12px',
              background: 'var(--bg-elev, #ffffff)',
              border: `1px solid ${focused ? 'var(--ai-500)' : 'var(--border-strong, var(--border))'}`,
              borderRadius: 7, color: 'var(--text)', fontSize: 13,
              fontFamily: 'var(--font-body)', outline: 'none',
              boxShadow: focused ? '0 0 0 3px color-mix(in oklch, var(--ai-500) 12%, transparent)' : 'none',
              transition: 'border-color 0.15s, box-shadow 0.15s',
            }} />
          <button onClick={addChip} disabled={!inputVal.trim()}
            style={{
              padding: '0 14px', height: 38, borderRadius: 7, border: 'none',
              background: inputVal.trim() ? 'var(--ai-500)' : 'var(--bg-sunken)',
              color: inputVal.trim() ? '#fff' : 'var(--text-dim)',
              cursor: inputVal.trim() ? 'pointer' : 'not-allowed', fontSize: 12,
              transition: 'background 0.15s, color 0.15s', whiteSpace: 'nowrap', flexShrink: 0,
            }}>+</button>
        </div>
      )}

      {sortable && filled.length > 0 && (
        <>
          <div style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', marginTop: 8, marginBottom: 6, letterSpacing: '0.05em' }}>
            Arrasta para ordenar - o 1.° tem maior peso na geração
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
            {filled.map((item, i) => {
              const isDragging = dragIdx === i;
              const isOver     = dragOver === i;
              const isEditing  = editIdx === i;
              return (
                <div key={i}
                  draggable={!isEditing}
                  onDragStart={() => !isEditing && setDragIdx(i)}
                  onDragOver={e => { e.preventDefault(); if (!isEditing) setDragOver(i); }}
                  onDrop={() => !isEditing && handleDrop(i)}
                  onDragEnd={() => { setDragIdx(null); setDragOver(null); }}
                  style={{
                    display: 'flex', alignItems: 'flex-start', gap: 8,
                    padding: isEditing ? '6px 10px' : '8px 10px', borderRadius: 7,
                    background: isEditing ? 'var(--bg-elev, #fff)' : isDragging ? 'color-mix(in oklch, var(--ai-500) 5%, transparent)' : isOver ? 'color-mix(in oklch, var(--ai-500) 12%, transparent)' : 'var(--bg-elev, #fff)',
                    border: `1px solid ${isEditing ? 'var(--ai-500)' : isOver ? 'var(--ai-500)' : isDragging ? 'color-mix(in oklch, var(--ai-500) 30%, transparent)' : 'var(--border)'}`,
                    boxShadow: isEditing ? '0 0 0 3px color-mix(in oklch, var(--ai-500) 12%, transparent)' : 'none',
                    opacity: isDragging ? 0.5 : 1,
                    cursor: isEditing ? 'default' : 'grab',
                    transition: 'border-color 0.1s, background 0.1s',
                    userSelect: isEditing ? 'auto' : 'none',
                  }}>
                  {/* Drag handle */}
                  <span style={{ color: isEditing ? 'var(--border)' : 'var(--text-dim)', fontSize: 14, lineHeight: '20px', flexShrink: 0, cursor: isEditing ? 'default' : 'grab' }}>⠿</span>
                  {/* Priority badge */}
                  <span style={{
                    fontSize: 9, fontWeight: 700, fontFamily: 'var(--font-mono)',
                    padding: '2px 5px', borderRadius: 4, flexShrink: 0,
                    background: i === 0 ? 'color-mix(in oklch, var(--ai-500) 15%, transparent)' : 'var(--bg-sunken)',
                    color: i === 0 ? 'var(--ai-500)' : 'var(--text-dim)',
                    alignSelf: 'center',
                  }}>{i + 1}.°</span>
                  {/* Text or edit input */}
                  {isEditing ? (
                    <textarea
                      autoFocus
                      rows={1}
                      value={editVal}
                      ref={el => { if (el) { el.style.height = 'auto'; el.style.height = `${el.scrollHeight}px`; } }}
                      onChange={e => setEditVal(e.target.value)}
                      onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); saveEdit(); } if (e.key === 'Escape') cancelEdit(); }}
                      onBlur={saveEdit}
                      style={{ flex: 1, fontSize: 13, color: 'var(--text)', lineHeight: 1.5, background: 'transparent', border: 'none', outline: 'none', fontFamily: 'var(--font-body)', padding: 0, resize: 'none', overflow: 'hidden', whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }}
                    />
                  ) : (
                    <span
                      onClick={() => startEdit(i)}
                      title="Clica para editar"
                      style={{ flex: 1, fontSize: 13, color: 'var(--text)', lineHeight: 1.5, wordBreak: 'break-word', cursor: 'text' }}
                    >{item}</span>
                  )}
                  {/* Delete */}
                  {!isEditing && (
                    <button onClick={() => del(i)} style={{
                      background: 'none', border: 'none', padding: '0 2px',
                      color: 'var(--text-dim)', cursor: 'pointer', fontSize: 15, lineHeight: 1,
                      flexShrink: 0, alignSelf: 'center',
                    }}>×</button>
                  )}
                  {/* Save/cancel when editing */}
                  {isEditing && (
                    <div style={{ display: 'flex', gap: 4, flexShrink: 0, alignSelf: 'center' }}>
                      <button onMouseDown={e => { e.preventDefault(); saveEdit(); }} style={{ background: 'none', border: 'none', padding: '0 2px', color: 'var(--success, #16a34a)', cursor: 'pointer', fontSize: 13, lineHeight: 1 }}>✓</button>
                      <button onMouseDown={e => { e.preventDefault(); cancelEdit(); }} style={{ background: 'none', border: 'none', padding: '0 2px', color: 'var(--text-dim)', cursor: 'pointer', fontSize: 15, lineHeight: 1 }}>×</button>
                    </div>
                  )}
                </div>
              );
            })}
          </div>
        </>
      )}

      {!sortable && filled.length > 0 && (
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 8, alignItems: 'flex-start' }}>
          {filled.map((item, i) => {
            const isEditing = editIdx === i;
            return isEditing ? (
              <div key={i} style={{
                display: 'flex', alignItems: 'flex-start', gap: 4, width: '100%', boxSizing: 'border-box',
                padding: '6px 8px', borderRadius: 6,
                background: 'var(--bg-elev, #fff)',
                border: '1px solid var(--ai-500)',
                boxShadow: '0 0 0 3px color-mix(in oklch, var(--ai-500) 12%, transparent)',
                fontSize: 12,
              }}>
                <textarea
                  autoFocus
                  rows={1}
                  value={editVal}
                  ref={el => { if (el) { el.style.height = 'auto'; el.style.height = `${el.scrollHeight}px`; } }}
                  onChange={e => setEditVal(e.target.value)}
                  onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); saveEdit(); } if (e.key === 'Escape') cancelEdit(); }}
                  onBlur={saveEdit}
                  style={{ flex: 1, minWidth: 160, width: '100%', fontSize: 12, color: 'var(--text)', lineHeight: 1.45, background: 'transparent', border: 'none', outline: 'none', fontFamily: 'var(--font-body)', resize: 'none', overflow: 'hidden', whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }}
                />
                <button onMouseDown={e => { e.preventDefault(); saveEdit(); }} style={{ background: 'none', border: 'none', padding: '0 1px', color: 'var(--success, #16a34a)', cursor: 'pointer', fontSize: 12, lineHeight: 1 }}>✓</button>
                <button onMouseDown={e => { e.preventDefault(); cancelEdit(); }} style={{ background: 'none', border: 'none', padding: '0 1px', color: 'var(--text-dim)', cursor: 'pointer', fontSize: 13, lineHeight: 1 }}>×</button>
              </div>
            ) : (
              <div key={i} title="Clica para editar" style={{
                display: 'inline-flex', alignItems: 'flex-start', gap: 5,
                padding: '4px 10px', borderRadius: 6,
                background: 'color-mix(in oklch, var(--ai-500) 8%, transparent)',
                border: '1px solid color-mix(in oklch, var(--ai-500) 20%, transparent)',
                fontSize: 12, color: 'var(--text)', lineHeight: 1.45,
                maxWidth: '100%', cursor: 'pointer',
              }}
                onClick={() => startEdit(i)}
              >
                <span style={{ whiteSpace: 'normal', overflowWrap: 'anywhere', minWidth: 0 }}>{item}</span>
                <button onClick={e => { e.stopPropagation(); del(i); }} style={{
                  background: 'none', border: 'none', padding: '0 0 0 2px',
                  color: 'var(--text-dim)', cursor: 'pointer', fontSize: 14, lineHeight: 1,
                  display: 'flex', alignItems: 'center', flexShrink: 0,
                }}>×</button>
              </div>
            );
          })}
          {maxItems && (
            <span style={{ marginLeft: 'auto', fontSize: 10, padding: '3px 7px', borderRadius: 99, background: 'var(--bg-sunken)', color: filled.length >= maxItems ? 'var(--danger, #CF2E2E)' : 'var(--text-dim)', fontFamily: 'var(--font-mono)', alignSelf: 'center', flexShrink: 0 }}>
              {filled.length}/{maxItems}
            </span>
          )}
        </div>
      )}
    </div>
  );
};

const ChannelToggle = ({ selected, onChange }) => (
  <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
    {CHANNELS_OPTS.map(ch => {
      const active = (selected || []).includes(ch.id);
      return (
        <button key={ch.id} onClick={() => onChange(
          active ? (selected || []).filter(c => c !== ch.id) : [...(selected || []), ch.id]
        )} style={{
          display: 'inline-flex', alignItems: 'center', gap: 5,
          padding: '6px 12px', borderRadius: 7, fontSize: 12, cursor: 'pointer',
          border: `1.5px solid ${active ? 'var(--ai-500)' : 'var(--border-strong, var(--border))'}`,
          background: active ? 'color-mix(in oklch, var(--ai-500) 10%, transparent)' : 'transparent',
          color: active ? 'var(--ai-500)' : 'var(--text-muted)',
          fontWeight: active ? 600 : 400,
          fontFamily: 'var(--font-body)',
          transition: 'border-color .15s, background .15s, color .15s',
        }}>
          {ch.label}
          {ch.hint && <span style={{ fontSize: 10, opacity: 0.65, fontWeight: 400 }}>{ch.hint}</span>}
        </button>
      );
    })}
  </div>
);

// ─── Mercados geográficos ──────────────────────────────────────────────────────

const BRAND_MARKETS_CONFIG = {
  digidelta: { tipo: 'fixo', opcoes: ['Portugal', 'Espanha'] },
  mimaki:    { tipo: 'fixo', opcoes: ['Portugal', 'Espanha'] },
  netscreen: { tipo: 'fixo', opcoes: ['Portugal', 'Espanha'] },
  sensek:    { tipo: 'fixo', opcoes: ['Portugal', 'Espanha'] },
  biond:     { tipo: 'geo' },
  decal:     { tipo: 'geo' },
  alldecor:  { tipo: 'geo' },
};

const GEO_HIERARCHY = {
  'Europa': [
    'Portugal','Espanha','França','Alemanha','Reino Unido','Itália','Países Baixos','Bélgica',
    'Luxemburgo','Suíça','Áustria','Suécia','Noruega','Dinamarca','Finlândia','Irlanda',
    'Polónia','República Checa','Eslováquia','Hungria','Roménia','Bulgária','Grécia',
    'Croácia','Sérvia','Eslovénia','Lituânia','Letónia','Estónia','Ucrânia','Rússia',
    'Bielorrússia','Moldávia','Albânia','Macedónia do Norte','Bósnia-Herzegovina','Montenegro',
    'Kosovo','Malta','Chipre','Islândia','Liechtenstein','Mónaco','Andorra','San Marino',
  ],
  'Américas': [
    'EUA','Canadá','México','Brasil','Argentina','Chile','Colômbia','Peru','Venezuela',
    'Equador','Bolívia','Paraguai','Uruguai','Costa Rica','Panamá','Guatemala','Honduras',
    'El Salvador','Nicarágua','Cuba','República Dominicana','Jamaica','Haiti','Porto Rico',
    'Trinidad e Tobago','Belize','Guiana','Suriname',
  ],
  'Ásia-Pacífico': [
    'China','Japão','Coreia do Sul','Índia','Austrália','Nova Zelândia','Singapura',
    'Indonésia','Malásia','Tailândia','Vietnam','Filipinas','Taiwan','Hong Kong','Macau',
    'Bangladesh','Paquistão','Sri Lanka','Myanmar','Camboja','Laos','Mongólia',
    'Nepal','Cazaquistão','Uzbequistão','Brunei','Timor-Leste',
  ],
  'Médio Oriente': [
    'Emirados Árabes Unidos','Arábia Saudita','Qatar','Kuwait','Omã','Bahrein',
    'Israel','Jordânia','Líbano','Turquia','Irão','Iraque','Síria','Iémen','Palestina',
    'Afeganistão',
  ],
  'África': [
    'África do Sul','Nigéria','Quénia','Gana','Etiópia','Tanzânia','Uganda','Ruanda',
    'Marrocos','Tunísia','Argélia','Líbia','Egipto','Angola','Moçambique','Zimbabué',
    'Zâmbia','Senegal','Costa do Marfim','Camarões','Madagáscar','Namíbia','Botswana',
    'Mali','Serra Leoa','Guiné','Cabo Verde','Ilhas Maurícias',
  ],
  'Oceânia': [
    'Austrália','Nova Zelândia','Papua Nova Guiné','Fiji','Ilhas Salomão','Vanuatu',
    'Samoa','Tonga','Micronésia',
  ],
};

const MarketToggle = ({ opcoes, value, onChange }) => (
  <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
    {opcoes.map(m => {
      const active = (value || []).includes(m);
      return (
        <button key={m} onClick={() => onChange(active ? (value||[]).filter(x=>x!==m) : [...(value||[]),m])} style={{
          padding: '6px 14px', borderRadius: 7, fontSize: 12, cursor: 'pointer',
          border: `1.5px solid ${active ? 'var(--ai-500)' : 'var(--border-strong, var(--border))'}`,
          background: active ? 'color-mix(in oklch, var(--ai-500) 10%, transparent)' : 'transparent',
          color: active ? 'var(--ai-500)' : 'var(--text-muted)',
          fontWeight: active ? 700 : 400, fontFamily: 'var(--font-body)',
        }}>{m}</button>
      );
    })}
  </div>
);

const GeoMarketSelect = ({ value, onChange }) => {
  const [openCont, setOpenCont] = React.useState(null);
  const selected = value || [];
  const countInContinent = (cont) => GEO_HIERARCHY[cont].filter(p => selected.includes(p)).length;
  const toggleContinent = (cont) => {
    const countries = GEO_HIERARCHY[cont];
    const allSelected = countries.every(p => selected.includes(p));
    if (allSelected) onChange(selected.filter(p => !countries.includes(p)));
    else onChange([...new Set([...selected, ...countries])]);
  };
  const toggleCountry = (country) => {
    onChange(selected.includes(country) ? selected.filter(p => p !== country) : [...selected, country]);
  };
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
        {Object.keys(GEO_HIERARCHY).map(cont => {
          const cnt = countInContinent(cont);
          const isOpen = openCont === cont;
          return (
            <button key={cont} onClick={() => setOpenCont(isOpen ? null : cont)} style={{
              padding: '5px 12px', borderRadius: 7, fontSize: 12, cursor: 'pointer',
              border: `1.5px solid ${cnt > 0 ? 'var(--ai-500)' : 'var(--border-strong, var(--border))'}`,
              background: isOpen ? 'color-mix(in oklch, var(--ai-500) 15%, transparent)' : cnt > 0 ? 'color-mix(in oklch, var(--ai-500) 6%, transparent)' : 'transparent',
              color: cnt > 0 ? 'var(--ai-500)' : 'var(--text-muted)',
              fontWeight: cnt > 0 ? 700 : 400, fontFamily: 'var(--font-body)',
              display: 'flex', alignItems: 'center', gap: 5,
            }}>
              {cont}
              {cnt > 0 && <span style={{ fontSize: 10, background: 'var(--ai-500)', color: '#fff', borderRadius: 99, padding: '0 5px', fontWeight: 700 }}>{cnt}</span>}
            </button>
          );
        })}
      </div>
      {openCont && (
        <div style={{ background: 'var(--bg-sunken, #f1f5f9)', borderRadius: 8, padding: '12px 14px' }}>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
            <span style={{ fontSize: 11, fontWeight: 700, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '0.06em' }}>{openCont}</span>
            <button onClick={() => toggleContinent(openCont)} style={{ fontSize: 10, color: 'var(--ai-500)', background: 'none', border: 'none', cursor: 'pointer', fontFamily: 'var(--font-body)', fontWeight: 600 }}>
              {GEO_HIERARCHY[openCont].every(p => selected.includes(p)) ? 'Desseleccionar todos' : 'Seleccionar todos'}
            </button>
          </div>
          <div style={{ display: 'flex', gap: 5, flexWrap: 'wrap' }}>
            {GEO_HIERARCHY[openCont].map(country => {
              const active = selected.includes(country);
              return (
                <button key={country} onClick={() => toggleCountry(country)} style={{
                  padding: '4px 10px', borderRadius: 6, fontSize: 11, cursor: 'pointer',
                  border: `1.5px solid ${active ? 'var(--ai-500)' : 'var(--border)'}`,
                  background: active ? 'color-mix(in oklch, var(--ai-500) 10%, transparent)' : 'var(--bg-elev, #fff)',
                  color: active ? 'var(--ai-500)' : 'var(--text-muted)',
                  fontWeight: active ? 700 : 400, fontFamily: 'var(--font-body)',
                }}>{country}</button>
              );
            })}
          </div>
        </div>
      )}
      {selected.length > 0 && (
        <div style={{ fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-body)' }}>
          {selected.length} mercado{selected.length !== 1 ? 's' : ''} seleccionado{selected.length !== 1 ? 's' : ''}:
          <span style={{ display: 'block', marginTop: 4, color: 'var(--text)', fontWeight: 500, lineHeight: 1.5, overflowWrap: 'anywhere' }}>{selected.join(' · ')}</span>
        </div>
      )}
    </div>
  );
};

// Normalização de nomes de países (importação pode trazer EN/ES → PT)
const COUNTRY_NORMALIZE = {
  'spain':'Espanha','españa':'Espanha','espagne':'Espanha',
  'france':'França','frankreich':'França',
  'germany':'Alemanha','deutschland':'Alemanha','allemagne':'Alemanha',
  'portugal':'Portugal',
  'united kingdom':'Reino Unido','uk':'Reino Unido','england':'Reino Unido','great britain':'Reino Unido',
  'italy':'Itália','italia':'Itália','italie':'Itália',
  'netherlands':'Países Baixos','holland':'Países Baixos','holanda':'Países Baixos',
  'belgium':'Bélgica','belgique':'Bélgica','bélgica':'Bélgica',
  'switzerland':'Suíça','suiza':'Suíça','schweiz':'Suíça',
  'austria':'Áustria','autriche':'Áustria',
  'sweden':'Suécia','suède':'Suécia','suecia':'Suécia',
  'norway':'Noruega','norvège':'Noruega','noruega':'Noruega',
  'denmark':'Dinamarca','danemark':'Dinamarca',
  'finland':'Finlândia','finlande':'Finlândia',
  'ireland':'Irlanda','irlande':'Irlanda',
  'poland':'Polónia','pologne':'Polónia','polonia':'Polónia',
  'brazil':'Brasil','brésil':'Brasil',
  'usa':'EUA','united states':'EUA','us':'EUA','états-unis':'EUA','estados unidos':'EUA',
  'canada':'Canadá','canada':'Canadá',
  'mexico':'México','mexique':'México',
  'argentina':'Argentina','chile':'Chile','colombia':'Colômbia','peru':'Peru',
  'china':'China','japan':'Japão','japon':'Japão','japón':'Japão',
  'south korea':'Coreia do Sul','korea':'Coreia do Sul','corée du sud':'Coreia do Sul',
  'india':'Índia','inde':'Índia',
  'australia':'Austrália','australie':'Austrália',
  'new zealand':'Nova Zelândia','nouvelle-zélande':'Nova Zelândia',
  'singapore':'Singapura','singapour':'Singapura',
  'turkey':'Turquia','turquie':'Turquia','turquía':'Turquia',
  'uae':'Emirados Árabes Unidos','united arab emirates':'Emirados Árabes Unidos',
  'saudi arabia':'Arábia Saudita','arabie saoudite':'Arábia Saudita',
  'south africa':'África do Sul','afrique du sud':'África do Sul',
  'russia':'Rússia','russie':'Rússia','rusia':'Rússia',
  'ukraine':'Ucrânia','ukraine':'Ucrânia',
  'greece':'Grécia','grèce':'Grécia','grecia':'Grécia',
  'croatia':'Croácia','croatie':'Croácia','croacia':'Croácia',
  'czech republic':'República Checa','czechia':'República Checa',
  'hungary':'Hungria','hongrie':'Hungria',
  'romania':'Roménia','roumanie':'Roménia','rumania':'Roménia',
  'bulgaria':'Bulgária','bulgarie':'Bulgária',
  'slovakia':'Eslováquia','slovaquie':'Eslováquia',
  'slovenia':'Eslovénia','slovénie':'Eslovénia',
  'luxembourg':'Luxemburgo','luxemburg':'Luxemburgo',
  'benelux':'Países Baixos',
};

const normalizeCountry = (name) => {
  if (!name) return name;
  // Strip parenthetical info: "Turquia (DB Reklam...)" → "Turquia"
  const stripped = name.replace(/\s*\(.*?\)\s*/g, '').trim();
  const key = stripped.toLowerCase().trim();
  return COUNTRY_NORMALIZE[key] || stripped;
};

// Mapa de regiões → países individuais (para marcas geo)
const REGION_EXPAND = {
  'países nórdicos': ['Suécia','Noruega','Dinamarca','Finlândia'],
  'nórdicos': ['Suécia','Noruega','Dinamarca','Finlândia'],
  'nordic': ['Suécia','Noruega','Dinamarca','Finlândia'],
  'nordics': ['Suécia','Noruega','Dinamarca','Finlândia'],
  'benelux': ['Bélgica','Países Baixos','Luxemburgo'],
  'ibéria': ['Portugal','Espanha'],
  'ibérico': ['Portugal','Espanha'],
  'iberian peninsula': ['Portugal','Espanha'],
  'península ibérica': ['Portugal','Espanha'],
  'dach': ['Alemanha','Áustria','Suíça'],
  'mena': ['Emirados Árabes Unidos','Arábia Saudita','Qatar','Kuwait','Omã','Bahrein','Israel','Jordânia','Líbano','Turquia','Marrocos','Tunísia','Argélia','Egipto'],
  'apac': ['China','Japão','Coreia do Sul','Índia','Austrália','Nova Zelândia','Singapura','Indonésia','Malásia','Tailândia','Vietnam'],
  'latam': ['Brasil','Argentina','Chile','Colômbia','Peru','México'],
};

const expandRegion = (name) => {
  const key = name.toLowerCase().trim();
  return REGION_EXPAND[key] || null;
};

const GeoMarketField = ({ brand, value, onChange }) => {
  const cfg = BRAND_MARKETS_CONFIG[brand];
  const allGeoFlat = React.useMemo(() => Object.values(GEO_HIERARCHY).flat(), []);

  const mapped = React.useMemo(() => {
    const raw = (value || []).map(normalizeCountry).filter(Boolean);
    if (!cfg) return raw;

    if (cfg.tipo === 'fixo') {
      // Correspondência directa: manter só os que estão na lista
      const direct = raw.filter(v => cfg.opcoes.includes(v));
      if (direct.length > 0) return direct;
      // Se a extracção encontrou conteúdo geo mas nenhum corresponde → auto-seleccionar todos os mercados da marca
      if (raw.length > 0) return [...cfg.opcoes];
      return [];
    }

    if (cfg.tipo === 'geo') {
      const result = [];
      for (const v of raw) {
        // Expandir regiões → países individuais
        const expanded = expandRegion(v);
        if (expanded) { expanded.forEach(c => result.includes(c) || result.push(c)); continue; }
        // Aceitar países directos que existam no GEO_HIERARCHY
        if (allGeoFlat.includes(v)) { result.includes(v) || result.push(v); }
      }
      return result;
    }

    return raw;
  }, [value, brand]);

  // Sincronizar com o form state quando o mapeamento alterar os valores
  React.useEffect(() => {
    if (!brand) return;
    const orig = value || [];
    if (mapped.length !== orig.length || mapped.some((v, i) => v !== orig[i])) {
      onChange(mapped);
    }
  }, [mapped]);

  if (!brand) return (
    <div style={{ fontSize: 12, color: 'var(--text-dim)', fontStyle: 'italic', padding: '6px 0' }}>
      Selecciona a marca primeiro
    </div>
  );
  if (cfg?.tipo === 'fixo') return <MarketToggle opcoes={cfg.opcoes} value={mapped} onChange={onChange} />;
  if (cfg?.tipo === 'geo')  return <GeoMarketSelect value={mapped} onChange={onChange} />;
  return <ChipInput items={mapped} onChange={onChange} placeholder="ex: Portugal, Espanha - prima Enter" />;
};

// ─── Blocos do formulário ──────────────────────────────────────────────────────

const Block1 = ({ data, upd, isAll, allBrands, selectedBrand, onBrandChange, products, selectedSegment, onSegmentChange, selectedFamily, onFamilyChange, productId, onProductChange }) => {
  const hasSeg = (products || []).some(p => p.segment);

  const segments = hasSeg
    ? [...new Set((products || []).map(p => p.segment))].filter(Boolean).sort()
    : [];

  const familiesAll = hasSeg
    ? [...new Set((products || []).filter(p => !selectedSegment || p.segment === selectedSegment).map(p => p.category))].filter(Boolean).sort()
    : [...new Set((products || []).map(p => p.category))].filter(Boolean).sort();

  const filteredProducts = (products || []).filter(p =>
    (!hasSeg || !selectedSegment || p.segment === selectedSegment) &&
    (!selectedFamily || p.category === selectedFamily)
  );

  const noMarca = isAll && !selectedBrand;

  return (
  <div>
    <FormCard title="Identificação">
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: 18, alignItems: 'start' }}>
        {isAll && (
          <Field label="Marca">
            <TSelect value={selectedBrand} onChange={onBrandChange}
              options={(allBrands || []).map(b => ({ value: b.slug, label: b.name }))}
              placeholder="Seleccionar marca..." />
          </Field>
        )}
        {hasSeg && (
          <Field label="Categoria">
            <TSelect value={selectedSegment} onChange={onSegmentChange}
              options={segments.map(s => ({ value: s, label: s }))}
              placeholder={noMarca ? 'Selecciona a marca primeiro' : 'Seleccionar...'} />
          </Field>
        )}
        <Field label={hasSeg ? 'Sub-categoria' : 'Categoria'}>
          <TSelect value={selectedFamily} onChange={onFamilyChange}
            options={familiesAll.map(f => ({ value: f, label: f }))}
            placeholder={noMarca ? 'Selecciona a marca primeiro' : hasSeg && !selectedSegment ? 'Selecciona primeiro a categoria' : 'Seleccionar...'} />
        </Field>
        <Field label="Produto / Modelo">
          <TSelect value={productId ? String(productId) : ''} onChange={onProductChange}
            options={filteredProducts.map(p => ({ value: String(p.id), label: p.name }))}
            placeholder={!selectedFamily ? 'Selecciona a ' + (hasSeg ? 'sub-categoria' : 'categoria') + ' primeiro' : 'Seleccionar...'} />
        </Field>
      </div>
    </FormCard>

    <FormCard title="Descrição">
      <Field campo="commercial_name" label="Nome comercial" hint="É o nome do briefing na lista e no topo desta página" required>
        <TInput value={data.commercial_name} onChange={v => upd('commercial_name', v)} placeholder={ph('commercial_name', selectedBrand)} />
      </Field>
      <Field campo="elevator_pitch" label="Elevator pitch" hint="O que faz, as novidades da página e as aplicações. Até 5 frases" required>
        <TInput value={data.elevator_pitch} onChange={v => upd('elevator_pitch', v)}
          placeholder={ph('elevator_pitch', selectedBrand)} multiline />
      </Field>
    </FormCard>

    <FormCard title="Características">
      <Field campo="usps" label="USPs - Pontos de valor únicos" hint="Ordena do mais importante para o menos - o 1.° tem maior peso na geração" required>
        <ChipInput items={data.usps} onChange={v => upd('usps', v)}
          placeholder={ph('usps', selectedBrand)} sortable />
      </Field>
      <div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)', gap: 18, alignItems: 'start' }}>
        <Field campo="certifications" label="Certificações técnicas" hint="Ordena por relevância">
          <ChipInput items={data.certifications} onChange={v => upd('certifications', v)}
            placeholder={ph('certifications', selectedBrand)} sortable />
        </Field>
        <Field campo="applications" label="Aplicações primárias" hint="A 1.ª é o caso de uso principal">
          <ChipInput items={data.applications} onChange={v => upd('applications', v)}
            placeholder={ph('applications', selectedBrand)} sortable />
        </Field>
      </div>
    </FormCard>
  </div>
  );
};

// ── Configuração de mercados por marca ────────────────────────────────────────
const Block2 = ({ data, upd, brand }) => (
  <div>
    <FormCard title="Perfil">
      <div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)', gap: 18, alignItems: 'start' }}>
        <Field campo="decision_maker" label="Decisor de compra" required>
          <TInput value={data.decision_maker} onChange={v => upd('decision_maker', v)} placeholder={ph('decision_maker', brand)} />
        </Field>
        <Field campo="end_user" label="Utilizador final" required>
          <TInput value={data.end_user} onChange={v => upd('end_user', v)} placeholder={ph('end_user', brand)} />
        </Field>
      </div>
    </FormCard>

    <FormCard title="Psicografia">
      <Field campo="pain_points" label="Dores principais que o produto resolve" hint="A 1.ª dor é o gatilho dominante nos hooks" required>
        <ChipInput items={data.pain_points} onChange={v => upd('pain_points', v)}
          placeholder={ph('pain_points', brand)} sortable />
      </Field>
      <div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)', gap: 18, alignItems: 'start' }}>
        <Field campo="motivators" label="Motivadores de compra" hint="O 1.° é o ângulo de desejo principal">
          <ChipInput items={data.motivators} onChange={v => upd('motivators', v)}
            placeholder={ph('motivators', brand)} sortable />
        </Field>
        <Field campo="objections" label="Objecções comuns" hint="A 1.ª é a mais crítica a antecipar">
          <ChipInput items={data.objections} onChange={v => upd('objections', v)}
            placeholder={ph('objections', brand)} sortable />
        </Field>
      </div>
    </FormCard>

    <FormCard title="Decisão">
      <div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)', gap: 18, alignItems: 'start' }}>
        <Field campo="purchase_trigger" label="Trigger de compra" hint="O que precipita a decisão de compra" required>
          <TInput value={data.purchase_trigger} onChange={v => upd('purchase_trigger', v)}
            placeholder={ph('purchase_trigger', brand)} />
        </Field>
      </div>
    </FormCard>
  </div>
);

const Block3 = ({ data, upd, brand }) => (
  <div>
    <FormCard title="Panorama competitivo">
      <div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)', gap: 18, alignItems: 'start' }}>
        <Field campo="competitors" label="Concorrentes directos" required>
          <ChipInput items={data.competitors} onChange={v => upd('competitors', v)}
            placeholder={ph('competitors', brand)} sortable />
        </Field>
        <Field campo="differentiation_args" label="Argumentos de diferenciação" hint="O 1.° é o diferenciador mais forte" required>
          <ChipInput items={data.differentiation_args} onChange={v => upd('differentiation_args', v)}
            placeholder={ph('differentiation_args', brand)} sortable />
        </Field>
      </div>
    </FormCard>

    <FormCard title="Posicionamento">
      <Field campo="market_trends" label="Tendências de mercado relevantes">
        <ChipInput items={data.market_trends} onChange={v => upd('market_trends', v)}
          placeholder={ph('market_trends', brand)} sortable />
      </Field>
      <Field campo="positioning_narrative" label="Narrativa de posicionamento desejada">
        <TInput value={data.positioning_narrative} onChange={v => upd('positioning_narrative', v)}
          placeholder={ph('positioning_narrative', brand)} multiline />
      </Field>
    </FormCard>

    <FormCard title="Notas internas">
      <Field campo="sensitive_info" label="Informação sensível sobre concorrência" hint="Uso interno - não exposto em conteúdos gerados por IA">
        <TInput value={data.sensitive_info} onChange={v => upd('sensitive_info', v)}
          placeholder={ph('sensitive_info', brand)} multiline />
      </Field>
    </FormCard>
  </div>
);

const OFFER_MECHANIC_LABELS = {
  digirent: 'Digirent',
  printplan: 'PrintPlan',
  voucher: 'Voucher on Demand',
  direct_discount: 'Desconto Directo',
  trade_in: 'Trade-In',
  testing_roll: 'Testing Roll',
  sample_request: 'Amostra / Sample',
  promo_bundle: 'Promoção (N+M)',
  custom: 'Oferta à medida',
};
// Sempre disponíveis - cobrem campanhas tipo DotMatrix 2+1 e ofertas fora do catálogo
const OFFER_ALWAYS_MECHANICS = ['promo_bundle', 'custom'];

const CommercialOfferBlock = ({ data, upd, brand, chrome = true }) => {
  const [solutions, setSolutions] = React.useState({});
  const [loadingSolutions, setLoadingSolutions] = React.useState(!!brand);
  React.useEffect(() => {
    if (!brand) { setSolutions({}); setLoadingSolutions(false); return; }
    setLoadingSolutions(true);
    fetch(`/api/marketing/brand-solutions?marca_slug=${encodeURIComponent(brand)}`)
      .then(r => r.ok ? r.json() : {})
      .then(d => setSolutions(d || {}))
      .catch(() => setSolutions({}))
      .finally(() => setLoadingSolutions(false));
  }, [brand]);

  const offer = data.commercial_offer || null;
  const updOffer = (field, val) => upd('commercial_offer', { ...(offer || {}), [field]: val });
  const catalogTypes = Object.keys(solutions);
  const availableMechanics = [...new Set([
    ...catalogTypes,
    ...OFFER_ALWAYS_MECHANICS,
    ...(offer?.type ? [offer.type] : []),
  ])];
  const tiers = offer?.type ? (solutions[offer.type] || []) : [];
  const isPromo = offer?.type === 'promo_bundle';
  const isCustom = offer?.type === 'custom';

  const CTA_OPTS = [
    { value: 'quote_request',  label: 'Pedido de proposta' },
    { value: 'demo_request',   label: 'Pedido de demonstração' },
    { value: 'testing_roll',   label: 'Pedir Testing Roll' },
    { value: 'sample_request', label: 'Pedir Amostra' },
    { value: 'visit_showroom', label: 'Visita ao showroom' },
    { value: 'whatsapp',       label: 'WhatsApp' },
    { value: 'form',           label: 'Formulário' },
    { value: 'call',           label: 'Chamada directa' },
    { value: 'landing',        label: 'Landing page' },
    { value: 'none',           label: 'Sem CTA específico' },
  ];

  const chipStyle = (active, muted) => ({
    padding: '5px 12px', borderRadius: 6, fontSize: 12, fontWeight: 600, cursor: 'pointer',
    background: active ? 'var(--ai-500)' : 'var(--bg-sunken)',
    color: active ? '#fff' : (muted ? 'var(--text-muted)' : 'var(--text)'),
    border: active ? 'none' : '1px solid var(--border)',
    fontFamily: 'var(--font-body)',
  });

  const startMechanic = (type) => {
    if (type === 'promo_bundle') {
      upd('commercial_offer', {
        type: 'promo_bundle',
        buy_qty: 2,
        get_qty: 1,
        get_item: '',
        condition: '',
        validity: '',
        details: '',
        primary_cta: '',
        negotiable: false,
      });
      return;
    }
    if (type === 'custom') {
      upd('commercial_offer', {
        type: 'custom',
        headline: '',
        condition: '',
        validity: '',
        details: '',
        primary_cta: '',
        negotiable: false,
      });
      return;
    }
    upd('commercial_offer', { type, tier: null, details: '', primary_cta: '', negotiable: false });
  };

  const promoSummary = isPromo && offer.buy_qty && offer.get_qty
    ? `${offer.buy_qty}+${offer.get_qty}${offer.get_item ? ` · ${offer.get_item}` : ''}`
    : null;

  const offerBody = (
    <>
      {!brand && (
        <div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 10, lineHeight: 1.45 }}>
          Selecciona a marca na Identificação para ver mecânicas do catálogo. Podes já definir uma promoção ou oferta à medida.
        </div>
      )}
      {brand && loadingSolutions && catalogTypes.length === 0 && (
        <div style={{ fontSize: 11, color: 'var(--text-muted)', marginBottom: 8 }}>A carregar mecânicas da marca…</div>
      )}
      <div style={{ marginBottom: 0 }}>
        <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text)', marginBottom: 8 }}>Mecânica</div>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
          <button type="button" onClick={() => upd('commercial_offer', null)} style={chipStyle(!offer, true)}>
            Sem oferta específica
          </button>
          {availableMechanics.map(type => (
            <button type="button" key={type} onClick={() => startMechanic(type)} style={chipStyle(offer?.type === type, false)}>
              {OFFER_MECHANIC_LABELS[type] || type}
            </button>
          ))}
        </div>
      </div>

      {offer?.type && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 14, padding: 16, marginTop: 14, background: 'var(--bg, #f8fafc)', borderRadius: 8, border: '1px solid var(--border)' }}>
          {isPromo && (
            <>
              <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(120px, 1fr))', gap: 12 }}>
                <div>
                  <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 6, textTransform: 'uppercase', letterSpacing: '0.06em', fontFamily: 'var(--font-mono)' }}>Compra (N)</div>
                  <TInput value={offer.buy_qty ?? ''} onChange={v => updOffer('buy_qty', v === '' ? '' : (parseInt(v, 10) || v))} placeholder="2" />
                </div>
                <div>
                  <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 6, textTransform: 'uppercase', letterSpacing: '0.06em', fontFamily: 'var(--font-mono)' }}>Leva (M)</div>
                  <TInput value={offer.get_qty ?? ''} onChange={v => updOffer('get_qty', v === '' ? '' : (parseInt(v, 10) || v))} placeholder="1" />
                </div>
              </div>
              <div>
                <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 6, textTransform: 'uppercase', letterSpacing: '0.06em', fontFamily: 'var(--font-mono)' }}>O que leva (brinde / produto extra)</div>
                <TInput value={offer.get_item || ''} onChange={v => updOffer('get_item', v)} placeholder="ex: 1 rolo de laminação gloss ou mate, no mesmo formato" />
              </div>
              <div>
                <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 6, textTransform: 'uppercase', letterSpacing: '0.06em', fontFamily: 'var(--font-mono)' }}>Condição de compra</div>
                <TInput value={offer.condition || ''} onChange={v => updOffer('condition', v)} placeholder="ex: Na compra de 2 rolos de vinil DotMatrix" multiline />
              </div>
              {promoSummary && (
                <div style={{ fontSize: 12, color: 'var(--ai-500, #3859D0)', fontWeight: 600, lineHeight: 1.4 }}>
                  Resumo: {promoSummary}
                </div>
              )}
            </>
          )}

          {isCustom && (
            <>
              <div>
                <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 6, textTransform: 'uppercase', letterSpacing: '0.06em', fontFamily: 'var(--font-mono)' }}>Título da oferta</div>
                <TInput value={offer.headline || ''} onChange={v => updOffer('headline', v)} placeholder="ex: Pack lançamento DotMatrix + laminação" />
              </div>
              <div>
                <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 6, textTransform: 'uppercase', letterSpacing: '0.06em', fontFamily: 'var(--font-mono)' }}>Condições</div>
                <TInput value={offer.condition || ''} onChange={v => updOffer('condition', v)} placeholder="Quem beneficia, volumes mínimos, exclusões…" multiline />
              </div>
            </>
          )}

          {!isPromo && !isCustom && tiers.length > 0 && (
            <div>
              <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 6, textTransform: 'uppercase', letterSpacing: '0.06em', fontFamily: 'var(--font-mono)' }}>Tier *</div>
              <TSelect value={offer.tier || ''} onChange={v => updOffer('tier', v)}
                options={[{ value: '', label: 'Selecciona o tier…' }, ...tiers.map(t => ({ value: t.tier, label: t.label }))]} />
              {offer.tier && tiers.find(t => t.tier === offer.tier)?.description && (
                <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 5, lineHeight: 1.55, fontFamily: 'var(--font-body)' }}>
                  {tiers.find(t => t.tier === offer.tier).description}
                </div>
              )}
            </div>
          )}

          <div>
            <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 6, textTransform: 'uppercase', letterSpacing: '0.06em', fontFamily: 'var(--font-mono)' }}>
              {isPromo || isCustom ? 'Detalhes / copy da oferta' : 'Detalhes da oferta'}
            </div>
            <TInput
              value={offer.details || offer.descricao || ''}
              onChange={v => updOffer('details', v)}
              placeholder={isPromo
                ? 'ex: Sistema vinil + laminação testado em conjunto, gloss ou mate, compatibilidade garantida'
                : 'ex: 36 meses, €1.250/mês, entrada 20%'}
              multiline
            />
          </div>

          {(isPromo || isCustom) && (
            <div>
              <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 6, textTransform: 'uppercase', letterSpacing: '0.06em', fontFamily: 'var(--font-mono)' }}>Validade</div>
              <TInput value={offer.validity || ''} onChange={v => updOffer('validity', v)} placeholder="ex: Até 31/12/2026 · stocks limitados" />
            </div>
          )}

          <div>
            <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 6, textTransform: 'uppercase', letterSpacing: '0.06em', fontFamily: 'var(--font-mono)' }}>CTA principal</div>
            <TSelect value={offer.primary_cta || ''} onChange={v => updOffer('primary_cta', v)}
              options={[{ value: '', label: 'Selecciona o CTA…' }, ...CTA_OPTS]} />
          </div>
          <label style={{ display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', userSelect: 'none' }}>
            <input type="checkbox" checked={!!offer.negotiable} onChange={e => updOffer('negotiable', e.target.checked)} />
            <span style={{ fontSize: 12, color: 'var(--text)', fontFamily: 'var(--font-body)' }}>
              {isPromo || isCustom ? 'Condições negociáveis' : 'Tier negociável'}
              <span style={{ color: 'var(--text-muted)', fontStyle: 'italic' }}> (indicativo - o comercial fecha as condições)</span>
            </span>
          </label>
        </div>
      )}
    </>
  );
  if (!chrome) return offerBody;
  return <FormCard campo="commercial_offer" title="Oferta Comercial">{offerBody}</FormCard>;
};

// ─── KpiChannelInput ───────────────────────────────────────────────────────────
const KPIS_CANAL_LABELS = {
  negocio: 'KPI de negócio',
  meta_ads: 'Meta Ads', linkedin_ads: 'LinkedIn Ads', linkedin_organic: 'LinkedIn',
  google_ads_search: 'Google Search', google_ads_display: 'Google Display',
  email: 'Email', whatsapp: 'WhatsApp', website: 'Website / Loja', muppi_led: 'Painéis LED',
  equipa_consumiveis: 'Equipa Consumíveis', equipa_equipamentos: 'Equipa Equipamentos', equipa_sdr: 'Equipa SDR',
  comercial: 'Equipa comercial', daily_prospect: 'Daily Prospect', distribuidores: 'Distribuidores', logistica: 'Logística',
};
const KPIS_CANAL_ABBR = {
  negocio: 'NEG',
  meta_ads: 'META', linkedin_ads: 'LIN', linkedin_organic: 'ORG', google_ads_search: 'GGL',
  google_ads_display: 'DISP', email: 'EML', whatsapp: 'WA',
  website: 'WEB', muppi_led: 'LED',
  equipa_consumiveis: 'CON', equipa_equipamentos: 'EQP', equipa_sdr: 'SDR',
  comercial: 'COM', daily_prospect: 'SDR', distribuidores: 'DIS', logistica: 'LOG',
};
const KPI_SEM_MEDIA_PAGA = new Set(['negocio', 'linkedin_organic', 'equipa_consumiveis', 'equipa_equipamentos', 'equipa_sdr', 'comercial', 'daily_prospect', 'distribuidores', 'logistica', 'email', 'whatsapp', 'website']);

const KpiChannelInput = ({ items, onChange, selectedChannels }) => {
  const safeItems    = Array.isArray(items) ? items : [];
  const safeChanList = ['negocio'].concat(
    (Array.isArray(selectedChannels) && selectedChannels.length > 0 ? selectedChannels : Object.keys(KPIS_CANAL_LABELS).filter(k => k !== 'negocio' && !['comercial', 'daily_prospect', 'logistica'].includes(k)))
      .filter(c => c !== 'negocio')
  );

  const [editCanal, setEditCanal] = React.useState('');
  const [editKpi,   setEditKpi]   = React.useState('');

  // Normaliza: string legado → { canal:'', kpi: string }
  const normalized = safeItems.map(function(k) {
    return typeof k === 'string' ? { canal: '', kpi: k } : (k && k.kpi ? k : null);
  }).filter(Boolean);

  // Canal efectivo para o select (reset se não está na lista)
  const activeCanal = safeChanList.includes(editCanal) ? editCanal : (safeChanList[0] || '');

  function add() {
    if (!editKpi.trim()) return;
    onChange(normalized.concat([{ canal: activeCanal, kpi: editKpi.trim() }]));
    setEditKpi('');
  }

  function remove(i) { onChange(normalized.filter(function(_, idx) { return idx !== i; })); }

  var st = { background:'var(--bg-elev,#fff)', border:'1px solid var(--border-strong,var(--border))', borderRadius:6, fontSize:12, color:'var(--text)', padding:'7px 10px', fontFamily:'inherit', outline:'none', boxSizing:'border-box' };

  return (
    React.createElement('div', null,
      React.createElement('div', { style: { display:'flex', gap:6, marginBottom: normalized.length > 0 ? 8 : 0 } },
        React.createElement('select', { value: activeCanal, onChange: function(e) { setEditCanal(e.target.value); }, style: Object.assign({}, st, { width:148, flexShrink:0 }) },
          React.createElement('option', { value: '' }, 'Canal...'),
          safeChanList.map(function(c) { return React.createElement('option', { key: c, value: c }, KPIS_CANAL_LABELS[c] || c); })
        ),
        React.createElement('input', { value: editKpi, onChange: function(e) { setEditKpi(e.target.value); }, onKeyDown: function(e) { if (e.key === 'Enter') { e.preventDefault(); add(); } }, placeholder: 'ex: 3 envios na campanha', style: Object.assign({}, st, { flex:1 }) }),
        React.createElement('button', { onClick: add, className: 'btn', style: { flexShrink:0, height:34, padding:'0 12px', fontSize:12 } }, '+')
      ),
      normalized.length > 0 && React.createElement('div', { style: { display:'flex', flexWrap:'wrap', gap:6, marginTop:6 } },
        normalized.map(function(item, i) {
          var abbr = item.canal ? (KPIS_CANAL_ABBR[item.canal] || item.canal.slice(0,3).toUpperCase()) : null;
          var isOrphan = item.canal && item.canal !== 'negocio' && safeChanList.length > 0 && !safeChanList.includes(item.canal);
          var paidMismatch = KPI_SEM_MEDIA_PAGA.has(item.canal) && /\b(cpl|cpc|roas)\b/i.test(item.kpi || '');
          var warn = isOrphan || paidMismatch;
          var warnTitle = isOrphan ? 'Canal removido - este KPI ficou órfão' : (paidMismatch ? 'CPL, CPC e ROAS não se aplicam a este canal' : undefined);
          return React.createElement('span', { key: i, style: { display:'inline-flex', alignItems:'flex-start', gap:5, maxWidth:'100%', background: warn ? 'color-mix(in srgb,#f59e0b 8%,transparent)' : 'var(--bg-sunken)', border: warn ? '1px solid color-mix(in srgb,#f59e0b 40%,transparent)' : '1px solid var(--border)', borderRadius:5, padding:'3px 8px 3px 5px', fontSize:12 }, title: warnTitle },
            abbr ? React.createElement('span', { style: { fontSize:9, fontWeight:700, background: warn ? '#f59e0b' : 'var(--ai-500,#3859D0)', color:'#fff', borderRadius:3, padding:'1px 4px', fontFamily:'var(--font-mono)', flexShrink:0 } }, abbr) : null,
            React.createElement('span', { style: { color: warn ? '#92400e' : 'var(--text)', whiteSpace: 'normal', overflowWrap: 'anywhere', lineHeight: 1.4 } }, item.kpi),
            warn && React.createElement('span', { style: { fontSize:9, color:'#92400e', fontFamily:'var(--font-mono)', marginLeft:2 } }, '!'),
            React.createElement('button', { onClick: function() { remove(i); }, style: { background:'none', border:'none', cursor:'pointer', color:'var(--text-dim)', padding:'0 0 0 2px', lineHeight:1, fontSize:14 } }, '×')
          );
        })
      ),
      React.createElement('div', { style: { fontSize:11, color:'var(--text-dim)', marginTop:4 } }, 'KPI de negócio e um por canal. CPL, CPC e ROAS só em canais pagos e só se a fonte os indicar.')
    )
  );
};

const Block4 = ({ data, upd, brand, aiSuggested, onSuggestBudget, suggestingBudget, budgetSuggested, budgetRationale, budgetError }) => (
  <div>
    {/* Banner apenas para sugestão via Objectivos - o banner global já cobre o caso aiSuggested */}
    {budgetSuggested && (
      <div style={{ marginBottom: 14, padding: '10px 14px', borderRadius: 8, fontSize: 11, display: 'flex', alignItems: 'flex-start', gap: 8,
        background: 'color-mix(in oklch, var(--success) 8%, transparent)',
        border: '1px solid color-mix(in oklch, var(--success) 25%, transparent)',
        color: 'var(--success)',
      }}>
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0, marginTop: 1 }}>
          <polyline points="20 6 9 17 4 12"/>
        </svg>
        <div>
          <span style={{ fontWeight: 500 }}>Campanha sugerida com base nos Objectivos - revê e confirma.</span>
          {budgetRationale && (
            <div style={{ marginTop: 4, color: 'var(--text-muted)', fontWeight: 400, lineHeight: 1.5 }}>{budgetRationale}</div>
          )}
        </div>
      </div>
    )}
    {budgetError && (
      <div style={{ marginBottom: 14, padding: '8px 12px', borderRadius: 6, fontSize: 11.5, color: 'var(--danger)', lineHeight: 1.55,
        background: 'color-mix(in oklch, var(--danger) 6%, transparent)', border: '1px solid color-mix(in oklch, var(--danger) 20%, transparent)',
        display: 'flex', alignItems: 'flex-start', gap: 6 }}>
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" style={{ flexShrink: 0, marginTop: 1 }}>
          <path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>
        </svg>
        <span>{budgetError}</span>
      </div>
    )}

    <FormCard title={
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
        <span>Estratégia</span>
        {onSuggestBudget && (
          <button onClick={onSuggestBudget} disabled={suggestingBudget}
            style={{ background: 'none', border: 'none', cursor: suggestingBudget ? 'default' : 'pointer', padding: 0, display: 'inline-flex', alignItems: 'center', gap: 5, color: suggestingBudget ? 'var(--text-dim)' : 'var(--ai-500)', fontSize: 10, fontWeight: 600, letterSpacing: '0.04em', textTransform: 'uppercase', fontFamily: 'var(--font-mono)' }}>
            {suggestingBudget
              ? <><div style={{ width: 10, height: 10, borderRadius: '50%', border: '1.5px solid var(--text-dim)', borderTopColor: 'var(--ai-500)', animation: 'spin 0.8s linear infinite' }} />A sugerir...</>
              : <><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>{budgetSuggested ? 'Re-sugerir' : 'Sugerir com AI'}</>
            }
          </button>
        )}
      </div>
    }>
      <div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)', gap: 18, alignItems: 'start' }}>
        <Field campo="objective" label="Objectivo primário" required>
          <TSelect value={data.objective} onChange={v => upd('objective', v)}
            options={[
              { value: 'awareness',  label: 'Awareness' },
              { value: 'lead_gen',   label: 'Geração de leads' },
              { value: 'activacao',  label: 'Activação / Promoção' },
              { value: 'conversion', label: 'Conversão' },
              { value: 'retention',  label: 'Retenção / Upsell' },
            ]} />
        </Field>
        <Field campo="kpis" label="KPIs de sucesso" hint="Um KPI de negócio e um por canal. Números só quando a fonte os traz.">
          <KpiChannelInput items={Array.isArray(data.kpis) ? data.kpis : []} onChange={v => upd('kpis', v)} selectedChannels={Array.isArray(data.channels) ? data.channels : []} />
        </Field>
      </div>
      <Field campo="channels" label="Canais prioritários" required>
        <ChannelToggle selected={data.channels} onChange={v => upd('channels', v)} />
      </Field>
      {(data.channels || []).includes('meta_ads') && (
        <Field campo="meta_ads_conversao" label="Meta Ads - Tipo de conversão" hint="Define como o utilizador converte - vem do briefing para o conceito">
          <select value={data.meta_ads_conversao || ''} onChange={e => upd('meta_ads_conversao', e.target.value)}
            style={{ width: '100%', padding: '9px 12px', borderRadius: 8, fontSize: 13, background: 'var(--bg-app,#f8fafc)', border: '1.5px solid var(--border,#dde3ef)', color: data.meta_ads_conversao ? 'var(--text,#1d2e38)' : 'var(--text-dim,#94a3b8)', appearance: 'none' }}>
            <option value="">Seleccionar tipo de conversão…</option>
            {META_CONVERSAO_OPTS.map(o => <option key={o.value} value={o.value}>{o.label}</option>)}
          </select>
        </Field>
      )}
    </FormCard>

    <FormCard title="Identidade">
      <div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr)', gap: 18, alignItems: 'start' }}>
        <Field campo="tone" label="Tom e linguagem" required>
          <TInput value={data.tone} onChange={v => upd('tone', v)} placeholder={ph('tone', brand)} />
        </Field>
        <Field label="Início">
          <TDateInput value={data.timeline_start} onChange={v => upd('timeline_start', v)} />
        </Field>
        <Field label="Fim">
          <TDateInput value={data.timeline_end} onChange={v => upd('timeline_end', v)} />
        </Field>
      </div>
      <Field campo="key_message" label="Mensagem-chave" hint="A mensagem central que todos os conteúdos devem transmitir" required>
        <TInput value={data.key_message} onChange={v => upd('key_message', v)}
          placeholder={ph('key_message', brand)} multiline />
      </Field>
    </FormCard>

    <FormCard title="Histórico">
      <Field campo="previous_campaigns" label="Campanhas anteriores relevantes">
        <ChipInput items={data.previous_campaigns} onChange={v => upd('previous_campaigns', v)}
          placeholder={ph('previous_campaigns', brand)} sortable />
      </Field>
    </FormCard>

    <CommercialOfferBlock data={data} upd={upd} brand={brand} />
  </div>
);

const Block5 = ({ data, upd, brand, aiSuggested }) => (
  <div>
    {/* Banner coberto pelo banner global do form - omitido aqui para evitar duplicação */}
    <FormCard title="Limites de conteúdo">
      <div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)', gap: 18, alignItems: 'start' }}>
        <Field campo="prohibited_claims" label="Claims proibidos" hint="O 1.° é o mais crítico a evitar">
          <ChipInput items={data.prohibited_claims} onChange={v => upd('prohibited_claims', v)}
            placeholder={ph('prohibited_claims', brand)} sortable />
        </Field>
        <Field campo="off_brand_messages" label="Mensagens fora do posicionamento">
          <ChipInput items={data.off_brand_messages} onChange={v => upd('off_brand_messages', v)}
            placeholder={ph('off_brand_messages', brand)} sortable />
        </Field>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)', gap: 18, alignItems: 'start' }}>
        <Field campo="competitors_not_name" label="Concorrentes a não nomear">
          <ChipInput items={data.competitors_not_name} onChange={v => upd('competitors_not_name', v)}
            placeholder={ph('competitors_not_name', brand)} sortable />
        </Field>
        <Field campo="out_of_scope_markets" label="Mercados / segmentos fora de âmbito">
          <ChipInput items={data.out_of_scope_markets} onChange={v => upd('out_of_scope_markets', v)}
            placeholder={ph('out_of_scope_markets', brand)} sortable />
        </Field>
      </div>
      <Field campo="other_restrictions" label="Outras restrições">
        <ChipInput items={data.other_restrictions} onChange={v => upd('other_restrictions', v)}
          placeholder={ph('other_restrictions', brand)} sortable />
      </Field>
    </FormCard>
  </div>
);

// ─── Block 6 - Apreciação AI ──────────────────────────────────────────────────


const Block6 = ({ briefingId, apreciation: initialApreciation, userRole, calcProgressValue, onUpdate, importOrigin, previewData, form, brandName, onApplyImprovement, onNavigateToBlock, skipAutoRun }) => {
  const [apreciation, setApreciation] = React.useState(initialApreciation || null);
  const [phase, setPhase] = React.useState('idle'); // idle | loading | error
  const [messages, setMessages] = React.useState([]);
  const [currentStep, setCurrentStep] = React.useState(0);
  const [errorMsg, setErrorMsg] = React.useState('');
  const [applyState, setApplyState]             = React.useState({});
  const [expandedBlock, setExpandedBlock]       = React.useState(null);
  const [expandedField, setExpandedField]       = React.useState(null);
  const [improvementsApplied, setImprovementsApplied] = React.useState(false);
  const [consulta, setConsulta] = React.useState(null);
  const [edicaoManual, setEdicaoManual] = React.useState(null);
  const [scoreGain, setScoreGain]               = React.useState(0);
  const [improvedFields, setImprovedFields]     = React.useState(() => new Set(initialApreciation?.improved_fields || [])); // campos melhorados (persiste na BD) // pontos ganhos pelas melhorias aplicadas

  // Pesos dos campos - carregados do servidor (fonte única de verdade)
  const [FIELD_WEIGHTS, setFieldWeights] = React.useState({
    b1: { blockW: 0.27, fields: { elevator_pitch: 0.25, usps: 0.45, certifications: 0.20 } },
    b2: { blockW: 0.60, fields: { pain_points: 0.28, motivators: 0.11, objections: 0.07, purchase_trigger: 0.05, decision_maker: 0.27, end_user: 0.22 } },
    b3: { blockW: 0.13, fields: { competitors: 0.20, market_trends: 0.10, positioning_narrative: 0.35, differentiation_args: 0.35 } },
    b4: { blockW: 0, fields: {} },
    b5: { blockW: 0,    fields: {} },
  });
  React.useEffect(() => {
    fetch('/api/marketing/briefings/field-weights')
      .then(r => r.ok ? r.json() : null)
      .then(d => { if (d) setFieldWeights(d); })
      .catch(() => {});
  }, []);

  const estimateGain = (bKey, fieldName, currentFieldScore, numCriteriaFixed) => {
    const fw = FIELD_WEIGHTS[bKey];
    if (!fw) return 0;
    const fieldW = fw.fields[fieldName] || 0.25;
    const estimatedNewScore = Math.min(95, currentFieldScore + numCriteriaFixed * 18);
    return Math.max(1, Math.round((estimatedNewScore - currentFieldScore) / 100 * fieldW * fw.blockW * 100));
  };
  const stepStartRef  = React.useRef(null);
  const lastStepAtRef = React.useRef(0);
  const [stepPct, setStepPct] = React.useState(0);

  const canGenerate = (userRole === 'strategist' || userRole === 'admin') && (!!briefingId || !!previewData);
  const hasContent  = calcProgressValue >= 40;

  const scoreColor = (s) => s >= 80 ? 'var(--ai-500, #3859D0)' : s >= 60 ? 'var(--text-muted, #4a5e7a)' : 'var(--text-dim, #6b7fa3)';
  const scoreLabel = (s) => s >= 90 ? 'Excelente' : s >= 70 ? 'Bom' : s >= 40 ? 'Suficiente' : 'Insuficiente';

  const processLines = async (lines) => {
    for (const line of lines) {
      if (!line.startsWith('data: ')) continue;
      let evt; try { evt = JSON.parse(line.slice(6)); } catch { continue; }
      if (evt.type === 'status') {
        const wait = 500 - (Date.now() - lastStepAtRef.current);
        if (wait > 0 && lastStepAtRef.current > 0) await new Promise(r => setTimeout(r, wait));
        setCurrentStep(evt.step); setMessages(m => [...m, evt.message]);
        lastStepAtRef.current = Date.now();
      }
      else if (evt.type === 'done') {
        const data = limparTravessao(evt.data);
        setApreciation(data); onUpdate?.(data); setPhase('idle');
        setApplyState({});
      }
      else if (evt.type === 'error') { setErrorMsg(evt.message); setPhase('error'); }
    }
  };

  const [pontoEmCurso, setPontoEmCurso] = React.useState('');
  const [pontoErro, setPontoErro] = React.useState('');

  const runPonto = async (bKey, field, evento) => {
    evento.stopPropagation();
    if (!canGenerate || pontoEmCurso || phase === 'loading') return;
    setPontoEmCurso(`${bKey}.${field}`);
    setPontoErro('');
    try {
      const inlineData = previewData || (form ? {
        block1: form.block1, block2: form.block2, block3: form.block3,
        block4: form.block4, block5: form.block5,
        brand_name: brandName || '',
      } : {});
      const res = await fetch('/api/marketing/briefings/apreciation-ponto', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ ...(inlineData || {}), briefing_id: briefingId || undefined, bloco: bKey, campo: field, apreciation }),
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(data.error || 'Não foi possível analisar este ponto.');
      const limpo = limparTravessao(data);
      setApreciation(limpo);
      onUpdate?.(limpo);
    } catch (err) {
      setPontoErro(err.message || 'Não foi possível analisar este ponto.');
    } finally {
      setPontoEmCurso('');
    }
  };

  const runGenerate = async () => {
    if (!canGenerate) return;
    setPhase('loading'); setMessages([]); setCurrentStep(0); setErrorMsg('');
    setImprovementsApplied(false);
    setScoreGain(0);
    lastStepAtRef.current = 0;
    try {
      // Sempre usa o endpoint inline (aceita dados directamente, sem precisar de briefing guardado)
      const inlineData = previewData || (form ? {
        block1: form.block1, block2: form.block2, block3: form.block3,
        block4: form.block4, block5: form.block5,
        brand_name: brandName || '',
      } : null);

      const res = await fetch('/api/marketing/briefings/apreciation-full', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ ...(inlineData || {}), briefing_id: briefingId || undefined }),
      });
      if (!res.ok) {
        let msg = `HTTP ${res.status}`;
        try { const j = await res.json(); msg = j.error || msg; } catch {}
        setErrorMsg(msg); setPhase('error'); return;
      }
      const reader = res.body.getReader();
      const decoder = new TextDecoder();
      let buf = '';
      while (true) {
        const { done, value } = await reader.read();
        // Process value even when done=true - last chunk may carry the final SSE event
        if (value) {
          buf += decoder.decode(value, { stream: !done });
          const lines = buf.split('\n');
          buf = done ? '' : (lines.pop() ?? '');
          await processLines(lines);
        }
        if (done) {
          // Flush any remaining incomplete line in buf
          if (buf.startsWith('data: ')) await processLines([buf]);
          break;
        }
      }
    } catch (e) { setErrorMsg(e.message); setPhase('error'); }
  };

  // Auto-gerar ao abrir o tab - só quando não existe apreciação guardada com score ≥ 80
  // skipAutoRun=true em duplicados: apreciação herdada do original, não re-corre
  React.useEffect(() => {
    if (skipAutoRun) return;
    const hasApreciation = (initialApreciation?.quality_score ?? 0) > 0;
    const hasEvals = !!(initialApreciation?.evals);
    if ((!hasApreciation || !hasEvals) && canGenerate && hasContent) runGenerate();
  }, []);

  const STEPS = [
    { step: 1, label: 'A carregar briefing',          duration: 1500  },
    { step: 2, label: 'A avaliar Bloco 1 - Produto',  duration: 8000  },
    { step: 3, label: 'A avaliar Bloco 2 - Cliente',  duration: 8000  },
    { step: 4, label: 'A avaliar Bloco 3 - Mercado',  duration: 8000  },
    { step: 5, label: 'A preparar restrições', duration: 1500  },
    { step: 6, label: 'A avaliar Bloco 5 - Guardrails',duration: 8000 },
    { step: 7, label: 'A calcular score global',      duration: 3000  },
  ];

  // Countdown timer - tempo restante estimado
  const [timeLeft, setTimeLeft] = React.useState(0);
  const totalMs = STEPS.reduce((s, st) => s + st.duration, 0); // ~44.5s total
  const EST_TOKENS = 17; // ~17K tokens (8.5K input + 8.5K output)
  // Claude Sonnet 4.6: $3/MTok input + $15/MTok output → ~$0.15 → ~€0.14
  const EST_COST_EUR = '€0.14';

  React.useEffect(() => {
    if (phase !== 'loading') { setTimeLeft(0); return; }
    // Calcular tempo restante: soma das durações dos steps não completados
    const remaining = STEPS.filter(s => s.step >= currentStep).reduce((acc, s) => acc + s.duration, 0);
    setTimeLeft(Math.ceil(remaining / 1000));
    const iv = setInterval(() => setTimeLeft(t => Math.max(0, t - 1)), 1000);
    return () => clearInterval(iv);
  }, [phase, currentStep]);

  // Timer de progresso por step - igual às campanhas
  React.useEffect(() => {
    if (phase !== 'loading' || currentStep <= 0) { setStepPct(0); return; }
    stepStartRef.current = Date.now();
    setStepPct(0);
  }, [currentStep, phase]);

  React.useEffect(() => {
    if (phase !== 'loading' || currentStep <= 0) return;
    const def = STEPS.find(s => s.step === currentStep);
    if (!def?.duration) return;
    const iv = setInterval(() => {
      const elapsed = Date.now() - (stepStartRef.current || Date.now());
      setStepPct(Math.min((elapsed / def.duration) * 90, 90));
    }, 80);
    return () => clearInterval(iv);
  }, [currentStep, phase]);

  const sources = importOrigin?.sources || [];

  const BLOCK_NAMES_B6 = { block1: '1. Produto', block2: '2. Cliente', block3: '3. Mercado', block4: '4. Campanha', block5: '5. Restrições' };

  const formatVal = (v) => {
    if (v == null || v === '' || (Array.isArray(v) && v.length === 0)) return null;
    if (Array.isArray(v)) {
      const items = v.filter(i => i != null && String(i).trim());
      if (!items.length) return null;
      // Decisão 8: lista numerada por linha para arrays
      return items.map((item, i) => `${i + 1}. ${item && typeof item === 'object' ? (item.kpi ? `${item.canal || 'negocio'}: ${item.kpi}` : JSON.stringify(item)) : item}`).join('\n');
    }
    if (v && typeof v === 'object') {
      if (v.type || v.headline || v.buy_qty) {
        return [v.type, v.buy_qty && v.get_qty ? `${v.buy_qty}+${v.get_qty}` : '', v.get_item, v.headline, v.condition].filter(Boolean).join('. ');
      }
      return JSON.stringify(v);
    }
    return String(v).trim() || null;
  };

  const handleApply = async (text, tipo, index, criterionData) => {
    // Se criterionData presente, usar index directamente como chave (já é o stateKey correcto)
    const key = criterionData ? index : `${tipo}_${index}`;
    setApplyState(s => ({ ...s, [key]: { status: 'loading' } }));
    try {
      const body = criterionData
        ? { ...criterionData, form_context: form || {}, brand_name: brandName || '' }
        : { suggestion: text, tipo, form_context: form || {}, brand_name: brandName || '' };
      const res = await fetch('/api/marketing/briefings/improve-field', {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
      });
      if (!res.ok) { const j = await res.json().catch(() => ({})); throw new Error(j.error || `HTTP ${res.status}`); }
      const result = limparTravessao(await res.json());
      if (result.block && result.field && result.value != null) {
        setApplyState(s => ({ ...s, [key]: { status: 'review', result } }));
      } else {
        throw new Error('Resposta inválida');
      }
    } catch (e) {
      console.error('[improve-field]', e.message);
      setApplyState(s => ({ ...s, [key]: { status: 'error', message: e.message } }));
    }
  };

  // Actualiza evals sem chamada Claude: marca critérios como passed e recalcula scores
  const updateEvalsAfterImprovement = (blockStr, fieldName, currentAprec) => {
    if (!currentAprec?.evals) return currentAprec;
    const bKey = blockStr.replace('block', 'b'); // 'block1' → 'b1'
    const blockEval = currentAprec.evals[bKey];
    if (!blockEval || !Array.isArray(blockEval.fields)) return currentAprec;

    const updatedFields = blockEval.fields.map(f => {
      if (f.field !== fieldName) return f;
      const updatedCriteria = (f.criteria || []).map(c => c.passed ? c : { ...c, passed: true, feedback: null });
      const total = updatedCriteria.length;
      const passed = updatedCriteria.filter(c => c.passed).length;
      const newScore = total > 0 ? Math.min(95, Math.round((passed / total) * 100)) : Math.max(f.score, 80);
      return { ...f, criteria: updatedCriteria, score: newScore };
    });

    // Recalcular block_score pela média ponderada dos campos
    const fieldWeights = FIELD_WEIGHTS[bKey]?.fields || {};
    const totalW = Object.values(fieldWeights).reduce((s, w) => s + w, 0) || 100;
    const newBlockScore = Math.round(
      updatedFields.reduce((sum, f) => sum + (f.score * ((fieldWeights[f.field] || 0) / totalW)), 0)
    );

    const newEvals = { ...currentAprec.evals, [bKey]: { ...blockEval, fields: updatedFields, block_score: newBlockScore } };

    // Decisão 1: quality_score calculado por fórmula (consistente com potencialGlobal)
    const b1s = newEvals.b1?.block_score || 0;
    const b2s = newEvals.b2?.block_score || 0;
    const b3s = newEvals.b3?.block_score || 0;
    const newQualityScore = Math.round(b1s * (FIELD_WEIGHTS.b1?.blockW || 0.27) + b2s * (FIELD_WEIGHTS.b2?.blockW || 0.60) + b3s * (FIELD_WEIGHTS.b3?.blockW || 0.13));

    const updated = {
      ...currentAprec,
      quality_score: newQualityScore,
      evals: newEvals,
      breakdown: { ...currentAprec.breakdown, [bKey]: { ...(currentAprec.breakdown?.[bKey] || {}), score: newBlockScore } },
    };
    return updated;
  };

  const handleConfirm = (key, result) => {
    let value = result.value;
    if (typeof value === 'string' && value.trim().startsWith('[')) {
      try { value = JSON.parse(value); } catch {}
    }
    const antes = formatVal(form?.[result.block]?.[result.field]) || '';
    onApplyImprovement?.(result.block, result.field, value, result.field_label);
    if (result.block && result.field) {
      const fieldKey = `${result.block}.${result.field}`;
      setImprovedFields(prev => new Set([...prev, fieldKey]));
      const evalsUpdated = updateEvalsAfterImprovement(result.block, result.field, apreciation);
      const newImprovedFields = [...new Set([...(apreciation?.improved_fields || []), fieldKey])];
      const fontes = (Array.isArray(result._kb_sources) ? result._kb_sources : []).slice(0, 8).map(f => ({
        file_name: f.file_name || '',
        source: f.source || '',
        source_label: f.source_label || '',
        type: f.type === 'url' ? 'url' : 'doc',
        pages: Array.isArray(f.pages) ? f.pages.filter(n => Number(n) > 0).slice(0, 30) : [],
        folder: f.folder || '',
        url: /^https?:\/\//i.test(String(f.url || '')) ? f.url : '',
        sem_pagina: f.sem_pagina === true,
        pagina_registada: f.pagina_registada === true,
      }));
      const registo = {
        block: result.block,
        field: result.field,
        field_label: result.field_label || result.field,
        antes: String(antes).slice(0, 6000),
        depois: String(formatVal(value) || '').slice(0, 6000),
        fontes,
        kb_total: result._kb_total || 0,
        kb_confidence: result._kb_confidence || '',
        kb_avg_score: result._kb_avg_score || 0,
        em: new Date().toISOString(),
      };
      const correccoes = [...(apreciation?.correccoes_aplicadas || []), registo].slice(-40);
      const updated = { ...evalsUpdated, improved_fields: newImprovedFields, correccoes_aplicadas: correccoes };
      setApreciation(updated);
      onUpdate?.(updated);
      const gain = evalsUpdated.quality_score - (apreciation?.quality_score || 0);
      if (gain > 0) setScoreGain(prev => prev + gain);
    }
    setApplyState(s => ({ ...s, [key]: { status: 'done', ...result } }));
    setImprovementsApplied(true);
  };

  const handleReject = (key) => {
    setApplyState(s => ({ ...s, [key]: { status: 'idle' } }));
  };

  const ApplyBtn = ({ text, tipo, index }) => {
    const key  = `${tipo}_${index}`;
    const item = applyState[key] || { status: 'idle' };
    const st   = item.status;
    if (!onApplyImprovement) return null;
    if (st === 'review') return null; // review card takes over
    return (
      <React.Fragment>
      <button
        onClick={() => st === 'error' ? setApplyState(s => ({ ...s, [key]: { status: 'idle' } })) : handleApply(text, tipo, index)}
        disabled={st === 'loading' || st === 'done'}
        style={{ flexShrink: 0, background: 'none', border: 'none', cursor: st === 'done' ? 'default' : 'pointer', padding: '2px 0', display: 'flex', alignItems: 'center', gap: 4,
          fontSize: 10, fontWeight: 600, letterSpacing: '0.03em', fontFamily: 'var(--font-mono)',
          color: st === 'done' ? 'var(--success)' : st === 'error' ? 'var(--danger)' : 'var(--ai-500)',
          opacity: st === 'done' ? 0.8 : 1,
        }}>
        {st === 'loading' && <div style={{ width: 10, height: 10, borderRadius: '50%', border: '1.5px solid var(--border)', borderTopColor: 'var(--ai-500)', animation: 'spin 0.8s linear infinite' }} />}
        {st === 'done'    && <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>}
        {st === 'error'   && <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 11-2.12-9.36L23 10"/></svg>}
        {st === 'idle'    && <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>}
        {st === 'loading' ? 'A gerar...' : st === 'done' ? 'Aplicado' : st === 'error' ? 'Tentar de novo' : 'Melhorar'}
      </button>
      {st === 'error' && item.message && (
        <div style={{ fontSize: 11, color: 'var(--danger)', lineHeight: 1.45, marginTop: 6 }}>{item.message}</div>
      )}
      </React.Fragment>
    );
  };

  // Botão de melhoria específico para critérios - sabe o campo exacto
  const CriterionApplyBtn = ({ bKey, fieldName, critId, critLabel, feedback, currentValue, overrideStateKey, overrideCriterionData }) => {
    const stateKey = overrideStateKey || `crit_${bKey}_${fieldName}_${critId}`;
    const item = applyState[stateKey] || { status: 'idle' };
    const st   = item.status;
    if (!onApplyImprovement) return null;
    if (st === 'review') return null;
    const blockMap = { b1: 'block1', b2: 'block2', b3: 'block3', b4: 'block4', b5: 'block5' };
    const criterionData = overrideCriterionData || {
      block: blockMap[bKey], field: fieldName,
      criterion_id: critId, criterion_label: critLabel,
      feedback, current_value: currentValue,
    };
    const label = overrideStateKey ? `Melhorar ${fieldName.replace(/_/g,' ')}` : 'Melhorar';
    return (
      <React.Fragment>
      <button
        onClick={() => st === 'error'
          ? setApplyState(s => ({ ...s, [stateKey]: { status: 'idle' } }))
          : handleApply(feedback, 'lacuna', stateKey, criterionData)}
        disabled={st === 'loading' || st === 'done'}
        style={{ flexShrink: 0, background: st === 'idle' ? 'color-mix(in oklch, var(--ai-500) 8%, transparent)' : 'none',
          border: st === 'idle' ? '1px solid color-mix(in oklch, var(--ai-500) 25%, transparent)' : 'none',
          borderRadius: 5, cursor: st === 'done' ? 'default' : 'pointer',
          padding: st === 'idle' ? '3px 8px' : '2px 0',
          display: 'flex', alignItems: 'center', gap: 4,
          fontSize: 10, fontWeight: 600, fontFamily: 'var(--font-mono)',
          color: st === 'done' ? 'var(--success)' : st === 'error' ? 'var(--danger)' : 'var(--ai-500)',
        }}>
        {st === 'loading' && <div style={{ width: 10, height: 10, borderRadius: '50%', border: '1.5px solid var(--border)', borderTopColor: 'var(--ai-500)', animation: 'spin 0.8s linear infinite' }} />}
        {st === 'done'    && <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>}
        {st === 'error'   && <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 11-2.12-9.36L23 10"/></svg>}
        {st === 'idle'    && <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>}
        {st === 'loading' ? 'A gerar...' : st === 'done' ? 'Aplicado' : st === 'error' ? 'Tentar de novo' : label}
      </button>
      {st === 'error' && item.message && (
        <div style={{ fontSize: 11, color: 'var(--danger)', lineHeight: 1.45, marginTop: 6 }}>{item.message}</div>
      )}
      </React.Fragment>
    );
  };

  // Card de transparência KB - substitui KbSourcesLine
  const KbSourcesLine = ({ sources = [], total = 0, confidence, avgScore, semanticSources = [] }) => {
    const [expanded, setExpanded] = React.useState(false);
    const [showModal, setShowModal] = React.useState(false);
    if (!sources || sources.length === 0) return null;

    const LinhaFonte = ({ s }) => {
      const href = hrefFonteKb(s);
      const nome = String(s.file_name || s.source_label || 'Documento');
      const nomeUrl = s.type === 'url' && href && nome === href;
      const pags = textoPaginasKb(s.pages);
      return (
        <div style={{ padding:'8px 10px', borderRadius:5, border:'1px solid var(--border)', marginBottom:4 }}>
          {href ? (
            <a href={href} target="_blank" rel="noopener noreferrer" style={{ fontSize:12, color:'var(--ai-500,#3859D0)', textDecoration:'none', overflowWrap:'anywhere', lineHeight:1.45 }}>{nomeUrl ? href : nome}</a>
          ) : (
            <div style={{ fontSize:12, color:'var(--text)', overflowWrap:'anywhere', lineHeight:1.45 }}>{nome}</div>
          )}
          {s.folder ? <div style={{ fontSize:11, color:'var(--text-muted)', marginTop:3, overflowWrap:'anywhere' }}>{s.folder}</div> : null}
          {s.type === 'url' && href && !nomeUrl ? (
            <a href={href} target="_blank" rel="noopener noreferrer" style={{ display:'block', marginTop:3, fontSize:11, color:'var(--ai-500,#3859D0)', textDecoration:'none', overflowWrap:'anywhere', lineHeight:1.45 }}>{href}</a>
          ) : null}
          {s.type !== 'url' && pags ? (
            <div style={{ fontSize:11, color:'var(--text-muted)', marginTop:3 }}>{pags}</div>
          ) : null}
          {s.type !== 'url' && href ? (
            <a href={href} target="_blank" rel="noopener noreferrer" style={{ display:'inline-block', marginTop:6, fontSize:11, fontWeight:650, color:'var(--ai-500,#3859D0)', textDecoration:'none' }}>Abrir no OneDrive</a>
          ) : null}
        </div>
      );
    };

    const confianca = confiancaKbApresentada(confidence, sources);
    const confColor = confianca === 'high' ? '#15803d' : confianca === 'medium' ? '#b45309' : confianca === 'low' ? '#dc2626' : '#94a3b8';
    const confLabel = confianca === 'high' ? 'Alta' : confianca === 'medium' ? 'Média' : confianca === 'low' ? 'Baixa' : '—';
    const confBg    = confianca === 'high' ? 'rgba(21,128,61,.08)' : confianca === 'medium' ? 'rgba(180,83,9,.08)' : confianca === 'low' ? 'rgba(220,38,38,.06)' : 'transparent';
    const links = [];
    const hrefVistos = new Set();
    for (const s of sources) {
      const href = hrefFonteKb(s);
      if (!href || hrefVistos.has(href)) continue;
      hrefVistos.add(href);
      const nome = String(s.file_name || '').trim();
      const rotulo = s.type === 'url' && (!nome || nome === href) ? href : (nome || s.source_label || 'Documento');
      links.push({ href, rotulo });
    }
    const nExcertos = Number(total) || sources.length;
    const textoExcertos = nExcertos === 1 ? '1 excerto' : `${nExcertos} excertos`;

    // Agrupar por source_label para evitar repetição
    const grouped = sources.reduce((acc, s) => {
      const key = s.source_label || s.source;
      if (!acc[key]) acc[key] = [];
      acc[key].push(s);
      return acc;
    }, {});

    return (
      <>
        {/* Card compacto */}
        <div style={{ marginBottom:8, marginTop:4, borderRadius:7, border:'1px solid var(--border)', overflow:'hidden' }}>
          <div style={{ display:'flex', alignItems:'center', gap:8, padding:'6px 10px', background:'var(--bg-sunken)', cursor:'pointer' }} onClick={() => setExpanded(e => !e)}>
            <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="var(--text-dim)" strokeWidth="2" strokeLinecap="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
            <span style={{ fontSize:10, fontFamily:'var(--font-mono)', color:'var(--text-muted)', flex:1 }}>
              KB verificado · {textoExcertos} · {Object.keys(grouped).join(', ')}
            </span>
            {confidence && (
              <span style={{ fontSize:9, fontWeight:700, padding:'1px 6px', borderRadius:3, background:confBg, color:confColor, fontFamily:'var(--font-mono)' }}>
                {confLabel}
              </span>
            )}
            <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="var(--text-dim)" strokeWidth="2.5" strokeLinecap="round" style={{ transform: expanded ? 'rotate(180deg)' : 'none', transition:'transform .15s' }}><polyline points="6 9 12 15 18 9"/></svg>
          </div>
          {links.length > 0 && (
            <div style={{ padding:'6px 10px', borderTop:'1px solid var(--border)', background:'#fff' }} onClick={e => e.stopPropagation()}>
              {links.map(l => (
                <a key={l.href} href={l.href} target="_blank" rel="noopener noreferrer" style={{ display:'block', fontSize:11, color:'var(--ai-500,#3859D0)', textDecoration:'none', overflowWrap:'anywhere', lineHeight:1.45 }}>{l.rotulo}</a>
              ))}
            </div>
          )}
          {expanded && (
            <div style={{ padding:'8px 10px', fontSize:10, fontFamily:'var(--font-mono)', borderTop:'1px solid var(--border)' }}>
              {Object.entries(grouped).map(([label, items]) => (
                <div key={label} style={{ marginBottom:4 }}>
                  <span style={{ fontWeight:600, color:'var(--text-muted)' }}>{label}</span>
                  <div style={{ marginTop:4 }}>
                    {items.map((s, i) => <LinhaFonte key={i} s={s} />)}
                  </div>
                </div>
              ))}
              {semanticSources.length > 0 && (
                <div style={{ marginTop:4, color:'var(--text-dim)', borderTop:'1px solid var(--border)', paddingTop:4 }}>
                  + análise semântica: {semanticSources.map(s => s.source_label || s.source).join(', ')}
                </div>
              )}
              <button onClick={() => setShowModal(true)} style={{ marginTop:6, background:'none', border:'none', color:'var(--ai-500)', cursor:'pointer', fontSize:10, fontFamily:'var(--font-mono)', padding:0 }}>
                Ver fontes detalhadas →
              </button>
            </div>
          )}
        </div>

        {/* Modal fontes detalhadas */}
        {showModal && (
          <div style={{ position:'fixed', inset:0, background:'rgba(0,0,0,0.4)', zIndex:9999, display:'flex', alignItems:'center', justifyContent:'center' }} onClick={() => setShowModal(false)}>
            <div style={{ background:'var(--bg-elev,#fff)', borderRadius:10, padding:'20px 24px', maxWidth:680, width:'92%', maxHeight:'80vh', overflowY:'auto', boxShadow:'0 8px 32px rgba(17,41,84,.18)' }} onClick={e => e.stopPropagation()}>
              <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:16 }}>
                <div style={{ fontSize:14, fontWeight:700, color:'var(--text)' }}>Fontes consultadas na KB</div>
                <button onClick={() => setShowModal(false)} style={{ background:'none', border:'none', cursor:'pointer', color:'var(--text-dim)', fontSize:18, lineHeight:1 }}>×</button>
              </div>
              <div style={{ fontSize:11, color:'var(--text-muted)', marginBottom:12 }}>
                {total} excertos · confiança <strong style={{ color:confColor }}>{confLabel}</strong>{avgScore ? ` (score médio ${avgScore})` : ''}
              </div>
              {Object.entries(grouped).map(([label, items]) => (
                <div key={label} style={{ marginBottom:14 }}>
                  <div style={{ fontSize:11, fontWeight:700, color:'var(--text)', marginBottom:6, padding:'4px 8px', background:'var(--bg-sunken)', borderRadius:5 }}>{label}</div>
                  {items.map((s, i) => <LinhaFonte key={i} s={s} />)}
                </div>
              ))}
            </div>
          </div>
        )}
      </>
    );
  };

  const CriterionReviewCard = ({ bKey, fieldName, critId, overrideStateKey, numCriteriaFixed = 1 }) => {
    const stateKey = overrideStateKey || `crit_${bKey}_${fieldName}_${critId}`;
    const item = applyState[stateKey];
    if (!item || item.status !== 'review' || !item.result) return null;
    const { block, field, field_label, value, _kb_sources, _kb_total, _kb_confidence, _kb_avg_score, _kb_semantic_sources } = item.result;
    const currentRaw = form?.[block]?.[field];
    const beforeStr  = formatVal(currentRaw);
    let afterParsed  = value;
    if (typeof value === 'string' && value.trim().startsWith('[')) {
      try { afterParsed = JSON.parse(value); } catch {}
    }
    const afterStr = formatVal(afterParsed);
    return (
      <div style={{ margin: '6px 0 10px', padding: '12px 14px', borderRadius: 9, background: 'rgba(56,89,208,.04)', border: '1px solid rgba(56,89,208,.18)' }}>
        <div style={{ fontSize: 9, fontWeight: 700, color: 'var(--ai-500)', marginBottom: 10, fontFamily: 'var(--font-mono)', letterSpacing: '0.04em', textTransform: 'uppercase' }}>
          Sugestão Digi AI - {field_label || field}
        </div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 10 }}>
          <div>
            <div style={{ fontSize: 9, fontWeight: 600, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', letterSpacing: '0.06em', marginBottom: 4, textTransform: 'uppercase' }}>Actual</div>
            <div style={{ fontSize: 11, color: 'var(--text-muted)', background: 'var(--bg-sunken)', borderRadius: 6, padding: '8px 10px', lineHeight: 1.7, whiteSpace: 'pre-line' }}>
              {beforeStr || <span style={{ fontStyle: 'italic' }}>não preenchido</span>}
            </div>
          </div>
          <div>
            <div style={{ fontSize: 9, fontWeight: 600, color: 'var(--ai-500)', fontFamily: 'var(--font-mono)', letterSpacing: '0.06em', marginBottom: 4, textTransform: 'uppercase' }}>Sugestão Digi AI</div>
            {edicaoManual?.alvo === 'sugestao' && edicaoManual.key === stateKey ? (
              <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
                {CAMPOS_LISTA_TEXTO.has(field) && <div style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>Uma linha por item.</div>}
                <textarea value={edicaoManual.texto} onChange={e => setEdicaoManual(s => s ? { ...s, texto: e.target.value } : s)} rows={6} style={{ width: '100%', boxSizing: 'border-box', padding: '8px 10px', borderRadius: 6, border: '1px solid rgba(56,89,208,.35)', background: '#fff', color: 'var(--text)', fontSize: 12, fontFamily: 'var(--font-body)', lineHeight: 1.5, resize: 'vertical' }} />
                <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
                  <button type="button" className="btn" style={{ fontSize: 11, height: 28, padding: '0 12px' }} onClick={() => setEdicaoManual(null)}>Cancelar</button>
                  <button type="button" className="btn btn-ai" style={{ fontSize: 11, height: 28, padding: '0 12px' }} onClick={() => {
                    const valor = valorEdicaoCampo(field, edicaoManual.texto, afterParsed);
                    setApplyState(s => {
                      const cur = s[stateKey];
                      if (!cur?.result) return s;
                      return { ...s, [stateKey]: { ...cur, result: { ...cur.result, value: valor } } };
                    });
                    setEdicaoManual(null);
                  }}>Guardar</button>
                </div>
              </div>
            ) : (
              <div style={{ fontSize: 11, color: 'var(--text)', background: 'rgba(56,89,208,.06)', border: '1px solid rgba(56,89,208,.15)', borderRadius: 6, padding: '8px 10px', lineHeight: 1.7, whiteSpace: 'pre-line' }}>
                {afterStr || '—'}
              </div>
            )}
          </div>
        </div>
        <KbSourcesLine sources={_kb_sources} total={_kb_total} confidence={_kb_confidence} avgScore={_kb_avg_score} semanticSources={_kb_semantic_sources} />
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <button className="btn" style={{ fontSize: 11 }} onClick={() => setApplyState(s => ({ ...s, [stateKey]: { status: 'idle' } }))}>Rejeitar</button>
          <div style={{ display: 'flex', gap: 6 }}>
            {onApplyImprovement && (
              <button className="btn" style={{ fontSize: 11 }} onClick={() => {
                if (edicaoManual?.alvo === 'sugestao' && edicaoManual.key === stateKey) { setEdicaoManual(null); return; }
                setEdicaoManual({
                  key: stateKey, alvo: 'sugestao',
                  block, field, label: field_label || field,
                  texto: textoEdicaoCampo(field, afterParsed),
                  aGravar: false, erro: '',
                });
              }}>
                Editar manualmente
              </button>
            )}
            <button className="btn btn-ai" style={{ fontSize: 11, display: 'flex', alignItems: 'center', gap: 5 }}
              onClick={() => {
                const editada = edicaoManual?.alvo === 'sugestao' && edicaoManual.key === stateKey;
                const result = editada ? { ...item.result, value: valorEdicaoCampo(field, edicaoManual.texto, afterParsed) } : item.result;
                if (editada) setEdicaoManual(null);
                handleConfirm(stateKey, result);
              }}>
              <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
              Confirmar e aplicar
            </button>
          </div>
        </div>
      </div>
    );
  };

  const ReviewCard = ({ itemKey }) => {
    const item = applyState[itemKey];
    if (!item || item.status !== 'review' || !item.result) return null;
    const { block, field, field_label, value, _kb_sources, _kb_total, _kb_confidence, _kb_avg_score, _kb_semantic_sources } = item.result;
    const currentRaw = form?.[block]?.[field];
    const beforeStr  = formatVal(currentRaw);
    let afterParsed  = value;
    if (typeof value === 'string' && value.trim().startsWith('[')) {
      try { afterParsed = JSON.parse(value); } catch {}
    }
    const afterStr = formatVal(afterParsed);
    const blockLabel = BLOCK_NAMES_B6[block] || block;
    return (
      <div style={{ margin: '4px 0 10px 8px', padding: '14px 16px', borderRadius: 9, background: 'rgba(56,89,208,.04)', border: '1px solid rgba(56,89,208,.18)' }}>
        <div style={{ fontSize: 10, fontWeight: 700, color: 'var(--ai-500)', marginBottom: 12, fontFamily: 'var(--font-mono)', letterSpacing: '0.04em', textTransform: 'uppercase' }}>
          {field_label || field} - {blockLabel}
        </div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 12 }}>
          <div>
            <div style={{ fontSize: 9, fontWeight: 600, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', letterSpacing: '0.06em', marginBottom: 5, textTransform: 'uppercase' }}>Actual</div>
            <div style={{ fontSize: 11, color: 'var(--text-muted)', background: 'var(--bg-sunken, #f1f5f9)', borderRadius: 6, padding: '8px 10px', lineHeight: 1.7, whiteSpace: 'pre-line' }}>
              {beforeStr || <span style={{ color: 'var(--text-dim)', fontStyle: 'italic' }}>não preenchido</span>}
            </div>
          </div>
          <div>
            <div style={{ fontSize: 9, fontWeight: 600, color: 'var(--ai-500)', fontFamily: 'var(--font-mono)', letterSpacing: '0.06em', marginBottom: 5, textTransform: 'uppercase' }}>Sugestão Digi AI</div>
            <div style={{ fontSize: 11, color: 'var(--text)', background: 'rgba(56,89,208,.06)', border: '1px solid rgba(56,89,208,.15)', borderRadius: 6, padding: '8px 10px', lineHeight: 1.7, whiteSpace: 'pre-line' }}>
              {afterStr || '—'}
            </div>
          </div>
        </div>
        <KbSourcesLine sources={_kb_sources} total={_kb_total} confidence={_kb_confidence} avgScore={_kb_avg_score} semanticSources={_kb_semantic_sources} />
        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
          <button className="btn" style={{ fontSize: 11 }} onClick={() => handleReject(itemKey)}>Rejeitar</button>
          <button className="btn btn-ai" style={{ fontSize: 11, display: 'flex', alignItems: 'center', gap: 5 }} onClick={() => handleConfirm(itemKey, item.result)}>
            <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
            Confirmar
          </button>
        </div>
      </div>
    );
  };

  return (
    <div>
      {consulta && (() => {
        const lista = (apreciation?.correccoes_aplicadas || [])
          .filter(c => c.block === consulta.block && c.field === consulta.field)
          .slice()
          .reverse();
        const quando = (iso) => {
          const d = new Date(iso);
          if (Number.isNaN(d.getTime())) return '';
          return d.toLocaleString('pt-PT', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' });
        };
        return (
          <div style={{ position:'fixed', inset:0, background:'rgba(0,0,0,0.4)', zIndex:9999, display:'flex', alignItems:'center', justifyContent:'center' }} onClick={() => setConsulta(null)}>
            <div style={{ background:'var(--bg-elev,#fff)', borderRadius:10, padding:'20px 24px', maxWidth:680, width:'92%', maxHeight:'80vh', overflowY:'auto', boxShadow:'0 8px 32px rgba(17,41,84,.18)' }} onClick={e => e.stopPropagation()}>
              <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:16 }}>
                <div style={{ fontSize:14, fontWeight:700, color:'var(--text)' }}>{consulta.label || lista[0]?.field_label || 'Correcção aplicada'}</div>
                <button type="button" onClick={() => setConsulta(null)} style={{ background:'none', border:'none', cursor:'pointer', color:'var(--text-dim)', fontSize:18, lineHeight:1 }}>×</button>
              </div>
              {lista.map((c, i) => (
                <div key={`${c.em || i}`} style={{ marginBottom: i < lista.length - 1 ? 18 : 0, paddingBottom: i < lista.length - 1 ? 18 : 0, borderBottom: i < lista.length - 1 ? '1px solid var(--border)' : 'none' }}>
                  {c.em && <div style={{ fontSize:11, color:'var(--text-muted)', marginBottom:10 }}>{quando(c.em)}</div>}
                  <div style={{ fontSize:9, fontWeight:600, color:'var(--text-dim)', fontFamily:'var(--font-mono)', letterSpacing:'0.06em', marginBottom:4, textTransform:'uppercase' }}>Antes</div>
                  <div style={{ fontSize:12, color:'var(--text-muted)', background:'var(--bg-sunken)', borderRadius:6, padding:'8px 10px', lineHeight:1.6, whiteSpace:'pre-line', marginBottom:10 }}>{c.antes || 'não preenchido'}</div>
                  <div style={{ fontSize:9, fontWeight:600, color:'var(--ai-500)', fontFamily:'var(--font-mono)', letterSpacing:'0.06em', marginBottom:4, textTransform:'uppercase' }}>Depois</div>
                  <div style={{ fontSize:12, color:'var(--text)', background:'rgba(56,89,208,.06)', border:'1px solid rgba(56,89,208,.15)', borderRadius:6, padding:'8px 10px', lineHeight:1.6, whiteSpace:'pre-line' }}>{c.depois || '—'}</div>
                  {(c.fontes || []).length > 0
                    ? <div style={{ marginTop:10 }}><KbSourcesLine sources={c.fontes} total={c.kb_total} confidence={c.kb_confidence} avgScore={c.kb_avg_score} /></div>
                    : <div style={{ marginTop:10, fontSize:11, color:'var(--text-muted)' }}>A base consultada não ficou registada.</div>}
                </div>
              ))}
            </div>
          </div>
        );
      })()}
      {/* Fontes importadas - integradas no score card abaixo */}

      {/* Loading - mesmo padrão que campanhas (conceito/copy) */}
      {phase === 'loading' && (() => {
        const totalSteps = STEPS.length;
        const doneSteps  = STEPS.filter(s => s.step < currentStep).length;
        const overallPct = Math.round(((doneSteps + stepPct / 100) / totalSteps) * 100);
        return (
          <div style={{ background: 'var(--bg-elev, #fff)', border: '1px solid var(--border, #e2e8f0)', borderRadius: 10, padding: '24px 24px 20px', marginBottom: 16 }}>
            <style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
            {/* Header */}
            <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12, marginBottom: 18 }}>
              <div style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(56,89,208,.08)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
                <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="var(--ai-500, #3859D0)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                  <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
                </svg>
              </div>
              <div style={{ flex: 1 }}>
                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 5 }}>
                  <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--text, #1d2e38)', fontFamily: 'var(--font-display, Montserrat, sans-serif)' }}>A gerar apreciação</div>
                  <span style={{ fontSize: 13, fontWeight: 700, color: 'var(--ai-500, #3859D0)', fontFamily: 'var(--font-mono, monospace)' }}>{overallPct}%</span>
                </div>
                <div style={{ height: 4, borderRadius: 99, background: 'var(--bg-sunken, #f1f5f9)', overflow: 'hidden', marginBottom: 4 }}>
                  <div style={{ height: '100%', borderRadius: 99, background: 'var(--ai-500, #3859D0)', width: `${overallPct}%`, transition: 'width 0.1s linear' }} />
                </div>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
                  <span style={{ fontSize: 10, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>Digi AI</span>
                  <span style={{ fontSize: 10, color: 'var(--text-dim)' }}>·</span>
                  <span style={{ fontSize: 10, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>~{EST_TOKENS}K tokens</span>
                  <span style={{ fontSize: 10, color: 'var(--text-dim)' }}>·</span>
                  <span style={{ fontSize: 10, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>~{EST_COST_EUR}</span>
                  <span style={{ fontSize: 10, color: 'var(--text-dim)' }}>·</span>
                  <span style={{
                    fontSize: 11, fontWeight: 700, fontFamily: 'var(--font-mono)',
                    color: 'var(--ai-500, #3859D0)',
                    display: 'flex', alignItems: 'center', gap: 3,
                  }}>
                    <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
                      <circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>
                    </svg>
                    {timeLeft}s
                  </span>
                </div>
              </div>
            </div>
            {/* Steps */}
            <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
              {STEPS.map(s => {
                const done    = currentStep > s.step;
                const active  = currentStep === s.step;
                const pending = currentStep < s.step;
                const pct     = done ? 100 : active ? stepPct : 0;
                return (
                  <div key={s.step}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: active ? 5 : 0 }}>
                      <div style={{
                        width: 26, height: 26, borderRadius: '50%', flexShrink: 0,
                        display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 10, fontWeight: 700,
                        background: done ? 'rgba(21,128,61,.1)' : active ? 'rgba(56,89,208,.1)' : 'var(--bg-sunken, #f1f5f9)',
                        border: `1.5px solid ${done ? 'rgba(21,128,61,.3)' : active ? 'var(--ai-500, #3859D0)' : 'var(--border, #e2e8f0)'}`,
                        color: done ? '#15803d' : active ? 'var(--ai-500, #3859D0)' : '#94a3b8',
                        transition: 'all 0.3s',
                      }}>
                        {done
                          ? <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
                          : s.step
                        }
                      </div>
                      <span style={{ fontSize: 12.5, fontWeight: active ? 600 : 400, color: pending ? '#94a3b8' : 'var(--text, #1d2e38)', flex: 1 }}>{s.label}</span>
                      {active && <span style={{ fontSize: 10, fontWeight: 700, color: 'var(--ai-500, #3859D0)', fontFamily: 'var(--font-mono, monospace)', flexShrink: 0 }}>{Math.round(pct)}%</span>}
                      {done  && <span style={{ fontSize: 10, color: '#15803d', fontFamily: 'var(--font-mono, monospace)', flexShrink: 0 }}>100%</span>}
                    </div>
                    {active && (
                      <div style={{ marginLeft: 36, height: 3, borderRadius: 99, background: 'rgba(56,89,208,.1)', overflow: 'hidden' }}>
                        <div style={{ height: '100%', borderRadius: 99, background: 'var(--ai-500, #3859D0)', width: `${pct}%`, transition: 'width 0.1s linear' }} />
                      </div>
                    )}
                    {done && (
                      <div style={{ marginLeft: 36, height: 2, borderRadius: 99, background: 'rgba(21,128,61,.2)' }} />
                    )}
                  </div>
                );
              })}
            </div>
          </div>
        );
      })()}

      {/* Error */}
      {phase === 'error' && (
        <FormCard>
          <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 12, padding: '8px 0', textAlign: 'center' }}>
            <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="var(--danger)" strokeWidth="1.5" strokeLinecap="round">
              <path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>
            </svg>
            <div style={{ fontSize: 13, color: 'var(--danger)' }}>{errorMsg}</div>
            {canGenerate && <button className="btn btn-ai" onClick={runGenerate} style={{ marginTop: 4 }}>Tentar novamente</button>}
          </div>
        </FormCard>
      )}

      {/* Empty state */}
      {phase !== 'loading' && !apreciation && phase !== 'error' && (
        <FormCard>
          <div style={{ textAlign: 'center', padding: '24px 24px 16px', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 12 }}>
            <div style={{ width: 40, height: 40, borderRadius: 12, background: 'color-mix(in oklch, var(--ai-500) 10%, transparent)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
              <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--ai-500)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
              </svg>
            </div>
            <div style={{ fontSize: 15, fontWeight: 600, color: 'var(--text)' }}>Apreciação não gerada</div>
            <div style={{ fontSize: 13, color: 'var(--text-muted)', maxWidth: 400, lineHeight: 1.65 }}>
              {!hasContent
                ? 'Preenche pelo menos 40% dos blocos 1-3 para gerar a apreciação.'
                : <>A apreciação é <strong>obrigatória</strong> antes de submeter - o score deve atingir ≥ 80.</>}
            </div>
            {canGenerate && (
              <button className="btn btn-ai" onClick={runGenerate} disabled={!hasContent}>Gerar Apreciação</button>
            )}
          </div>
        </FormCard>
      )}

      {/* Generated content */}
      {apreciation && phase !== 'loading' && (() => {
        const score    = apreciation.quality_score || 0;
        const diag     = apreciation.diagnostico || {};
        const bk       = apreciation.breakdown || {};
        const insights = apreciation.insights_melhoria || [];
        const BLOCK_NAMES  = { b1: 'Bloco 1 - Produto', b2: 'Bloco 2 - Cliente', b3: 'Bloco 3 - Mercado', b5: 'Restrições' };
        const BLOCK_KEYS   = ['b1','b2','b3','b5'];
        const evals        = apreciation.evals || {};
        const hasEvalsData = !!(apreciation.evals);
        const scoreReady = score >= 80;
        return (
          <>
            {/* Score global */}
            <div style={{ background: 'var(--bg-elev, #ffffff)', border: `2px solid ${scoreReady ? 'color-mix(in srgb, var(--ai-500, #3859D0) 25%, transparent)' : 'var(--border)'}`, borderRadius: 10, padding: '20px 24px', marginBottom: 16, display: 'flex', alignItems: 'flex-start', gap: 20 }}>
              {/* Coluna esquerda - número */}
              <div style={{ textAlign: 'center', flexShrink: 0, minWidth: 52 }}>
                <div style={{ fontSize: 44, fontWeight: 700, color: scoreColor(score), lineHeight: 1, fontFamily: 'var(--font-display)' }}>{score}</div>
                <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: scoreColor(score), letterSpacing: '0.06em', marginTop: 3 }}>{scoreLabel(score)}</div>
              </div>
              {/* Coluna central - barra + meta */}
              <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 6, paddingTop: 4 }}>
                <div style={{ height: 8, borderRadius: 4, background: 'var(--border)', overflow: 'hidden' }}>
                  <div style={{ width: `${score}%`, height: '100%', background: scoreColor(score), borderRadius: 4, transition: 'width 0.6s ease' }} />
                </div>
                <div style={{ display: 'flex', alignItems: 'center', gap: 5, fontSize: 12, color: scoreReady ? 'var(--ai-500, #3859D0)' : 'var(--text-muted, #4a5e7a)', fontWeight: 600 }}>
                  {scoreReady
                    ? <><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg> Pronto para submeter</>
                    : `Necessário ≥ 80 para submeter · faltam ${80 - score} pontos`
                  }
                </div>
                <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
                  <span style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>
                    Gerado {apreciation.generated_at ? new Date(apreciation.generated_at).toLocaleDateString('pt-PT', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' }) : ''}
                  </span>
                  {sources.length > 0 && (
                    <>
                      <span style={{ fontSize: 10, color: 'var(--border)', fontFamily: 'var(--font-mono)' }}>·</span>
                      <span style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>
                        <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                          <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/>
                        </svg>
                        {sources.map(s => s.name || s.url).join(', ')}
                      </span>
                    </>
                  )}
                </div>
              </div>
              {/* Botão */}
              {canGenerate && (
                <button className="btn" onClick={runGenerate} style={{ flexShrink: 0, fontSize: 11, display: 'flex', alignItems: 'center', gap: 5, marginTop: 2 }}>
                  <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
                    <polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 11-2.12-9.36L23 10"/>
                  </svg>
                  Analisar Novamente
                </button>
              )}
            </div>

            {/* Card: análise por campo em falta - score < 100% mas sem evals */}
            {!hasEvalsData && score < 100 && canGenerate && (
              <div style={{ display:'flex', alignItems:'center', gap:14, padding:'14px 18px', borderRadius:9, background:'color-mix(in srgb, var(--ai-500,#3859D0) 5%, transparent)', border:'1px solid color-mix(in srgb, var(--ai-500,#3859D0) 20%, transparent)', marginBottom:8 }}>
                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--ai-500,#3859D0)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink:0 }}><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
                <div style={{ flex:1 }}>
                  <div style={{ fontSize:13, fontWeight:600, color:'var(--text)', marginBottom:2 }}>
                    {score}/100 - há melhorias disponíveis
                  </div>
                  <div style={{ fontSize:11, color:'var(--text-muted)' }}>
                    Corre a análise por campo para ver exactamente o que melhorar em cada bloco.
                  </div>
                </div>
                <button className="btn btn-ai" onClick={runGenerate} style={{ fontSize:12, flexShrink:0 }}>
                  Ver melhorias →
                </button>
              </div>
            )}

            {/* Banner melhorias aplicadas */}
            {improvementsApplied && (
              <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '10px 16px', borderRadius: 9, background: 'color-mix(in srgb, var(--ai-500, #3859D0) 6%, transparent)', border: '1px solid color-mix(in srgb, var(--ai-500, #3859D0) 20%, transparent)', marginBottom: 4 }}>
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--ai-500, #3859D0)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0 }}><polyline points="20 6 9 17 4 12"/></svg>
                <div style={{ flex: 1 }}>
                  <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text)', marginBottom: 2 }}>
                    Melhorias aplicadas
                    {scoreGain > 0 && <span style={{ color: 'var(--ai-500, #3859D0)', marginLeft: 8 }}>+{scoreGain} pts</span>}
                  </div>
                  {scoreGain > 0 && (
                    <div style={{ fontSize: 11, color: 'var(--text-muted)' }}>
                      Score actualizado: <strong style={{ color: 'var(--ai-500, #3859D0)' }}>{score}/100</strong>
                    </div>
                  )}
                </div>
                {canGenerate && <button className="btn" style={{ fontSize: 11, height: 28, padding: '0 12px', flexShrink: 0 }} onClick={runGenerate}>Re-analisar</button>}
              </div>
            )}

            {/* Resumo narrativo */}
            {apreciation.resumo && (
              <FormCard title="Resumo">
                <p style={{ margin: 0, fontSize: 13, color: 'var(--text)', lineHeight: 1.75 }}>{apreciation.resumo}</p>
              </FormCard>
            )}

            {/* Score por bloco - reformulado */}
            {(() => {
              // Labels PT para campos
              const FIELD_LABELS = {
                elevator_pitch: 'Elevator Pitch', usps: 'USPs - Pontos de Valor', certifications: 'Certificações Técnicas', differentiation_args: 'Argumentos de Diferenciação', applications: 'Aplicações Primárias',
                pain_points: 'Dores Principais', motivators: 'Motivadores de Compra', objections: 'Objecções Comuns', purchase_trigger: 'Trigger de Compra', decision_maker: 'Decisor de Compra', end_user: 'Utilizador Final',
                competitors: 'Concorrentes', market_trends: 'Tendências de Mercado', positioning_narrative: 'Posicionamento Desejado', sensitive_info: 'Intel Competitiva',
                key_message: 'Mensagem-Chave', objective: 'Objectivo', channels: 'Canais', commercial_offer: 'Oferta comercial', meta_ads_conversao: 'Conversão Meta', tone: 'Tom da Campanha', kpis: 'KPIs de Sucesso',
                prohibited_claims: 'Claims Proibidos', off_brand_messages: 'Mensagens Off-Brand', competitors_not_name: 'Concorrentes a Não Nomear', out_of_scope_markets: 'Mercados Fora de Âmbito', other_restrictions: 'Outras Restrições',
              };
              const CRIT_LABELS = { especificidade: 'Especificidade', especificidade_contexto: 'Especificidade do contexto', linguagem_persona: 'Linguagem da persona', consequencia_real: 'Consequência real', ordenacao_prioridade: 'Ordenação por prioridade', ordenacao_forca: 'Ordenação por força', ordenacao_frequencia: 'Ordenação por frequência', ordenacao_urgencia: 'Ordenação por urgência', ordenacao_impacto: 'Ordenação por impacto', ordenacao_criticidade: 'Ordenação por criticidade', ordenacao_relevancia: 'Ordenação por relevância', resultado_concreto: 'Resultado concreto', distincao_dores: 'Distinção das dores', especificidade_persona: 'Específico para esta persona', respondivel_usp: 'Respondível por USP/certificação', linguagem_hesitante: 'Linguagem real do hesitante', evento_concreto: 'Evento concreto', timing_identificavel: 'Timing identificável', ligacao_objectivo: 'Ligação ao objectivo', sintese_real: 'Síntese real', tensao_diferenciadora: 'Tensão diferenciadora', linguagem_mercado: 'Linguagem do mercado', coerencia_usps_dores: 'Coerência com USPs e dores', defensabilidade: 'Defensabilidade das claims', beneficio_cliente: 'Benefício para o cliente', unicidade_real: 'Unicidade real', ligacao_dor: 'Ligação a dor declarada', nao_sobreposicao: 'Sem sobreposição entre USPs', ligacao_usp: 'Ligação a USP específico', reconhecimento: 'Reconhecimento no mercado', especificidade_completa: 'Nome completo e específico', relevancia_regulatoria: 'Relevância regulatória', posicionamento_competitivo: 'Posicionamento competitivo real', verificabilidade: 'Verificabilidade', relevancia_decisor: 'Relevância para o decisor', distincao_usps: 'Distinção dos USPs', ligacao_concorrentes: 'Ligação aos concorrentes', relevancia_temporal: 'Relevância temporal', ligacao_produto: 'Ligação ao produto', relevancia_geografica: 'Relevância geográfica', accionabilidade_hooks: 'Accionabilidade para hooks', clareza_posicao: 'Clareza da posição', unicidade: 'Unicidade', coerencia_usps_diff: 'Coerência com USPs', credibilidade: 'Credibilidade', identificacao_especifica: 'Identificação específica', contexto_ameaca: 'Contexto da ameaça', coerencia_diferenciacao: 'Coerência com diferenciação', pontos_fortes: 'Pontos fortes percebidos', diferenciacao_real: 'Diferenciação real', tensao_criativa: 'Tensão criativa', clareza_destinatario: 'Clareza do destinatário', completude: 'Completude', ligacao_dor_dominante: 'Ligação à dor dominante', coerencia_kpis: 'Coerência com KPIs', coerencia_canais: 'Coerência com canais', coerencia_mensagem: 'Coerência com mensagem-chave', coerencia_timeline: 'Coerência com timeline', adequacao_produto: 'Adequação ao produto', especificidade_accionavel: 'Especificidade accionável', inclui_evitar: 'Inclui o que evitar', coerencia_persona: 'Coerência com a persona', diferenciacao_marca: 'Diferenciação da marca', aplicabilidade_multicanal: 'Aplicabilidade multi-canal', mensuráveis: 'Concretos sem meta inventada', coerencia_objectivo: 'Coerência com objectivo', kpi_negocio: 'Negócio separado do canal', media_paga: 'CPL, CPC e ROAS só no pago', catalogo_canais: 'Catálogo único', coerencia_canais_objectivo: 'Coerência com o objectivo', organico_vs_pago: 'Orgânico e pago', oferta_quando_activacao: 'Oferta quando há promoção', tipo_oferta: 'Tipo de oferta', completude_oferta: 'Oferta completa', tipo_definido: 'Tipo definido', coerencia_cta: 'Coerência com o CTA', contexto_profissional: 'Contexto profissional específico', responsabilidades: 'Responsabilidades e pressões', papel_decisao: 'Papel na decisão', coerencia_dores: 'Coerência com as dores', indicadores_confianca: 'Indicadores de confiança', contexto_uso: 'Contexto de uso diário', dor_diferente: 'Dor diferente do decisor', influencia_decisao: 'Influência na decisão', distincao_decisor: 'Distinção do decisor', confianca_tecnica: 'Critérios de confiança técnica', razao_documentada: 'Razão documentada', cobertura_riscos: 'Cobertura dos riscos', especificidade_estrategica: 'Especificidade estratégica', ligacao_posicionamento: 'Ligação ao posicionamento', cobertura_derives: 'Cobertura das derives', razao_estrategica: 'Razão estratégica', completude_concorrentes: 'Completude', alternativa: 'Alternativa definida', clareza_ambito: 'Clareza do âmbito', coerencia_mercados: 'Coerência com mercados declarados', classificacao_correcta: 'Classificação correcta' };
              const fieldLabel  = (f) => FIELD_LABELS[f] || f.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
              const critLabel   = (id) => CRIT_LABELS[id] || id.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
              const scoreLabel  = (s) => s === 0 ? 'Campo vazio' : s < 40 ? 'Fraco' : s < 60 ? 'A melhorar' : s < 80 ? 'Suficiente' : s < 90 ? 'Bom' : 'Excelente';

              // Calcular score potencial (se todos os campos melhorassem a 85)
              // potScore: score potencial se todas as melhorias fossem aplicadas
              // Regra: sem breakdown de campos → assume potencial de 85 (não sabemos o que está mal mas pode melhorar)
              //        com breakdown → campos com falhas vão a 85, campos ok ficam como estão
              //        tudo passou → mantém score actual
              const potScore = (bkk) => {
                const fields = evals[bkk]?.fields || [];
                if (!fields.length) return 100; // sem detalhe → potencial máximo
                const allPass = fields.every(f => (f.criteria||[]).every(c => c.passed));
                if (allPass) return Math.round(fields.reduce((s, f) => s + f.score, 0) / Math.max(fields.length, 1));
                // Campos com falhas → potencial 100 (se todas as sugestões forem aplicadas)
                return Math.round(fields.reduce((sum, f) => sum + (f.criteria?.some(c=>!c.passed) ? 100 : f.score), 0) / Math.max(fields.length, 1));
              };
              const potentialGlobal = Math.min(100, Math.round(
                potScore('b2') * 0.60 + potScore('b1') * 0.27 + potScore('b3') * 0.13
              ));
              // Blocos realmente vazios (score=0): o utilizador não preencheu
              const emptyHighWeightBlocks = ['b1','b2','b3'].filter(k => (bk[k]?.score ?? 0) === 0);
              // Blocos com conteúdo mas sem breakdown de campos (score>0, fields=[]): avaliação incompleta
              const noBreakdownBlocks = ['b1','b2','b3'].filter(k => {
                const hasScore = (bk[k]?.score ?? 0) > 0;
                const hasNoFields = (evals[k]?.fields||[]).length === 0;
                return hasScore && hasNoFields;
              });
              const totalCriteriaImprovements = BLOCK_KEYS.reduce((n, bkk) => n + (evals[bkk]?.fields||[]).reduce((m, f) => m + (f.criteria||[]).filter(c=>!c.passed).length, 0), 0);
              const totalImprovements = totalCriteriaImprovements + emptyHighWeightBlocks.length;

              const BLOCK_WEIGHT_LABEL = { b1:'Produto (peso 27%)', b2:'Cliente-alvo (peso 60%)', b3:'Mercado (peso 13%)' };
              const BLOCK_WEIGHT_PTS   = { b1: Math.round(85*0.27), b2: Math.round(85*0.60), b3: Math.round(85*0.13) };

              return (
                <FormCard title="Avaliação de qualidade">
                  {pontoErro && <div style={{ fontSize: 12, color: 'var(--danger)', marginBottom: 8 }}>{pontoErro}</div>}
                  {/* Resumo actual → potencial */}
                  <div style={{ display: 'grid', gridTemplateColumns: '1fr auto 1fr', gap: 12, alignItems: 'center', marginBottom: 16, padding: '14px 18px', borderRadius: 10, background: 'var(--bg-sunken)', border: '1px solid var(--border)' }}>
                    <div style={{ textAlign: 'center' }}>
                      <div style={{ fontSize: 28, fontWeight: 700, color: scoreColor(score), fontFamily: 'var(--font-display)', lineHeight: 1 }}>{score}</div>
                      <div style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', marginTop: 4 }}>Score actual</div>
                      <div style={{ fontSize: 10, color: scoreColor(score), fontFamily: 'var(--font-body)', marginTop: 2, fontWeight: 600 }}>{scoreLabel(score)}</div>
                    </div>
                    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4 }}>
                      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--text-dim)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>
                      <div style={{ fontSize: 9, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', textAlign: 'center', whiteSpace: 'nowrap' }}>{totalImprovements} melhorias{'\n'}disponíveis</div>
                    </div>
                    <div style={{ textAlign: 'center' }}>
                      <div style={{ fontSize: 28, fontWeight: 700, color: scoreColor(potentialGlobal), fontFamily: 'var(--font-display)', lineHeight: 1 }}>~{potentialGlobal}</div>
                      <div style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', marginTop: 4 }}>Score potencial</div>
                      <div style={{ fontSize: 10, color: scoreColor(potentialGlobal), fontFamily: 'var(--font-body)', marginTop: 2, fontWeight: 600 }}>{potentialGlobal >= 80 ? 'Pronto para submeter' : scoreLabel(potentialGlobal)}</div>
                    </div>
                  </div>

                  {/* Blocos realmente vazios → utilizador tem de preencher */}
                  {emptyHighWeightBlocks.map(bkk => (
                    <div key={bkk} style={{ display: 'flex', alignItems: 'flex-start', gap: 10, padding: '10px 14px', borderRadius: 8, background: 'color-mix(in oklch, var(--danger) 5%, transparent)', border: '1px solid color-mix(in oklch, var(--danger) 20%, transparent)', marginBottom: 8 }}>
                      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--danger)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0, marginTop: 1 }}><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
                      <div style={{ flex: 1 }}>
                        <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text)', marginBottom: 2 }}>
                          {{ b1:'Bloco 1 - Produto', b2:'Bloco 2 - Cliente-alvo', b3:'Bloco 3 - Mercado' }[bkk]} está vazio - principal limitador do score
                        </div>
                        <div style={{ fontSize: 11, color: 'var(--text-muted)', lineHeight: 1.5 }}>
                          Com peso de {({b1:'27%',b2:'60%',b3:'13%'})[bkk]}, preenchê-lo pode adicionar até <strong>+{BLOCK_WEIGHT_PTS[bkk]} pontos</strong> ao score.
                        </div>
                      </div>
                      {onNavigateToBlock && (
                        <button className="btn btn-ai" style={{ fontSize: 11, height: 28, padding: '0 12px', flexShrink: 0 }}
                          onClick={() => onNavigateToBlock(parseInt(bkk.slice(1), 10))}>
                          Ir ao bloco →
                        </button>
                      )}
                    </div>
                  ))}

                  {/* Blocos com conteúdo mas sem breakdown → avaliação incompleta */}

                  <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
                    {BLOCK_KEYS.map(bKey => {
                      const isGuardrail = bKey === 'b5';
                      const blockScore  = bk[bKey]?.score ?? 0;
                      const blockEval   = evals[bKey];
                      const fields      = blockEval?.fields || [];
                      const failedCount = fields.reduce((n, f) => n + (f.criteria||[]).filter(c=>!c.passed).length, 0);
                      const isExpanded  = expandedBlock === bKey;
                      const bLabel      = { b1:'Bloco 1 - Produto', b2:'Bloco 2 - Cliente-alvo', b3:'Bloco 3 - Mercado', b5:'Bloco 5 - Restrições' }[bKey];

                      return (
                        <div key={bKey} style={{ border: `1px solid ${isGuardrail ? 'var(--border)' : 'var(--border)'}`, borderRadius: 10, overflow: 'hidden', opacity: isGuardrail ? 0.85 : 1 }}>
                          {/* Block header */}
                          <div onClick={() => setExpandedBlock(isExpanded ? null : bKey)} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '12px 16px', cursor: 'pointer', background: isExpanded ? 'var(--bg-sunken)' : 'transparent' }}>
                            <div style={{ flex: 1 }}>
                              <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                                <span style={{ fontSize: 12, fontWeight: 600, color: 'var(--text)' }}>{bLabel}</span>
                                {isGuardrail && <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', background: 'var(--bg-sunken)', padding: '1px 5px', borderRadius: 3 }}>não conta para o score</span>}
                              </div>
                            </div>
                            {failedCount > 0 && <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', padding: '2px 8px', borderRadius: 99, background: 'var(--bg-sunken)', color: 'var(--text-muted)', fontWeight: 700, whiteSpace: 'nowrap' }}>{failedCount} {failedCount === 1 ? 'ponto' : 'pontos'} a melhorar</span>}
                            {failedCount === 0 && fields.length > 0 && <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--ai-500, #3859D0)' }}>✓ Tudo bem</span>}
                            {!isGuardrail && (
                              <>
                                <div style={{ width: 80, height: 5, borderRadius: 99, background: 'var(--border)', overflow: 'hidden', flexShrink: 0 }}>
                                  <div style={{ width: `${blockScore}%`, height: '100%', background: scoreColor(blockScore), borderRadius: 99 }} />
                                </div>
                                <div style={{ textAlign: 'right', flexShrink: 0, minWidth: 70 }}>
                                  <div style={{ fontSize: 13, fontWeight: 700, color: scoreColor(blockScore), fontFamily: 'var(--font-mono)', lineHeight: 1 }}>{blockScore}</div>
                                  <div style={{ fontSize: 9, color: 'var(--text-dim)', fontFamily: 'var(--font-body)', marginTop: 1 }}>{scoreLabel(blockScore)}</div>
                                </div>
                              </>
                            )}
                            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="var(--text-dim)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0, transform: isExpanded ? 'rotate(180deg)' : 'none', transition: 'transform .15s' }}><polyline points="6 9 12 15 18 9"/></svg>
                          </div>

                          {/* Fields */}
                          {isExpanded && (
                            <div style={{ borderTop: '1px solid var(--border)', padding: '10px 16px', display: 'flex', flexDirection: 'column', gap: 6 }}>
                              {fields.length === 0 && <div style={{ fontSize: 11, color: 'var(--text-dim)', fontStyle: 'italic', padding: '4px 0' }}>Regenera a apreciação para ver o detalhe por campo.</div>}
                              {fields.map(fieldEval => {
                                const fKey   = `${bKey}.${fieldEval.field}`;
                                const fExp   = expandedField === fKey;
                                const fFailed = (fieldEval.criteria||[]).filter(c=>!c.passed);
                                const fPassed = (fieldEval.criteria||[]).filter(c=>c.passed);
                                const fVal   = form?.[`block${bKey.slice(1)}`]?.[fieldEval.field];
                                const fEmpty  = fieldEval.score === 0;
                                const temConsulta = (apreciation?.correccoes_aplicadas || []).some(c => c.block === `block${bKey.slice(1)}` && c.field === fieldEval.field);
                                return (
                                  <div key={fieldEval.field} style={{ border: '1px solid var(--border)', borderRadius: 8, overflow: 'hidden', background: 'transparent' }}>
                                    <div onClick={() => setExpandedField(fExp ? null : fKey)} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '9px 12px', cursor: 'pointer', background: fExp ? 'var(--bg-sunken)' : 'transparent' }}>
                                      <div style={{ flex: 1, display: 'flex', alignItems: 'center', gap: 8 }}>
                                        <span style={{ fontSize: 12, color: 'var(--text)', fontWeight: fFailed.length > 0 ? 500 : 400 }}>{fieldLabel(fieldEval.field)}</span>
                                        {fEmpty && <span style={{ fontSize: 9, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', background: 'var(--bg-sunken)', padding: '1px 6px', borderRadius: 3 }}>Campo vazio</span>}
                                      </div>
                                      {fFailed.length > 0 && !fEmpty && <span style={{ fontSize: 10, color: scoreColor(fieldEval.score), fontFamily: 'var(--font-mono)', whiteSpace: 'nowrap' }}>{fFailed.length} {fFailed.length === 1 ? 'ponto' : 'pontos'} a melhorar</span>}
                                      {fFailed.length === 0 && <span style={{ fontSize: 10, color: 'var(--ai-500, #3859D0)', fontFamily: 'var(--font-mono)' }}>✓</span>}
                                      <div style={{ width: 50, height: 4, borderRadius: 99, background: 'var(--border)', overflow: 'hidden', flexShrink: 0 }}>
                                        <div style={{ width: `${fieldEval.score}%`, height: '100%', background: scoreColor(fieldEval.score), borderRadius: 99 }} />
                                      </div>
                                      <div style={{ fontSize: 11, fontWeight: 700, color: scoreColor(fieldEval.score), fontFamily: 'var(--font-mono)', width: 24, textAlign: 'right', flexShrink: 0 }}>{fieldEval.score}</div>
                                      {(fFailed.length > 0 || fPassed.length > 0) && <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="var(--text-dim)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0, transform: fExp ? 'rotate(180deg)' : 'none', transition: 'transform .15s' }}><polyline points="6 9 12 15 18 9"/></svg>}
                                    </div>
                                    {fExp && (
                                      <div style={{ borderTop: '1px solid var(--border)', padding: '10px 12px', display: 'flex', flexDirection: 'column', gap: 8 }}>
                                        {(fPassed.length > 0 || temConsulta || canGenerate) && (
                                          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                                            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, flex: 1 }}>
                                              {fPassed.map(c => <span key={c.id} style={{ fontSize: 9, fontFamily: 'var(--font-mono)', color: 'var(--ai-500, #3859D0)', background: 'color-mix(in srgb, var(--ai-500, #3859D0) 8%, transparent)', padding: '2px 7px', borderRadius: 99 }}>✓ {critLabel(c.id)}</span>)}
                                            </div>
                                            <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginLeft: 'auto', flexShrink: 0 }}>
                                              {canGenerate && (
                                                <button type="button" onClick={e => runPonto(bKey, fieldEval.field, e)} disabled={!!pontoEmCurso || phase === 'loading'}
                                                  style={{ background: 'none', border: 'none', padding: 0, cursor: pontoEmCurso || phase === 'loading' ? 'default' : 'pointer', fontSize: 10, fontWeight: 650, color: 'var(--ai-500, #3859D0)', fontFamily: 'var(--font-mono)', whiteSpace: 'nowrap', opacity: pontoEmCurso && pontoEmCurso !== `${bKey}.${fieldEval.field}` ? 0.4 : 1 }}>
                                                  {pontoEmCurso === `${bKey}.${fieldEval.field}` ? 'A analisar...' : 'Analisar'}
                                                </button>
                                              )}
                                              {temConsulta && (
                                                <button type="button" onClick={e => { e.stopPropagation(); setConsulta({ block: `block${bKey.slice(1)}`, field: fieldEval.field, label: fieldLabel(fieldEval.field) }); }} style={{ background:'none', border:'none', padding:0, cursor:'pointer', fontSize:10, fontWeight:650, color:'var(--ai-500,#3859D0)', fontFamily:'var(--font-mono)', whiteSpace:'nowrap' }}>Consultar</button>
                                              )}
                                            </div>
                                          </div>
                                        )}
                                        {fFailed.map(crit => (
                                          <div key={crit.id} style={{ padding: '10px 12px', background: 'var(--bg-sunken)', border: '1px solid var(--border)', borderRadius: 7 }}>
                                            <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text)', marginBottom: 4 }}>{critLabel(crit.id)}</div>
                                            <div style={{ fontSize: 11, color: 'var(--text-muted)', lineHeight: 1.6 }}>{crit.feedback}</div>
                                          </div>
                                        ))}
                                        {fFailed.length > 0 && (() => {
                                          const fsk = `field_${bKey}_${fieldEval.field}`;
                                          const bm  = { b1:'block1', b2:'block2', b3:'block3', b4:'block4', b5:'block5' };
                                          const fcd = { block: bm[bKey], field: fieldEval.field, criteria_failed: fFailed.map(c=>({id:c.id,label:critLabel(c.id),feedback:c.feedback})), current_value: fVal };
                                          const aEditar = edicaoManual?.key === fsk;
                                          const guardarEdicao = async () => {
                                            if (!edicaoManual || edicaoManual.aGravar) return;
                                            setEdicaoManual(s => ({ ...s, aGravar: true, erro: '' }));
                                            const valor = valorEdicaoCampo(edicaoManual.field, edicaoManual.texto, form?.[edicaoManual.block]?.[edicaoManual.field]);
                                            const ok = await onApplyImprovement?.(edicaoManual.block, edicaoManual.field, valor, edicaoManual.label);
                                            if (ok === false) { setEdicaoManual(s => s ? { ...s, aGravar: false, erro: 'Não foi possível gravar.' } : s); return; }
                                            setEdicaoManual(null);
                                          };
                                          return (
                                            <>
                                              {aEditar && edicaoManual?.alvo !== 'sugestao' && (
                                                <div style={{ display: 'flex', flexDirection: 'column', gap: 8, paddingTop: 4, borderTop: '1px solid var(--border)' }}>
                                                  {(CAMPOS_LISTA_TEXTO.has(edicaoManual.field) || edicaoManual.field === 'kpis' || edicaoManual.field === 'channels') && (
                                                    <div style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>Uma linha por item.</div>
                                                  )}
                                                  <textarea value={edicaoManual.texto} onChange={e => setEdicaoManual(s => s ? { ...s, texto: e.target.value } : s)} rows={4} style={{ width: '100%', boxSizing: 'border-box', padding: '8px 10px', borderRadius: 7, border: '1px solid var(--border-strong, var(--border))', background: 'var(--bg-elev, #fff)', color: 'var(--text)', fontSize: 12, fontFamily: 'var(--font-body)', lineHeight: 1.5, resize: 'vertical' }} />
                                                  {edicaoManual.erro && <div style={{ fontSize: 11, color: 'var(--danger)' }}>{edicaoManual.erro}</div>}
                                                  <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
                                                    <button type="button" className="btn" style={{ fontSize: 11, height: 28, padding: '0 12px' }} onClick={() => setEdicaoManual(null)} disabled={edicaoManual.aGravar}>Cancelar</button>
                                                    <button type="button" className="btn btn-ai" style={{ fontSize: 11, height: 28, padding: '0 12px' }} onClick={guardarEdicao} disabled={edicaoManual.aGravar}>{edicaoManual.aGravar ? 'A gravar...' : 'Guardar'}</button>
                                                  </div>
                                                </div>
                                              )}
                                              <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', paddingTop: 4, borderTop: '1px solid var(--border)' }}>
                                                {onApplyImprovement && <button className="btn" style={{ fontSize: 11, height: 28, padding: '0 12px' }} onClick={() => {
                                                  const rev = applyState[fsk];
                                                  if (rev?.status === 'review' && rev.result) {
                                                    if (edicaoManual?.alvo === 'sugestao' && edicaoManual.key === fsk) { setEdicaoManual(null); return; }
                                                    let v = rev.result.value;
                                                    if (typeof v === 'string' && v.trim().startsWith('[')) { try { v = JSON.parse(v); } catch {} }
                                                    setEdicaoManual({ key: fsk, alvo: 'sugestao', block: fcd.block, field: fcd.field, label: fieldLabel(fieldEval.field), texto: textoEdicaoCampo(fcd.field, v), aGravar: false, erro: '' });
                                                    return;
                                                  }
                                                  setEdicaoManual(aEditar ? null : { key: fsk, block: fcd.block, field: fcd.field, label: fieldLabel(fieldEval.field), texto: textoEdicaoCampo(fcd.field, fVal), aGravar: false, erro: '' });
                                                }}>Editar manualmente</button>}
                                                <CriterionApplyBtn bKey={bKey} fieldName={fieldEval.field} critId="all" critLabel={fieldLabel(fieldEval.field)} feedback={fFailed.map(c=>c.feedback).join(' | ')} currentValue={fVal} overrideStateKey={fsk} overrideCriterionData={fcd} />
                                              </div>
                                              <CriterionReviewCard bKey={bKey} fieldName={fieldEval.field} critId="all" overrideStateKey={fsk} numCriteriaFixed={fFailed.length} />
                                            </>
                                          );
                                        })()}
                                      </div>
                                    )}
                                  </div>
                                );
                              })}
                            </div>
                          )}
                        </div>
                      );
                    })}
                  </div>
                </FormCard>
              );
            })()}

          </>
        );
      })()}
    </div>
  );
};

// ─── BriefingDropdown + BriefingDateRangePicker - nomes únicos para evitar conflito com screen_objectivos.jsx ──────────

const BriefingDropdown = ({ label, value, options, onChange, disabled }) => {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (!open) return;
    const onClick = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', onClick);
    return () => document.removeEventListener('mousedown', onClick);
  }, [open]);
  const current = options.find(o => o.value === value) || options[0];
  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <button onClick={() => !disabled && setOpen(v => !v)} disabled={disabled} style={{
        background: 'var(--bg-elev, #ffffff)', color: disabled ? 'var(--text-dim, #475569)' : 'var(--text, #112954)',
        border: '1px solid var(--border, #dde3ef)', borderRadius: 6,
        padding: '6px 10px', fontSize: 11.5,
        display: 'inline-flex', alignItems: 'center', gap: 8,
        cursor: disabled ? 'not-allowed' : 'pointer', opacity: disabled ? 0.55 : 1,
        fontFamily: 'inherit', transition: 'border-color .15s',
      }}>
        <span style={{ color: 'var(--text-dim, #475569)', fontSize: 10, textTransform: 'uppercase', letterSpacing: '.06em', fontFamily: 'var(--font-mono, monospace)' }}>{label}</span>
        <span style={{ fontWeight: 500, color: disabled ? 'var(--text-dim, #475569)' : 'var(--text, #112954)' }}>{current?.label || '—'}</span>
        <span style={{ color: 'var(--text-muted, #64748b)', fontSize: 9 }}>▾</span>
      </button>
      {open && (
        <div className="animate-in" style={{
          position: 'absolute', top: 'calc(100% + 4px)', left: 0, zIndex: 50,
          background: 'var(--bg-elev, #ffffff)', border: '1px solid var(--border, #dde3ef)', borderRadius: 6,
          boxShadow: '0 8px 24px rgba(17,41,84,0.12)', minWidth: 180, padding: 4, maxHeight: 280, overflowY: 'auto',
        }}>
          {options.map(o => (
            <button key={o.value} onClick={() => { onChange(o.value); setOpen(false); }} style={{
              display: 'block', width: '100%', textAlign: 'left',
              background: o.value === value ? 'color-mix(in oklch, var(--ai-500, #3859D0) 12%, transparent)' : 'transparent',
              color: o.value === value ? 'var(--ai-500, #3859D0)' : 'var(--text, #112954)',
              border: 'none', padding: '7px 10px', borderRadius: 4,
              fontSize: 12, fontWeight: o.value === value ? 600 : 500,
              cursor: 'pointer', fontFamily: 'inherit',
            }}>{o.label}</button>
          ))}
        </div>
      )}
    </div>
  );
};

// presets/resolver opcionais: outra lista de períodos; resolver(id) devolve { start, end } ou null (Personalizado).
const BriefingDateRangePicker = ({ value, onChange, presets: presetsProp, resolver, label = 'Período', compact = false, align = 'right' }) => {
  const ref = React.useRef(null);
  const [open, setOpen] = React.useState(false);
  const [over, setOver] = React.useState(false);
  const [overPreset, setOverPreset] = React.useState(null);
  const [overBtn, setOverBtn] = React.useState(null);
  const [draftStart, setDraftStart] = React.useState(value.start);
  const [draftEnd, setDraftEnd] = React.useState(value.end);
  const [draftPreset, setDraftPreset] = React.useState(value.preset);
  const [anchor, setAnchor] = React.useState(() => {
    const d = new Date(value.end); d.setDate(1); d.setMonth(d.getMonth() - 1); return d;
  });
  const [pickStart, setPickStart] = React.useState(true);

  React.useEffect(() => {
    if (!open) return;
    const onClick = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', onClick);
    return () => document.removeEventListener('mousedown', onClick);
  }, [open]);
  React.useEffect(() => {
    if (!open) return;
    setDraftStart(value.start); setDraftEnd(value.end); setDraftPreset(value.preset);
    const d = new Date(value.end); d.setDate(1); d.setMonth(d.getMonth() - 1); setAnchor(d);
    setPickStart(true);
  }, [open]);

  const stripTime = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
  const addDays = (d, n) => { const x = new Date(d); x.setDate(x.getDate() + n); return x; };
  const fmt = (d) => d.toLocaleDateString('pt-PT', { day: 'numeric', month: 'short', year: 'numeric' }).replace('.', '');
  const fmtShort = (d) => d.toLocaleDateString('pt-PT', { day: 'numeric', month: 'short' }).replace('.', '');

  const presets = presetsProp || [
    { id: 'all',        label: 'Todo o período' },
    { id: 'custom',     label: 'Personalizado' },
    { id: 'today',      label: 'Hoje' },
    { id: 'yesterday',  label: 'Ontem' },
    { id: 'last_7d',    label: 'Últimos 7 dias' },
    { id: 'last_28d',   label: 'Últimos 28 dias' },
    { id: 'last_30d',   label: 'Últimos 30 dias' },
    { id: 'this_month', label: 'Este mês' },
    { id: 'last_month', label: 'Mês passado' },
    { id: 'last_90d',   label: 'Últimos 90 dias' },
    { id: 'qtd',        label: 'Trimestre actual' },
    { id: 'this_year',  label: 'Este ano (Jan - Hoje)' },
    { id: 'last_year',  label: 'Ano passado' },
  ];

  const presetLabels = Object.fromEntries(presets.map(p => [p.id, p.label]));

  const applyPreset = (id) => {
    if (resolver) {
      const r = resolver(id);
      if (!r) return;
      setDraftPreset(id);
      setDraftStart(r.start);
      setDraftEnd(r.end);
      setAnchor(new Date(r.end.getFullYear(), r.end.getMonth() - 1, 1));
      setPickStart(true);
      return;
    }
    if (id === 'all') { setDraftPreset('all'); return; }
    const today = stripTime(new Date());
    let s = today, e = today;
    switch (id) {
      case 'today':      break;
      case 'yesterday':  s = e = addDays(today, -1); break;
      case 'last_7d':    s = addDays(today, -6); break;
      case 'last_28d':   s = addDays(today, -27); break;
      case 'last_30d':   s = addDays(today, -29); break;
      case 'last_90d':   s = addDays(today, -89); break;
      case 'this_month': s = new Date(today.getFullYear(), today.getMonth(), 1); break;
      case 'last_month':
        s = new Date(today.getFullYear(), today.getMonth() - 1, 1);
        e = new Date(today.getFullYear(), today.getMonth(), 0);
        break;
      case 'qtd':
        s = new Date(today.getFullYear(), Math.floor(today.getMonth() / 3) * 3, 1);
        break;
      case 'this_year': s = new Date(today.getFullYear(), 0, 1); break;
      case 'last_year':
        s = new Date(today.getFullYear() - 1, 0, 1);
        e = new Date(today.getFullYear() - 1, 11, 31);
        break;
      case 'custom': return;
      default: return;
    }
    setDraftPreset(id);
    setDraftStart(s);
    setDraftEnd(e);
    setAnchor(new Date(e.getFullYear(), e.getMonth() - 1, 1));
    setPickStart(true);
  };

  const handleDayClick = (d) => {
    if (pickStart || !draftStart || (draftStart && draftEnd && d < draftStart)) {
      setDraftStart(d); setDraftEnd(d); setPickStart(false); setDraftPreset('custom');
    } else {
      if (d < draftStart) { setDraftStart(d); }
      else { setDraftEnd(d); setPickStart(true); }
      setDraftPreset('custom');
    }
  };

  const apply = () => {
    onChange({ preset: draftPreset, start: draftStart, end: draftEnd });
    setOpen(false);
  };
  const cancel = () => {
    setDraftStart(value.start); setDraftEnd(value.end); setDraftPreset(value.preset);
    setOpen(false);
  };

  const triggerLabel = presetLabels[value.preset] || 'Personalizado';
  const triggerRange = value.preset === 'all' ? '' : `${fmtShort(value.start)} - ${fmt(value.end)}`;

  const Month = ({ date }) => {
    const y = date.getFullYear(), m = date.getMonth();
    const firstDay = new Date(y, m, 1).getDay();
    const daysInMonth = new Date(y, m + 1, 0).getDate();
    const monthLabel = date.toLocaleDateString('pt-PT', { month: 'long', year: 'numeric' }).toUpperCase();
    const cells = [];
    for (let i = 0; i < firstDay; i++) cells.push(null);
    for (let d = 1; d <= daysInMonth; d++) cells.push(new Date(y, m, d));
    const inRange = (d) => draftStart && draftEnd && d >= draftStart && d <= draftEnd;
    const isStart = (d) => draftStart && d.getTime() === stripTime(draftStart).getTime();
    const isEnd   = (d) => draftEnd   && d.getTime() === stripTime(draftEnd).getTime();
    return (
      <div style={{ marginBottom: 10 }}>
        <div style={{ fontSize: 10.5, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '.08em', padding: '4px 6px' }}>{monthLabel}</div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 1, fontSize: 11 }}>
          {cells.map((d, i) => {
            if (!d) return <div key={i} />;
            const r = inRange(d), s = isStart(d), e = isEnd(d);
            const isEdge = s || e;
            return (
              <button key={i} onClick={() => handleDayClick(d)} style={{
                width: 30, height: 30, lineHeight: '30px', textAlign: 'center', padding: 0,
                border: 'none', cursor: 'pointer', fontFamily: 'inherit',
                background: isEdge ? 'var(--ai-500)' : (r ? 'color-mix(in oklch, var(--ai-500) 18%, transparent)' : 'transparent'),
                color: isEdge ? '#fff' : (r ? 'var(--ai-500)' : 'var(--text)'),
                borderRadius: isEdge ? '50%' : (r ? 0 : 4),
                fontWeight: isEdge ? 600 : 500, fontSize: 11.5,
                transition: 'background .12s',
              }}
              onMouseEnter={(ev) => { if (!isEdge && !r) ev.currentTarget.style.background = 'var(--bg-hover)'; }}
              onMouseLeave={(ev) => { if (!isEdge && !r) ev.currentTarget.style.background = 'transparent'; }}>
                {d.getDate()}
              </button>
            );
          })}
        </div>
      </div>
    );
  };

  const nextAnchor = new Date(anchor.getFullYear(), anchor.getMonth() + 1, 1);

  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <button type="button" onClick={() => setOpen(v => !v)}
        onMouseEnter={() => setOver(true)} onMouseLeave={() => setOver(false)}
        style={{
        background: over ? 'var(--bg-hover, #f1f5f9)' : 'var(--bg-elev, #ffffff)', color: 'var(--text, #112954)',
        border: `1px solid ${over || open ? 'var(--ai-500, #3859D0)' : 'var(--border, #dde3ef)'}`, borderRadius: 6,
        padding: '6px 10px', fontSize: 11.5,
        display: 'inline-flex', alignItems: 'center', gap: 8,
        cursor: 'pointer', fontFamily: 'inherit',
        transition: 'border-color .15s, background .15s',
      }}>
        <span style={{ color: 'var(--text-dim, #475569)', fontSize: 10, textTransform: 'uppercase', letterSpacing: '.06em', fontFamily: 'var(--font-mono, monospace)' }}>{label}</span>
        <span style={{ fontWeight: 600, color: 'var(--ai-500, #3859D0)', background: 'color-mix(in oklch, var(--ai-500, #3859D0) 12%, transparent)', padding: '1px 6px', borderRadius: 3 }}>{triggerLabel}</span>
        {triggerRange && !(compact && value.preset !== 'custom') && <span style={{ color: 'var(--text-muted, #64748b)', fontFamily: 'var(--font-mono, monospace)', fontSize: 11 }}>{triggerRange}</span>}
        <span style={{ color: 'var(--text-muted, #64748b)', fontSize: 9 }}>▾</span>
      </button>

      {open && (
        <div style={{
          position: 'absolute', top: 'calc(100% + 6px)', [align === 'left' ? 'left' : 'right']: 0, zIndex: 60,
          background: 'var(--bg-elev, #ffffff)', border: '1px solid var(--border, #dde3ef)', borderRadius: 8,
          boxShadow: '0 12px 32px rgba(0,0,0,0.18)',
          display: 'flex', minWidth: 580, maxWidth: 640,
        }}>
          <div style={{ width: 200, borderRight: '1px solid var(--border, #dde3ef)', padding: '6px 0', maxHeight: 460, overflowY: 'auto' }}>
            {presets.map(p => {
              const activo = draftPreset === p.id || overPreset === p.id;
              return (
              <button key={p.id} type="button" onClick={() => applyPreset(p.id)}
                onMouseEnter={() => setOverPreset(p.id)} onMouseLeave={() => setOverPreset(null)}
                style={{
                display: 'block', width: '100%', textAlign: 'left',
                background: activo ? 'color-mix(in oklch, var(--ai-500, #3859D0) 12%, transparent)' : 'transparent',
                color: activo ? 'var(--ai-500, #3859D0)' : 'var(--text, #112954)',
                border: 'none', padding: '8px 14px', borderRadius: 4,
                fontSize: 12.5, fontWeight: draftPreset === p.id ? 600 : 500,
                cursor: 'pointer', fontFamily: 'inherit',
                transition: 'background .12s, color .12s',
              }}>{p.label}</button>
              );
            })}
          </div>
          <div style={{ flex: 1, padding: 14, display: 'flex', flexDirection: 'column' }}>
            <div style={{ display: 'flex', gap: 8, marginBottom: 10 }}>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 9.5, color: 'var(--text-dim, #475569)', textTransform: 'uppercase', letterSpacing: '.08em', fontFamily: 'var(--font-mono, monospace)', marginBottom: 3 }}>Data início</div>
                <div style={{ border: `1px solid ${pickStart ? 'var(--ai-500, #3859D0)' : 'var(--border, #dde3ef)'}`, borderRadius: 4, padding: '5px 8px', fontSize: 12, background: 'var(--bg, #f5f7fa)' }}>
                  {fmt(draftStart)}
                </div>
              </div>
              <div style={{ alignSelf: 'flex-end', padding: '6px 0', color: 'var(--text-muted, #64748b)' }}>—</div>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 9.5, color: 'var(--text-dim, #475569)', textTransform: 'uppercase', letterSpacing: '.08em', fontFamily: 'var(--font-mono, monospace)', marginBottom: 3 }}>Data fim</div>
                <div style={{ border: `1px solid ${!pickStart ? 'var(--ai-500, #3859D0)' : 'var(--border, #dde3ef)'}`, borderRadius: 4, padding: '5px 8px', fontSize: 12, background: 'var(--bg, #f5f7fa)' }}>
                  {fmt(draftEnd)}
                </div>
              </div>
            </div>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }}>
              <button onClick={() => setAnchor(new Date(anchor.getFullYear(), anchor.getMonth() - 1, 1))} style={{ background: 'none', border: '1px solid var(--border, #dde3ef)', borderRadius: 4, width: 24, height: 24, color: 'var(--text-muted, #64748b)', cursor: 'pointer' }}>‹</button>
              <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 1, flex: 1, padding: '0 8px' }}>
                {['D','S','T','Q','Q','S','S'].map((d, i) => (
                  <div key={i} style={{ textAlign: 'center', fontSize: 10, color: 'var(--text-dim, #475569)', fontFamily: 'var(--font-mono, monospace)', textTransform: 'uppercase' }}>{d}</div>
                ))}
              </div>
              <button onClick={() => setAnchor(new Date(anchor.getFullYear(), anchor.getMonth() + 1, 1))} style={{ background: 'none', border: '1px solid var(--border, #dde3ef)', borderRadius: 4, width: 24, height: 24, color: 'var(--text-muted, #64748b)', cursor: 'pointer' }}>›</button>
            </div>
            <div style={{ flex: 1, overflowY: 'auto', maxHeight: 360, padding: '0 30px' }}>
              <Month date={anchor} />
              <Month date={nextAnchor} />
            </div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 10, paddingTop: 10, borderTop: '1px solid var(--border, #dde3ef)' }}>
              <span style={{ flex: 1, minWidth: 0, fontSize: 10.5, color: 'var(--text-dim, #475569)', fontFamily: 'var(--font-mono, monospace)', letterSpacing: '.02em', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                {draftPreset === 'all' ? 'Sem limite de datas' : (draftStart && draftEnd ? `${Math.round((stripTime(draftEnd) - stripTime(draftStart)) / 86400000) + 1} dias` : '')}
              </span>
              <button type="button" onClick={cancel}
                onMouseEnter={() => setOverBtn('cancelar')} onMouseLeave={() => setOverBtn(null)}
                style={{
                  height: 28, padding: '0 12px', borderRadius: 6, fontSize: 12, fontWeight: 500, fontFamily: 'inherit', cursor: 'pointer',
                  background: overBtn === 'cancelar' ? 'var(--bg-hover, #f1f5f9)' : 'var(--bg-elev, #ffffff)',
                  color: 'var(--text, #112954)',
                  border: `1px solid ${overBtn === 'cancelar' ? 'var(--border-strong, #bec9de)' : 'var(--border, #dde3ef)'}`,
                  transition: 'background .15s, border-color .15s',
                }}>Cancelar</button>
              <button type="button" onClick={apply}
                onMouseEnter={() => setOverBtn('aplicar')} onMouseLeave={() => setOverBtn(null)}
                style={{
                  height: 28, padding: '0 14px', borderRadius: 6, fontSize: 12, fontWeight: 600, fontFamily: 'inherit', cursor: 'pointer',
                  background: 'var(--ai-500, #3859D0)', color: '#fff',
                  border: '1px solid var(--ai-500, #3859D0)',
                  filter: overBtn === 'aplicar' ? 'brightness(1.08)' : 'none',
                  transition: 'filter .15s',
                }}>Aplicar</button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
};

// ─── Lista de briefings ────────────────────────────────────────────────────────

const brfTh = {
  textAlign: 'left', fontSize: 10, fontWeight: 600, color: 'var(--text-dim)',
  textTransform: 'uppercase', letterSpacing: '.04em', padding: '8px 10px',
  borderBottom: '1px solid var(--border)', whiteSpace: 'nowrap', background: '#fff',
};
const brfTd = {
  fontSize: 12, padding: '10px', borderBottom: '1px solid var(--border)',
  verticalAlign: 'top', whiteSpace: 'normal', overflowWrap: 'anywhere', color: 'var(--text)',
};
const brfVazio = () => <span style={{ color: 'var(--text-dim)' }}>—</span>;
const BRF_OBJECTIVO = {
  awareness: 'Awareness',
  lead_gen: 'Geração de leads',
  activacao: 'Activação / Promoção',
  conversion: 'Conversão',
  retention: 'Retenção / Upsell',
};
const BRF_MECANICA = {
  digirent: 'Digirent',
  printplan: 'PrintPlan',
  voucher: 'Voucher on Demand',
  direct_discount: 'Desconto Directo',
  trade_in: 'Trade-In',
  testing_roll: 'Testing Roll',
  sample_request: 'Amostra',
  promo_bundle: 'Promoção (N+M)',
  custom: 'Oferta à medida',
};
const brfLista = (v) => {
  const items = Array.isArray(v) ? v : (v ? [v] : []);
  const texto = items.map(x => String(x || '').trim()).filter(Boolean).join(' · ');
  return texto || null;
};
const BRF_PAIS = {
  'Portugal':'PT','Espanha':'ES','França':'FR','Alemanha':'DE','Reino Unido':'UK','Itália':'IT',
  'Países Baixos':'NL','Bélgica':'BE','Luxemburgo':'LU','Suíça':'CH','Áustria':'AT','Suécia':'SE',
  'Noruega':'NO','Dinamarca':'DK','Finlândia':'FI','Irlanda':'IE','Polónia':'PL','República Checa':'CZ',
  'Eslováquia':'SK','Hungria':'HU','Roménia':'RO','Bulgária':'BG','Grécia':'GR','Croácia':'HR',
  'Sérvia':'RS','Eslovénia':'SI','Lituânia':'LT','Letónia':'LV','Estónia':'EE','Ucrânia':'UA',
  'Rússia':'RU','Bielorrússia':'BY','Moldávia':'MD','Albânia':'AL','Macedónia do Norte':'MK',
  'Bósnia-Herzegovina':'BA','Montenegro':'ME','Kosovo':'XK','Malta':'MT','Chipre':'CY','Islândia':'IS',
  'Liechtenstein':'LI','Mónaco':'MC','Andorra':'AD','San Marino':'SM',
  'EUA':'US','Canadá':'CA','México':'MX','Brasil':'BR','Argentina':'AR','Chile':'CL','Colômbia':'CO',
  'Peru':'PE','Venezuela':'VE','Equador':'EC','Bolívia':'BO','Paraguai':'PY','Uruguai':'UY',
  'Costa Rica':'CR','Panamá':'PA','Guatemala':'GT','Honduras':'HN','El Salvador':'SV','Nicarágua':'NI',
  'Cuba':'CU','República Dominicana':'DO','Jamaica':'JM','Haiti':'HT','Porto Rico':'PR',
  'Trinidad e Tobago':'TT','Belize':'BZ','Guiana':'GY','Suriname':'SR',
  'China':'CN','Japão':'JP','Coreia do Sul':'KR','Índia':'IN','Austrália':'AU','Nova Zelândia':'NZ',
  'Singapura':'SG','Indonésia':'ID','Malásia':'MY','Tailândia':'TH','Vietnam':'VN','Filipinas':'PH',
  'Taiwan':'TW','Hong Kong':'HK','Macau':'MO','Bangladesh':'BD','Paquistão':'PK','Sri Lanka':'LK',
  'Myanmar':'MM','Camboja':'KH','Laos':'LA','Mongólia':'MN','Nepal':'NP','Cazaquistão':'KZ',
  'Uzbequistão':'UZ','Brunei':'BN','Timor-Leste':'TL',
  'Emirados Árabes Unidos':'AE','Arábia Saudita':'SA','Qatar':'QA','Kuwait':'KW','Omã':'OM',
  'Bahrein':'BH','Israel':'IL','Jordânia':'JO','Líbano':'LB','Turquia':'TR','Irão':'IR','Iraque':'IQ',
  'Síria':'SY','Iémen':'YE','Palestina':'PS','Afeganistão':'AF',
  'África do Sul':'ZA','Nigéria':'NG','Quénia':'KE','Gana':'GH','Etiópia':'ET','Tanzânia':'TZ',
  'Uganda':'UG','Ruanda':'RW','Marrocos':'MA','Tunísia':'TN','Argélia':'DZ','Líbia':'LY','Egipto':'EG',
  'Angola':'AO','Moçambique':'MZ','Zimbabué':'ZW','Zâmbia':'ZM','Senegal':'SN','Costa do Marfim':'CI',
  'Camarões':'CM','Madagáscar':'MG','Namíbia':'NA','Botswana':'BW','Mali':'ML','Serra Leoa':'SL',
  'Guiné':'GN','Cabo Verde':'CV','Ilhas Maurícias':'MU',
  'Papua Nova Guiné':'PG','Fiji':'FJ','Ilhas Salomão':'SB','Vanuatu':'VU','Samoa':'WS','Tonga':'TO','Micronésia':'FM',
};
const brfMercados = (v) => {
  const items = Array.isArray(v) ? v : (v ? [v] : []);
  const siglas = items.map(x => {
    const nome = String(x || '').trim();
    return nome ? (BRF_PAIS[nome] || nome) : '';
  }).filter(Boolean);
  return siglas.length ? siglas.join(' · ') : null;
};
const brfData = (v) => {
  if (!v) return null;
  const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(String(v));
  return m ? `${m[3]}/${m[2]}/${m[1]}` : null;
};
const brfMecanica = (offer) => {
  if (!offer) return null;
  let o = offer;
  if (typeof offer === 'string') {
    try { o = JSON.parse(offer); } catch { return null; }
  }
  if (!o || !o.type) return null;
  if (o.type === 'promo_bundle' && o.buy_qty && o.get_qty) {
    return `Promoção ${o.buy_qty}+${o.get_qty}`;
  }
  if (o.type === 'custom' && o.headline) return o.headline;
  return BRF_MECANICA[o.type] || o.type;
};

const CHANNEL_CHIPS = {
  // Formato novo (CHANNELS_OPTS actuais)
  meta_ads:            { abbr: 'META', label: 'Meta Ads' },
  linkedin_ads:        { abbr: 'LIN',  label: 'LinkedIn Ads' },
  linkedin_organic:    { abbr: 'ORG',  label: 'LinkedIn' },
  google_ads_search:   { abbr: 'GGL',  label: 'Google Search' },
  google_ads_display:  { abbr: 'DISP', label: 'Google Display' },
  website:             { abbr: 'WEB',  label: 'Website / Loja' },
  muppi_led:           { abbr: 'LED',  label: 'Painéis LED' },
  equipa_consumiveis:  { abbr: 'CON',  label: 'Equipa Consumíveis' },
  equipa_equipamentos: { abbr: 'EQP',  label: 'Equipa Equipamentos' },
  equipa_sdr:          { abbr: 'SDR',  label: 'Equipa SDR' },
  comercial:           { abbr: 'COM',  label: 'Equipa comercial' },
  daily_prospect:      { abbr: 'SDR',  label: 'Daily Prospect' },
  distribuidores:      { abbr: 'DIS',  label: 'Distribuidores' },
  logistica:           { abbr: 'LOG',  label: 'Logística' },
  // Formato antigo (retrocompatibilidade)
  email:               { abbr: 'EML',  label: 'Email' },
  whatsapp:            { abbr: 'WA',   label: 'WhatsApp' },
  linkedin:            { abbr: 'LIN',  label: 'LinkedIn' },
  google_ads:          { abbr: 'GGL',  label: 'Google Ads' },
  facebook:            { abbr: 'FB',   label: 'Facebook' },
  instagram:           { abbr: 'IG',   label: 'Instagram' },
  youtube:             { abbr: 'YT',   label: 'YouTube' },
  social:              { abbr: 'SOC',  label: 'Social' },
  site:                { abbr: 'WEB',  label: 'Website' },
  led:                 { abbr: 'LED',  label: 'Painéis LED' },
  ads:                 { abbr: 'ADS',  label: 'Ads' },
};

const BRF_CANAL_ICON = {
  meta_ads: 'simple-icons:meta',
  facebook: 'simple-icons:facebook',
  instagram: 'simple-icons:instagram',
  linkedin: 'simple-icons:linkedin',
  linkedin_ads: 'simple-icons:linkedin',
  linkedin_organic: 'simple-icons:linkedin',
  equipa_consumiveis: 'lucide:users',
  equipa_equipamentos: 'lucide:users',
  equipa_sdr: 'lucide:phone',
  comercial: 'lucide:users',
  daily_prospect: 'lucide:phone',
  distribuidores: 'lucide:truck',
  logistica: 'lucide:package',
  google_ads: 'simple-icons:google',
  google_ads_search: 'simple-icons:google',
  google_ads_display: 'simple-icons:google',
  whatsapp: 'simple-icons:whatsapp',
  youtube: 'simple-icons:youtube',
  email: 'lucide:mail',
  website: 'lucide:globe',
  site: 'lucide:globe',
  muppi_led: 'lucide:monitor',
  led: 'lucide:monitor',
  social: 'lucide:share-2',
  ads: 'lucide:megaphone',
};

const BrfCanais = ({ channels }) => {
  if (!channels.length) return brfVazio();
  const Icone = window.MktIcon;
  return (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 7, flexWrap: 'wrap', color: 'var(--text)' }}>
      {channels.map((ch, i) => {
        const meta = CHANNEL_CHIPS[ch];
        const label = meta?.label || ch;
        const icon = BRF_CANAL_ICON[ch];
        return (
          <span key={`${ch}-${i}`} title={label} aria-label={label} style={{ display: 'inline-flex', lineHeight: 0 }}>
            {Icone && icon
              ? <Icone name={icon} size={16} />
              : <span style={{ fontSize: 11, lineHeight: 1 }}>{meta?.abbr || String(ch).slice(0, 3).toUpperCase()}</span>}
          </span>
        );
      })}
    </span>
  );
};

const SortHeader = ({ label, sortKey, sortBy, onSort, width, style: extraStyle }) => {
  const active = sortBy.key === sortKey;
  return (
    <th style={{ ...brfTh, width, cursor: sortKey ? 'pointer' : 'default', userSelect: 'none', ...extraStyle }}
      onClick={() => sortKey && onSort(sortKey)}>
      <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
        {label}
        {active && sortKey && window.Icon &&
          <window.Icon name={sortBy.dir === 'asc' ? 'arrowUp' : 'arrowDown'} size={9} style={{ color: 'var(--ai-500)' }} />}
      </span>
    </th>
  );
};

const BriefingPdfModal = ({ briefing, onClose }) => {
  const [pessoas, setPessoas] = React.useState(null);
  const [escolhidos, setEscolhidos] = React.useState({});
  const [busca, setBusca] = React.useState('');
  const [externoInput, setExternoInput] = React.useState('');
  const [externos, setExternos] = React.useState([]);
  const [nota, setNota] = React.useState('');
  const [aTrabalhar, setATrabalhar] = React.useState(false);
  const [confirmar, setConfirmar] = React.useState(false);
  const [erro, setErro] = React.useState('');
  const [ok, setOk] = React.useState('');
  const emailRe = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i;
  React.useEffect(() => {
    let vivo = true;
    fetch('/api/marketing/activacao/destinatarios-teste')
      .then(r => r.json())
      .then(d => { if (vivo) setPessoas(d.pessoas || []); })
      .catch(() => { if (vivo) setPessoas([]); });
    return () => { vivo = false; };
  }, []);
  React.useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape' && !aTrabalhar) onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose, aTrabalhar]);
  const nome = briefing?.commercial_name || briefing?.product_name || `Briefing ${briefing?.id}`;
  const q = busca.trim().toLowerCase();
  const visiveis = (pessoas || []).filter(p => !q || `${p.nome} ${p.email} ${p.empresa} ${p.departamento}`.toLowerCase().includes(q));
  const seleccionados = (pessoas || []).filter(p => escolhidos[p.email]);
  const destinatarios = () => [...seleccionados.map(p => p.email), ...externos];
  const adicionarExterno = () => {
    const partes = externoInput.split(/[,;\s]+/).map(s => s.trim().toLowerCase()).filter(Boolean);
    if (!partes.length) return;
    if (partes.some(e => !emailRe.test(e))) { setErro('Há um email externo inválido.'); return; }
    const doPortal = new Set((pessoas || []).map(p => p.email));
    setEscolhidos(prev => {
      const next = { ...prev };
      partes.filter(e => doPortal.has(e)).forEach(e => { next[e] = true; });
      return next;
    });
    setExternos(prev => [...new Set([...prev, ...partes.filter(e => !doPortal.has(e))])]);
    setExternoInput('');
    setErro('');
    setConfirmar(false);
  };
  const descarregar = async () => {
    setATrabalhar(true); setErro('');
    try {
      const r = await fetch(`/api/marketing/briefings/${briefing.id}/pdf`);
      if (!r.ok) {
        const b = await r.json().catch(() => ({}));
        throw new Error(b.error || 'Não foi possível gerar o PDF.');
      }
      const blob = await r.blob();
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = `briefing-${nome}.pdf`;
      document.body.appendChild(a);
      a.click();
      a.remove();
      setTimeout(() => URL.revokeObjectURL(url), 1000);
    } catch (e) { setErro(e.message); }
    finally { setATrabalhar(false); }
  };
  const enviar = async () => {
    const lista = destinatarios();
    if (!lista.length) { setErro('Escolhe pelo menos um destinatário.'); return; }
    if (!confirmar) { setConfirmar(true); setErro(''); return; }
    setATrabalhar(true); setErro(''); setOk('');
    try {
      const r = await fetch(`/api/marketing/briefings/${briefing.id}/pdf-email`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ destinatarios: lista, nota }),
      });
      const b = await r.json().catch(() => ({}));
      if (!r.ok) throw new Error(b.error || 'Não foi possível enviar o PDF.');
      setOk(`Email enviado para ${b.destinatarios?.length || lista.length} destinatário(s).`);
      setConfirmar(false);
    } catch (e) { setErro(e.message); setConfirmar(false); }
    finally { setATrabalhar(false); }
  };
  const campo = { display: 'block', width: '100%', marginTop: 4, boxSizing: 'border-box', border: '1px solid var(--border, #dde3ef)', borderRadius: 8, padding: '8px 10px', font: 'inherit', fontSize: 13, background: 'var(--bg, #f8fafc)', color: 'var(--text, #112954)' };
  return (
    <div role="dialog" aria-modal="true" aria-label="Enviar PDF do briefing" onClick={() => { if (!aTrabalhar) onClose(); }} style={{ position: 'fixed', inset: 0, zIndex: 400, background: 'rgba(15, 23, 42, .45)', display: 'flex', alignItems: 'flex-start', justifyContent: 'center', padding: '48px 16px', overflow: 'auto' }}>
      <div onClick={e => e.stopPropagation()} style={{ width: 'min(680px, 100%)', background: '#fff', color: '#112954', borderRadius: 12, border: '1px solid #dde3ef', boxShadow: '0 16px 50px rgba(15,23,42,.2)', padding: '22px 22px 18px' }}>
        <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 11, fontWeight: 650, letterSpacing: '.06em', color: '#94A4C4', fontFamily: 'var(--font-mono, monospace)' }}>BRIEFING</div>
            <h2 style={{ margin: '4px 0 0', fontSize: 18, fontWeight: 700 }}>Enviar PDF por email</h2>
          </div>
          <button type="button" onClick={onClose} disabled={aTrabalhar} aria-label="Fechar" style={{ border: 0, background: 'transparent', fontSize: 20, lineHeight: 1, cursor: 'pointer', color: '#64748b' }}>×</button>
        </div>
        <p style={{ margin: '10px 0 0', fontSize: 13, color: '#4a5e7a', lineHeight: 1.5 }}>
          O PDF de <strong>{nome}</strong> sai com o layout do portal, paginado em folhas A4. Usa a versão guardada do briefing.
        </p>
        <div style={{ marginTop: 16 }}>
          <div style={{ fontSize: 11, fontWeight: 650, color: '#6b7fa3', marginBottom: 6 }}>Quem recebe</div>
          {(seleccionados.length > 0 || externos.length > 0) ? (
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 8 }}>
              {seleccionados.map(p => (
                <span key={p.email} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, maxWidth: '100%', padding: '4px 8px', borderRadius: 99, background: '#f1f5f9', border: '1px solid #dde3ef', fontSize: 12 }}>
                  <span style={{ overflowWrap: 'anywhere' }}><b>{p.nome}</b> <span style={{ color: '#64748b' }}>{p.email}</span></span>
                  <button type="button" aria-label={`Retirar ${p.nome}`} onClick={() => setEscolhidos(prev => ({ ...prev, [p.email]: false }))} style={{ border: 0, background: 'transparent', cursor: 'pointer', color: '#64748b', font: 'inherit', lineHeight: 1, padding: 0 }}>×</button>
                </span>
              ))}
              {externos.map(e => (
                <span key={e} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, maxWidth: '100%', padding: '4px 8px', borderRadius: 99, background: '#f1f5f9', border: '1px solid #dde3ef', fontSize: 12 }}>
                  <span style={{ overflowWrap: 'anywhere' }}><b>{e}</b> <span style={{ color: '#64748b' }}>externo</span></span>
                  <button type="button" aria-label={`Retirar ${e}`} onClick={() => setExternos(prev => prev.filter(x => x !== e))} style={{ border: 0, background: 'transparent', cursor: 'pointer', color: '#64748b', font: 'inherit', lineHeight: 1, padding: 0 }}>×</button>
                </span>
              ))}
            </div>
          ) : (
            <div style={{ fontSize: 12.5, color: '#4a5e7a', marginBottom: 8 }}>Ninguém seleccionado. Escolhe utilizadores do portal ou acrescenta um email externo.</div>
          )}
          <input value={busca} onChange={e => setBusca(e.target.value)} placeholder="Procurar utilizador do portal…" style={campo} />
          <div className="scrollbar" style={{ maxHeight: 220, overflowY: 'auto', marginTop: 8, border: '1px solid #dde3ef', borderRadius: 8 }}>
            {pessoas == null && <div style={{ padding: 10, fontSize: 12.5, color: '#4a5e7a' }}>A carregar utilizadores…</div>}
            {pessoas && visiveis.length === 0 && <div style={{ padding: 10, fontSize: 12.5, color: '#4a5e7a' }}>Nenhum utilizador com esse nome ou email.</div>}
            {visiveis.map((p, i) => (
              <label key={p.email} style={{ display: 'flex', gap: 8, alignItems: 'flex-start', padding: '7px 10px', borderTop: i ? '1px solid #ECEFF5' : 0, fontSize: 13, cursor: 'pointer' }}>
                <input type="checkbox" checked={!!escolhidos[p.email]} onChange={() => { setEscolhidos(prev => ({ ...prev, [p.email]: !prev[p.email] })); setConfirmar(false); }} />
                <span>
                  <b>{p.nome}</b>
                  <span style={{ display: 'block', color: '#64748b', fontSize: 12, overflowWrap: 'anywhere' }}>{p.email}{[p.departamento, p.empresa].filter(x => x && x !== '—').length ? ` · ${[p.departamento, p.empresa].filter(x => x && x !== '—').join(' · ')}` : ''}</span>
                </span>
              </label>
            ))}
          </div>
        </div>
        <div style={{ marginTop: 12 }}>
          <div style={{ fontSize: 12, color: '#4a5e7a' }}>Email externo</div>
          <div style={{ display: 'flex', gap: 8, marginTop: 4 }}>
            <input value={externoInput} onChange={e => setExternoInput(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); adicionarExterno(); } }} placeholder="nome@empresa.com" style={{ ...campo, marginTop: 0, flex: 1 }} />
            <button type="button" onClick={adicionarExterno} style={{ border: '1px solid #dde3ef', background: 'transparent', color: '#112954', borderRadius: 8, padding: '8px 12px', fontSize: 13, fontWeight: 650, cursor: 'pointer', fontFamily: 'inherit' }}>Adicionar</button>
          </div>
        </div>
        <label style={{ display: 'block', marginTop: 12, fontSize: 12, color: '#4a5e7a' }}>
          Nota no email
          <textarea value={nota} onChange={e => setNota(e.target.value)} rows={3} style={{ display: 'block', width: '100%', marginTop: 4, boxSizing: 'border-box', border: '1px solid #dde3ef', borderRadius: 8, padding: '8px 10px', font: 'inherit', fontSize: 13, background: '#f8fafc', color: '#112954', resize: 'vertical' }} />
        </label>
        {erro && <div style={{ marginTop: 12, fontSize: 13, color: '#dc2626' }}>{erro}</div>}
        {ok && <div style={{ marginTop: 12, fontSize: 13, color: '#15803d' }}>{ok}</div>}
        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16, flexWrap: 'wrap' }}>
          <button type="button" onClick={descarregar} disabled={aTrabalhar} style={{ border: '1px solid #dde3ef', background: 'transparent', color: '#112954', borderRadius: 8, padding: '8px 14px', fontSize: 13, fontWeight: 650, cursor: aTrabalhar ? 'wait' : 'pointer', fontFamily: 'inherit' }}>Descarregar PDF</button>
          <button type="button" onClick={enviar} disabled={aTrabalhar} style={{ border: 0, background: '#3859D0', color: '#fff', borderRadius: 8, padding: '8px 14px', fontSize: 13, fontWeight: 650, cursor: aTrabalhar ? 'wait' : 'pointer', fontFamily: 'inherit' }}>{confirmar ? 'Confirmar envio' : 'Enviar agora'}</button>
        </div>
      </div>
    </div>
  );
};

const BriefingRow = ({ b, isAll, onSelect, onEdit, onDelete, onSubmit, onGenerateStrategy, onCriarCampanha, onVerCampanha, onDuplicar, onEnviarPdf, selected, onToggle, userRole }) => {
  const [menuOpen, setMenuOpen] = React.useState(false);
  const [menuPos, setMenuPos] = React.useState({ top: 0, right: 0 });
  const menuRef     = React.useRef(null);
  const btnRef      = React.useRef(null);
  const dropdownRef = React.useRef(null);
  const pct = parseInt(b.completion_pct) || 0;

  React.useEffect(() => {
    if (!menuOpen) return;
    const handler = (e) => {
      const inMenu     = menuRef.current     && menuRef.current.contains(e.target);
      const inDropdown = dropdownRef.current && dropdownRef.current.contains(e.target);
      if (!inMenu && !inDropdown) setMenuOpen(false);
    };
    document.addEventListener('mousedown', handler);
    document.addEventListener('touchstart', handler);
    return () => {
      document.removeEventListener('mousedown', handler);
      document.removeEventListener('touchstart', handler);
    };
  }, [menuOpen]);

  return (
    <tr className="brf-row" onClick={onSelect} style={{ cursor: 'pointer' }}>
      <td style={brfTd} onClick={e => e.stopPropagation()}>
        <input type="checkbox" checked={!!selected} onChange={onToggle} />
      </td>
      <td style={{ ...brfTd, fontWeight: 650 }} title={b.commercial_name || b.product_name || ''}>
        {b.commercial_name || b.product_name || brfVazio()}
      </td>
      {isAll && <td style={brfTd}>{b.brand_name || brfVazio()}</td>}
      <td style={brfTd}>{b.product_name || brfVazio()}</td>
      <td style={brfTd}>
        {(() => {
          const cfg = STATUS_CONFIG[estadoBriefing(b.status)] || STATUS_CONFIG.rascunho;
          return <span style={{ fontSize: 12, fontWeight: 650, color: cfg.color, whiteSpace: 'nowrap' }}>{cfg.label}</span>;
        })()}
      </td>
      <td style={brfTd}>{Number(b.n_campanhas) > 0 ? b.n_campanhas : brfVazio()}</td>
      <td style={brfTd}>{b.quality_score != null ? <>{b.quality_score}<span style={{ color: 'var(--text-dim)' }}>/100</span></> : (pct ? `${pct}%` : brfVazio())}</td>
      <td style={brfTd}>{b.updated_at ? new Date(b.updated_at).toLocaleDateString('pt-PT') : brfVazio()}</td>
      <td style={brfTd}>{b.created_by_name || brfVazio()}</td>
      <td style={brfTd}>{b.approved_by_name || brfVazio()}</td>
      <td style={{ ...brfTd, textAlign: 'right' }} onClick={e => e.stopPropagation()}>
        <div ref={menuRef} style={{ display: 'inline-block' }}>
          <button
            ref={btnRef}
            onClick={() => {
              if (!menuOpen) {
                const r = btnRef.current.getBoundingClientRect();
                setMenuPos({ top: r.bottom + 4, right: window.innerWidth - r.right });
              }
              setMenuOpen(o => !o);
            }}
            style={{
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              width: 28, height: 28, borderRadius: 6, cursor: 'pointer',
              background: menuOpen ? 'rgba(0,0,0,0.06)' : 'transparent',
              border: 'none', outline: 'none', color: 'var(--text-muted)', transition: 'background 120ms ease',
              fontSize: 16, lineHeight: 1,
            }}
            onMouseEnter={e => { if (!menuOpen) e.currentTarget.style.background = 'rgba(0,0,0,0.06)'; }}
            onMouseLeave={e => { if (!menuOpen) e.currentTarget.style.background = 'transparent'; }}
          >⋮</button>

          {menuOpen && ReactDOM.createPortal(
            <div ref={dropdownRef} className="animate-in" style={{
              position: 'fixed', top: menuPos.top, right: menuPos.right, zIndex: 9999,
              background: '#ffffff', border: '1px solid var(--border, #ECEFF5)',
              borderRadius: 8, minWidth: 176, padding: 4,
              boxShadow: '0 4px 16px rgba(17,41,84,0.10)',
            }}>
              {(() => {
                const st       = estadoBriefing(b.status);
                const isAdmin  = userRole === 'admin' || userRole === 'board';
                const temCampanhas = Number(b.n_campanhas) > 0 || b.status === 'em_campanha';
                const itemStyle   = { display: 'block', width: '100%', textAlign: 'left', background: 'transparent', border: 'none', padding: '8px 12px', borderRadius: 4, fontSize: 13, color: 'var(--text, #283252)', cursor: 'pointer', fontFamily: 'inherit', whiteSpace: 'nowrap' };
                const dangerStyle = { ...itemStyle, color: 'var(--danger, #DC2626)' };
                const onHover  = e => e.currentTarget.style.background = 'rgba(0,0,0,0.04)';
                const offHover = e => e.currentTarget.style.background = 'transparent';
                const onDanger = e => e.currentTarget.style.background = 'rgba(220,38,38,0.06)';

                const canSubmit   = st === 'rascunho' || st === 'rejeitado';
                const canCampanha = st === 'aprovado' && !!onCriarCampanha;
                const canDelete   = isAdmin || st === 'rascunho' || st === 'rejeitado';
                return (
                  <>
                    <button onClick={() => { setMenuOpen(false); onEdit(); }} style={itemStyle} onMouseEnter={onHover} onMouseLeave={offHover}>
                      Editar
                    </button>
                    {onDuplicar && (
                      <button onClick={() => { setMenuOpen(false); onDuplicar(b); }} style={itemStyle} onMouseEnter={onHover} onMouseLeave={offHover}>
                        Duplicar
                      </button>
                    )}
                    {canSubmit && onSubmit && (
                      <button onClick={() => { setMenuOpen(false); onSubmit(b); }} style={itemStyle} onMouseEnter={onHover} onMouseLeave={offHover}>
                        {(st === 'rejeitado' || st === 'alteracoes_pedidas') ? 'Submeter novamente' : 'Submeter'}
                      </button>
                    )}
                    {temCampanhas && onVerCampanha && (
                      <button onClick={() => { setMenuOpen(false); onVerCampanha(b); }} style={itemStyle} onMouseEnter={onHover} onMouseLeave={offHover}>
                        Ver campanhas
                      </button>
                    )}
                    {canCampanha && (
                      <button onClick={() => { setMenuOpen(false); onCriarCampanha(b); }} style={itemStyle} onMouseEnter={onHover} onMouseLeave={offHover}>
                        Criar campanha
                      </button>
                    )}
                    {onEnviarPdf && (
                      <button onClick={() => { setMenuOpen(false); onEnviarPdf(b); }} style={itemStyle} onMouseEnter={onHover} onMouseLeave={offHover}>
                        Enviar PDF por email
                      </button>
                    )}
                    {canDelete && (
                      <>
                        <div style={{ height: 1, background: 'var(--border, #ECEFF5)', margin: '4px 0' }} />
                        <button onClick={() => { setMenuOpen(false); onDelete(); }} style={dangerStyle} onMouseEnter={onDanger} onMouseLeave={offHover}>
                          Apagar
                        </button>
                      </>
                    )}
                  </>
                );
              })()}
            </div>,
            document.body
          )}
        </div>
      </td>

    </tr>
  );
};

// ─── Painel de aprovações ──────────────────────────────────────────────────────
const ApprovalPanel = ({ briefing, approvals = [], userEmail, onNoteChange, noteValue, noteError, showNotes = true, children }) => {
  const brandSlug   = briefing?.brand_slug;
  const required    = getRequiredApprovers(brandSlug);
  const curStatus   = briefing?.status;
  const activeRole  = getActiveRole(curStatus, required.map(r => r.role));

  const actionLabel = (action) => ({
    approved:          'aprovado',
    rejected:          'rejeitado',
    changes_requested: 'alterações pedidas',
  }[action] || action);

  return (
    <div style={{ marginBottom: 24, padding: 16, borderRadius: 8, background: 'rgba(217,119,6,.07)', border: '1px solid rgba(217,119,6,.3)' }}>
      <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text)', marginBottom: 14, letterSpacing: '0.03em' }}>
        Fluxo de aprovação
      </div>

      {/* Pipeline visual por fase */}
      <div style={{ display: 'flex', flexDirection: 'column', gap: 10, marginBottom: 18 }}>
        {required.map(({ role, name }, idx) => {
          const ROLE_LABELS = { fc_dir: 'Marketing Manager', brand_dir: 'CEO', rl: 'Chairman' };
          const stageLabel  = `Fase ${idx + 1} · ${ROLE_LABELS[role] || role}`;

          // Separar decisão final de rascunho de notas - 'saved' não conta como decisão
          const finalAprov  = approvals.find(a => a.role === role && a.action !== 'saved');
          const savedAprov  = approvals.find(a => a.role === role && a.action === 'saved');
          const ok          = finalAprov?.action === 'approved';
          const nok         = finalAprov?.action === 'rejected' || finalAprov?.action === 'changes_requested';
          const isActive    = role === activeRole && !finalAprov;
          const isPending   = !finalAprov && role !== activeRole;
          const displayNote = finalAprov || savedAprov;

          return (
            <div key={role} style={{
              display: 'flex', alignItems: 'flex-start', gap: 12,
              padding: '10px 12px', borderRadius: 7,
              border: `1px solid ${isActive ? 'rgba(217,119,6,.5)' : ok ? 'rgba(21,128,61,.25)' : nok ? 'rgba(220,38,38,.25)' : 'var(--border)'}`,
              background: isActive ? 'rgba(217,119,6,.05)' : ok ? 'rgba(21,128,61,.04)' : nok ? 'rgba(220,38,38,.04)' : 'transparent',
              opacity: isPending ? 0.5 : 1,
            }}>
              <span style={{ fontSize: 16, width: 20, textAlign: 'center', flexShrink: 0, marginTop: 1,
                color: ok ? '#15803d' : nok ? '#dc2626' : isActive ? '#d97706' : 'var(--text-dim)' }}>
                {ok ? '✓' : nok ? '✗' : isActive ? '◉' : '○'}
              </span>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.06em', marginBottom: 2 }}>
                  {stageLabel}
                </div>
                <div style={{ fontSize: 13, fontWeight: 500, color: ok ? '#15803d' : nok ? '#dc2626' : 'var(--text)' }}>
                  {name}
                  {isActive && <span style={{ fontSize: 11, color: '#d97706', marginLeft: 8, fontWeight: 400 }}>· a aguardar decisão</span>}
                  {isPending && <span style={{ fontSize: 11, color: 'var(--text-dim)', marginLeft: 8, fontWeight: 400 }}>· aguarda fase anterior</span>}
                </div>
                {displayNote && (
                  <div style={{ fontSize: 11, color: 'var(--text-dim)', marginTop: 2 }}>
                    {finalAprov ? actionLabel(finalAprov.action) : 'rascunho'} por {displayNote.approver_name || name}
                    {displayNote.created_at && (
                      <> · {new Date(displayNote.created_at).toLocaleString('pt-PT', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' })}</>
                    )}
                    {displayNote.notes ? ` · "${displayNote.notes}"` : ''}
                  </div>
                )}
              </div>
            </div>
          );
        })}
      </div>

      {showNotes && (
        <>
          <Field label="Notas (obrigatório se rejeitar ou pedir alterações, opcional se aprovar)">
            <TInput value={noteValue} onChange={onNoteChange}
              placeholder="Sugestões de melhoria, campos a corrigir, ajustes de posicionamento..." multiline />
          </Field>
          {noteError && <div style={{ marginTop: 8, fontSize: 12, color: 'var(--danger)', fontWeight: 500 }}>{noteError}</div>}
        </>
      )}

      {children}
    </div>
  );
};

const BRF_CAMPO_LABEL = {
  commercial_name: 'Nome comercial',
  elevator_pitch: 'Argumento de venda',
  usps: 'Pontos de valor',
  certifications: 'Certificações',
  applications: 'Aplicações',
  pain_points: 'Dores',
  objections: 'Objecções',
  competitors: 'Concorrentes',
  differentiation_args: 'Diferenciação',
  key_message: 'Mensagem-chave',
  geo_markets: 'Mercados',
  decision_maker: 'Decisor',
  end_user: 'Utilizador final',
  motivators: 'Motivações',
  purchase_trigger: 'Gatilho de compra',
  market_trends: 'Tendências',
  positioning_narrative: 'Posicionamento',
  sensitive_info: 'Informação sensível',
  objective: 'Objectivo',
  channels: 'Canais',
  commercial_offer: 'Oferta comercial',
  meta_ads_conversao: 'Conversão Meta',
  kpis: 'Indicadores',
  tone: 'Tom',
  previous_campaigns: 'Campanhas anteriores',
  prohibited_claims: 'Alegações proibidas',
  off_brand_messages: 'Mensagens fora da marca',
  competitors_not_name: 'Concorrentes a não nomear',
  out_of_scope_markets: 'Mercados fora de âmbito',
  other_restrictions: 'Outras restrições',
};

const brfDia = (v) => {
  if (!v) return '';
  const d = new Date(v);
  if (Number.isNaN(d.getTime())) return '';
  return d.toLocaleDateString('pt-PT', { day: '2-digit', month: '2-digit', year: 'numeric' });
};

function brfLinhasNotas(revisoes, approvals) {
  const eventos = (revisoes || [])
    .filter(r => r.tipo === 'evento' || r.tipo === 'pedido')
    .map(r => ({
      id: 'r' + r.id,
      raw: r,
      kind: r.acao,
      nome: r.autor_nome || 'Equipa',
      texto: r.texto,
      at: r.created_at,
    }));
  const rascunhos = (approvals || [])
    .filter(a => a.action === 'saved' && String(a.notes || '').trim())
    .map(a => ({
      id: 's' + (a.id || a.role || 'nota'),
      raw: null,
      kind: 'saved',
      nome: a.approver_name || 'Aprovador',
      texto: a.notes,
      at: a.created_at,
    }));
  return [...eventos, ...rascunhos].sort((a, b) => new Date(b.at || 0) - new Date(a.at || 0));
}

function brfNotaMeta(kind) {
  if (kind === 'submetido') return { verb: 'Submeteu', bg: 'rgba(56,89,208,0.10)', fg: '#3859D0' };
  if (kind === 'editado') return { verb: 'Editou', bg: 'rgba(56,89,208,0.10)', fg: '#3859D0' };
  if (kind === 'approved') return { verb: 'Aprovou', bg: 'rgba(21,128,61,0.10)', fg: '#15803d' };
  if (kind === 'rejected') return { verb: 'Rejeitou', bg: 'rgba(220,38,38,0.10)', fg: '#dc2626' };
  if (kind === 'saved') return { verb: 'Rascunho', bg: '#F1F5F9', fg: '#64748b' };
  if (kind === 'decisao') return { verb: 'Decisão', bg: 'rgba(56,89,208,0.10)', fg: '#3859D0' };
  return { verb: 'Pediu alterações', bg: 'rgba(217,119,6,0.12)', fg: '#b45309' };
}

function dicaNota(l) {
  const meta = brfNotaMeta(l.kind);
  const dia = l.at ? brfDia(l.at) : '';
  const texto = String(l.texto || '').replace(/\s+/g, ' ').trim();
  const curto = texto.length > 160 ? `${texto.slice(0, 157)}…` : texto;
  return [l.nome, meta.verb, dia, curto].filter(Boolean).join(' · ');
}

function NotaIcon({ kind }) {
  const p = { width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round' };
  if (kind === 'decisao') return <svg {...p}><rect x="8" y="2" width="8" height="4" rx="1"/><path d="M16 4h2a2 2 0 012 2v14a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2h2"/></svg>;
  if (kind === 'approved') return <svg {...p}><path d="M20 6L9 17l-5-5"/></svg>;
  if (kind === 'submetido') return <svg {...p}><path d="M22 2L11 13"/><path d="M22 2l-7 20-4-9-9-4 20-7z"/></svg>;
  if (kind === 'editado') return <svg {...p}><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 013 3L7 19l-4 1 1-4 12.5-12.5z"/></svg>;
  if (kind === 'rejected') return <svg {...p}><circle cx="12" cy="12" r="9"/><path d="M15 9l-6 6M9 9l6 6"/></svg>;
  if (kind === 'saved') return <svg {...p}><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><path d="M14 2v6h6"/></svg>;
  return <svg {...p}><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg>;
}

function brfTextoSug(v) {
  if (v && typeof v === 'object' && !Array.isArray(v)) {
    if (v.type || v.headline || v.buy_qty) {
      const partes = [v.type, v.buy_qty && v.get_qty ? `${v.buy_qty}+${v.get_qty}` : '', v.get_item, v.headline, v.condition].filter(Boolean);
      return partes.join('. ') || '—';
    }
    return JSON.stringify(v);
  }
  if (Array.isArray(v)) {
    if (!v.length) return '—';
    return v.map((x, i) => `${i + 1}. ${x && typeof x === 'object' ? (x.kpi ? `${x.canal || 'negocio'}: ${x.kpi}` : JSON.stringify(x)) : x}`).join('\n');
  }
  const s = String(v ?? '').trim();
  return s || '—';
}

const BRF_BLOCO_LABEL = { block1: 'Produto', block2: 'Cliente-alvo', block3: 'Mercado', block4: 'Campanha', block5: 'Restrições' };

function BriefingAnaliseCampos({ mapa }) {
  if (!mapa?.length) return null;
  const ordenado = [...mapa].sort((a, b) => (a.campo === 'elevator_pitch' ? -1 : b.campo === 'elevator_pitch' ? 1 : 0));
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginTop: 10 }}>
      {ordenado.map((item, n) => (
        <div key={`${item.bloco}.${item.campo}.${n}`} style={{ padding: 10, borderRadius: 8, border: item.campo === 'elevator_pitch' ? '1px solid #d97706' : '1px solid var(--border)', background: '#fff' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
            <div style={{ fontSize: 13, fontWeight: 700, color: '#112954' }}>{BRF_CAMPO_LABEL[item.campo] || item.campo}</div>
            {item.campo === 'elevator_pitch' && <span style={{ fontSize: 9, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase', padding: '2px 6px', borderRadius: 4, background: 'rgba(217,119,6,0.12)', color: '#b45309', fontFamily: 'var(--font-mono, monospace)' }}>Argumento</span>}
          </div>
          <div style={{ fontSize: 10, color: 'var(--text-dim)', marginTop: 2 }}>{BRF_BLOCO_LABEL[item.bloco] || 'Briefing'}</div>
          {item.de_onde && (
            <>
              <div style={{ fontSize: 10, fontWeight: 650, letterSpacing: '0.04em', textTransform: 'uppercase', color: '#94A4C4', marginTop: 8 }}>{item.explicito ? 'A nota indica este campo' : 'De onde saiu'}</div>
              {!item.explicito && <div style={{ fontSize: 11, color: '#4a5e7a', marginTop: 2 }}>A nota não nomeia o campo. Corresponde a esta passagem do que está preenchido:</div>}
              <div style={{ fontSize: 12, color: '#112954', whiteSpace: 'pre-wrap', overflowWrap: 'anywhere', lineHeight: 1.45, marginTop: 4, paddingLeft: 8, borderLeft: '2px solid #d97706' }}>{item.de_onde}</div>
            </>
          )}
          <div style={{ fontSize: 10, fontWeight: 650, letterSpacing: '0.04em', textTransform: 'uppercase', color: '#b45309', marginTop: 8 }}>O que a nota pede</div>
          <div style={{ fontSize: 12, color: '#112954', whiteSpace: 'pre-wrap', overflowWrap: 'anywhere', lineHeight: 1.45, marginTop: 2 }}>{item.pedido}</div>
        </div>
      ))}
    </div>
  );
}

function brfCamposRev(row) {
  let c = row?.campos;
  if (typeof c === 'string') { try { c = JSON.parse(c); } catch { c = null; } }
  return c && typeof c === 'object' ? c : {};
}

function brfItensCampo(v) {
  if (Array.isArray(v)) {
    return v.map(x => {
      if (x && typeof x === 'object') return x.kpi ? `${x.canal || 'negocio'}: ${x.kpi}` : brfTextoSug(x);
      return String(x ?? '').trim();
    }).filter(Boolean);
  }
  if (v && typeof v === 'object') return [brfTextoSug(v)].filter(s => s && s !== '—');
  const s = String(v ?? '').trim();
  return s ? [s] : [];
}

function brfAlteracaoCampo(antes, depois) {
  const a = brfItensCampo(antes);
  const d = brfItensCampo(depois);
  if (!(Array.isArray(antes) || Array.isArray(depois))) {
    return { lista: false, antes: a[0] || '', depois: d[0] || '' };
  }
  const sa = new Set(a);
  const sd = new Set(d);
  return { lista: true, entrou: d.filter(x => !sa.has(x)), saiu: a.filter(x => !sd.has(x)) };
}

const PedidoCorreccao = ({ briefingId, pedido, correcoes = [], valores, onApplyCorrecao, onIrAoCampo, autorNome, autorEmail }) => {
  const mapaBd = Array.isArray(pedido?.campos?.mapa) ? pedido.campos.mapa : [];
  const aplicadas = (correcoes || []).filter(r => String(r.pedido_id) === String(pedido?.id));
  const porCampo = new Map();
  for (const row of aplicadas) {
    const campo = brfCamposRev(row).campo;
    if (campo && !porCampo.has(campo)) porCampo.set(campo, row);
  }
  const camposPedido = mapaBd.map(m => m.campo).filter(Boolean);
  const tudoAplicado = porCampo.size > 0 && (camposPedido.length === 0 || camposPedido.every(c => porCampo.has(c)));
  const vistas = new Set();
  const aplicadasOrdenadas = [];
  for (const campo of camposPedido) {
    const row = porCampo.get(campo);
    if (!row || vistas.has(campo)) continue;
    vistas.add(campo);
    aplicadasOrdenadas.push(row);
  }
  for (const [campo, row] of porCampo) {
    if (vistas.has(campo)) continue;
    vistas.add(campo);
    aplicadasOrdenadas.push(row);
  }
  const [rever, setRever] = React.useState(false);
  const [analise, setAnalise] = React.useState(null);
  const [aAnalisar, setAAnalisar] = React.useState(false);
  const [aCorrigir, setACorrigir] = React.useState(false);
  const [erro, setErro] = React.useState('');
  const [sugestoes, setSugestoes] = React.useState([]);
  const [estado, setEstado] = React.useState({});
  const mapa = analise?.mapa?.length ? analise.mapa : mapaBd;
  const btn = (primary, disabled) => ({
    fontSize: 11, fontWeight: 650, padding: '6px 10px', borderRadius: 6,
    border: primary ? 'none' : '1px solid var(--border)',
    background: primary ? 'var(--ai-500, #3859D0)' : 'transparent',
    color: primary ? '#fff' : 'var(--text)',
    cursor: disabled ? 'default' : 'pointer', opacity: disabled ? 0.55 : 1, fontFamily: 'inherit',
  });

  const analisar = async () => {
    if (aAnalisar) return;
    setAAnalisar(true);
    setErro('');
    try {
      const data = await window.MktBriefingsAPI.analisarPedido(briefingId, pedido.id);
      if (!data?.mapa?.length) {
        setErro('A Digi não ligou o pedido a campos do briefing.');
        return;
      }
      setAnalise(data);
    } catch (e) {
      setErro(e.message || 'Não foi possível analisar o pedido.');
    } finally {
      setAAnalisar(false);
    }
  };

  const corrigir = async () => {
    if (aCorrigir || !mapa.length) return;
    setACorrigir(true);
    setErro('');
    try {
      const data = await window.MktBriefingsAPI.corrigirPedido(briefingId, { pedido_id: pedido.id, mapa });
      setSugestoes(Array.isArray(data?.sugestoes) ? data.sugestoes : []);
      setEstado({});
    } catch (e) {
      setErro(e.message || 'Não foi possível corrigir o pedido.');
    } finally {
      setACorrigir(false);
    }
  };

  const aceitar = async (sug) => {
    const key = `${sug.bloco}.${sug.campo}`;
    if (estado[key]) return;
    setEstado(s => ({ ...s, [key]: 'a gravar' }));
    setErro('');
    try {
      const ok = await onApplyCorrecao?.(pedido.id, sug, autorNome, autorEmail);
      setEstado(s => ({ ...s, [key]: ok === false ? 'erro' : 'aceite' }));
      if (ok === false) setErro('Não foi possível gravar o campo.');
    } catch (e) {
      setEstado(s => ({ ...s, [key]: 'erro' }));
      setErro(e.message || 'Não foi possível gravar a correcção.');
    }
  };

  const ultima = aplicadasOrdenadas[aplicadasOrdenadas.length - 1] || aplicadas[0];
  return (
    <div style={{ marginTop: 12, paddingTop: 12, borderTop: '1px solid var(--border)' }}>
      {aplicadasOrdenadas.length > 0 && (
        <div style={{ marginBottom: 12 }}>
          <div style={{ fontSize: 11, fontWeight: 700, color: '#15803d' }}>Correcções aplicadas{ultima?.created_at ? ` · ${brfDia(ultima.created_at)}` : ''}</div>
          {ultima?.autor_nome && <div style={{ fontSize: 11, color: '#4a5e7a', marginTop: 2 }}>{ultima.autor_nome}</div>}
          {aplicadasOrdenadas.map(row => {
            const c = brfCamposRev(row);
            const alt = brfAlteracaoCampo(c.antes, c.depois);
            const linha = (rotulo, texto, i) => (
              <div key={`${rotulo}-${i}`} style={{ marginTop: 8 }}>
                <div style={{ fontSize: 10, fontWeight: 650, letterSpacing: '0.04em', textTransform: 'uppercase', color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>{rotulo}</div>
                <div style={{ fontSize: 12, color: '#112954', lineHeight: 1.45, marginTop: 2, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }}>{texto}</div>
              </div>
            );
            return (
              <button key={row.id} type="button" onClick={() => onIrAoCampo?.(c.bloco, c.campo)} style={{
                display: 'block', width: '100%', textAlign: 'left', background: 'transparent',
                border: 0, borderTop: '1px solid var(--border)', marginTop: 10, padding: '10px 0 0',
                cursor: 'pointer', fontFamily: 'inherit', color: 'inherit',
              }}>
                <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 8 }}>
                  <span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)' }}>{BRF_CAMPO_LABEL[c.campo] || c.campo}</span>
                  <span style={{ fontSize: 11, color: 'var(--text-muted)', flexShrink: 0 }}>{BRF_BLOCO_LABEL[c.bloco] || ''}</span>
                </div>
                {alt.lista ? (
                  <>
                    {alt.entrou.map((texto, i) => linha('Entrou', texto, i))}
                    {alt.saiu.map((texto, i) => linha('Saiu', texto, i))}
                    {!alt.entrou.length && !alt.saiu.length && <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 6 }}>O texto ficou igual.</div>}
                  </>
                ) : (
                  <>
                    {linha('Antes', alt.antes || '—', 0)}
                    {linha('Depois', alt.depois || '—', 1)}
                  </>
                )}
              </button>
            );
          })}
        </div>
      )}
      {tudoAplicado && !rever ? (
        <button type="button" onClick={() => setRever(true)} style={btn(false, false)}>Analisar outra vez</button>
      ) : (
      <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
        <button type="button" onClick={analisar} disabled={aAnalisar} style={btn(false, aAnalisar)}>
          {aAnalisar ? 'A analisar...' : (analise?.mapa?.length ? 'Analisado' : 'Analisar')}
        </button>
        <button type="button" onClick={corrigir} disabled={aCorrigir || !mapa.length} style={btn(true, aCorrigir || !mapa.length)}>
          {aCorrigir ? 'A corrigir...' : 'Corrigir'}
        </button>
      </div>
      )}
      {erro && <div style={{ marginTop: 8, fontSize: 11, color: '#dc2626', lineHeight: 1.4 }}>{erro}</div>}
      {analise?.mapa?.length > 0 && <BriefingAnaliseCampos mapa={analise.mapa} />}
      {sugestoes.map(sug => {
        const key = `${sug.bloco}.${sug.campo}`;
        const st = estado[key];
        const igual = JSON.stringify(sug.valor_actual ?? null) === JSON.stringify(sug.valor_proposto ?? null);
        const podeAceitar = !igual && st !== 'aceite' && st !== 'a gravar';
        return (
          <div key={key} style={{ marginTop: 10, padding: 10, borderRadius: 8, border: '1px solid var(--border)', background: 'color-mix(in srgb, var(--ai-500, #3859D0) 4%, transparent)' }}>
            <div style={{ fontSize: 10, fontWeight: 650, letterSpacing: '0.04em', textTransform: 'uppercase', color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>{BRF_CAMPO_LABEL[sug.campo] || sug.campo}</div>
            <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 4, lineHeight: 1.4 }}>{sug.pedido}</div>
            {igual ? (
              <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 8, lineHeight: 1.45 }}>{sug.nota || 'Sem alteração proposta para este campo.'}</div>
            ) : (
              <>
                <div style={{ fontSize: 10, fontWeight: 650, color: 'var(--text-dim)', marginTop: 8 }}>Actual</div>
                <div style={{ fontSize: 12, color: 'var(--text)', whiteSpace: 'pre-wrap', overflowWrap: 'anywhere', lineHeight: 1.45 }}>{brfTextoSug(sug.valor_actual)}</div>
                <div style={{ fontSize: 10, fontWeight: 650, color: 'var(--ai-500, #3859D0)', marginTop: 8 }}>Sugestão</div>
                <div style={{ fontSize: 12, color: 'var(--text)', whiteSpace: 'pre-wrap', overflowWrap: 'anywhere', lineHeight: 1.45 }}>{brfTextoSug(sug.valor_proposto)}</div>
                {sug.incompleto && sug.nota && <div style={{ fontSize: 11, color: '#d97706', marginTop: 6, lineHeight: 1.4 }}>{sug.nota}</div>}
              </>
            )}
            {!igual && (sug.fontes || []).length > 0 && (
              <div style={{ fontSize: 10, color: 'var(--text-dim)', marginTop: 6, lineHeight: 1.45 }}>
                {(sug.fontes || []).map((f, i) => {
                  const href = hrefFonteKb(f);
                  const nome = String(f.file_name || '');
                  const pags = textoPaginasKb(f.pages);
                  const texto = [f.label || f.source_label, f.type === 'url' ? '' : nome, f.folder, f.type === 'url' ? '' : pags].filter(Boolean).join(' · ');
                  const linha = f.type === 'url' && href ? href : texto;
                  return href
                    ? <a key={i} href={href} target="_blank" rel="noopener noreferrer" style={{ display: 'block', color: 'var(--ai-500)', textDecoration: 'none', overflowWrap: 'anywhere' }}>{linha}{f.type !== 'url' ? ' · Abrir no OneDrive' : ''}</a>
                    : <div key={i} style={{ overflowWrap: 'anywhere' }}>{texto}</div>;
                })}
              </div>
            )}
            {!igual && (
              <div style={{ display: 'flex', gap: 8, marginTop: 8, alignItems: 'center' }}>
                {st === 'aceite' ? <span style={{ fontSize: 11, color: '#15803d' }}>Aceite</span>
                  : st === 'ignorada' ? <span style={{ fontSize: 11, color: 'var(--text-dim)' }}>Ignorada</span>
                  : (
                    <>
                      <button type="button" onClick={() => aceitar(sug)} disabled={!podeAceitar} style={btn(true, !podeAceitar)}>{st === 'a gravar' ? 'A gravar...' : 'Aceitar'}</button>
                      <button type="button" onClick={() => setEstado(s => ({ ...s, [key]: 'ignorada' }))} disabled={st === 'a gravar'} style={btn(false, st === 'a gravar')}>Ignorar</button>
                    </>
                  )}
              </div>
            )}
          </div>
        );
      })}
    </div>
  );
};

const BRF_ROLE_LABELS = { fc_dir: 'Marketing Manager', brand_dir: 'CEO', rl: 'Chairman' };

function brfFasesAprovacao(briefing, approvals, revisoes) {
  const required = getRequiredApprovers(briefing?.brand_slug);
  const curStatus = briefing?.status;
  const activeRole = getActiveRole(curStatus, required.map(r => r.role));
  const daVolta = aprovacoesDaVolta(approvals, curStatus);
  const pedidos = (revisoes || []).filter(r => r.tipo === 'pedido');
  const latest = pedidos[0] || null;
  const cicloReposto = curStatus === 'alteracoes_pedidas' || curStatus === 'rejeitado';
  const withDate = (label, at) => {
    const d = brfDia(at);
    return d ? `${label} · ${d}` : label;
  };
  const fromAprov = (a) => {
    if (!a) return null;
    if (a.action === 'approved') return { text: withDate('Aprovado', a.created_at), color: '#15803d' };
    if (a.action === 'rejected') return { text: withDate('Rejeitou', a.created_at), color: '#dc2626' };
    if (a.action === 'changes_requested') return { text: withDate('Pediu alterações', a.created_at), color: '#d97706' };
    return null;
  };
  const fromPedido = (p) => {
    if (!p) return null;
    const rejected = p.acao === 'rejected';
    return { text: withDate(rejected ? 'Rejeitou' : 'Pediu alterações', p.created_at), color: rejected ? '#dc2626' : '#d97706' };
  };
  const phaseNote = (role, idx) => {
    if (!curStatus || curStatus === 'rascunho') return { text: 'Ainda sem decisão', color: '#94A4C4' };
    const finalAprov = daVolta.find(a => a.role === role && a.action !== 'saved');
    const pedidoRole = pedidos.find(p => p.role === role);
    if (cicloReposto) {
      if (latest && latest.role === role) return fromPedido(latest);
      const recorded = fromAprov(finalAprov) || fromPedido(pedidoRole);
      if (recorded) return recorded;
      const interruptIdx = latest ? required.findIndex(r => r.role === latest.role) : -1;
      if (interruptIdx >= 0 && idx < interruptIdx) return { text: 'Aprovado', color: '#15803d' };
      return { text: 'Ainda sem decisão', color: '#94A4C4' };
    }
    if (curStatus === 'aprovado' || curStatus === 'em_campanha') {
      return fromAprov(finalAprov) || { text: 'Aprovado', color: '#15803d' };
    }
    if (finalAprov) return fromAprov(finalAprov);
    if (role === activeRole) return { text: 'A aguardar decisão', color: '#d97706' };
    const activeIdx = activeRole ? required.findIndex(r => r.role === activeRole) : -1;
    if (activeIdx >= 0 && idx < activeIdx) return { text: 'Aprovado', color: '#15803d' };
    return { text: 'Aguarda fase anterior', color: '#94A4C4' };
  };
  return required.map(({ role, name }, idx) => {
    const note = phaseNote(role, idx);
    return { role, label: BRF_ROLE_LABELS[role] || role, name, text: note.text, color: note.color };
  });
}

const BriefingFluxoBar = ({ briefing, approvals, revisoes }) => {
  const fases = brfFasesAprovacao(briefing, approvals, revisoes);
  if (!fases.length) return null;
  const tom = (color) => {
    if (color === '#15803d') return { bg: 'rgba(21,128,61,0.10)', fg: '#15803d' };
    if (color === '#dc2626') return { bg: 'rgba(220,38,38,0.10)', fg: '#dc2626' };
    if (color === '#d97706') return { bg: 'rgba(217,119,6,0.12)', fg: '#b45309' };
    return { bg: '#F1F5F9', fg: '#64748b' };
  };
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px',
      background: '#fff', borderRadius: 8,
      boxShadow: '0 1px 4px rgba(40,41,61,0.04), 0 4px 16px rgba(96,97,112,0.08)',
      border: '1px solid #ECEFF5', overflowX: 'auto', flexWrap: 'nowrap',
    }}>
      {fases.map((fase, i) => {
        const badge = tom(fase.color);
        return (
          <React.Fragment key={fase.role}>
            <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8, flexShrink: 0 }}>
              <span style={{ fontSize: 12, fontWeight: 700, color: '#112954', whiteSpace: 'nowrap' }}>{fase.label}</span>
              <span style={{ fontSize: 12, color: '#4a5e7a', whiteSpace: 'nowrap' }}>{fase.name}</span>
              <span style={{
                fontSize: 9, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase',
                padding: '3px 7px', borderRadius: 4, background: badge.bg, color: badge.fg,
                fontFamily: 'var(--font-mono, monospace)', whiteSpace: 'nowrap',
              }}>{fase.text}</span>
            </span>
            {i < fases.length - 1 && <span style={{ color: '#7a8fa8', fontSize: 12, flexShrink: 0 }}>›</span>}
          </React.Fragment>
        );
      })}
    </div>
  );
};

const BriefingSideRail = ({ briefing, valores, revisoes = [], approvals = [], canApprove, noteValue, onNoteChange, noteError, onSaveNote, onRequestChanges, onApprove, onReject, savingNote, noteSaved, acting, overlay, onClose, onApplyCorrecao, onIrAoCampo, autorNome, autorEmail, stickyTop = 16 }) => {
  const linhas = brfLinhasNotas(revisoes, approvals);
  const pedidoCorrigirId = linhas.find(l => l.kind === 'changes_requested')?.id;
  const [notaAberta, setNotaAberta] = React.useState({});
  const zonaRef = React.useRef(null);
  const [alturaZona, setAlturaZona] = React.useState(0);
  const [railLargo, setRailLargo] = React.useState(() => {
    try { return sessionStorage.getItem('mkt-notas-rail') !== 'curta'; } catch { return true; }
  });
  const guardarLargura = (larga) => {
    setRailLargo(larga);
    try { sessionStorage.setItem('mkt-notas-rail', larga ? 'larga' : 'curta'); } catch { /* o browser pode bloquear */ }
  };
  const encolherNotas = () => {
    const h = zonaRef.current?.offsetHeight || 0;
    if (h) setAlturaZona(h);
    guardarLargura(false);
  };
  const estiloIconeNotas = {
    width: 36, height: 36, borderRadius: 8, border: '1px solid var(--border)',
    background: '#fff', color: 'var(--text-muted)', cursor: 'pointer',
    display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, padding: 0,
  };
  const ocupado = savingNote || acting;
  const accao = {
    cursor: ocupado ? 'default' : 'pointer', opacity: ocupado ? 0.6 : 1,
  };

  const notas = (
    <FormCard>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, paddingBottom: 8, borderBottom: '1px solid var(--border)' }}>
        <div style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>Notas</div>
        {!overlay && (
          <button type="button" onClick={encolherNotas} aria-label="Encolher notas" title="Encolher" style={estiloIconeNotas}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 6 15 12 9 18"/></svg>
          </button>
        )}
      </div>
      {linhas.length === 0 ? (
        <div style={{ fontSize: 13, color: 'var(--text-muted)', lineHeight: 1.5 }}>Ainda não há notas neste briefing.</div>
      ) : linhas.map((l, i) => {
        const aberto = i === 0 || l.id === pedidoCorrigirId || notaAberta[l.id];
        const meta = brfNotaMeta(l.kind);
        const podeFechar = i !== 0 && l.id !== pedidoCorrigirId;
        return (
          <div key={l.id} style={{ display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
            <button type="button" onClick={() => { if (podeFechar) setNotaAberta(s => ({ ...s, [l.id]: !s[l.id] })); }}
              style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, width: '100%', textAlign: 'left', background: 'none', border: 'none', padding: 0, cursor: podeFechar ? 'pointer' : 'default', fontFamily: 'inherit' }}>
              <span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)', minWidth: 0 }}>{l.nome}</span>
              {l.at && <span style={{ fontSize: 11, color: 'var(--text-muted)', flexShrink: 0 }}>{brfDia(l.at)}</span>}
            </button>
            <span style={{
              alignSelf: 'flex-start',
              fontSize: 9, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase',
              padding: '3px 7px', borderRadius: 4, background: meta.bg, color: meta.fg,
              fontFamily: 'var(--font-mono, monospace)',
            }}>{meta.verb}</span>
            {aberto && (
              <div style={{ fontSize: 13, color: 'var(--text)', lineHeight: 1.5, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }}>{l.texto}</div>
            )}
            {aberto && l.id === pedidoCorrigirId && l.raw && (
              <PedidoCorreccao
                briefingId={briefing?.id}
                pedido={l.raw}
                correcoes={(revisoes || []).filter(r => r.tipo === 'correcao')}
                valores={valores}
                onApplyCorrecao={onApplyCorrecao}
                onIrAoCampo={onIrAoCampo}
                autorNome={autorNome}
                autorEmail={autorEmail}
              />
            )}
          </div>
        );
      })}
    </FormCard>
  );

  const decisao = canApprove ? (
    <FormCard title="Decisão">
      <Field label="Nota" hint="Obrigatória para pedir alterações ou rejeitar.">
        <TInput value={noteValue} onChange={onNoteChange}
          placeholder="Sugestões de melhoria, campos a corrigir..." multiline />
      </Field>
      {noteError && <div style={{ fontSize: 12, color: 'var(--danger)', fontWeight: 500 }}>{noteError}</div>}
      <div style={{ display: 'flex', gap: 6, flexWrap: 'nowrap', alignItems: 'center' }}>
        <button type="button" className="btn btn-primary btn-sm" onClick={onApprove} disabled={ocupado} style={{ ...accao, whiteSpace: 'nowrap', flexShrink: 0 }}>Aprovar</button>
        <button type="button" className="btn btn-sm" onClick={onRequestChanges} disabled={ocupado} style={{ ...accao, whiteSpace: 'nowrap', flexShrink: 0 }}>{acting ? 'A enviar...' : 'Pedir alterações'}</button>
        <button type="button" className="btn btn-sm" onClick={onReject} disabled={ocupado} style={{ ...accao, whiteSpace: 'nowrap', flexShrink: 0, color: 'var(--danger, #dc2626)', borderColor: 'color-mix(in oklch, var(--danger, #dc2626) 35%, var(--border))' }}>Rejeitar</button>
        <button type="button" className="btn btn-ghost btn-sm" onClick={onSaveNote} disabled={ocupado} style={{ ...accao, whiteSpace: 'nowrap', flexShrink: 0 }}>{savingNote ? 'A guardar...' : noteSaved ? 'Nota guardada' : 'Guardar rascunho'}</button>
      </div>
    </FormCard>
  ) : null;

  const corpo = (
    <>
      {overlay && (
        <div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 8 }}>
          <button type="button" onClick={onClose} aria-label="Fechar notas" style={{ background: 'none', border: '1px solid var(--border)', width: 30, height: 30, borderRadius: 6, cursor: 'pointer', color: 'var(--text-muted)', fontSize: 16, lineHeight: 1 }}>×</button>
        </div>
      )}
      {notas}
      {decisao && (overlay
        ? decisao
        : <div style={{ position: 'sticky', top: stickyTop, zIndex: 2 }}>{decisao}</div>)}
    </>
  );

  if (overlay) {
    return (
      <aside className="scrollbar" style={{
        position: 'absolute', top: 0, right: 0, bottom: 0, zIndex: 30,
        width: 'min(560px, 100%)', boxSizing: 'border-box', overflowY: 'auto',
        padding: 12, background: 'var(--bg, #f5f7fa)',
        boxShadow: '-8px 0 24px rgba(17,41,84,0.12)',
      }}>
        {corpo}
      </aside>
    );
  }

  if (!railLargo) {
    const icones = linhas.length ? linhas : [{ id: 'vazio', kind: 'saved', nome: 'Notas', texto: 'Ainda não há notas neste briefing.', at: null }];
    return (
      <aside style={{
        width: 88,
        flexShrink: 0,
        alignSelf: 'stretch',
        boxSizing: 'border-box',
        padding: '16px 12px 64px 0',
      }}>
        <div style={{
          height: alturaZona || '100%',
          minHeight: alturaZona || undefined,
          boxSizing: 'border-box',
          background: 'var(--bg-elev, #ffffff)',
          border: '1px solid var(--border)',
          borderRadius: 10,
          padding: '12px 8px',
          display: 'flex',
          flexDirection: 'column',
          alignItems: 'center',
          gap: 8,
        }}>
          <button type="button" onClick={() => guardarLargura(true)} aria-label="Alargar notas" title="Alargar" style={estiloIconeNotas}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 6 9 12 15 18"/></svg>
          </button>
          {icones.map(l => {
            const meta = l.id === 'vazio' ? { bg: '#F1F5F9', fg: '#64748b' } : brfNotaMeta(l.kind);
            const dica = l.id === 'vazio' ? l.texto : dicaNota(l);
            return (
              <button key={l.id} type="button" onClick={() => guardarLargura(true)} title={dica} aria-label={dica}
                style={{ ...estiloIconeNotas, background: meta.bg, color: meta.fg, borderColor: 'transparent' }}>
                <NotaIcon kind={l.kind} />
              </button>
            );
          })}
          {canApprove && (
            <button type="button" onClick={() => guardarLargura(true)} title="Decisão" aria-label="Decisão"
              style={{ ...estiloIconeNotas, marginTop: 'auto', background: 'rgba(56,89,208,0.10)', color: '#3859D0', borderColor: 'transparent' }}>
              <NotaIcon kind="decisao" />
            </button>
          )}
        </div>
      </aside>
    );
  }

  return (
    <aside style={{
      width: 560,
      flexShrink: 0,
      alignSelf: 'stretch',
      boxSizing: 'border-box',
      overflow: 'visible',
      padding: '16px 40px 64px 0',
    }}>
      <div ref={zonaRef}>{corpo}</div>
    </aside>
  );
};

// ── Briefing Rápido - placeholders por marca (ordem: objectivo, mensagem, público, restrições) ──
const BR_PLACEHOLDERS = {
  mimaki: [
    'ex: Gerar leads qualificados para demonstração técnica. Posicionar a Mimaki como a escolha certa para gráficas que querem crescer.',
    'ex: Produz mais, em menos espaço, com a tecnologia Mimaki.',
    'ex: Directores de produção e responsáveis de compras de gráficas médias e grandes em Portugal e Espanha.',
    'ex: Não comparar directamente com Roland ou Epson. Evitar promessas de preço sem contexto técnico.',
  ],
  biond: [
    'ex: Aumentar a notoriedade dos filmes BIOND junto de impressoras e distribuidores. Posicionar como a alternativa sustentável ao PVC.',
    'ex: Imprime com consciência. BIOND é o vinil que cuida do planeta.',
    'ex: Responsáveis de compras e gestores de produto de empresas de sinalização com compromissos ESG.',
    'ex: Não focar excessivamente nas certificações técnicas. Não comparar directamente com 3M ou Avery.',
  ],
  decal: [
    'ex: Gerar leads de distribuidores e aplicadores para a gama Decal. Reforçar a posição como fornecedor de referência em Portugal.',
    'ex: O vinil que trabalha tão bem quanto tu.',
    'ex: Aplicadores profissionais e gestores de compras de empresas de sinalização e decoração exterior.',
    'ex: Não mencionar preços. Evitar comparações directas com Orafal ou Avery sem dados de suporte.',
  ],
  digidelta: [
    'ex: Comunicar ao mercado uma nova solução, parceria ou marco da Digidelta. Reforçar a posição como distribuidor de referência.',
    'ex: A Digidelta: o parceiro que te dá acesso ao melhor do mercado de impressão digital.',
    'ex: Clientes actuais e potenciais da Digidelta em Portugal e Espanha. Integradores e parceiros do sector.',
    'ex: Não apresentar como simples revendedor. Evitar linguagem demasiado técnica sem contexto de negócio.',
  ],
  netscreen: [
    'ex: Anunciar a parceria com a Fabulux e posicionar a NetScreen como a empresa mais forte e preparada no mercado LED profissional em Portugal.',
    'ex: A NetScreen está mais forte do que nunca.',
    'ex: Clientes actuais, potenciais clientes, integradores AV, entidades públicas e comunicação social.',
    'ex: Não apresentar como novo fornecedor. A Fabulux não é o protagonista. A NetScreen não depende da Fabulux.',
  ],
  sensek: [
    'ex: Posicionar o Sensek como o sistema de personalização têxtil mais acessível para negócios de bordado e transfer digital.',
    'ex: O teu negócio têxtil começa aqui.',
    'ex: Proprietários de pequenos negócios têxteis, bordadeiras e empresas de personalização de vestuário.',
    'ex: Não comparar directamente com Brother ou Tajima. Evitar linguagem demasiado industrial.',
  ],
  alldecor: [
    'ex: Aumentar o tráfego ao site AllDecor e gerar pedidos de instalação. Posicionar como a escolha premium de decoração bio-based.',
    'ex: Transforma o teu espaço num fim de semana.',
    'ex: Proprietários de casa, designers de interiores e gestores de espaços comerciais em Portugal.',
    'ex: Não focar apenas na sustentabilidade - o design e a facilidade de aplicação são o argumento principal.',
  ],
};

// ── Briefing Rápido - perguntas sequenciais ───────────────────────────────────
const BR_PERGUNTAS = [
  {
    bloco: 'Produto', label: 'B1',
    pergunta: 'O que queres que este produto passe a significar no mercado?',
    hint: 'Serve para a Digi perceber o produto. A oferta e os mercados ficam na campanha.',
    placeholder: 'ex: Anunciar a parceria com a Fabulux e posicionar a NetScreen como a empresa mais forte e preparada no mercado LED profissional em Portugal.',
  },
  {
    bloco: 'Produto', label: 'B2',
    pergunta: 'Se tivesses 10 segundos, como descrevias o produto?',
    hint: 'A frase que distingue o produto. A mensagem de cada campanha define-se depois.',
    placeholder: 'ex: A NetScreen está mais forte do que nunca.',
  },
  {
    bloco: 'Público', label: 'B3',
    pergunta: 'A quem se dirige esta campanha?',
    hint: 'Quem são as pessoas que precisam de ver isto?',
    placeholder: 'ex: Clientes actuais, potenciais clientes, integradores AV, entidades públicas e comunicação social.',
  },
  {
    bloco: 'Restrições', label: 'B4',
    pergunta: 'O que esta campanha NÃO deve comunicar ou parecer?',
    hint: 'Erros a evitar, mensagens incorrectas, percepções indesejadas - pode deixar em branco.',
    placeholder: 'ex: Não apresentar como novo fornecedor. A Fabulux não é o protagonista. A NetScreen não depende da Fabulux.',
  },
];

const BriefingRapidoModal = ({ allBrands, onDone, onClose, prefill }) => {
  const [selectedBrand, setSelectedBrand] = React.useState(prefill?.marca || '');
  const [currentQ,      setCurrentQ]      = React.useState(prefill ? 0 : -1); // -1 = seleccionar marca
  const [respostas,     setRespostas]      = React.useState(() => Object.fromEntries((prefill?.respostas || []).map((r, i) => [i, r || ''])));
  const [generating,    setGenerating]     = React.useState(false);
  const [genStep,       setGenStep]        = React.useState('');
  const [genLog,        setGenLog]         = React.useState([]);
  const [genPct,        setGenPct]         = React.useState(0);
  const [error,         setError]          = React.useState('');

  const total     = BR_PERGUNTAS.length;
  const pct       = currentQ < 0 ? 0 : Math.round(((currentQ + 1) / total) * 100);
  const isLast    = currentQ === total - 1;
  const pergunta  = currentQ >= 0 ? BR_PERGUNTAS[currentQ] : null;

  const handleGerar = async () => {
    setGenerating(true); setError(''); setGenLog([]); setGenPct(0); setGenStep('A analisar as respostas...');
    const descricao = BR_PERGUNTAS.map((p, i) =>
      `BLOCO ${p.label} - ${p.bloco.toUpperCase()}: "${respostas[i] || '(sem resposta)'}"`
    ).join('\n');
    try {
      const res = await fetch('/api/marketing/briefings/rapido', {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ descricao, brand_name: selectedBrand }),
      });
      const reader = res.body.getReader(); const decoder = new TextDecoder(); let buf = '';
      let _lastStepAt = 0;
      while (true) {
        const { done, value } = await reader.read();
        if (value) {
          buf += decoder.decode(value, { stream: !done });
          const lines = buf.split('\n'); buf = done ? '' : (lines.pop() ?? '');
          for (const line of lines) {
            if (!line.startsWith('data: ')) continue;
            let evt; try { evt = JSON.parse(line.slice(6)); } catch { continue; }
            if (evt.type === 'status') {
              const wait = 500 - (Date.now() - _lastStepAt);
              if (wait > 0 && _lastStepAt > 0) await new Promise(r => setTimeout(r, wait));
              setGenLog(l => [...l, evt.message]); setGenStep(evt.message);
              if (evt.step) setGenPct(Math.round((evt.step / 3) * 100));
              _lastStepAt = Date.now();
            }
            else if (evt.type === 'done') setTimeout(() => onDone(limparTravessao(evt.data), selectedBrand, undefined, prefill?.origem || null), 700);
            else if (evt.type === 'error') throw new Error(evt.message);
          }
        }
        if (done) break;
      }
    } catch (e) {
      setError(e.message || 'Erro inesperado');
      setGenerating(false);
    }
  };

  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,.45)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}>
      <div style={{ background: '#fff', borderRadius: 14, width: '100%', maxWidth: 560, boxShadow: '0 24px 80px rgba(0,0,0,.2)', display: 'flex', flexDirection: 'column' }}>

        {/* Header */}
        <div style={{ padding: '18px 24px 14px', borderBottom: '1px solid #f1f5f9' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: currentQ >= 0 ? 12 : 0 }}>
            <div style={{ width: 30, height: 30, borderRadius: 8, background: 'rgba(56,89,208,.1)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
              <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#3859D0" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
            </div>
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 14, fontWeight: 700, color: '#1d2e38', fontFamily: 'Montserrat, sans-serif' }}>Briefing Rápido</div>
              {currentQ >= 0 && !generating && (
                <div style={{ fontSize: 11, color: '#94a3b8' }}>
                  Pergunta {currentQ + 1} de {total}
                  {selectedBrand && <span style={{ marginLeft: 8, color: '#3859D0', fontWeight: 600 }}>{allBrands?.find(b=>b.slug===selectedBrand)?.name}</span>}
                </div>
              )}
            </div>
            {!generating && <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 16, color: '#94a3b8', padding: 4, lineHeight: 1 }}>✕</button>}
          </div>
          {/* Barra de progresso */}
          {currentQ >= 0 && !generating && (
            <div style={{ height: 4, background: '#f1f5f9', borderRadius: 99, overflow: 'hidden' }}>
              <div style={{ height: '100%', background: '#3859D0', borderRadius: 99, width: `${pct}%`, transition: 'width .3s' }} />
            </div>
          )}
        </div>

        {/* Body */}
        <div style={{ padding: '20px 24px', display: 'flex', flexDirection: 'column', gap: 16, minHeight: 220 }}>

          {prefill?.origem && !generating && (
            <div style={{ fontSize: 11.5, lineHeight: 1.5, color: '#3859D0', background: 'rgba(56,89,208,.06)', border: '1px solid rgba(56,89,208,.18)', borderRadius: 8, padding: '8px 12px' }}>
              <strong>Proposta da análise da Digi AI em Resultados</strong>
              {prefill.origem.titulo ? <> · {prefill.origem.titulo}</> : null}
              <div style={{ color: '#64748b', marginTop: 2 }}>As 4 respostas vêm da proposta. Revê cada uma; o briefing fica gravado quando o guardares no editor.</div>
            </div>
          )}

          {/* Seleccionar marca */}
          {currentQ < 0 && !generating && (
            <>
              <div style={{ fontSize: 13, color: '#475569', lineHeight: 1.6 }}>
                Vou fazer-te <strong>4 perguntas rápidas</strong>.<br/>
                A Digi AI preenche o resto com base nas tuas respostas.
              </div>
              {allBrands?.length > 0 && (
                <div>
                  <label style={{ fontSize: 11, fontWeight: 600, color: '#64748b', letterSpacing: '0.04em', display: 'block', marginBottom: 6 }}>MARCA</label>
                  <select value={selectedBrand} onChange={e => setSelectedBrand(e.target.value)} style={{ width: '100%', padding: '8px 10px', fontSize: 13, border: '1px solid #e2e8f0', borderRadius: 7, background: '#fff', color: '#1d2e38', outline: 'none' }}>
                    <option value="">Seleccionar marca...</option>
                    {allBrands.map(b => <option key={b.slug} value={b.slug}>{b.name}</option>)}
                  </select>
                </div>
              )}
            </>
          )}

          {/* Pergunta actual */}
          {currentQ >= 0 && !generating && pergunta && (
            <>
              <div>
                <div style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', color: '#3859D0', fontFamily: 'monospace', marginBottom: 6 }}>
                  {pergunta.label} - {pergunta.bloco}
                </div>
                <div style={{ fontSize: 14, fontWeight: 600, color: '#1d2e38', fontFamily: 'Montserrat, sans-serif', lineHeight: 1.4, marginBottom: 4 }}>
                  {pergunta.pergunta}
                </div>
                <div style={{ fontSize: 11, color: '#94a3b8' }}>{pergunta.hint}</div>
              </div>
              <textarea
                autoFocus
                value={respostas[currentQ] || ''}
                onChange={e => setRespostas(r => ({ ...r, [currentQ]: e.target.value }))}
                rows={5}
                placeholder={(BR_PLACEHOLDERS[selectedBrand] || [])[currentQ] || pergunta.placeholder}
                style={{ width: '100%', padding: '10px 12px', fontSize: 13, border: '1px solid #e2e8f0', borderRadius: 8, resize: 'vertical', fontFamily: 'Inter, sans-serif', lineHeight: 1.6, outline: 'none', boxSizing: 'border-box', color: '#1d2e38' }}
              />
              <div style={{ fontSize: 10, color: '#94a3b8' }}>
                Podes deixar em branco - a AI preenche com base no contexto da marca e das outras respostas.
              </div>
            </>
          )}

          {/* Generating */}
          {generating && (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 16, padding: '8px 0' }}>
              <style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
              {/* Header Digi AI */}
              <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                <div style={{ width: 32, height: 32, borderRadius: 8, background: 'rgba(56,89,208,.1)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#3859D0" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
                </div>
                <div>
                  <div style={{ fontSize: 13, fontWeight: 700, color: '#1d2e38', fontFamily: 'Montserrat, sans-serif' }}>Digi AI</div>
                  <div style={{ fontSize: 11, color: '#94a3b8' }}>a preencher o briefing...</div>
                </div>
                <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0 }}>
                <span style={{ fontSize: 11, fontWeight: 600, color: '#3859D0', fontFamily: 'monospace' }}>{genPct}%</span>
                <div style={{ width: 18, height: 18, borderRadius: '50%', border: '2px solid #e2e8f0', borderTopColor: '#3859D0', animation: 'spin 0.8s linear infinite' }} />
              </div>
              </div>
              {/* Log de fases */}
              <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                {genLog.map((msg, i) => (
                  <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                    <div style={{ width: 18, height: 18, borderRadius: '50%', background: i < genLog.length - 1 ? 'rgba(16,185,129,.15)' : 'rgba(56,89,208,.1)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
                      {i < genLog.length - 1
                        ? <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#10b981" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
                        : <div style={{ width: 6, height: 6, borderRadius: '50%', background: '#3859D0' }} />
                      }
                    </div>
                    <span style={{ fontSize: 12, color: i < genLog.length - 1 ? '#94a3b8' : '#1d2e38', fontWeight: i < genLog.length - 1 ? 400 : 500 }}>{msg}</span>
                  </div>
                ))}
                {genLog.length === 0 && (
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                    <div style={{ width: 18, height: 18, borderRadius: '50%', background: 'rgba(56,89,208,.1)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                      <div style={{ width: 6, height: 6, borderRadius: '50%', background: '#3859D0' }} />
                    </div>
                    <span style={{ fontSize: 12, color: '#1d2e38', fontWeight: 500 }}>A analisar as respostas...</span>
                  </div>
                )}
              </div>
            </div>
          )}

          {error && <div style={{ fontSize: 12, color: '#ef4444', background: 'rgba(239,68,68,.06)', borderRadius: 6, padding: '8px 12px' }}>{error}</div>}
        </div>

        {/* Footer */}
        {!generating && (
          <div style={{ padding: '14px 24px', borderTop: '1px solid #f1f5f9', display: 'flex', gap: 8, justifyContent: 'space-between', alignItems: 'center' }}>
            <button onClick={onClose} className="btn" style={{ fontSize: 12 }}>Cancelar</button>
            <div style={{ display: 'flex', gap: 8 }}>
              {currentQ >= 0 && (
                <button onClick={() => setCurrentQ(q => q - 1)} className="btn" style={{ fontSize: 12 }}>Voltar</button>
              )}
              {currentQ >= 0 && !isLast && (
                <button onClick={() => setCurrentQ(q => q + 1)} className="btn btn-ai" style={{ fontSize: 12 }}>Seguinte</button>
              )}
              {currentQ >= 0 && isLast && (
                <button onClick={handleGerar} className="btn btn-ai" style={{ fontSize: 12 }}>Gerar Briefing</button>
              )}
              {currentQ < 0 && (
                <button onClick={() => setCurrentQ(0)} disabled={!selectedBrand && (allBrands?.length > 0)} className="btn btn-ai" style={{ fontSize: 12 }}>Iniciar</button>
              )}
            </div>
          </div>
        )}
      </div>
    </div>
  );
};

// ─── Modal de confirmação de eliminação ───────────────────────────────────────
const DeleteConfirmModal = ({ pendingDelete, onConfirm, onClose }) => {
  const [confirmText, setConfirmText] = React.useState('');
  const canConfirm = confirmText === 'DELETE';
  const count  = pendingDelete?.ids?.length || 0;
  const labels = pendingDelete?.labels || [];
  const isBulk = count > 1;
  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,.45)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}>
      <div style={{ background: '#fff', borderRadius: 14, width: '100%', maxWidth: 480, boxShadow: '0 24px 80px rgba(0,0,0,.2)', display: 'flex', flexDirection: 'column' }}>

        {/* Header */}
        <div style={{ padding: '18px 24px 14px', borderBottom: '1px solid #f1f5f9' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <div style={{ width: 30, height: 30, borderRadius: 8, background: 'rgba(220,38,38,.1)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#dc2626" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                <polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/>
                <path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a1 1 0 011-1h4a1 1 0 011 1v2"/>
              </svg>
            </div>
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 14, fontWeight: 700, color: '#1d2e38', fontFamily: 'Montserrat, sans-serif' }}>
                {isBulk ? `Eliminar ${count} briefings` : 'Eliminar briefing'}
              </div>
              <div style={{ fontSize: 11, color: '#94a3b8' }}>Esta acção é permanente e não pode ser revertida</div>
            </div>
            <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#94a3b8', padding: 4, lineHeight: 1, display: 'flex' }}>
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
            </button>
          </div>
        </div>

        {/* Body */}
        <div style={{ padding: '20px 24px', display: 'flex', flexDirection: 'column', gap: 16 }}>
          <div style={{ borderRadius: 8, background: 'rgba(220,38,38,.04)', border: '1px solid rgba(220,38,38,.15)', padding: '10px 14px' }}>
            {labels.slice(0, 3).map((label, i) => (
              <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '3px 0' }}>
                <div style={{ width: 4, height: 4, borderRadius: '50%', background: '#dc2626', flexShrink: 0 }} />
                <span style={{ fontSize: 12, color: '#1d2e38', fontWeight: 500 }}>{label}</span>
              </div>
            ))}
            {labels.length > 3 && (
              <div style={{ fontSize: 11, color: '#94a3b8', marginTop: 4, paddingLeft: 12 }}>+{labels.length - 3} mais</div>
            )}
          </div>
          <div>
            <label style={{ fontSize: 11, fontWeight: 600, color: '#64748b', letterSpacing: '0.04em', display: 'block', marginBottom: 6 }}>
              ESCREVE{' '}
              <span style={{ fontFamily: 'monospace', color: '#dc2626', background: 'rgba(220,38,38,.08)', padding: '1px 5px', borderRadius: 3 }}>DELETE</span>
              {' '}PARA CONFIRMAR
            </label>
            <input
              autoFocus type="text" value={confirmText}
              onChange={e => setConfirmText(e.target.value)}
              placeholder="DELETE"
              onKeyDown={e => e.key === 'Enter' && canConfirm && onConfirm()}
              style={{
                width: '100%', boxSizing: 'border-box', padding: '9px 12px', borderRadius: 8, fontSize: 13,
                border: `1px solid ${canConfirm ? 'rgba(220,38,38,.4)' : '#e2e8f0'}`,
                outline: 'none', fontFamily: 'Inter, sans-serif', color: '#1d2e38',
                letterSpacing: '0.05em', transition: 'border-color 0.15s',
              }}
            />
          </div>
        </div>

        {/* Footer */}
        <div style={{ padding: '14px 24px', borderTop: '1px solid #f1f5f9', display: 'flex', gap: 8, justifyContent: 'space-between' }}>
          <button onClick={onClose} className="btn" style={{ fontSize: 12 }}>Cancelar</button>
          <button onClick={onConfirm} disabled={!canConfirm} className="btn"
            style={{ fontSize: 12, background: canConfirm ? '#dc2626' : '#f1f5f9', color: canConfirm ? '#fff' : '#94a3b8', borderColor: canConfirm ? '#dc2626' : '#e2e8f0', cursor: canConfirm ? 'pointer' : 'default', transition: 'all 0.15s' }}>
            {isBulk ? `Eliminar ${count} briefings` : 'Eliminar briefing'}
          </button>
        </div>
      </div>
    </div>
  );
};

function brfMesmaPessoa(a, b) {
  const chave = (s) => String(s || '').replace(/\s+/g, ' ').trim().toLowerCase();
  const ka = chave(a);
  const kb = chave(b);
  return !!ka && ka === kb;
}

const BriefingsList = ({ brandId, onSelect, onNew, onEdit, onImport, onGenerateStrategy, onCriarCampanha, onVerCampanha, onDuplicar, userRole, userName, userEmail }) => {
  const [briefings, setBriefings] = React.useState(null);
  window.useMktZoneLoading(briefings == null);
  const [filterEstado, setFilterEstado] = React.useState(() => {
    try {
      const f = localStorage.getItem('mkt-inicio-filter-estado');
      if (f) { localStorage.removeItem('mkt-inicio-filter-estado'); return f; }
    } catch {}
    return 'all';
  });
  const [filterMarca, setFilterMarca] = React.useState('all');
  const [filterProduto, setFilterProduto] = React.useState('all');
  const [filterCriador, setFilterCriador] = React.useState('all');
  const [rapidoPrefill, setRapidoPrefill] = React.useState(() => {
    try {
      const raw = localStorage.getItem('mkt-briefing-prefill');
      if (!raw) return null;
      localStorage.removeItem('mkt-briefing-prefill');
      const p = JSON.parse(raw);
      return p && Array.isArray(p.respostas) ? p : null;
    } catch { return null; }
  });
  const [showRapido, setShowRapido] = React.useState(() => !!rapidoPrefill);
  const [busca, setBusca] = React.useState(false);
  const [pdfAlvo, setPdfAlvo] = React.useState(null);
  const isAll = !brandId || brandId === 'todas';

  const today = React.useMemo(() => { const d = new Date(); d.setHours(23,59,59,999); return d; }, []);
  const [dateRange, setDateRange] = React.useState({ preset: 'all', start: new Date(today.getFullYear(), 0, 1), end: today });

  const [aiUsage, setAiUsage] = React.useState(null);

  const loadAiUsage = React.useCallback((bId, dRange) => {
    const p = new URLSearchParams();
    if (bId && bId !== 'todas') p.set('brand', bId);
    if (dRange && dRange.preset !== 'all') {
      p.set('date_from', dRange.start.toISOString().slice(0, 10));
      p.set('date_to',   dRange.end.toISOString().slice(0, 10));
    }
    const qs = p.toString() ? '?' + p.toString() : '';
    fetch(`/api/marketing/api-usage-summary${qs}`)
      .then(r => r.ok ? r.json() : null).then(d => setAiUsage(d || null)).catch(() => setAiUsage(null));
  }, []);

  const load = React.useCallback(() => {
    setBriefings(null);
    window.MktBriefingsAPI.list(isAll ? 'todas' : brandId).then(d => setBriefings(d || []));
    loadAiUsage(brandId, dateRange);
  }, [brandId, dateRange, loadAiUsage]);

  React.useEffect(load, [load]);

  const [showDeleteModal, setShowDeleteModal] = React.useState(false);
  const [pendingDelete, setPendingDelete]     = React.useState(null);

  const handleDelete = (id, label) => {
    setPendingDelete({ ids: [id], labels: [label || `Briefing #${id}`] });
    setShowDeleteModal(true);
  };

  const handleDeleteConfirm = async () => {
    if (!pendingDelete) return;
    await Promise.all(pendingDelete.ids.map(id => window.MktBriefingsAPI.remove(id)));
    if (pendingDelete.ids.length > 1) clearSelection();
    setShowDeleteModal(false);
    setPendingDelete(null);
    load();
  };

  const handleSubmitRow = async (b) => {
    if ((parseInt(b.completion_pct) || 0) < 80) {
      alert(`Briefing ${b.completion_pct || 0}% preenchido - mínimo 80% para submeter.`);
      return;
    }
    try {
      await window.MktBriefingsAPI.submit(b.id);
      load();
    } catch (e) {
      alert(e.message || 'Erro ao submeter briefing.');
    }
  };

  const kpi = React.useMemo(() => {
    if (!briefings) return { total: 0, draft: 0, submitted: 0, approved: 0, rejected: 0, avgPct: 0 };
    const meu = (b) => brfMesmaPessoa(b.created_by_name, userName)
      || (userEmail && b.created_by_email && String(b.created_by_email).toLowerCase() === String(userEmail).toLowerCase());
    const base = userRole === 'board'
      ? briefings.filter(b => meu(b) || ['submetido','validado_ceo','aprovado','em_campanha'].includes(b.status))
      : briefings;
    const total = base.length;
    const byStatus = s => base.filter(b => estadoBriefing(b.status) === s).length;
    const avgPct = total ? Math.round(base.reduce((a, b) => a + (parseInt(b.completion_pct) || 0), 0) / total) : 0;
    return { total, draft: byStatus('rascunho'), altPedidas: byStatus('alteracoes_pedidas'), submitted: byStatus('submetido'), validado_fc: byStatus('validado_fc'), validado_ceo: byStatus('validado_ceo'), approved: byStatus('aprovado'), rejected: byStatus('rejeitado'), avgPct };
  }, [briefings, userRole, userName, userEmail]);

  const BRAND_ORDER = ['mimaki', 'biond', 'decal', 'alldecor', 'sensek', 'netscreen', 'digidelta'];
  const BRAND_NAMES = { mimaki: 'Mimaki', biond: 'BIOND', decal: 'Decal', alldecor: 'AllDecor', sensek: 'Sensek', netscreen: 'NetScreen', digidelta: 'Digidelta' };
  const { setBrand: setActiveBrand } = window.useMktBrand ? window.useMktBrand() : { setBrand: () => {} };
  const [allBrands, setAllBrands] = React.useState([]);
  React.useEffect(() => {
    window.MktBriefingsAPI.getBrands().then(d => {
      const list = (d || []).filter(b => b.slug !== 'todas').map(b => ({
        ...b,
        name: BRAND_NAMES[b.slug] || b.name,
      }));
      list.sort((a, b) => {
        const ia = BRAND_ORDER.indexOf(a.slug), ib = BRAND_ORDER.indexOf(b.slug);
        return (ia === -1 ? 99 : ia) - (ib === -1 ? 99 : ib);
      });
      setAllBrands(list);
    });
  }, []);
  const brands = allBrands;

  const produtos = React.useMemo(() => {
    if (!briefings) return [];
    const seen = new Set();
    briefings.forEach(b => { const v = b.commercial_name || b.product_name; if (v) seen.add(v); });
    return [...seen].sort().map(name => ({ value: name, label: name }));
  }, [briefings]);

  const criadores = React.useMemo(() => {
    if (!briefings) return [];
    const seen = new Set();
    briefings.forEach(b => { if (b.created_by_name) seen.add(b.created_by_name); });
    return [...seen].sort().map(name => ({ value: name, label: name }));
  }, [briefings]);

  const [sortBy, setSortBy] = React.useState({ key: 'updated_at', dir: 'desc' });
  const onSort = (key) => setSortBy(s => ({ key, dir: s.key === key && s.dir === 'asc' ? 'desc' : 'asc' }));
  const [filterAttention, setFilterAttention] = React.useState(false);
  const [selectedIds, setSelectedIds] = React.useState(new Set());
  const [bulkStatusOpen, setBulkStatusOpen] = React.useState(false);
  const bulkStatusRef = React.useRef(null);

  // Atenção: estes três memos têm de vir ANTES de `filtered` (que os usa)
  const myDecisions = React.useMemo(() => {
    if (userRole !== 'strategist' || !userName || !briefings) return [];
    return briefings.filter(b =>
      b.created_by_name === userName &&
      (b.status === 'aprovado' || b.status === 'rejeitado')
    );
  }, [briefings, userRole, userName]);

  const pendingApprovals = React.useMemo(() => {
    if (userRole !== 'board' && userRole !== 'admin' || !briefings) return [];
    return briefings.filter(b => ['submetido','validado_fc','validado_ceo'].includes(b.status));
  }, [briefings, userRole]);

  const attentionIds = React.useMemo(() => {
    const items = (userRole === 'board' || userRole === 'admin') ? pendingApprovals : myDecisions;
    return new Set(items.map(b => b.id));
  }, [pendingApprovals, myDecisions, userRole]);

  const filtered = React.useMemo(() => {
    if (!briefings) return [];
    const dateFrom = dateRange.preset !== 'all' ? new Date(dateRange.start.getFullYear(), dateRange.start.getMonth(), dateRange.start.getDate()) : null;
    const dateTo   = dateRange.preset !== 'all' ? new Date(dateRange.end.getFullYear(),   dateRange.end.getMonth(),   dateRange.end.getDate() + 1) : null;
    const list = briefings.filter(b => {
      const meu = brfMesmaPessoa(b.created_by_name, userName)
        || (userEmail && b.created_by_email && String(b.created_by_email).toLowerCase() === String(userEmail).toLowerCase());
      return (filterEstado === 'all' || (filterEstado === 'pendentes' ? ['submetido','validado_fc','validado_ceo'].includes(b.status) : estadoBriefing(b.status) === filterEstado)) &&
      (filterMarca   === 'all' || b.brand_slug === filterMarca) &&
      (filterProduto === 'all' || (b.commercial_name || b.product_name) === filterProduto) &&
      (filterCriador === 'all' || b.created_by_name === filterCriador) &&
      (!dateFrom || !b.created_at || new Date(b.created_at) >= dateFrom) &&
      (!dateTo   || !b.created_at || new Date(b.created_at) <  dateTo) &&
      (userRole !== 'board' || meu || ['submetido','validado_fc','validado_ceo','aprovado','em_campanha'].includes(b.status)) &&
      (!filterAttention || attentionIds.has(b.id) || meu);
    });
    return list.sort((a, b) => {
      let av, bv;
      switch (sortBy.key) {
        case 'commercial_name': av = (a.commercial_name || a.product_name || '').toLowerCase(); bv = (b.commercial_name || b.product_name || '').toLowerCase(); break;
        case 'brand_name':    av = (a.brand_name || '').toLowerCase();      bv = (b.brand_name || '').toLowerCase();      break;
        case 'status':        av = estadoBriefing(a.status);                bv = estadoBriefing(b.status);                break;
        case 'completion':    av = parseInt(a.completion_pct) || 0;        bv = parseInt(b.completion_pct) || 0;        break;
        case 'quality_score': av = a.quality_score != null ? a.quality_score : (parseInt(a.completion_pct)||0)/100; bv = b.quality_score != null ? b.quality_score : (parseInt(b.completion_pct)||0)/100; break;
        case 'updated_at':    av = a.updated_at || '';                      bv = b.updated_at || '';                      break;
        case 'created_by':    av = (a.created_by_name || '').toLowerCase(); bv = (b.created_by_name || '').toLowerCase(); break;
        case 'approved_by':   av = (a.approved_by_name || '').toLowerCase();bv = (b.approved_by_name || '').toLowerCase();break;
        default:              av = a[sortBy.key] || '';                     bv = b[sortBy.key] || '';
      }
      if (av < bv) return sortBy.dir === 'asc' ? -1 : 1;
      if (av > bv) return sortBy.dir === 'asc' ? 1 : -1;
      return 0;
    });
  }, [briefings, filterEstado, filterMarca, filterProduto, filterCriador, dateRange, sortBy, filterAttention, attentionIds, userRole, userName, userEmail]);

  const hasActiveFilter = filterEstado !== 'all' || filterMarca !== 'all' || filterProduto !== 'all' || filterCriador !== 'all' || dateRange.preset !== 'all' || filterAttention;

  const clearFilters = () => {
    setFilterEstado('all'); setFilterMarca('all');
    setFilterProduto('all'); setFilterCriador('all');
    setFilterAttention(false);
    setSelectedIds(new Set());
    setDateRange({ preset: 'all', start: new Date(today.getFullYear(), 0, 1), end: today });
  };

  // ── Bulk selection ──────────────────────────────────────────────────────────
  const allSelected  = filtered.length > 0 && filtered.every(b => selectedIds.has(b.id));
  const someSelected = selectedIds.size > 0;

  const toggleSelect    = (id) => setSelectedIds(prev => { const n = new Set(prev); n.has(id) ? n.delete(id) : n.add(id); return n; });
  const toggleSelectAll = () => allSelected ? setSelectedIds(new Set()) : setSelectedIds(new Set(filtered.map(b => b.id)));
  const clearSelection  = () => { setSelectedIds(new Set()); setBulkStatusOpen(false); };

  const handleBulkDelete = () => {
    const ids    = [...selectedIds];
    const labels = ids.map(id => { const b = (briefings || []).find(x => x.id === id); return b?.commercial_name || b?.product_name || `Briefing #${id}`; });
    setPendingDelete({ ids, labels });
    setShowDeleteModal(true);
  };

  const handleBulkStatus = async (status) => {
    setBulkStatusOpen(false);
    const ids = [...selectedIds];
    try {
      if (status === 'submetido') {
        await Promise.all(ids.map(id => window.MktBriefingsAPI.submit(id)));
      } else {
        const failed = [];
        await Promise.all(ids.map(async (id) => {
          try { await window.MktBriefingsAPI.setStatus(id, status); }
          catch (e) { failed.push(e.message || 'Erro'); }
        }));
        if (failed.length) {
          const msg = [...new Set(failed)][0];
          alert(failed.length === ids.length
            ? msg
            : `${ids.length - failed.length} actualizado(s). ${failed.length} não mudaram: ${msg}`);
        }
      }
    } catch (e) { alert(e.message || 'Erro ao actualizar estado.'); }
    clearSelection(); load();
  };

  const handleBulkExport = () => {
    const rows = (briefings || []).filter(b => selectedIds.has(b.id)).map(b => ({
      id: b.id, produto: b.commercial_name || b.product_name, marca: b.brand_name,
      estado: b.status, criado_por: b.created_by_name, actualizado: b.updated_at,
    }));
    const blob = new Blob([JSON.stringify(rows, null, 2)], { type: 'application/json' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = `briefings-${new Date().toISOString().slice(0, 10)}.json`;
    a.click();
  };
  // ────────────────────────────────────────────────────────────────────────────

  const kpiPendentes = kpi.submitted + (kpi.validado_fc || 0) + (kpi.validado_ceo || 0);
  const kpiCards = [
    { label: 'Total',        value: kpi.total,            accent: '#3859D0', filter: 'all' },
    { label: 'Rascunhos',    value: kpi.draft,            accent: '#7c3aed', filter: 'rascunho' },
    { label: 'Pendentes',    value: kpiPendentes,         accent: '#b45309', filter: 'pendentes' },
    { label: 'Aprovados',    value: kpi.approved,         accent: '#0891b2', filter: 'aprovado' },
    { label: 'Alterações',   value: kpi.altPedidas || 0,  accent: '#15803d', filter: 'alteracoes_pedidas' },
  ];
  const ordem = sortBy.key === 'commercial_name' ? 'nome'
    : sortBy.key === 'quality_score' ? 'qualidade'
    : sortBy.key === 'status' ? 'estado'
    : 'recentes';
  const setOrdem = (v) => {
    if (v === 'nome') setSortBy({ key: 'commercial_name', dir: 'asc' });
    else if (v === 'qualidade') setSortBy({ key: 'quality_score', dir: 'desc' });
    else if (v === 'estado') setSortBy({ key: 'status', dir: 'asc' });
    else setSortBy({ key: 'updated_at', dir: 'desc' });
  };
  const opcoesEstado = (userRole === 'board' || userRole === 'ceo') ? [
    { value: 'all', label: 'Todos' },
    { value: 'pendentes', label: 'Pendentes' },
    { value: 'submetido', label: 'Para Manager' },
    { value: 'validado_fc', label: 'Para CEO' },
    { value: 'validado_ceo', label: 'Para Chairman' },
    { value: 'aprovado', label: 'Aprovado' },
  ] : [
    { value: 'all', label: 'Todos' },
    { value: 'pendentes', label: 'Pendentes' },
    { value: 'rascunho', label: 'Rascunho' },
    { value: 'alteracoes_pedidas', label: 'Alterações Pedidas' },
    { value: 'submetido', label: 'Para Manager' },
    { value: 'validado_fc', label: 'Para CEO' },
    { value: 'validado_ceo', label: 'Para Chairman' },
    { value: 'aprovado', label: 'Aprovado' },
    { value: 'rejeitado', label: 'Rejeitado' },
  ];

  const attnCount  = attentionIds.size;
  const attnColor  = 'var(--ai-500, #3859D0)';
  const attnLabel  = (userRole === 'board' || userRole === 'admin')
    ? `${attnCount} briefing${attnCount !== 1 ? 's' : ''} pendente${attnCount !== 1 ? 's' : ''} de aprovação`
    : `${attnCount} decisão${attnCount !== 1 ? 'ões' : ''} recente${attnCount !== 1 ? 's' : ''} sobre os teus briefings`;

  return (
    <div data-theme="light" style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', '--bg': '#f8fafc', '--bg-elev': '#ffffff', '--bg-hover': '#f1f5f9', '--bg-sunken': '#e8edf5', '--border': '#dde3ef', '--border-strong': '#bec9de', '--text': '#112954', '--text-muted': '#4a5e7a', '--text-dim': '#6b7fa3', '--ai-500': '#3859D0' }}>

      {showRapido && (
        <BriefingRapidoModal
          allBrands={allBrands}
          prefill={rapidoPrefill}
          onClose={() => { setShowRapido(false); setRapidoPrefill(null); }}
          onDone={(data, brandSlug, tipoCampanha, origem) => {
            setShowRapido(false);
            setRapidoPrefill(null);
            onNew?.({ _prefill: data, _brand: brandSlug, _tipo: tipoCampanha, _origem: origem || null });
          }}
        />
      )}

      {showDeleteModal && (
        <DeleteConfirmModal
          pendingDelete={pendingDelete}
          onConfirm={handleDeleteConfirm}
          onClose={() => { setShowDeleteModal(false); setPendingDelete(null); }}
        />
      )}

      {/* ── Page header (fixo) ── */}
      <div style={{ background: 'var(--bg-elev)', borderBottom: '1px solid var(--border)', padding: '20px 40px 0', flexShrink: 0 }}>

        {/* Breadcrumb */}
        <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: '0.07em', color: 'var(--text-muted)', textTransform: 'uppercase', marginBottom: 6, fontFamily: 'var(--font-mono)' }}>
          MARKETING · BRIEFINGS
        </div>

        <h1 style={{ margin: 0, fontSize: 21, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-display)', letterSpacing: '-0.01em', lineHeight: 1.2 }}>
          Briefings
        </h1>

        {/* Separador */}
        <div style={{ height: 1, background: 'var(--border)', margin: '16px -40px 0' }} />

        {/* Brand tabs */}
        <div style={{ display: 'flex', gap: 0, overflowX: 'auto' }}>
          {[{ slug: 'todas', name: 'Todas' }, ...allBrands].map(b => {
            const active = b.slug === 'todas' ? isAll : brandId === b.slug;
            return (
              <button key={b.slug} onClick={() => setActiveBrand(b.slug === 'todas' ? null : b.slug)}
                style={{
                  background: 'none', border: 'none', outline: 'none',
                  borderBottom: `2px solid ${active ? 'var(--ai-500)' : 'transparent'}`,
                  color: active ? 'var(--text)' : 'var(--text-muted)',
                  fontSize: 13, fontWeight: active ? 600 : 500,
                  fontFamily: 'var(--font-display)', letterSpacing: '.01em',
                  padding: '10px 18px 12px',
                  cursor: 'pointer', whiteSpace: 'nowrap',
                  transition: 'color .15s, border-color .15s',
                }}>
                {b.name}
              </button>
            );
          })}
        </div>
      </div>

      {/* ── Scrollable body ── */}
      <div className="scrollbar" style={{ flex: 1, minHeight: 0, minWidth: 0, overflowY: 'auto', overflowX: 'hidden', padding: '28px 40px 0', background: 'var(--bg, #f8fafc)', display: 'flex', flexDirection: 'column', gap: 18 }}>

        {/* ── Filter row / Bulk bar ── */}
        {someSelected ? (
          <div style={{
            display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8,
            padding: '8px 12px', borderRadius: 8,
            background: 'color-mix(in srgb, var(--ai-500, #3859D0) 6%, var(--bg-elev, #ffffff))',
            border: '1px solid color-mix(in srgb, var(--ai-500, #3859D0) 20%, var(--border, #dde3ef))',
            animation: 'slide-up 0.2s ease-out both',
          }}>
            <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
              <span style={{ fontSize: 12, fontWeight: 700, color: 'var(--ai-500, #3859D0)', fontFamily: 'var(--font-mono)', letterSpacing: '0.04em' }}>
                {selectedIds.size} seleccionado{selectedIds.size !== 1 ? 's' : ''}
              </span>
              <div style={{ width: 1, height: 14, background: 'var(--border, #dde3ef)' }} />
              <button onClick={handleBulkDelete}
                style={{ background: 'none', border: '1px solid var(--border, #dde3ef)', borderRadius: 5, padding: '4px 10px', fontSize: 12, cursor: 'pointer', color: 'var(--danger, #dc2626)', fontFamily: 'inherit' }}
                onMouseEnter={e => e.currentTarget.style.background = 'color-mix(in srgb, var(--danger, #dc2626) 8%, transparent)'}
                onMouseLeave={e => e.currentTarget.style.background = 'none'}>
                Apagar
              </button>
              <div style={{ position: 'relative' }} ref={bulkStatusRef}>
                <button onClick={() => setBulkStatusOpen(o => !o)}
                  style={{ background: 'none', border: '1px solid var(--border, #dde3ef)', borderRadius: 5, padding: '4px 10px', fontSize: 12, cursor: 'pointer', color: 'var(--text, #112954)', fontFamily: 'inherit', display: 'flex', alignItems: 'center', gap: 4 }}
                  onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-hover, #f1f5f9)'}
                  onMouseLeave={e => e.currentTarget.style.background = 'none'}>
                  Mudar estado <span style={{ fontSize: 9 }}>▾</span>
                </button>
                {bulkStatusOpen && (
                  <div className="animate-in" style={{ position: 'absolute', top: 'calc(100% + 4px)', left: 0, zIndex: 60, background: 'var(--bg-elev, #ffffff)', border: '1px solid var(--border, #dde3ef)', borderRadius: 6, minWidth: 160, boxShadow: '0 8px 24px rgba(17,41,84,0.12)', padding: 4 }}>
                    {[
                      { value: 'submetido',   label: 'Submeter' },
                      { value: 'aprovado',    label: 'Aprovado' },
                      { value: 'rejeitado',   label: 'Rejeitado' },
                    ].map(opt => (
                      <button key={opt.value} onClick={() => handleBulkStatus(opt.value)}
                        style={{ display: 'block', width: '100%', padding: '9px 12px', background: 'none', border: 'none', cursor: 'pointer', fontSize: 13, color: 'var(--text, #112954)', textAlign: 'left', fontFamily: 'inherit' }}
                        onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-hover, #f1f5f9)'}
                        onMouseLeave={e => e.currentTarget.style.background = 'none'}>
                        {opt.label}
                      </button>
                    ))}
                  </div>
                )}
              </div>
              <button onClick={handleBulkExport}
                style={{ background: 'none', border: '1px solid var(--border, #dde3ef)', borderRadius: 5, padding: '4px 10px', fontSize: 12, cursor: 'pointer', color: 'var(--text, #112954)', fontFamily: 'inherit' }}
                onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-hover, #f1f5f9)'}
                onMouseLeave={e => e.currentTarget.style.background = 'none'}>
                Exportar JSON
              </button>
            </div>
            <button onClick={clearSelection}
              style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 11, color: 'var(--text-muted, #4a5e7a)', fontFamily: 'var(--font-mono)', padding: '4px 6px' }}>
              ✕ cancelar
            </button>
          </div>
        ) : (
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
              <BriefingDropdown label="Estado" value={filterEstado} onChange={setFilterEstado} options={opcoesEstado} />
              <BriefingDropdown label="Produto" value={filterProduto} onChange={setFilterProduto}
                options={[{ value: 'all', label: 'Todos' }, ...produtos]}
              />
              <BriefingDropdown label="Criado por" value={filterCriador} onChange={setFilterCriador}
                options={[{ value: 'all', label: 'Todos' }, ...criadores]}
              />
              {isAll && (
                <BriefingDropdown label="Marca" value={filterMarca} onChange={setFilterMarca}
                  options={[{ value: 'all', label: 'Todas' }, ...brands.map(b => ({ value: b.slug, label: b.name }))]}
                />
              )}
              <BriefingDropdown label="Ordem" value={ordem} onChange={setOrdem}
                options={[
                  { value: 'recentes', label: 'Recentes' },
                  { value: 'nome', label: 'Nome' },
                  { value: 'qualidade', label: 'Qualidade' },
                  { value: 'estado', label: 'Estado' },
                ]}
              />
              <BriefingDateRangePicker value={dateRange} onChange={setDateRange} />
              {hasActiveFilter && (
                <button onClick={clearFilters} style={{
                  background: 'none', border: 'none', color: 'var(--text-muted)', fontSize: 11,
                  cursor: 'pointer', padding: '4px 6px', fontFamily: 'var(--font-mono)',
                }}>✕ limpar</button>
              )}
            </div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginLeft: 'auto' }}>
              {window.ResIcone && (
                <window.ResIcone label="Procurar briefings" activo={busca} onClick={() => setBusca(v => !v)}>
                  {window.ResIconeLupa && <window.ResIconeLupa />}
                </window.ResIcone>
              )}
              {window.ResMenuAccoes && (
                <window.ResMenuAccoes icone align="right" onOpen={() => setBusca(false)} items={[
                  { id: 'criar', label: 'Criar', sub: 'Briefing novo, bloco a bloco', onClick: onNew },
                  { id: 'rapido', label: 'Briefing rápido', sub: 'Perguntas curtas, a Digi preenche', onClick: () => setShowRapido(true) },
                  { id: 'importar', label: 'Importar', sub: 'PDF ou DOCX para preencher com IA', onClick: onImport },
                ]} />
              )}
            </div>
          </div>
        )}

        <div style={{ display: 'flex', gap: 12 }}>
          {kpiCards.map(k => {
            const on = k.filter === 'all' ? filterEstado === 'all' : filterEstado === k.filter;
            return (
              <div key={k.filter} onClick={() => setFilterEstado(on && k.filter !== 'all' ? 'all' : k.filter)} style={{
                flex: '1 1 0', background: '#fff',
                border: `1px solid ${on ? k.accent : 'var(--border)'}`,
                borderRadius: 8, padding: '12px 16px 0', cursor: 'pointer',
                display: 'flex', flexDirection: 'column', gap: 4, overflow: 'hidden',
              }}>
                <div style={{ fontSize: 9.5, fontWeight: 600, letterSpacing: '0.10em', textTransform: 'uppercase', color: on ? k.accent : 'var(--text-muted)', fontFamily: 'var(--font-mono)', whiteSpace: 'nowrap' }}>{k.label}</div>
                <div style={{ fontSize: 22, fontWeight: 700, fontFamily: 'var(--font-display)', color: k.accent, lineHeight: 1, paddingBottom: 10 }}>{Number(k.value || 0).toLocaleString('pt-PT')}</div>
                <div style={{ height: 3, background: 'var(--bg-sunken)', overflow: 'hidden' }}><div style={{ height: '100%', width: '100%', background: k.accent }} /></div>
              </div>
            );
          })}
        </div>

        {/* ── Table ── */}
      {briefings === null ? (
        <div>
          {[1,2,3,4].map(i => (
            <div key={i} style={{ height: 44, borderRadius: 6, background: 'var(--bg-sunken, #eef1f6)', marginBottom: 6, opacity: 1 - i * 0.2 }} />
          ))}
        </div>
      ) : briefings.length === 0 ? (
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 12, color: 'var(--text-muted)', marginTop: 18 }}>
          <div style={{ fontSize: 14 }}>{isAll ? 'Ainda não há briefings.' : 'Ainda não há briefings para esta marca.'}</div>
          <div style={{ display: 'flex', gap: 8 }}>
            <button className="btn" style={{ height: 30, width: 96, padding: '0', fontSize: 12, whiteSpace: 'nowrap', justifyContent: 'center' }} onClick={onImport}>Importar</button>
            <button className="btn btn-ai" style={{ height: 30, width: 96, padding: '0', fontSize: 12, whiteSpace: 'nowrap', justifyContent: 'center' }} onClick={onNew}>Criar</button>
          </div>
        </div>
      ) : filtered.length === 0 ? (
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 8, color: 'var(--text-muted)', marginTop: 18 }}>
          <div style={{ fontSize: 14 }}>Sem resultados para os filtros actuais.</div>
          <button className="btn" style={{ height: 30, width: 96, padding: '0', fontSize: 12, whiteSpace: 'nowrap', justifyContent: 'center' }} onClick={clearFilters}>Limpar filtros</button>
        </div>
      ) : (
        <div className="scrollbar" style={{ background: '#fff', border: '1px solid var(--border)', borderRadius: 10, overflowX: 'auto', overflowY: 'visible', flexShrink: 0, width: '100%', maxWidth: '100%', minWidth: 0 }}>
          <style>{'.brf-row:hover{background:#f8fafc}'}</style>
          {(() => {
            const cols = [[36], [240], ...(isAll ? [[110]] : []), [180], [150], [110], [110], [110], [150], [150], [56]];
            const largura = cols.reduce((s, [w]) => s + w, 0);
            return (
          <table style={{ width: largura, minWidth: '100%', tableLayout: 'fixed', borderCollapse: 'collapse' }}>
            <colgroup>
              {cols.map(([w], i) => <col key={i} style={{ width: w }} />)}
            </colgroup>
            <thead style={{ position: 'sticky', top: 0, zIndex: 2, background: '#fff' }}>
              <tr>
                <th style={{ ...brfTh, width: 36 }}>
                  <input type="checkbox" checked={allSelected}
                    ref={el => { if (el) el.indeterminate = someSelected && !allSelected; }}
                    onChange={toggleSelectAll} />
                </th>
                <SortHeader label="Nome comercial" sortKey="commercial_name" sortBy={sortBy} onSort={onSort} />
                {isAll && <SortHeader label="Marca"       sortKey="brand_name"  sortBy={sortBy} onSort={onSort} />}
                <SortHeader label="Produto"      sortKey="product_name" sortBy={sortBy} onSort={onSort} />
                <SortHeader label="Estado"       sortKey="status"       sortBy={sortBy} onSort={onSort} />
                <SortHeader label="Campanhas"    sortKey="n_campanhas"  sortBy={sortBy} onSort={onSort} />
                <SortHeader label="Qualidade"    sortKey="quality_score" sortBy={sortBy} onSort={onSort} />
                <SortHeader label="Actualizado"  sortKey="updated_at"   sortBy={sortBy} onSort={onSort} />
                <SortHeader label="Criado por"   sortKey="created_by"   sortBy={sortBy} onSort={onSort} />
                <SortHeader label="Aprovado por" sortKey="approved_by"  sortBy={sortBy} onSort={onSort} />
                <th style={{ ...brfTh, textAlign: 'center' }}>Acções</th>
              </tr>
            </thead>
            <tbody>
              {filtered.map(b => (
                <BriefingRow key={b.id} b={b} isAll={isAll}
                  onSelect={() => onSelect(b)}
                  onEdit={() => onEdit(b)}
                  onDelete={() => handleDelete(b.id, b.commercial_name || b.product_name || `Briefing #${b.id}`)}
                  onSubmit={handleSubmitRow}
                  onGenerateStrategy={onGenerateStrategy}
                  onCriarCampanha={onCriarCampanha}
                  onVerCampanha={onVerCampanha}
                  onDuplicar={onDuplicar}
                  onEnviarPdf={setPdfAlvo}
                  selected={selectedIds.has(b.id)}
                  onToggle={() => toggleSelect(b.id)}
                  userRole={userRole}
                />
              ))}
            </tbody>
          </table>
            );
          })()}
        </div>
      )}
      </div> {/* fim scrollable body */}

      {/* ── Rodapé AI Budget (fixo) ── */}
      {aiUsage && parseFloat(aiUsage.custo_total) > 0 && (() => {
        const fmtEur = (v) => v != null ? `€${Number(v).toFixed(2)}` : '—';
        const fmtTok = (v) => Number(v || 0).toLocaleString('pt-PT');
        const periodLabel = dateRange.preset !== 'all'
          ? `${dateRange.start.toLocaleDateString('pt-PT', { day: '2-digit', month: 'short' })} - ${dateRange.end.toLocaleDateString('pt-PT', { day: '2-digit', month: 'short' })}`
          : 'total';
        const sep = <span style={{ margin: '0 5px', opacity: 0.35 }}>·</span>;
        return (
          <div style={{
            flexShrink: 0, borderTop: '1px solid var(--border, #dde3ef)',
            background: 'var(--bg-elev, #fff)', padding: '5px 40px',
            display: 'flex', alignItems: 'center', gap: 0,
            fontSize: 10.5, fontFamily: 'var(--font-mono)', color: 'var(--text-dim, #6b7fa3)',
          }}>
            <span style={{ fontWeight: 600, color: 'var(--text-muted, #4a5e7a)', marginRight: 10, fontSize: 10 }}>Digi AI Briefings</span>
            <span>Custo {periodLabel}: <strong style={{ color: 'var(--text-muted)', fontWeight: 600 }}>{fmtEur(aiUsage.custo_total)}</strong></span>
            {sep}<span>Média/briefing: <strong style={{ color: 'var(--text-muted)', fontWeight: 600 }}>{fmtEur(aiUsage.media_por_briefing)}</strong></span>
            {sep}<span>Tokens: <strong style={{ color: 'var(--text-muted)', fontWeight: 600 }}>{fmtTok(aiUsage.total_tokens)}</strong></span>
            {sep}<span>{Number(aiUsage.total_chamadas || 0)} chamadas API</span>
          </div>
        );
      })()}
      {busca && window.ResBusca && (
        <window.ResBusca
          itens={(briefings || []).map(b => ({
            id: b.id,
            tipo: 'Briefing',
            titulo: b.commercial_name || b.product_name || `Briefing #${b.id}`,
            detalhe: [b.brand_name, STATUS_CONFIG[b.status]?.label].filter(Boolean).join(' · '),
            texto: `${b.created_by_name || ''} ${b.product_name || ''}`,
          }))}
          placeholder="Procurar briefings"
          vazio="Nenhum briefing encontrado."
          onPick={(it) => {
            const b = (briefings || []).find(x => String(x.id) === String(it.id));
            if (b) onSelect(b);
          }}
          onClose={() => setBusca(false)}
        />
      )}
      {pdfAlvo && <BriefingPdfModal briefing={pdfAlvo} onClose={() => setPdfAlvo(null)} />}
    </div>
  );
};

// ─── Formulário multi-bloco ────────────────────────────────────────────────────

// ── Modal confirmação submit com nota - todos os utilizadores ─────────────────
const SubmitConfirmModal = ({ onConfirm, onClose, isRejected }) => {
  const [notes, setNotes] = React.useState('');
  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,.45)', zIndex: 9999, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
      <div style={{ background: '#fff', borderRadius: 12, padding: '28px 32px', width: 400, boxShadow: '0 8px 40px rgba(0,0,0,.18)' }}>
        <div style={{ fontSize: 15, fontWeight: 700, color: '#1d2e38', marginBottom: 6 }}>{isRejected ? 'Submeter novamente' : 'Submeter para aprovação'}</div>
        <div style={{ fontSize: 13, color: '#6b7280', marginBottom: 20 }}>O briefing será enviado para o fluxo de aprovação.</div>
        <div style={{ marginBottom: 20 }}>
          <div style={{ fontSize: 11, fontWeight: 600, color: '#6b7280', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 6 }}>Nota para quem aprova (fica no briefing)</div>
          <textarea
            value={notes}
            onChange={e => setNotes(e.target.value)}
            placeholder="Contexto adicional que será incluído no email de notificação..."
            rows={3}
            style={{ width: '100%', padding: '9px 12px', border: '1.5px solid #e2e2df', borderRadius: 7, fontSize: 13, fontFamily: 'inherit', color: '#1d2e38', resize: 'vertical', outline: 'none', boxSizing: 'border-box' }}
            onFocus={e => e.target.style.borderColor = '#3859D0'}
            onBlur={e => e.target.style.borderColor = '#e2e2df'}
          />
        </div>
        <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
          <button className="btn" style={{ height: 32, padding: '0 16px', fontSize: 13 }} onClick={onClose}>Cancelar</button>
          <button className="btn btn-ai" style={{ height: 32, padding: '0 16px', fontSize: 13 }} onClick={() => onConfirm(notes.trim() || null)}>
            {isRejected ? 'Submeter novamente →' : 'Submeter →'}
          </button>
        </div>
      </div>
    </div>
  );
};

// ── Modal alterações por gravar ao fechar o briefing - todos os utilizadores ──
const UnsavedChangesModal = ({ saving, onStay, onDiscard, onSave }) => (
  <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,.45)', zIndex: 10000, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}
    role="dialog" aria-modal="true" aria-label="Alterações por gravar"
    onClick={() => { if (!saving) onStay(); }}>
    <div style={{ background: '#fff', borderRadius: 12, padding: '28px 32px', width: 420, maxWidth: '100%', boxShadow: '0 8px 40px rgba(0,0,0,.18)' }}
      onClick={e => e.stopPropagation()}>
      <div style={{ fontSize: 15, fontWeight: 700, color: '#1d2e38', marginBottom: 6 }}>Alterações por gravar</div>
      <div style={{ fontSize: 13, color: '#6b7280', marginBottom: 24, lineHeight: 1.55 }}>
        Existem alterações neste briefing que ainda não foram gravadas. Queres gravar antes de sair?
      </div>
      <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', flexWrap: 'wrap' }}>
        <button type="button" className="btn" style={{ height: 32, padding: '0 16px', fontSize: 13 }} onClick={onStay} disabled={saving}>Cancelar</button>
        <button type="button" className="btn" style={{ height: 32, padding: '0 16px', fontSize: 13 }} onClick={onDiscard} disabled={saving}>Não gravar</button>
        <button type="button" className="btn btn-ai" style={{ height: 32, padding: '0 16px', fontSize: 13 }} onClick={onSave} disabled={saving}>
          {saving ? 'A gravar...' : 'Gravar e sair'}
        </button>
      </div>
    </div>
  </div>
);

// ── Modal envio directo (bypass fases) - só FC ────────────────────────────────
const DIRECT_TARGETS = [
  { email: 'armando.mota@digidelta.pt', name: 'Armando Mota',  label: 'CEO',                 warn: false },
  { email: 'bruno.rosa@digidelta.pt',   name: 'Bruno Rosa',    label: 'CEO · NetScreen',      warn: false },
  { email: 'rui.leitao@digidelta.pt',   name: 'Rui Leitão',   label: 'Chairman',             warn: true  },
  { email: 'fabio.costa@digidelta.pt',  name: 'Fábio Costa',  label: 'Auto-aprovação',       warn: true, selfApprove: true },
];

const DirectSubmitModal = ({ briefingId, submitterName, submitterEmail, onClose, onDone }) => {
  const [selected, setSelected] = React.useState('armando.mota@digidelta.pt');
  const [notes,    setNotes]    = React.useState('');
  const [loading,  setLoading]  = React.useState(false);
  const [error,    setError]    = React.useState('');
  const target = DIRECT_TARGETS.find(t => t.email === selected);

  const handleConfirm = async () => {
    setLoading(true); setError('');
    try {
      await window.MktBriefingsAPI.submitDirect(briefingId, {
        direct_to: selected,
        submitter_name: submitterName,
        submitter_email: submitterEmail,
        notes: notes.trim() || null,
      });
      onDone();
    } catch (e) {
      setError(e.message || 'Erro ao enviar.');
      setLoading(false);
    }
  };

  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,.45)', zIndex: 9999, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
      <div style={{ background: '#fff', borderRadius: 12, padding: '28px 32px', width: 420, boxShadow: '0 8px 40px rgba(0,0,0,.18)' }}>
        <div style={{ fontSize: 15, fontWeight: 700, color: '#1d2e38', marginBottom: 6 }}>Enviar directamente para aprovação</div>
        <div style={{ fontSize: 13, color: '#6b7280', marginBottom: 20 }}>O briefing é enviado directamente ao aprovador seleccionado, saltando as fases intermédias.</div>

        <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 20 }}>
          {DIRECT_TARGETS.map(t => (
            <label key={t.email} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px', border: `1.5px solid ${selected === t.email ? '#3859D0' : '#e2e2df'}`, borderRadius: 8, cursor: 'pointer', background: selected === t.email ? 'rgba(56,89,208,.05)' : '#fff' }}>
              <input type="radio" name="direct_to" value={t.email} checked={selected === t.email} onChange={() => setSelected(t.email)} style={{ accentColor: '#3859D0' }} />
              <div>
                <div style={{ fontSize: 13, fontWeight: 600, color: '#1d2e38' }}>{t.name}</div>
                <div style={{ fontSize: 11, color: '#6b7280' }}>{t.label}</div>
              </div>
            </label>
          ))}
        </div>

        {!target?.selfApprove && <div style={{ marginBottom: 16 }}>
          <div style={{ fontSize: 11, fontWeight: 600, color: '#6b7280', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 6 }}>Nota para quem aprova (fica no briefing)</div>
          <textarea
            value={notes}
            onChange={e => setNotes(e.target.value)}
            placeholder="Contexto adicional que será incluído no email de notificação..."
            rows={3}
            style={{ width: '100%', padding: '9px 12px', border: '1.5px solid #e2e2df', borderRadius: 7, fontSize: 13, fontFamily: 'inherit', color: '#1d2e38', resize: 'vertical', outline: 'none', boxSizing: 'border-box' }}
            onFocus={e => e.target.style.borderColor = '#3859D0'}
            onBlur={e => e.target.style.borderColor = '#e2e2df'}
          />
        </div>}

        {target?.selfApprove && (
          <div style={{ background: '#FEF3C7', border: '1px solid #F59E0B', borderRadius: 8, padding: '10px 14px', fontSize: 12, color: '#92400E', marginBottom: 16 }}>
            Aprovação directa - nenhum email é enviado. O briefing fica aprovado imediatamente.
          </div>
        )}
        {target?.warn && !target?.selfApprove && (
          <div style={{ background: '#FEF3C7', border: '1px solid #F59E0B', borderRadius: 8, padding: '10px 14px', fontSize: 12, color: '#92400E', marginBottom: 16 }}>
            Armando Mota não será notificado - o briefing vai directamente para o Chairman.
          </div>
        )}

        {error && <div style={{ color: '#dc2626', fontSize: 12, marginBottom: 12 }}>{error}</div>}

        <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
          <button className="btn" style={{ height: 32, padding: '0 16px', fontSize: 13 }} onClick={onClose} disabled={loading}>Cancelar</button>
          <button className="btn btn-ai" style={{ height: 32, padding: '0 16px', fontSize: 13 }} onClick={handleConfirm} disabled={loading}>
            {loading ? 'A aprovar...' : target?.selfApprove ? 'Aprovar directamente' : `Enviar para ${target?.name}`}
          </button>
        </div>
      </div>
    </div>
  );
};

const BRF_HEADER_BRANDS = { mimaki: '#0066CC', biond: '#E8A000', decal: '#00A86B', alldecor: '#7C3AED', sensek: '#DC2626', netscreen: '#0EA5E9', digidelta: '#3859D0' };

function BriefingHeaderMenu({ items }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (!open) return;
    const close = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', close);
    return () => document.removeEventListener('mousedown', close);
  }, [open]);
  if (!items.length) return null;
  const toneColor = { danger: '#dc2626', warning: '#d97706', primary: 'var(--ai-500, #3859D0)' };
  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <button type="button" aria-label="Acções" title="Acções" onClick={() => setOpen(o => !o)} style={{
        background: open ? '#f1f5f9' : 'none', border: '1px solid #e2e8f0', cursor: 'pointer',
        width: 30, height: 30, borderRadius: 6, color: '#64748b',
        display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
      }}>
        <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" aria-hidden="true">
          <path d="M2 3.5h10M2 7h10M2 10.5h10" />
        </svg>
      </button>
      {open && (
        <div style={{ position: 'absolute', top: 36, right: 0, zIndex: 40, minWidth: 196, background: '#fff', border: '1px solid var(--border, #ECEFF5)', borderRadius: 8, boxShadow: '0 8px 24px rgba(17,41,84,0.12)', padding: 4 }}>
          {items.map(item => (
            <button key={item.label} type="button" disabled={item.disabled} data-tutorial-step={item.tutorial}
              onClick={() => { if (item.disabled) return; setOpen(false); item.onClick(); }}
              style={{
                display: 'block', width: '100%', textAlign: 'left', background: 'none', border: 'none',
                cursor: item.disabled ? 'default' : 'pointer', padding: '8px 10px', borderRadius: 6,
                fontSize: 13, fontFamily: 'inherit', fontWeight: item.tone === 'primary' ? 600 : 500,
                color: toneColor[item.tone] || 'var(--text, #283252)', opacity: item.disabled ? 0.55 : 1,
              }}>
              {item.label}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

function BriefingScreenHeader({ isMobile, title, brandName, brandSlug, brandColor, status, savedLabel, activeBlock, onTab, onBack, menuItems, tabSteps, insetRight = 0 }) {
  const statusLabel = (STATUS_CONFIG[status] || STATUS_CONFIG.rascunho).label.toLowerCase();
  const bc = brandColor || BRF_HEADER_BRANDS[brandSlug] || 'var(--ai-500, #3859D0)';
  const meta = [savedLabel].filter(Boolean);
  return (
    <div style={{ background: '#ffffff', borderBottom: '1px solid #ECEFF5', padding: isMobile ? '16px 16px 0' : `16px ${40 + insetRight}px 0 40px`, flexShrink: 0 }}>
      <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: '0.07em', color: '#94A4C4', textTransform: 'uppercase', marginBottom: 6, fontFamily: 'var(--font-mono, monospace)' }}>
        MARKETING · BRIEFINGS
      </div>
      <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16 }}>
        <div style={{ minWidth: 0 }}>
          <h1 style={{ margin: 0, fontSize: 21, fontWeight: 700, color: '#283252', fontFamily: 'var(--font-display, Montserrat, sans-serif)', letterSpacing: '-0.01em', lineHeight: 1.2 }}>
            {title}
          </h1>
          <div style={{ fontSize: 12, color: '#94A4C4', marginTop: 6, display: 'flex', alignItems: 'center', gap: 0, flexWrap: 'wrap' }}>
            {brandName && <span style={{ color: bc, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.06em', fontSize: 10, fontFamily: 'var(--font-mono, monospace)' }}>{brandName}</span>}
            <span>{brandName ? ` · ${statusLabel}` : statusLabel}</span>
            {meta.map(part => <span key={part}>{` · ${part}`}</span>)}
          </div>
        </div>
        <div style={{ display: 'flex', gap: 8, flexShrink: 0, alignItems: 'center' }}>
          <BriefingHeaderMenu items={menuItems} />
          <button type="button" onClick={onBack} title="Voltar" aria-label="Voltar" style={{
            background: 'none', border: '1px solid #e2e8f0', cursor: 'pointer',
            width: 30, height: 30, borderRadius: 6, color: '#64748b',
            display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 16,
          }}>×</button>
        </div>
      </div>
      <div style={{ display: 'flex', gap: 0, marginTop: 14, marginLeft: -4, overflowX: 'auto' }}>
        {BRIEFING_TABS.map((tab) => {
          const active = activeBlock === tab.id;
          return (
            <button key={tab.id} type="button" onClick={() => onTab(tab.id)} data-tutorial-step={tab.tutorial || undefined} style={{
              background: 'none', border: 0, outline: 'none',
              borderBottom: `2px solid ${active ? bc : 'transparent'}`,
              color: active ? '#283252' : '#94A4C4',
              fontSize: 13, fontWeight: active ? 600 : 500,
              fontFamily: 'var(--font-display, Montserrat, sans-serif)', letterSpacing: '.01em',
              padding: '10px 16px 12px', cursor: 'pointer', whiteSpace: 'nowrap',
              transition: 'color .15s, border-color .15s',
            }}>{tab.label}</button>
          );
        })}
      </div>
    </div>
  );
}

const BriefingForm = ({ briefing, brandId, onSave, onSubmit, onCancel, onApproved, onRejected, onGenerateStrategy, onCriarCampanha, onVerCampanha, onCreated, userRole, userName, userEmail }) => {
  const isMobile = window.useMktMobile();
  const isAll = !brandId || brandId === 'todas';
  const [activeBlock, setActiveBlock] = React.useState(1);
  const [aiImproved, setAiImproved]   = React.useState({}); // { 'block2.end_user': 'Utilizador final', ... }
  const [allBrands, setAllBrands] = React.useState([]);
  const [selectedBrand, setSelectedBrand] = React.useState(
    briefing?.brand_slug || (isAll ? '' : brandId)
  );
  const [products, setProducts] = React.useState([]);
  const [selectedSegment, setSelectedSegment] = React.useState(briefing?.product_segment || briefing?.block1?.product_segment || '');
  const [selectedFamily, setSelectedFamily] = React.useState(briefing?.product_family || briefing?.block1?.product_family || '');
  const [saving,    setSaving]    = React.useState(false);
  const [saveError, setSaveError] = React.useState(null);
  const [savedAt,   setSavedAt]   = React.useState(null); // timestamp persistente
  const [justSaved, setJustSaved] = React.useState(false); // estado botão 2s
  const [approvalNotes, setApprovalNotes] = React.useState('');
  const [approvalNoteError, setApprovalNoteError] = React.useState('');
  const [savingNote, setSavingNote] = React.useState(false);
  const [noteSaved, setNoteSaved] = React.useState(false);
  const [acting, setActing] = React.useState(false);
  const [pdfAberto, setPdfAberto] = React.useState(false);
  const [approvals, setApprovals] = React.useState(briefing?.approvals || []);
  const [revisoes, setRevisoes] = React.useState(briefing?.revisoes || []);
  const [railOpen, setRailOpen] = React.useState(false);
  const [apreciation, setApreciation] = React.useState(briefing?.ai_apreciation || null);
  const [importBannerDismissed, setImportBannerDismissed] = React.useState(false);
  const [b5BannerDismissed, setB5BannerDismissed] = React.useState(false);
  const [suggestingB4B5, setSuggestingB4B5] = React.useState(false);
  const [showDirectModal,  setShowDirectModal]  = React.useState(false);
  const [showSubmitModal,  setShowSubmitModal]  = React.useState(false);
  const [showUnsavedModal, setShowUnsavedModal] = React.useState(false);
  const [toast, setToast] = React.useState(null); // { msg, type: 'success'|'error' }
  const [localStatus, setLocalStatus] = React.useState(null); // override após aprovação
  const showToast = (msg, type = 'success') => {
    setToast({ msg, type });
    setTimeout(() => setToast(null), 3500);
  };
  const curStatus = localStatus ?? briefing?.status;
  const toInputDate = (v) => {
    if (!v) return '';
    if (v instanceof Date) return v.toISOString().slice(0, 10);
    if (typeof v === 'string') return v.length > 10 ? v.slice(0, 10) : v;
    return '';
  };

  const LEGACY_CHAN = {
    linkedin: 'linkedin_ads', linkedin_ads: 'linkedin_ads', linkedin_organic: 'linkedin_organic',
    google_ads: 'google_ads_search', google_ads_search: 'google_ads_search', google_search: 'google_ads_search',
    google_display: 'google_ads_display', google_ads_display: 'google_ads_display',
    instagram: 'meta_ads', facebook: 'meta_ads', meta: 'meta_ads', meta_ads: 'meta_ads',
    social: 'meta_ads', social_media: 'meta_ads', paid_social: 'meta_ads',
    site: 'website', blog: 'website', website: 'website', loja: 'website',
    led: 'muppi_led', muppi_led: 'muppi_led', paineis: 'muppi_led', paineis_led: 'muppi_led',
    email: 'email', email_marketing: 'email',
    whatsapp: 'whatsapp', whatsapp_marketing: 'whatsapp',
    equipa_consumiveis: 'equipa_consumiveis', consumiveis: 'equipa_consumiveis',
    equipa_equipamentos: 'equipa_equipamentos', equipamentos: 'equipa_equipamentos',
    equipa_sdr: 'equipa_sdr', sdr: 'equipa_sdr', daily_prospect: 'equipa_sdr', dailyprospect: 'equipa_sdr',
    distribuidores: 'distribuidores', distribuidor: 'distribuidores',
  };
  const VALID_CHANS = new Set(['meta_ads','linkedin_ads','linkedin_organic','google_ads_search','google_ads_display','email','whatsapp','website','muppi_led','equipa_consumiveis','equipa_equipamentos','equipa_sdr','distribuidores']);
  const normChannels = (arr) => [...new Set((Array.isArray(arr) ? arr : []).map(ch => LEGACY_CHAN[(ch||'').toLowerCase().replace(/[^a-z_]/g,'')] || ch).filter(ch => VALID_CHANS.has(ch)))];

  // Normaliza kpis: string[] legado → {canal,kpi}[]
  const normKpis = (arr) => (Array.isArray(arr) ? arr : []).map(k =>
    typeof k === 'string' ? { canal: '', kpi: k } : k
  ).filter(k => k?.kpi?.trim());

  const buildForm = (src) => {
    const b4 = src.block4 || {};
    // Parse commercial_offer se veio como JSON string do backend (bug: GET não fazia parse antes)
    const parsedOffer = (() => {
      const raw = b4.commercial_offer;
      if (raw == null) return null;
      if (typeof raw === 'object') return raw;
      if (typeof raw === 'string') {
        const s = raw.trim();
        if (!s) return null;
        try { return JSON.parse(s); }
        catch { return { descricao: s }; }
      }
      return null;
    })();
    return limparTravessao({
      product_id: src.product_id || '',
      block1: { ...EMPTY_FORM.block1, ...(src.block1 || {}) },
      block2: { ...EMPTY_FORM.block2, ...(src.block2 || {}) },
      block3: { ...EMPTY_FORM.block3, ...(src.block3 || {}) },
      block4: { ...EMPTY_FORM.block4, ...b4, channels: normChannels(b4.channels), kpis: normKpis(b4.kpis), timeline_start: toInputDate(b4.timeline_start), timeline_end: toInputDate(b4.timeline_end), commercial_offer: parsedOffer },
      block5: { ...EMPTY_FORM.block5, ...(src.block5 || {}) },
    });
  };

  const [form, setForm] = React.useState(() => {
    console.log('[BriefingForm] briefing:', briefing, 'block1:', briefing?.block1);
    return briefing?.block1 ? buildForm(briefing) : { ...EMPTY_FORM };
  });
  const [formLoading, setFormLoading] = React.useState(!!(briefing?.id && !briefing?.block1));
  const creatingDraft = React.useRef(false);
  const formRef = React.useRef(form);
  formRef.current = form;
  const selectedBrandRef = React.useRef(selectedBrand);
  const selectedSegmentRef = React.useRef(selectedSegment);
  const selectedFamilyRef = React.useRef(selectedFamily);
  const approvalNotesRef = React.useRef(approvalNotes);
  const apreciationRef = React.useRef(apreciation);
  selectedBrandRef.current = selectedBrand;
  selectedSegmentRef.current = selectedSegment;
  selectedFamilyRef.current = selectedFamily;
  approvalNotesRef.current = approvalNotes;
  apreciationRef.current = apreciation;
  const baselineRef = React.useRef(null);

  const serializeState = (src = {}) => JSON.stringify({
    form: src.form ?? formRef.current,
    selectedBrand: src.selectedBrand ?? selectedBrandRef.current ?? '',
    selectedSegment: src.selectedSegment ?? selectedSegmentRef.current ?? '',
    selectedFamily: src.selectedFamily ?? selectedFamilyRef.current ?? '',
    approvalNotes: src.approvalNotes ?? approvalNotesRef.current ?? '',
    apreciation: src.apreciation ?? apreciationRef.current ?? null,
  });
  const markClean = (src) => { baselineRef.current = serializeState(src); };
  const hasUnsavedChanges = () => baselineRef.current != null && serializeState() !== baselineRef.current;
  const pendingLeaveRef = React.useRef(null);

  const pedirSaida = (continuar) => {
    if (window.__mktBriefingLeaving) return true;
    if (!hasUnsavedChanges()) return true;
    pendingLeaveRef.current = continuar;
    setShowUnsavedModal(true);
    return false;
  };

  const executarSaida = (fn) => {
    window.__mktBriefingLeaving = true;
    try { fn?.(); }
    finally { queueMicrotask(() => { window.__mktBriefingLeaving = false; }); }
  };

  const ficarNoBriefing = () => {
    pendingLeaveRef.current = null;
    setShowUnsavedModal(false);
  };

  React.useEffect(() => {
    window.mktPedirSaidaBriefing = pedirSaida;
    return () => {
      if (window.mktPedirSaidaBriefing === pedirSaida) delete window.mktPedirSaidaBriefing;
    };
  });

  // Editar da lista - buscar blocos via API
  React.useEffect(() => {
    if (!briefing?.id || briefing?.block1) return;
    window.MktBriefingsAPI.get(briefing.id).then(full => {
      if (!full) return;
      setForm(buildForm(full));
      setApreciation(full.ai_apreciation || null);
      const fullApprovals = full.approvals || [];
      setApprovals(fullApprovals);
      if (full.revisoes) setRevisoes(full.revisoes);
      // Pre-popular notas se aprovador tiver rascunho guardado
      const myRoles = getMyPendingRoles(userEmail, full.brand_slug, fullApprovals, full.status);
      if (myRoles.length > 0) {
        const draft = fullApprovals.find(a => a.action === 'saved' && a.role === myRoles[0]);
        if (draft?.notes) setApprovalNotes(draft.notes);
      }
      if (full.product_segment) setSelectedSegment(full.product_segment);
      if (full.product_family)  setSelectedFamily(full.product_family);
      setFormLoading(false);
    });
  }, []);

  // Baseline limpa depois do formulário estar carregado (novo ou fetch completo)
  React.useEffect(() => {
    if (formLoading) {
      baselineRef.current = null;
      return;
    }
    markClean();
  }, [formLoading]);

  React.useEffect(() => {
    if (!briefing?.id || !briefing?.block1) return;
    window.MktBriefingsAPI.get(briefing.id).then(full => {
      if (full?.revisoes) setRevisoes(full.revisoes);
    });
  }, [briefing?.id]);

  // Aviso nativo do browser se fechar o separador com alterações por gravar
  React.useEffect(() => {
    const onBeforeUnload = (e) => {
      if (!hasUnsavedChanges()) return;
      e.preventDefault();
      e.returnValue = '';
    };
    window.addEventListener('beforeunload', onBeforeUnload);
    return () => window.removeEventListener('beforeunload', onBeforeUnload);
  });

  // Carregar marcas quando modo "todas"
  const BRAND_DISPLAY = { mimaki: 'Mimaki', biond: 'BIOND', decal: 'Decal', sensek: 'Sensek', alldecor: 'AllDecor', digidelta: 'Digidelta', netscreen: 'NetScreen' };
  React.useEffect(() => {
    if (!isAll) return;
    window.MktBriefingsAPI.getBrands().then(d => {
      setAllBrands((d || []).filter(b => b.slug !== 'todas').map(b => ({ ...b, name: BRAND_DISPLAY[b.slug] || b.name })));
    });
  }, []);

  // Carregar produtos quando muda a marca - hidrata categoria/produto (incl. match por nome comercial)
  React.useEffect(() => {
    const bid = selectedBrand || (!isAll ? brandId : '');
    if (!bid || bid === 'todas') { setProducts([]); return; }
    window.MktBriefingsAPI.getProducts(bid).then(d => {
      const prods = d || [];
      setProducts(prods);
      const wasClean = !hasUnsavedChanges();
      const currentPid = formRef.current?.product_id || briefing?.product_id || '';
      const hadPersistedPid = !!(briefing?.product_id || formRef.current?.product_id);
      let existing = currentPid ? prods.find(p => String(p.id) === String(currentPid)) : null;

      // Importações / reopens sem product_id: tentar ligar pelo nome comercial
      if (!existing) {
        const famHint = selectedFamilyRef.current || formRef.current?.block1?.product_family || briefing?.product_family || '';
        existing = matchCatalogProduct(prods, {
          commercialName: formRef.current?.block1?.commercial_name || briefing?.commercial_name || briefing?.block1?.commercial_name || '',
          productName: briefing?.product_name || '',
          family: famHint,
        });
      }

      if (existing) {
        const nextSeg = existing.segment || selectedSegmentRef.current || '';
        const nextFam = existing.category || selectedFamilyRef.current || '';
        const nextPid = String(existing.id);
        if (existing.segment) setSelectedSegment(existing.segment);
        if (existing.category) setSelectedFamily(existing.category);
        const needForm = String(formRef.current?.product_id || '') !== nextPid
          || (existing.category && formRef.current?.block1?.product_family !== existing.category);
        let nextForm = formRef.current;
        if (needForm) {
          nextForm = {
            ...formRef.current,
            product_id: nextPid,
            block1: { ...(formRef.current.block1 || {}), product_family: existing.category || formRef.current.block1?.product_family || '' },
          };
          formRef.current = nextForm;
          setForm(nextForm);
        }
        // Produto já gravado → hidratação limpa. Match novo por nome → dirty para o user gravar.
        if (wasClean && hadPersistedPid) markClean({ form: nextForm, selectedSegment: nextSeg, selectedFamily: nextFam });
        return;
      }

      // Só família no block1 (ex.: importação parcial) → preencher categoria a partir do catálogo
      const fam = selectedFamilyRef.current || formRef.current?.block1?.product_family || '';
      if (fam && !selectedSegmentRef.current) {
        const hit = prods.find(p => p.category === fam);
        if (hit?.segment) {
          setSelectedSegment(hit.segment);
          if (wasClean) markClean({ selectedSegment: hit.segment, selectedFamily: fam });
        }
      }
    });
  }, [selectedBrand, brandId]);

  const handleBrandChange = (slug) => {
    setSelectedBrand(slug);
    setSelectedSegment('');
    setSelectedFamily('');
    setForm(f => ({ ...f, product_id: '' }));
  };

  const handleSegmentChange = (seg) => {
    setSelectedSegment(seg);
    setSelectedFamily('');
    setForm(f => ({ ...f, product_id: '' }));
  };

  const handleFamilyChange = (family) => {
    setSelectedFamily(family);
    setForm(f => ({ ...f, product_id: '' }));
  };

  const handleProductChange = (pid) => {
    const prod = products.find(p => String(p.id) === String(pid));
    setForm(f => ({
      ...f,
      product_id: pid,
      block1: { ...f.block1, product_family: prod?.category || f.block1.product_family },
    }));
  };

  const handleApplyImprovement = async (block, field, rawValue, fieldLabel) => {
    let value = limparTravessao(rawValue);
    if (typeof value === 'string' && /^[\[{]/.test(value.trim())) {
      try { value = JSON.parse(value); } catch {}
    }
    if (field === 'kpis') {
      const list = Array.isArray(value) ? value : (value ? [value] : []);
      value = list.map(k => {
        if (k && typeof k === 'object') return { canal: k.canal || 'negocio', kpi: String(k.kpi || '').trim() };
        const s = String(k || '').trim();
        const m = s.match(/^([a-z0-9_]+)\s*:\s*(.+)$/i);
        return m ? { canal: m[1].toLowerCase(), kpi: m[2].trim() } : { canal: 'negocio', kpi: s };
      }).filter(k => k.kpi);
    } else if (field === 'channels') {
      value = normChannels(Array.isArray(value) ? value : String(value || '').split(/[,\n]/));
    } else if (field === 'commercial_offer' && value && typeof value === 'object') {
      value = limparTravessao(value);
    } else if (field === 'meta_ads_conversao') {
      value = String(value || '').trim();
    }
    const base = formRef.current;
    const updatedForm = { ...base, [block]: { ...(base[block] || {}), [field]: value } };
    formRef.current = updatedForm;
    setForm(updatedForm);
    setAiImproved(prev => ({ ...prev, [`${block}.${field}`]: fieldLabel || field }));

    // Auto-save: apenas guarda o conteúdo dos blocos
    // A apreciação (evals, scores, improved_fields) é gerida pelo Block6 via onUpdate
    try {
      setSaving(true);
      await onSave(updatedForm, isAll ? selectedBrand : undefined, undefined);
      setSavedAt(new Date());
      setJustSaved(true);
      setTimeout(() => setJustSaved(false), 2000);
      markClean({ form: updatedForm });
      return true;
    } catch (e) {
      console.warn('[auto-save improvement]', e.message);
      return false;
    } finally {
      setSaving(false);
    }
  };

  const handleApplyCorrecao = async (pedidoId, sug, autorNome, autorEmail) => {
    const ok = await handleApplyImprovement(sug.bloco, sug.campo, sug.valor_proposto, BRF_CAMPO_LABEL[sug.campo] || sug.campo);
    if (!ok) return false;
    await window.MktBriefingsAPI.registarCorrecao(briefing.id, {
      pedido_id: pedidoId,
      texto: sug.pedido || 'Correcção aceite',
      autor_nome: autorNome,
      autor_email: autorEmail,
      campos: { bloco: sug.bloco, campo: sug.campo, antes: sug.valor_actual ?? null, depois: sug.valor_proposto },
    });
    const refreshed = await window.MktBriefingsAPI.get(briefing.id);
    if (refreshed?.revisoes) setRevisoes(refreshed.revisoes);
    return true;
  };

  const irAoCampo = React.useCallback((bloco, campo) => {
    const n = Number(String(bloco || '').replace(/\D/g, ''));
    if (n >= 1 && n <= 5 && n !== 4) setActiveBlock(n);
    const id = String(campo || '');
    let vezes = 0;
    const tentar = () => {
      vezes += 1;
      const el = id ? document.querySelector(`[data-briefing-campo="${id}"]`) : null;
      if (!el) {
        if (vezes < 12) setTimeout(tentar, 40);
        return;
      }
      el.scrollIntoView({ behavior: 'smooth', block: 'center' });
      const previo = el.style.boxShadow;
      el.style.boxShadow = '0 0 0 2px #3859D0';
      el.style.borderRadius = el.style.borderRadius || '8px';
      setTimeout(() => { el.style.boxShadow = previo; }, 1800);
    };
    setTimeout(tentar, 30);
  }, []);

  const handleDismissBlockImproved = (blockKey) => {
    setAiImproved(prev => {
      const next = { ...prev };
      Object.keys(next).filter(k => k.startsWith(blockKey + '.')).forEach(k => delete next[k]);
      return next;
    });
  };

  const upd = (block, field, value) =>
    setForm(f => ({ ...f, [block]: { ...f[block], [field]: limparTravessao(value) } }));

  const handleSuggestRestricoes = async () => {
    setSuggestingB4B5(true);
    try {
      const brand_name = selectedBrand || briefing?.brand_slug || briefing?.brand_name || '';
      const res = await fetch('/api/marketing/briefings/suggest-b4b5', {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ scope: 'restricoes', block1: form.block1, block2: form.block2, block3: form.block3, block4: form.block4, block5: form.block5, brand_name }),
      });
      const reader = res.body.getReader(); const dec = new TextDecoder(); let buf = '';
      while (true) {
        const { done, value } = await reader.read();
        if (value) {
          buf += dec.decode(value, { stream: !done });
          const lines = buf.split('\n'); buf = done ? '' : (lines.pop() ?? '');
          for (const line of lines) {
            if (!line.startsWith('data: ')) continue;
            let evt; try { evt = JSON.parse(line.slice(6)); } catch { continue; }
            if (evt.type === 'done') {
              const data = limparTravessao(evt.data || {});
              if (data.block5) setForm(f => ({ ...f, block5: data.block5 }));
              setB5BannerDismissed(true);
            }
          }
        }
        if (done) break;
      }
    } catch (e) { console.warn('[suggest-restricoes]', e.message); }
    finally { setSuggestingB4B5(false); }
  };

  const handleSave = async (opts = {}) => {
    const force = !!opts.force;
    setSaveError(null);
    setSavedAt(null);
    if (isAll && !selectedBrand) { setSaveError('Selecciona uma marca antes de guardar.'); return false; }
    setSaving(true);
    try {
      // Guardar notas de aprovação (se aprovador activo com role pendente)
      if (canApprove && pendingRoles.length > 0) {
        const approver_name = userName || window.currentUser?.nome_apresentar || window.currentUser?.nome || null;
        await window.MktBriefingsAPI.saveNotes(briefing.id, {
          notes: approvalNotes,
          role: pendingRoles[0],
          approver_name,
          approver_email: userEmail,
        });
      }
      // Guardar conteúdo do briefing (sempre - aprovador ou autor)
      await onSave(form, isAll ? selectedBrand : undefined, apreciation || undefined);
      const now = new Date();
      setSavedAt(now);
      setJustSaved(true);
      setTimeout(() => setJustSaved(false), 2000);
      markClean();
      return true;
    } catch (e) {
      setSaveError(e.message || 'Erro ao guardar. Tenta novamente.');
      return false;
    } finally {
      setSaving(false);
    }
  };

  const requestClose = () => {
    const go = () => onCancel?.();
    if (pedirSaida(go) === false) return;
    go();
  };

  const sairSemGravar = () => {
    const next = pendingLeaveRef.current || (() => onCancel?.());
    pendingLeaveRef.current = null;
    markClean();
    setShowUnsavedModal(false);
    executarSaida(next);
  };

  const handleSaveAndClose = async () => {
    const next = pendingLeaveRef.current || (() => onCancel?.());
    const ok = await handleSave({ force: true });
    if (!ok) return;
    pendingLeaveRef.current = null;
    setShowUnsavedModal(false);
    executarSaida(next);
  };

  // Escape fecha o briefing (com modal se houver alterações)
  React.useEffect(() => {
    const onKey = (e) => {
      if (e.key !== 'Escape') return;
      if (showUnsavedModal || showSubmitModal || showDirectModal || pdfAberto) return;
      e.preventDefault();
      requestClose();
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  });

  const isNew          = !briefing?.id;
  const isDraft        = !curStatus || curStatus === 'rascunho';
  const isRejected     = curStatus === 'rejeitado';
  const isAltPedidas   = curStatus === 'alteracoes_pedidas';
  const isSubmitted    = curStatus === 'submetido';
  const isValidadoFc   = curStatus === 'validado_fc';
  const isValidadoCeo  = curStatus === 'validado_ceo';
  const isApproved     = curStatus === 'aprovado' || curStatus === 'em_campanha';
  const isInApproval   = isSubmitted || isValidadoFc || isValidadoCeo;
  const isPrivilegedUser = userRole === 'admin' || userEmail === 'rui.leitao@digidelta.pt' || userEmail === 'armando.mota@digidelta.pt';
  const canSubmit      = (isDraft || isRejected || isAltPedidas) && calcProgress(form) >= 80 && (isPrivilegedUser || (apreciation?.quality_score ?? 0) >= 80);
  const brandSlug      = briefing?.brand_slug;
  const pendingRoles   = isInApproval ? getMyPendingRoles(userEmail, brandSlug, approvals, curStatus) : [];
  const canApprove     = pendingRoles.length > 0;
  const emailNorm = String(userEmail || '').toLowerCase();
  const paramsMkt = new URLSearchParams(window.location.search);
  const isCarlos = emailNorm === 'carlos.alves@decal.pt' || paramsMkt.get('dev') === 'carlos' || paramsMkt.get('mkt_role') === 'strategist';
  const canEditApproved = userRole === 'admin' || emailNorm === 'rui.leitao@digidelta.pt' || isCarlos;
  const temCampanhas = Number(briefing?.n_campanhas) > 0 || curStatus === 'em_campanha';
  const podeEditarAprovado = temCampanhas ? (userRole === 'admin' || isCarlos) : canEditApproved;
  const [editingApproved, setEditingApproved] = React.useState(false);
  // Aprovado desbloqueia com Editar (admin, Rui, Carlos).
  // Com campanhas, só o Carlos desbloqueia. O admin continua a poder gravar.
  const desbloqueado = editingApproved && isApproved && podeEditarAprovado;
  const chairmanEditaParaCeo = emailNorm === 'rui.leitao@digidelta.pt' && isValidadoFc;
  const isReadOnly     = userRole !== 'admin' && !canApprove && !chairmanEditaParaCeo && (isInApproval || (isApproved && !desbloqueado));
  const showRail       = true;
  const fluxoBrand     = briefing?.brand_slug || selectedBrand;
  const showFluxo      = !!fluxoBrand;
  const scrollRef = React.useRef(null);
  const fluxoRef = React.useRef(null);
  const [scrollBar, setScrollBar] = React.useState(0);
  const [fluxoAltura, setFluxoAltura] = React.useState(0);
  React.useLayoutEffect(() => {
    const el = scrollRef.current;
    if (!el) return undefined;
    const medir = () => setScrollBar(Math.max(0, el.offsetWidth - el.clientWidth));
    medir();
    let ro;
    if (typeof ResizeObserver !== 'undefined') {
      ro = new ResizeObserver(medir);
      ro.observe(el);
    }
    window.addEventListener('resize', medir);
    return () => {
      ro?.disconnect();
      window.removeEventListener('resize', medir);
    };
  }, [isMobile, showRail, curStatus, formLoading]);
  React.useLayoutEffect(() => {
    const el = fluxoRef.current;
    if (!el) { setFluxoAltura(0); return undefined; }
    const medir = () => setFluxoAltura(el.offsetHeight);
    medir();
    let ro;
    if (typeof ResizeObserver !== 'undefined') {
      ro = new ResizeObserver(medir);
      ro.observe(el);
    }
    return () => ro?.disconnect();
  }, [showFluxo, isMobile, curStatus, formLoading]);

  const handleSaveNote = async () => {
    if (!briefing?.id || !pendingRoles.length || savingNote) return;
    setSavingNote(true);
    setApprovalNoteError('');
    setNoteSaved(false);
    try {
      const approver_name = userName || window.currentUser?.nome_apresentar || window.currentUser?.nome || null;
      await window.MktBriefingsAPI.saveNotes(briefing.id, {
        notes: approvalNotes,
        role: pendingRoles[0],
        approver_name,
        approver_email: userEmail,
      });
      const roleGuardado = pendingRoles[0];
      setApprovals(prev => {
        const rest = (prev || []).filter(a => !(a.action === 'saved' && a.role === roleGuardado));
        if (!String(approvalNotes || '').trim()) return rest;
        return [{
          action: 'saved',
          role: roleGuardado,
          notes: approvalNotes,
          approver_name,
          created_at: new Date().toISOString(),
        }, ...rest];
      });
      setNoteSaved(true);
      setTimeout(() => setNoteSaved(false), 2000);
      // Notas gravadas: actualiza baseline sem limpar sujidade do formulário
      if (baselineRef.current != null) {
        try {
          const prev = JSON.parse(baselineRef.current);
          prev.approvalNotes = approvalNotesRef.current || '';
          baselineRef.current = JSON.stringify(prev);
        } catch { /* ignore */ }
      }
    } catch (e) {
      setApprovalNoteError(e.message || 'Não foi possível guardar a nota.');
    } finally {
      setSavingNote(false);
    }
  };

  const handleApproval = async (action) => {
    const needsNotes = action === 'rejected' || action === 'changes_requested';
    if (needsNotes && !approvalNotes.trim()) {
      setApprovalNoteError(
        action === 'rejected'
          ? 'Indica o motivo da rejeição antes de rejeitar.'
          : 'Indica as alterações necessárias antes de enviar.'
      );
      return;
    }
    setApprovalNoteError('');
    setActing(true);
    try {
      const approver_name = userName || window.currentUser?.nome_apresentar || window.currentUser?.nome || null;
      let lastResult;
      for (const role of pendingRoles) {
        lastResult = await window.MktBriefingsAPI.approve(briefing.id, {
          action,
          notes: approvalNotes,
          approver_name,
          approver_email: userEmail,
          role,
        });
      }
      // Refetch para actualizar estado local sem navegar
      const refreshed = await window.MktBriefingsAPI.get(briefing.id);
      if (refreshed?.approvals) setApprovals(refreshed.approvals);
      if (refreshed?.revisoes) setRevisoes(refreshed.revisoes);
      const newStatus = lastResult?.status || refreshed?.status;
      if (newStatus) setLocalStatus(newStatus);
      setApprovalNotes('');
      // Toast contextual
      if (action === 'approved') {
        const toastMsg = newStatus === 'aprovado'
          ? 'Briefing aprovado. Pode criar a campanha.'
          : 'Fase aprovada. Aguarda aprovação do Chairman.';
        showToast(toastMsg, 'success');
      } else if (action === 'rejected') {
        showToast('Briefing rejeitado. O autor será notificado.', 'error');
      } else {
        showToast('Alterações pedidas. O autor será notificado.', 'info');
      }
    } catch (e) {
      setSaveError(e.message || 'Erro ao processar. Tenta novamente.');
    } finally {
      setActing(false);
    }
  };

  if (formLoading) return (
    <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--text-muted)', fontSize: 13 }}>
      A carregar briefing...
    </div>
  );

  return (
    <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0 }}>

      {/* Toast pós-aprovação */}
      {toast && (
        <div style={{
          position: 'fixed', bottom: 24, right: 24, zIndex: 9999,
          padding: '12px 20px', borderRadius: 8, fontSize: 13, fontWeight: 500,
          background: toast.type === 'error' ? '#dc2626' : toast.type === 'info' ? '#d97706' : '#15803d',
          color: '#fff', boxShadow: '0 4px 16px rgba(0,0,0,0.2)',
          animation: 'fadeIn .2s ease',
        }}>
          {toast.msg}
        </div>
      )}

      <BriefingScreenHeader
        isMobile={isMobile}
        title={isNew ? 'Novo Briefing' : (String(form.block1?.commercial_name || '').trim() || briefing?.commercial_name || briefing?.block1?.commercial_name || briefing?.product_name || 'Briefing')}
        brandName={briefing?.brand_name || allBrands.find(b => b.slug === selectedBrand)?.name || ''}
        brandSlug={briefing?.brand_slug || selectedBrand}
        brandColor={briefing?.brand_color}
        status={curStatus || 'rascunho'}
        savedLabel={justSaved ? 'guardado' : (savedAt && !saving ? `guardado às ${savedAt.toLocaleTimeString('pt-PT', { hour: '2-digit', minute: '2-digit' })}` : '')}
        activeBlock={activeBlock}
        onBack={requestClose}
        onTab={(id) => {
          if (id === 6 && !briefing?.id && !creatingDraft.current) {
            const bid = isAll ? selectedBrand : brandId;
            if (bid) {
              creatingDraft.current = true;
              const snapshot = briefingParaGravar(form);
              window.MktBriefingsAPI.create({
                brand_id: bid,
                created_by_name: userName || '',
                created_by_email: userEmail || null,
                import_origin: briefing?._import_origin || null,
                ...snapshot,
              })
                .then(r => { if (r?.id) onCreated?.(r, snapshot); else creatingDraft.current = false; })
                .catch(() => { creatingDraft.current = false; });
            }
          }
          setActiveBlock(id);
        }}
        menuItems={[
          ...(!isReadOnly && !isApproved ? [{ label: saving ? 'A guardar...' : justSaved ? '✓ Guardado' : 'Guardar', onClick: handleSave, disabled: saving }] : []),
          ...(canSubmit ? [{
            label: isRejected ? 'Submeter novamente' : 'Submeter',
            tutorial: 'submit',
            onClick: () => {
              setSaveError(null);
              if (isAll && !selectedBrand) { setSaveError('Selecciona uma marca antes de submeter.'); return; }
              if (userEmail === 'fabio.costa@digidelta.pt') { setShowDirectModal(true); return; }
              setShowSubmitModal(true);
            },
          }] : []),
          ...(isApproved && podeEditarAprovado ? [editingApproved
            ? { label: saving ? 'A guardar...' : justSaved ? '✓ Guardado' : 'Guardar', onClick: handleSave, disabled: saving }
            : { label: 'Editar', onClick: () => setEditingApproved(true) }] : []),
          ...(isApproved ? [{ label: 'Criar campanha', onClick: () => onCriarCampanha?.(briefing), tone: 'primary', tutorial: 'approved' }] : []),
          ...(isApproved && Number(briefing?.n_campanhas) > 0 ? [{ label: 'Ver campanhas', onClick: () => onVerCampanha?.(briefing), tone: 'primary' }] : []),
          ...(briefing?.id ? [{ label: 'Enviar PDF por email', onClick: () => setPdfAberto(true) }] : []),
          ...(canApprove ? [
            { label: 'Rejeitar', onClick: () => handleApproval('rejected'), disabled: acting, tone: 'danger' },
            { label: 'Pedir alterações', onClick: () => handleApproval('changes_requested'), disabled: acting, tone: 'warning' },
            { label: acting ? 'A processar...' : 'Aprovar', onClick: () => handleApproval('approved'), disabled: acting, tone: 'primary' },
          ] : []),
        ]}
        insetRight={isMobile ? 0 : scrollBar}
      />
      {pdfAberto && briefing?.id && (
        <BriefingPdfModal
          briefing={{ ...briefing, commercial_name: form.block1?.commercial_name || briefing.commercial_name, product_name: briefing.product_name }}
          onClose={() => setPdfAberto(false)}
        />
      )}
      {saveError && <div style={{ fontSize: 11, color: 'var(--danger)', padding: isMobile ? '8px 16px 0' : '8px 40px 0', background: '#fff' }}>{saveError}</div>}
      {showSubmitModal && (
        <SubmitConfirmModal
          isRejected={isRejected}
          onClose={() => setShowSubmitModal(false)}
          onConfirm={(notes) => {
            setShowSubmitModal(false);
            onSubmit(form, isAll ? selectedBrand : undefined, apreciation || undefined, notes);
          }}
        />
      )}
      {showDirectModal && briefing?.id && (
        <DirectSubmitModal
          briefingId={briefing.id}
          submitterName={userName}
          submitterEmail={userEmail}
          onClose={() => setShowDirectModal(false)}
          onDone={() => { setShowDirectModal(false); onCancel && onCancel(); }}
        />
      )}
      {showUnsavedModal && (
        <UnsavedChangesModal
          saving={saving}
          onStay={ficarNoBriefing}
          onDiscard={sairSemGravar}
          onSave={handleSaveAndClose}
        />
      )}


      {/* Formulário e notas partilham o scroll. O aviso de campanha fica por cima dos dois, para o cartão da direita começar na mesma linha. */}
      <div style={{ flex: 1, minHeight: 0, position: 'relative', display: 'flex', flexDirection: 'column', background: 'var(--bg, #f5f7fa)' }}>
      <div ref={scrollRef} className="scrollbar" style={{ flex: 1, minHeight: 0, overflowY: 'auto', overflowX: 'hidden' }}>
      {showFluxo && (
        <div ref={fluxoRef} style={{ position: 'sticky', top: 0, zIndex: 4, boxSizing: 'border-box', background: 'var(--bg, #f5f7fa)', padding: isMobile ? '12px 12px 0' : '24px 40px 0' }}>
          <BriefingFluxoBar briefing={{ ...briefing, status: curStatus, brand_slug: briefing?.brand_slug || selectedBrand }} approvals={approvals} revisoes={revisoes} />
        </div>
      )}
      {isApproved && Number(briefing?.n_campanhas) > 0 && (() => {
        const nCamp = Number(briefing?.n_campanhas);
        const uma = nCamp === 1;
        const texto = Number.isFinite(nCamp)
          ? (uma ? 'Este briefing tem 1 campanha.' : `Este briefing tem ${nCamp} campanhas.`)
          : 'Este briefing tem campanhas.';
        return (
        <div style={{ boxSizing: 'border-box', padding: isMobile ? '12px 12px 0' : '16px 40px 0' }}>
          <div style={{ padding: '10px 14px', borderRadius: 7, background: 'color-mix(in srgb, #3859D0 8%, transparent)', border: '1px solid color-mix(in srgb, #3859D0 25%, transparent)', display: 'flex', alignItems: 'center', gap: 12, fontSize: 13, color: 'var(--text)' }}>
            <span style={{ fontSize: 16 }}>ℹ</span>
            <span style={{ flex: 1 }}>{texto}</span>
            <button className="btn" style={{ height: 28, padding: '0 12px', fontSize: 12, flexShrink: 0 }}
              onClick={() => onVerCampanha?.(briefing)}>
              {uma ? 'Ver campanha' : 'Ver campanhas'} →
            </button>
          </div>
        </div>
        );
      })()}
      <div style={{ display: 'flex', alignItems: 'stretch', minWidth: 0 }}>
      <div style={{ flex: 1, minWidth: 0, padding: isMobile ? 12 : ((showRail && !isMobile) ? '16px 16px 64px 40px' : (showFluxo ? '16px 40px 64px' : '24px 40px 64px')) }}>
        <div>
          {isMobile && showRail && (
            <button onClick={() => setRailOpen(true)} style={{ marginBottom: 16, height: 30, padding: '0 12px', fontSize: 12, borderRadius: 6, border: '1px solid var(--border)', background: '#fff', color: 'var(--text)', cursor: 'pointer', fontFamily: 'inherit' }}>
              Notas{brfLinhasNotas(revisoes, approvals).length ? ` (${brfLinhasNotas(revisoes, approvals).length})` : ''}
            </button>
          )}

          {briefing?._import_origin && !importBannerDismissed && (
            <div style={{ marginBottom: 20, padding: '10px 14px', borderRadius: 8, fontSize: 12, display: 'flex', alignItems: 'center', gap: 8, background: 'color-mix(in oklch, var(--ai-500) 8%, transparent)', border: '1px solid color-mix(in oklch, var(--ai-500) 25%, transparent)', color: 'var(--ai-500)' }}>
              <span style={{ flexShrink: 0 }}>✦</span>
              <span style={{ flex: 1 }}>{briefing._import_origin.origem === 'resultados-analise'
                ? `Proposta da análise da Digi AI em Resultados${briefing._import_origin.titulo ? ` · ${briefing._import_origin.titulo}` : ''}. Revê e guarda: a proposta passa a "briefing criado".`
                : 'Conteúdo extraído por IA - revê e confirma os dados antes de guardar. O Bloco 6 gera o resumo automaticamente.'}</span>
              <button onClick={() => setImportBannerDismissed(true)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--ai-500)', fontSize: 14, padding: '0 4px', flexShrink: 0, lineHeight: 1 }}>✕</button>
            </div>
          )}

          {/* Banner campos melhorados por AI - aparece no bloco activo */}
          {(() => {
            const blockKey = `block${activeBlock}`;
            const improved = Object.entries(aiImproved).filter(([k]) => k.startsWith(blockKey + '.'));
            if (!improved.length || activeBlock === 6) return null;
            return (
              <div style={{ marginBottom: 16, padding: '8px 14px', borderRadius: 8, background: 'rgba(245,158,11,.07)', border: '1px solid rgba(245,158,11,.25)', display: 'flex', alignItems: 'center', gap: 10 }}>
                <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#f59e0b" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0 }}><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
                <span style={{ fontSize: 11, color: '#92400e', flex: 1 }}>
                  <strong>Melhorado por AI:</strong> {improved.map(([, label]) => label).join(' · ')}
                </span>
                <button onClick={() => handleDismissBlockImproved(blockKey)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#92400e', fontSize: 12, padding: '0 2px', lineHeight: 1, opacity: 0.6 }}>
                  <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
                </button>
              </div>
            );
          })()}

          <div style={isReadOnly ? { pointerEvents: 'none', userSelect: 'text' } : {}}>
            {activeBlock === 1 && <Block1
              data={form.block1} upd={(f, v) => upd('block1', f, v)}
              isAll={isAll} allBrands={allBrands}
              selectedBrand={selectedBrand} onBrandChange={handleBrandChange}
              products={products}
              selectedSegment={selectedSegment} onSegmentChange={handleSegmentChange}
              selectedFamily={selectedFamily} onFamilyChange={handleFamilyChange}
              productId={form.product_id} onProductChange={handleProductChange}
            />}
            {activeBlock === 2 && <Block2 data={form.block2} upd={(f, v) => upd('block2', f, v)} brand={selectedBrand} />}
            {activeBlock === 3 && <Block3 data={form.block3} upd={(f, v) => upd('block3', f, v)} brand={selectedBrand} />}
            {activeBlock === 5 && (() => {
              const b5Empty = !(form.block5.prohibited_claims || []).some(v => v?.trim()) && !(form.block5.off_brand_messages || []).some(v => v?.trim());
              return (<>
                {b5Empty && !b5BannerDismissed && (
                  <div style={{ marginBottom:12, padding:'12px 14px', borderRadius:8, background:'color-mix(in srgb, var(--ai-500,#3859D0) 5%, transparent)', border:'1px solid color-mix(in srgb, var(--ai-500,#3859D0) 18%, transparent)', display:'flex', alignItems:'center', gap:12 }}>
                    <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="var(--ai-500,#3859D0)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{flexShrink:0}}><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
                    <div style={{ flex:1, fontSize:12, color:'var(--text-muted)' }}>
                      <strong style={{color:'var(--text)'}}>Restrições não definidas.</strong> Gera com a Digi AI ou preenche manualmente.
                    </div>
                    <div style={{ display:'flex', gap:6, flexShrink:0 }}>
                      <button className="btn btn-ai" style={{fontSize:11}} onClick={handleSuggestRestricoes} disabled={suggestingB4B5}>
                        {suggestingB4B5 ? 'A gerar...' : 'Gerar com Digi AI'}
                      </button>
                      <button className="btn" style={{fontSize:11}} onClick={() => setB5BannerDismissed(true)}>Preencher manualmente</button>
                    </div>
                  </div>
                )}
                <Block5 data={form.block5} upd={(f, v) => upd('block5', f, v)} brand={selectedBrand} aiSuggested={!!briefing?._import_origin && ((form.block5.prohibited_claims || []).some(v => v?.trim()) || (form.block5.off_brand_messages || []).some(v => v?.trim()))} />
              </>);
            })()}
          </div>
          {activeBlock === 6 && (
            <Block6
              briefingId={briefing?.id}
              apreciation={apreciation}
              skipAutoRun={!!briefing?._duplicate_of}
              previewData={!briefing?.id ? {
                block1: form.block1, block2: form.block2, block3: form.block3,
                block4: form.block4, block5: form.block5,
                brand_name: briefing?.brand_name || allBrands.find(b => b.slug === selectedBrand)?.name || selectedBrand || briefing?.brand_slug || '',
              } : null}
              userRole={userRole}
              calcProgressValue={calcProgress(form)}
              onUpdate={(data) => {
                setApreciation(data);
                if (briefing?.id) {
                  window.MktBriefingsAPI.update(briefing.id, { ai_apreciation: data }).catch(() => {});
                }
              }}
              importOrigin={briefing?._import_origin || briefing?.import_origin}
              form={form}
              brandName={briefing?.brand_name || allBrands.find(b => b.slug === selectedBrand)?.name || selectedBrand || briefing?.brand_slug || ''}
              onApplyImprovement={handleApplyImprovement}
              onNavigateToBlock={setActiveBlock}
            />
          )}
        </div>
      </div>
      {showRail && !isMobile && (
        <BriefingSideRail
          briefing={{ ...briefing, status: curStatus }}
          valores={form}
          approvals={approvals}
          revisoes={revisoes}
          canApprove={canApprove}
          noteValue={approvalNotes}
          onNoteChange={v => { setApprovalNotes(v); if (v.trim()) setApprovalNoteError(''); setNoteSaved(false); }}
          noteError={approvalNoteError}
          onSaveNote={handleSaveNote}
          onRequestChanges={() => handleApproval('changes_requested')}
          onApprove={() => handleApproval('approved')}
          onReject={() => handleApproval('rejected')}
          savingNote={savingNote}
          noteSaved={noteSaved}
          acting={acting}
          overlay={false}
          onClose={() => setRailOpen(false)}
          onApplyCorrecao={handleApplyCorrecao}
          onIrAoCampo={irAoCampo}
          autorNome={userName}
          autorEmail={userEmail}
          stickyTop={(fluxoAltura || 0) + 16}
        />
      )}
      </div>
      </div>
      {showRail && isMobile && railOpen && (
        <BriefingSideRail
          briefing={{ ...briefing, status: curStatus }}
          valores={form}
          approvals={approvals}
          revisoes={revisoes}
          canApprove={canApprove}
          noteValue={approvalNotes}
          onNoteChange={v => { setApprovalNotes(v); if (v.trim()) setApprovalNoteError(''); setNoteSaved(false); }}
          noteError={approvalNoteError}
          onSaveNote={handleSaveNote}
          onRequestChanges={() => handleApproval('changes_requested')}
          onApprove={() => handleApproval('approved')}
          onReject={() => handleApproval('rejected')}
          savingNote={savingNote}
          noteSaved={noteSaved}
          acting={acting}
          overlay
          onClose={() => setRailOpen(false)}
          onApplyCorrecao={handleApplyCorrecao}
          onIrAoCampo={(bloco, campo) => { setRailOpen(false); irAoCampo(bloco, campo); }}
          autorNome={userName}
          autorEmail={userEmail}
        />
      )}
      </div>

    </div>
  );
};

// ─── Vista de detalhe + aprovação ─────────────────────────────────────────────

const BriefingDetail = ({ briefing, onEdit, onBack, onApproved, onRejected, onGenerateStrategy, onCriarCampanha, onVerCampanha, userRole, userName, userEmail }) => {
  const isMobile = window.useMktMobile();
  const [notes, setNotes] = React.useState('');
  const [rejectError, setRejectError] = React.useState('');
  const [acting, setActing] = React.useState(false);
  const [activeBlock, setActiveBlock] = React.useState(1);
  const [full, setFull] = React.useState(briefing.block1 ? briefing : null);
  const [pdfAberto, setPdfAberto] = React.useState(false);

  React.useEffect(() => {
    if (briefing.block1) return;
    window.MktBriefingsAPI.get(briefing.id).then(d => { if (d) setFull(d); });
  }, [briefing.id]);

  const src          = full || briefing;
  const brandSlug    = src.brand_slug;
  const approvals    = src.approvals || [];
  const isInApproval = ['submetido','validado_fc','validado_ceo'].includes(briefing.status);
  const pendingRoles = isInApproval ? getMyPendingRoles(userEmail, brandSlug, approvals, briefing.status) : [];
  const canApprove   = pendingRoles.length > 0;

  const handle = async (action) => {
    const needsNotes = action === 'rejected' || action === 'changes_requested';
    if (needsNotes && !notes.trim()) {
      setRejectError(action === 'rejected' ? 'Indica o motivo da rejeição.' : 'Indica as alterações necessárias.');
      return;
    }
    setRejectError('');
    setActing(true);
    try {
      const approver_name = userName || window.currentUser?.nome_apresentar || window.currentUser?.nome || null;
      for (const role of pendingRoles) {
        await window.MktBriefingsAPI.approve(briefing.id, {
          action,
          notes,
          approver_name,
          approver_email: userEmail,
          role,
        });
      }
      action === 'approved' ? onApproved() : onRejected();
    } catch (e) {
      setRejectError(e.message || 'Erro ao processar. Tenta novamente.');
    } finally {
      setActing(false);
    }
  };

  const form = {
    product_id: src.product_id || '',
    block1: { ...EMPTY_FORM.block1, ...(src.block1 || {}) },
    block2: { ...EMPTY_FORM.block2, ...(src.block2 || {}) },
    block3: { ...EMPTY_FORM.block3, ...(src.block3 || {}) },
    block4: { ...EMPTY_FORM.block4, ...(src.block4 || {}) },
    block5: { ...EMPTY_FORM.block5, ...(src.block5 || {}) },
  };

  // Dados sintéticos para o Block1 mostrar marca/produto seleccionados correctamente
  const syntheticBrands   = src.brand_slug ? [{ slug: src.brand_slug, name: src.brand_name || src.brand_slug }] : [];
  const syntheticProducts = src.product_id ? [{ id: src.product_id, name: src.product_name || '', slug: '', segment: '', category: '' }] : [];

  if (!full && briefing.id) return (
    <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--text-muted)', fontSize: 13 }}>
      A carregar briefing...
    </div>
  );

  return (
    <div data-theme="light" style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0, '--bg': '#f8fafc', '--bg-elev': '#ffffff', '--bg-hover': '#f1f5f9', '--bg-sunken': '#e8edf5', '--border': '#dde3ef', '--border-strong': '#bec9de', '--text': '#112954', '--text-muted': '#4a5e7a', '--text-dim': '#6b7fa3', '--ai-500': '#3859D0' }}>

      <BriefingScreenHeader
        isMobile={isMobile}
        title={src.commercial_name || src.block1?.commercial_name || src.product_name || 'Briefing'}
        brandName={src.brand_name || ''}
        brandSlug={src.brand_slug}
        brandColor={src.brand_color}
        status={briefing.status || 'rascunho'}
        activeBlock={activeBlock}
        onBack={onBack}
        onTab={(id) => setActiveBlock(id)}
        menuItems={[
          ...((briefing.status === 'rascunho' || briefing.status === 'rejeitado') ? [{ label: 'Editar', onClick: () => onEdit(src) }] : []),
          ...((briefing.status === 'aprovado' || briefing.status === 'em_campanha') ? [{ label: 'Criar campanha', onClick: () => onCriarCampanha?.(briefing), tone: 'primary', tutorial: 'approved' }] : []),
          ...(Number((full || briefing).n_campanhas) > 0 ? [{ label: 'Ver campanhas', onClick: () => onVerCampanha?.(briefing), tone: 'primary' }] : []),
          { label: 'Enviar PDF por email', onClick: () => setPdfAberto(true) },
          ...(canApprove ? [
            { label: 'Rejeitar', onClick: () => handle('rejected'), disabled: acting, tone: 'danger' },
            { label: 'Pedir alterações', onClick: () => handle('changes_requested'), disabled: acting, tone: 'warning' },
            { label: acting ? 'A processar...' : 'Aprovar', onClick: () => handle('approved'), disabled: acting, tone: 'primary' },
          ] : []),
        ]}
      />
      {pdfAberto && <BriefingPdfModal briefing={src} onClose={() => setPdfAberto(false)} />}

      {/* Conteúdo */}
      <div className="scrollbar" style={{ flex: 1, minHeight: 0, overflowY: 'auto', padding: isMobile ? 12 : '24px 40px 64px', background: 'var(--bg, #f8fafc)' }}>
        <div>
          <div style={{ marginBottom: 16 }}>
            <BriefingFluxoBar briefing={src} approvals={approvals} revisoes={src.revisoes || []} />
          </div>

          {/* Banner rejeitado */}
          {briefing.status === 'rejeitado' && (
            <div style={{ marginBottom: 24, padding: 16, borderRadius: 8, background: 'color-mix(in oklch, var(--danger) 8%, transparent)', border: '1px solid color-mix(in oklch, var(--danger) 30%, transparent)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
              <div>
                <div style={{ fontWeight: 600, color: 'var(--danger)', fontSize: 13, marginBottom: 2 }}>Briefing rejeitado</div>
                <div style={{ fontSize: 12, color: 'var(--text-muted)' }}>Edita o briefing e volta a submeter para aprovação.</div>
              </div>
              <button className="btn btn-sm btn-ai" onClick={() => onEdit(src)}>Editar e corrigir</button>
            </div>
          )}

          {/* Painel de aprovações */}
          {briefing.status === 'submetido' && (
            <ApprovalPanel
              briefing={src}
              approvals={approvals}
              userEmail={userEmail}
              noteValue={notes}
              onNoteChange={v => { setNotes(v); if (v.trim()) setRejectError(''); }}
              noteError={rejectError}
            />
          )}

          {/* Blocos em leitura - pointer-events desligados, visual idêntico ao form */}
          <div style={{ pointerEvents: 'none', userSelect: 'text' }}>
            {activeBlock === 1 && <Block1
              data={form.block1} upd={() => {}}
              isAll={false} allBrands={syntheticBrands}
              selectedBrand={src.brand_slug} onBrandChange={() => {}}
              products={syntheticProducts}
              selectedSegment={''} onSegmentChange={() => {}}
              selectedFamily={''} onFamilyChange={() => {}}
              productId={form.product_id} onProductChange={() => {}}
            />}
            {activeBlock === 2 && <Block2 data={form.block2} upd={() => {}} brand={src.brand_slug} />}
            {activeBlock === 3 && <Block3 data={form.block3} upd={() => {}} brand={src.brand_slug} />}
            {activeBlock === 5 && <Block5 data={form.block5} upd={() => {}} brand={src.brand_slug} />}
          </div>
          {activeBlock === 6 && (
            <Block6
              briefingId={src.id}
              apreciation={src.ai_apreciation}
              userRole={userRole}
              calcProgressValue={calcProgress(form)}
              importOrigin={src.import_origin}
            />
          )}
        </div>
      </div>

      <div style={{ display: 'none' }}> {/* footer removido - stepper faz a navegação */}
      </div>
    </div>
  );
};

const IMPORT_STEPS = [
  { step: 1, label: 'Fontes recebidas' },
  { step: 2, label: 'Extracção de texto' },
  { step: 3, label: 'Envio para Digi AI' },
  { step: 4, label: 'Análise do conteúdo' },
  { step: 5, label: 'Estruturação da informação' },
];

// ─── Completeness helpers ─────────────────────────────────────────────────────

const COMPLETENESS_BLOCKS = [
  { id: 1, label: 'Produto', key: 'block1', fields: [
    { key: 'commercial_name', label: 'Nome comercial' },
    { key: 'elevator_pitch',  label: 'Elevator pitch' },
    { key: 'usps',            label: 'USPs' },
    { key: 'certifications',  label: 'Certificações' },
    { key: 'applications',    label: 'Aplicações' },
    { key: 'geo_markets',     label: 'Mercados geográficos' },
  ]},
  { id: 2, label: 'Cliente', key: 'block2', fields: [
    { key: 'decision_maker',     label: 'Decisor' },
    { key: 'end_user',           label: 'Utilizador final' },
    { key: 'pain_points',        label: 'Pain points' },
    { key: 'motivators',         label: 'Motivadores' },
    { key: 'objections',         label: 'Objecções' },
    { key: 'purchase_trigger',   label: 'Trigger de compra' },
  ]},
  { id: 3, label: 'Mercado', key: 'block3', fields: [
    { key: 'competitors',           label: 'Concorrentes' },
    { key: 'differentiation_args',  label: 'Diferenciação' },
    { key: 'market_trends',         label: 'Tendências de mercado' },
    { key: 'positioning_narrative', label: 'Narrativa posicionamento' },
    { key: 'sensitive_info',        label: 'Info sensível' },
  ]},
];

const isEmpty = v => {
  if (v === null || v === undefined) return true;
  if (Array.isArray(v)) return v.length === 0 || v.every(x => !String(x || '').trim());
  if (typeof v === 'string') return !v.trim();
  return false;
};

const calcCompleteness = (data) =>
  COMPLETENESS_BLOCKS.map(b => {
    const block = data?.[b.key] || {};
    const missing = b.fields.filter(f => isEmpty(block[f.key])).map(f => f.label);
    return { ...b, filled: b.fields.length - missing.length, total: b.fields.length, missing };
  });

// ─── Modal de importação com progresso SSE ────────────────────────────────────

const ImportModal = ({ onDone, onClose }) => {
  const [phase, setPhase]             = React.useState('pick'); // pick | loading | done | error
  const [messages, setMessages]       = React.useState([]);
  const [currentStep, setCurrentStep] = React.useState(0);
  const [errorMsg, setErrorMsg]       = React.useState('');
  const [dragging, setDragging]       = React.useState(false);
  const [doneData, setDoneData]       = React.useState(null);
  const [doneSources, setDoneSources] = React.useState([]);
  const [urlInput, setUrlInput]       = React.useState('');
  const [fileList, setFileList]       = React.useState([]);
  const [urlList, setUrlList]         = React.useState([]);
  const [suggestPhase, setSuggestPhase] = React.useState('idle');
  const [suggestMsgs, setSuggestMsgs]   = React.useState([]);
  const [suggestError, setSuggestError] = React.useState('');
  const [addStep, setAddStep]           = React.useState(false);
  const [newProd, setNewProd]           = React.useState({ brand_slug: '', segment: '', category: '', name: '' });
  const [prodCatalog, setProdCatalog]   = React.useState([]);
  const [brandsList, setBrandsList]     = React.useState([]);
  React.useEffect(() => {
    fetch('/api/marketing/brands').then(r => r.json()).then(rows => setBrandsList((rows || []).filter(b => b.slug && b.slug !== 'todas'))).catch(() => {});
  }, []);
  const [savingProd, setSavingProd]     = React.useState(false);
  const [openingForm, setOpeningForm]   = React.useState(false);
  const [extractProgress, setExtractProgress] = React.useState(0);
  const dropRef      = React.useRef(null);
  const fileRef      = React.useRef(null);
  const aprecPromise = React.useRef(null);
  const doneDataRef  = React.useRef(null);
  React.useEffect(() => { doneDataRef.current = doneData; }, [doneData]);

  const totalSources = fileList.length + urlList.length;
  const loadingPct   = Math.round((currentStep / 5) * 100);

  const resetPick = () => {
    setPhase('pick'); setMessages([]); setCurrentStep(0);
    setErrorMsg(''); setDoneData(null); setDoneSources([]); setAddStep(false);
    aprecPromise.current = null; setOpeningForm(false); setExtractProgress(0);
  };

  const addFiles = (incoming) => {
    const valid = Array.from(incoming).filter(f => f.name.endsWith('.pdf') || f.name.endsWith('.docx'));
    if (!valid.length) { setErrorMsg('Formato não suportado. Usa PDF ou DOCX.'); return; }
    setErrorMsg('');
    setFileList(prev => { const names = new Set(prev.map(f => f.name)); return [...prev, ...valid.filter(f => !names.has(f.name))]; });
  };

  const addUrl = () => {
    const u = urlInput.trim();
    if (!u.startsWith('http')) { setErrorMsg('URL inválido - deve começar por http:// ou https://'); return; }
    if (urlList.includes(u)) { setUrlInput(''); return; }
    setErrorMsg(''); setUrlList(prev => [...prev, u]); setUrlInput('');
  };

  const consumeSSE = async (res) => {
    const reader = res.body.getReader(); const decoder = new TextDecoder(); let buf = '';
    let _lastStepAt = 0;
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      buf += decoder.decode(value, { stream: true });
      const lines = buf.split('\n'); buf = lines.pop();
      for (const line of lines) {
        if (!line.startsWith('data: ')) continue;
        let evt; try { evt = JSON.parse(line.slice(6)); } catch { continue; }
        if (evt.type === 'status') {
          const wait = 500 - (Date.now() - _lastStepAt);
          if (wait > 0 && _lastStepAt > 0) await new Promise(r => setTimeout(r, wait));
          setCurrentStep(evt.step); setMessages(m => [...m, evt.message]);
          _lastStepAt = Date.now();
        }
        else if (evt.type === 'done') {
          const data = limparTravessao(evt.data);
          setCurrentStep(6); setDoneData(data); setDoneSources(evt.sources || []); setPhase('done');
          aprecPromise.current = fetch('/api/marketing/briefings/apreciation-preview', {
            method: 'POST', headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ block1: data.block1 || {}, block2: data.block2 || {}, block3: data.block3 || {}, block4: data.block4 || {}, block5: data.block5 || {}, brand_name: data.brand_name || data.brand_slug || '' }),
          }).then(r => r.ok ? r.json() : null).then(j => j ? limparTravessao(j) : null).catch(() => null);
        } else if (evt.type === 'progress') { setExtractProgress(evt.percent);
        } else if (evt.type === 'error') { setErrorMsg(evt.message); setPhase('error'); }
      }
    }
  };

  const runExtract = async () => {
    if (totalSources === 0) return;
    setPhase('loading'); setMessages([]); setCurrentStep(0);
    try {
      const fd = new FormData();
      fileList.forEach(f => fd.append('files', f));
      fd.append('urls', JSON.stringify(urlList));
      const res = await fetch('/api/marketing/briefings/extract-multi', { method: 'POST', body: fd });
      await consumeSSE(res);
    } catch (e) { setErrorMsg(e.message); setPhase('error'); }
  };

  const handleAddProduct = async () => {
    const prods = await fetch(`/api/marketing/products?brand_id=${doneData?.brand_slug || 'todas'}`).then(r => r.json()).catch(() => []);
    setProdCatalog(prods);
    setNewProd({ brand_slug: doneData?.brand_slug || '', segment: '', category: '', name: doneData?.block1?.commercial_name || '' });
    setAddStep(true);
  };

  const handleSaveProd = async () => {
    if (!newProd.name.trim()) return;
    setSavingProd(true);
    try {
      const r = await fetch('/api/marketing/products', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newProd) });
      const prod = await r.json();
      if (prod.id) { setDoneData(d => ({ ...d, product_id: String(prod.id), product_family: prod.category || '', product_segment: prod.segment || '' })); setAddStep(false); }
    } catch {}
    setSavingProd(false);
  };

  const handleOpenForm = async () => {
    if (openingForm) return;
    setOpeningForm(true);
    const aprec = await Promise.race([aprecPromise.current || Promise.resolve(null), new Promise(r => setTimeout(() => r(null), 8000))]);
    onDone({ ...(doneDataRef.current || doneData), ai_apreciation: aprec || undefined }, doneSources);
  };

  // Auto-suggest removido - utilizador decide se quer gerar B4/B5 no modal ou no formulário

  const runSuggest = async () => {
    setSuggestPhase('loading'); setSuggestMsgs([]); setSuggestError('');
    try {
      const res = await fetch('/api/marketing/briefings/suggest-b4b5', {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ scope: 'restricoes', block1: doneData?.block1 || {}, block2: doneData?.block2 || {}, block3: doneData?.block3 || {}, block4: doneData?.block4 || {}, block5: doneData?.block5 || {}, brand_name: doneData?.brand_slug || '' }),
      });
      const reader = res.body.getReader(); const dec = new TextDecoder(); let buf = '';
      while (true) {
        const { done, value } = await reader.read();
        // Processar value ANTES de verificar done - o último chunk pode conter o evento final
        if (value) {
          buf += dec.decode(value, { stream: !done });
          const lines = buf.split('\n');
          buf = done ? '' : (lines.pop() ?? '');
          for (const line of lines) {
            if (!line.startsWith('data: ')) continue;
            let evt; try { evt = JSON.parse(line.slice(6)); } catch { continue; }
            if (evt.type === 'status') setSuggestMsgs(m => [...m, evt.message]);
            else if (evt.type === 'done') {
              const data = limparTravessao(evt.data || {});
              setDoneData(prev => ({ ...prev, block5: data.block5 || prev.block5, _b5_suggested: true }));
              setSuggestPhase('done');
            }
            else if (evt.type === 'error') { setSuggestError(evt.message); setSuggestPhase('error'); }
          }
        }
        if (done) {
          // Flush buffer residual
          if (buf.startsWith('data: ')) {
            try { const evt = JSON.parse(buf.slice(6)); if (evt.type === 'done') { const data = limparTravessao(evt.data || {}); setDoneData(prev => ({ ...prev, block5: data.block5 || prev.block5, _b5_suggested: true })); setSuggestPhase('done'); } } catch {}
          }
          break;
        }
      }
    } catch (e) { setSuggestError(e.message); setSuggestPhase('error'); }
  };

  // ── SVG icons ──
  const IcUpload = () => <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#3859D0" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>;
  const IcClose  = () => <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>;
  const IcFile   = () => <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#64748b" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>;
  const IcLink   = () => <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#64748b" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71"/></svg>;
  const IcCheck  = (c) => <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke={c||'#10b981'} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>;
  const IcX      = (c) => <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke={c||'#ef4444'} strokeWidth="3" strokeLinecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>;
  const IcWarn   = () => <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#f59e0b" strokeWidth="2.5" strokeLinecap="round"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>;
  const IcLightning = () => <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#3859D0" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>;

  const inpStyle = { width: '100%', boxSizing: 'border-box', padding: '8px 10px', borderRadius: 7, fontSize: 12, border: '1px solid #e2e8f0', color: '#1d2e38', outline: 'none', fontFamily: 'Inter, sans-serif', background: '#fff' };
  const labelStyle = { fontSize: 10, fontWeight: 600, color: '#64748b', letterSpacing: '0.04em', display: 'block', marginBottom: 5 };

  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,.45)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}>
      <style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
      <div style={{ background: '#fff', borderRadius: 14, width: '100%', maxWidth: 560, maxHeight: 'calc(100vh - 48px)', boxShadow: '0 24px 80px rgba(0,0,0,.2)', display: 'flex', flexDirection: 'column' }}>

        {/* Header */}
        <div style={{ padding: '18px 24px 14px', borderBottom: '1px solid #f1f5f9', flexShrink: 0 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <div style={{ width: 30, height: 30, borderRadius: 8, background: 'rgba(56,89,208,.1)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
              <IcUpload />
            </div>
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 14, fontWeight: 700, color: '#1d2e38', fontFamily: 'Montserrat, sans-serif' }}>
                {addStep ? 'Adicionar produto' : 'Importar com IA'}
              </div>
              <div style={{ fontSize: 11, color: '#94a3b8' }}>
                {phase === 'pick'    && 'Ficheiros ou URLs - a Digi AI analisa e preenche o briefing'}
                {phase === 'loading' && 'a extrair e analisar o conteúdo...'}
                {phase === 'done'    && !addStep && 'extracção concluída - revê antes de abrir'}
                {phase === 'done'    && addStep  && 'regista o produto na base de dados'}
                {phase === 'error'   && 'ocorreu um erro na extracção'}
              </div>
            </div>
            {phase === 'loading'
              ? <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0 }}>
                  <span style={{ fontSize: 11, fontWeight: 600, color: '#3859D0', fontFamily: 'monospace' }}>{loadingPct}%</span>
                  <div style={{ width: 18, height: 18, borderRadius: '50%', border: '2px solid #e2e8f0', borderTopColor: '#3859D0', animation: 'spin 0.8s linear infinite' }} />
                </div>
              : <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#94a3b8', padding: 4, lineHeight: 1, display: 'flex' }}>
                  <IcClose />
                </button>
            }
          </div>
        </div>

        {/* Body */}
        <div style={{ padding: '20px 24px', display: 'flex', flexDirection: 'column', gap: 14, overflowY: 'auto', flex: 1, minHeight: 0 }}>

          {/* ── Pick ── */}
          {phase === 'pick' && (
            <>
              <input ref={fileRef} type="file" accept=".pdf,.docx" multiple style={{ display: 'none' }} onChange={e => addFiles(e.target.files)} />
              <div
                ref={dropRef}
                onDragOver={e => { e.preventDefault(); setDragging(true); }}
                onDragLeave={() => setDragging(false)}
                onDrop={e => { e.preventDefault(); setDragging(false); addFiles(e.dataTransfer.files); }}
                onClick={() => fileRef.current?.click()}
                style={{
                  border: `2px dashed ${dragging ? '#3859D0' : '#e2e8f0'}`,
                  borderRadius: 10, padding: '24px', textAlign: 'center',
                  background: dragging ? 'rgba(56,89,208,.04)' : '#fafbfc',
                  transition: 'all 0.15s', cursor: 'pointer',
                  display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8,
                }}
              >
                <div style={{ width: 36, height: 36, borderRadius: 10, background: dragging ? 'rgba(56,89,208,.1)' : '#f1f5f9', display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'all 0.15s' }}>
                  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke={dragging ? '#3859D0' : '#94a3b8'} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                    <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/>
                  </svg>
                </div>
                <div style={{ fontSize: 13, fontWeight: 500, color: '#1d2e38' }}>Arrasta ficheiros aqui ou clica para escolher</div>
                <div style={{ fontSize: 11, color: '#94a3b8' }}>PDF ou DOCX · múltiplos ficheiros · máx. 60 MB cada</div>
              </div>

              <div style={{ display: 'flex', gap: 8 }}>
                <input type="url" placeholder="https://... (página de produto, ficha técnica)" value={urlInput}
                  onChange={e => setUrlInput(e.target.value)} onKeyDown={e => e.key === 'Enter' && addUrl()}
                  style={{ ...inpStyle, flex: 1 }} />
                <button className="btn" onClick={addUrl} disabled={!urlInput.trim().startsWith('http')} style={{ flexShrink: 0, fontSize: 12 }}>+ URL</button>
              </div>

              {totalSources > 0 && (
                <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
                  <div style={{ fontSize: 10, fontWeight: 600, color: '#94a3b8', letterSpacing: '0.06em', textTransform: 'uppercase', marginBottom: 2, fontFamily: 'monospace' }}>
                    {totalSources} fonte{totalSources > 1 ? 's' : ''} para análise
                  </div>
                  {fileList.map((f, i) => (
                    <div key={f.name} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '7px 10px', borderRadius: 7, background: '#f8fafc', border: '1px solid #f1f5f9' }}>
                      <IcFile />
                      <span style={{ flex: 1, fontSize: 12, color: '#1d2e38', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{(f.name || '').normalize('NFC')}</span>
                      <span style={{ fontSize: 10, color: '#94a3b8', fontFamily: 'monospace', flexShrink: 0 }}>{(f.size / 1024 / 1024).toFixed(1)} MB</span>
                      <button onClick={() => setFileList(l => l.filter((_, j) => j !== i))} style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#94a3b8', padding: 2, lineHeight: 1, display: 'flex' }}>
                        <IcClose />
                      </button>
                    </div>
                  ))}
                  {urlList.map((u, i) => (
                    <div key={u} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '7px 10px', borderRadius: 7, background: '#f8fafc', border: '1px solid #f1f5f9' }}>
                      <IcLink />
                      <span style={{ flex: 1, fontSize: 12, color: '#1d2e38', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{u}</span>
                      <button onClick={() => setUrlList(l => l.filter((_, j) => j !== i))} style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#94a3b8', padding: 2, lineHeight: 1, display: 'flex' }}>
                        <IcClose />
                      </button>
                    </div>
                  ))}
                </div>
              )}

              {errorMsg && <div style={{ fontSize: 12, color: '#ef4444', background: 'rgba(239,68,68,.06)', borderRadius: 6, padding: '8px 12px' }}>{errorMsg}</div>}
            </>
          )}

          {/* ── Loading ── */}
          {phase === 'loading' && (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 16, padding: '8px 0' }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                <div style={{ width: 32, height: 32, borderRadius: 8, background: 'rgba(56,89,208,.1)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
                  <IcLightning />
                </div>
                <div>
                  <div style={{ fontSize: 13, fontWeight: 700, color: '#1d2e38', fontFamily: 'Montserrat, sans-serif' }}>Digi AI</div>
                  <div style={{ fontSize: 11, color: '#94a3b8' }}>a extrair e analisar o conteúdo...</div>
                </div>
              </div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                {messages.map((msg, i) => (
                  <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                    <div style={{ width: 18, height: 18, borderRadius: '50%', background: i < messages.length - 1 ? 'rgba(16,185,129,.15)' : 'rgba(56,89,208,.1)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
                      {i < messages.length - 1 ? <IcCheck /> : <div style={{ width: 6, height: 6, borderRadius: '50%', background: '#3859D0' }} />}
                    </div>
                    <span style={{ fontSize: 12, color: i < messages.length - 1 ? '#94a3b8' : '#1d2e38', fontWeight: i < messages.length - 1 ? 400 : 500 }}>
                      {msg}
                      {i === messages.length - 1 && extractProgress > 0 && extractProgress < 100 && (
                        <span style={{ marginLeft: 6, color: '#3859D0', fontWeight: 600, fontFamily: 'monospace' }}>{extractProgress}%</span>
                      )}
                    </span>
                  </div>
                ))}
                {messages.length === 0 && (
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                    <div style={{ width: 18, height: 18, borderRadius: '50%', background: 'rgba(56,89,208,.1)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                      <div style={{ width: 6, height: 6, borderRadius: '50%', background: '#3859D0' }} />
                    </div>
                    <span style={{ fontSize: 12, color: '#1d2e38', fontWeight: 500 }}>A processar as fontes...</span>
                  </div>
                )}
              </div>
            </div>
          )}

          {/* ── Done - completeness ── */}
          {phase === 'done' && doneData && !addStep && (() => {
            const completeness   = calcCompleteness(doneData);
            const productMissing = !doneData.product_id;
            const isEmptyVal     = (v) => !v || (Array.isArray(v) ? v.every(i => !i?.toString().trim()) : !String(v).trim());
            const b4 = doneData?.block4 || {}; const b5 = doneData?.block5 || {};
            const campanhaNaFonte = !isEmptyVal(b4.objective) || !isEmptyVal(b4.key_message) || !isEmptyVal(b4.channels);
            const b5Empty   = isEmptyVal(b5.prohibited_claims) && isEmptyVal(b5.off_brand_messages) && isEmptyVal(b5.competitors_not_name);
            return (
              <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
                {completeness.map(b => {
                  const full  = b.missing.length === 0;
                  const empty = b.filled === 0;
                  const color = full ? '#10b981' : empty ? '#ef4444' : '#f59e0b';
                  const bg    = full ? 'rgba(16,185,129,.06)' : empty ? 'rgba(239,68,68,.06)' : 'rgba(245,158,11,.06)';
                  return (
                    <div key={b.id} style={{ borderRadius: 8, padding: '8px 12px', background: bg, border: `1px solid ${full?'rgba(16,185,129,.2)':empty?'rgba(239,68,68,.2)':'rgba(245,158,11,.2)'}` }}>
                      <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                        <div style={{ width: 16, height: 16, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
                          {full ? <IcCheck /> : empty ? <IcX /> : <IcWarn />}
                        </div>
                        <span style={{ fontSize: 12, fontWeight: 600, color: '#1d2e38' }}>Bloco {b.id} - {b.label}</span>
                        <span style={{ marginLeft: 'auto', fontSize: 11, fontFamily: 'monospace', color }}>{b.filled}/{b.total}</span>
                      </div>
                      {b.missing.length > 0 && (
                        <div style={{ marginTop: 4, marginLeft: 24, fontSize: 11, color: '#64748b', lineHeight: 1.5 }}>Em falta: {b.missing.join(' · ')}</div>
                      )}
                    </div>
                  );
                })}

                {campanhaNaFonte && (
                  <div style={{ borderRadius: 8, padding: '8px 12px', background: 'rgba(16,185,129,.06)', border: '1px solid rgba(16,185,129,.2)', display: 'flex', alignItems: 'center', gap: 8 }}>
                    <div style={{ width: 16, height: 16, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}><IcCheck /></div>
                    <span style={{ fontSize: 12, color: '#1d2e38', lineHeight: 1.45 }}>Campanha encontrada na fonte. Fica guardada para a primeira campanha.</span>
                  </div>
                )}
                <div style={{ borderRadius: 8, padding: '8px 12px', background: doneData?._b5_suggested ? 'rgba(56,89,208,.07)' : b5Empty ? '#f8fafc' : 'rgba(16,185,129,.06)', border: `1px solid ${doneData?._b5_suggested ? 'rgba(56,89,208,.2)' : b5Empty ? '#f1f5f9' : 'rgba(16,185,129,.2)'}` }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                    <div style={{ width: 16, height: 16, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
                      {doneData?._b5_suggested ? <IcLightning /> : b5Empty
                        ? <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#94a3b8" strokeWidth="2.5" strokeLinecap="round"><line x1="5" y1="12" x2="19" y2="12"/></svg>
                        : <IcCheck />}
                    </div>
                    <span style={{ fontSize: 12, fontWeight: 600, color: '#1d2e38' }}>Restrições</span>
                    <span style={{ marginLeft: 'auto', fontSize: 11, color: doneData?._b5_suggested ? '#3859D0' : b5Empty ? '#94a3b8' : '#10b981', fontWeight: doneData?._b5_suggested ? 600 : 400 }}>
                      {doneData?._b5_suggested ? 'Sugerido pela Digi AI' : b5Empty ? 'Não encontrado' : 'Extraído'}
                    </span>
                  </div>
                </div>

                {b5Empty && suggestPhase === 'idle' && (
                  <div style={{ padding: '12px 14px', borderRadius: 8, background: 'rgba(56,89,208,.06)', border: '1px solid rgba(56,89,208,.18)' }}>
                    <div style={{ fontSize: 12, color: '#475569', marginBottom: 10, lineHeight: 1.5 }}>
                      <strong style={{ color: '#3859D0' }}>Restrições sem dados nas fontes.</strong>{' '}
                      Podes gerá-las agora. Revês o resultado na tab Restrições.
                    </div>
                    <button className="btn btn-ai" style={{ fontSize: 12 }} onClick={runSuggest}>Gerar restrições com Digi AI</button>
                  </div>
                )}
                {suggestPhase === 'loading' && (
                  <div style={{ padding: '12px 14px', borderRadius: 8, background: 'color-mix(in srgb, var(--ai-500,#3859D0) 6%, transparent)', border: '1px solid color-mix(in srgb, var(--ai-500,#3859D0) 20%, transparent)' }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
                      <div style={{ width: 14, height: 14, borderRadius: '50%', border: '2px solid color-mix(in srgb, var(--ai-500,#3859D0) 30%, transparent)', borderTopColor: 'var(--ai-500,#3859D0)', flexShrink: 0, animation: 'spin 0.8s linear infinite' }} />
                      <span style={{ fontSize: 12, fontWeight: 600, color: 'var(--ai-500,#3859D0)' }}>Digi AI a gerar as restrições</span>
                    </div>
                    <div style={{ fontSize: 11, color: '#64748b', lineHeight: 1.5, marginBottom: suggestMsgs.length ? 6 : 0 }}>
                      Claims, mensagens fora do posicionamento e concorrentes a não nomear, a partir do produto e do mercado já extraídos.
                    </div>
                    {suggestMsgs.length > 0 && (
                      <div style={{ fontSize: 10.5, color: '#94a3b8', fontFamily: 'var(--font-mono,monospace)', borderTop: '1px solid rgba(56,89,208,.1)', paddingTop: 5, marginTop: 2 }}>
                        {suggestMsgs[suggestMsgs.length - 1]}
                      </div>
                    )}
                  </div>
                )}
                {suggestPhase === 'done' && (
                  <div style={{ padding: '10px 14px', borderRadius: 8, background: 'color-mix(in srgb, var(--ai-500,#3859D0) 6%, transparent)', border: '1px solid color-mix(in srgb, var(--ai-500,#3859D0) 20%, transparent)', display: 'flex', alignItems: 'center', gap: 8 }}>
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--ai-500,#3859D0)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0 }}><polyline points="20 6 9 17 4 12"/></svg>
                    <span style={{ fontSize: 12, color: 'var(--ai-500,#3859D0)', fontWeight: 500 }}>Restrições geradas. Revê na tab Restrições antes de submeter.</span>
                  </div>
                )}
                {suggestPhase === 'error' && (
                  <div style={{ padding: '10px 14px', borderRadius: 8, background: 'rgba(239,68,68,.06)', border: '1px solid rgba(239,68,68,.2)', display: 'flex', alignItems: 'center', gap: 8 }}>
                    <span style={{ fontSize: 12, color: '#ef4444', flex: 1 }}>{suggestError}</span>
                    <button className="btn" style={{ fontSize: 11 }} onClick={() => setSuggestPhase('idle')}>Tentar novamente</button>
                  </div>
                )}

                {productMissing && (
                  <div style={{ padding: '10px 12px', borderRadius: 8, background: 'rgba(245,158,11,.06)', border: '1px solid rgba(245,158,11,.2)', display: 'flex', gap: 10, alignItems: 'flex-start' }}>
                    <div style={{ flexShrink: 0, marginTop: 1 }}><IcWarn /></div>
                    <div>
                      <div style={{ fontSize: 12, fontWeight: 600, color: '#1d2e38', marginBottom: 3 }}>
                        "{doneData.block1?.commercial_name || 'Produto não detectado'}" não está na base de dados
                      </div>
                      <div style={{ fontSize: 11, color: '#64748b', lineHeight: 1.55 }}>
                        Pode ser um produto novo (ainda não registado) <strong>ou estar descontinuado</strong>. Verifica na tabela de preços antes de adicionar - só adiciona se confirmar que está activo.
                      </div>
                    </div>
                  </div>
                )}
              </div>
            );
          })()}

          {/* ── Done - adicionar produto ── */}
          {phase === 'done' && doneData && addStep && (() => {
            const segments    = [...new Set(prodCatalog.map(p => p.segment).filter(Boolean))];
            const hasSegments = segments.length > 0;
            const categories  = [...new Set(prodCatalog.filter(p => !hasSegments || !newProd.segment || p.segment === newProd.segment).map(p => p.category).filter(Boolean))];
            return (
              <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
                <div><label style={labelStyle}>MARCA</label>
                  <select value={newProd.brand_slug} onChange={e => setNewProd(p => ({ ...p, brand_slug: e.target.value }))} style={inpStyle}>
                    <option value="">Escolher marca...</option>
                    {brandsList.map(b => <option key={b.slug} value={b.slug}>{b.name}</option>)}
                  </select>
                </div>
                {hasSegments && (
                  <div><label style={labelStyle}>SEGMENTO</label>
                    <select value={newProd.segment} onChange={e => setNewProd(p => ({ ...p, segment: e.target.value, category: '' }))} style={inpStyle}>
                      <option value="">Escolher segmento...</option>
                      {segments.map(s => <option key={s} value={s}>{s}</option>)}
                    </select>
                  </div>
                )}
                <div><label style={labelStyle}>CATEGORIA</label>
                  <select value={newProd.category} onChange={e => setNewProd(p => ({ ...p, category: e.target.value }))} style={inpStyle}>
                    <option value="">{hasSegments && !newProd.segment ? 'Escolhe o segmento primeiro' : 'Escolher categoria...'}</option>
                    {categories.map(c => <option key={c} value={c}>{c}</option>)}
                  </select>
                </div>
                <div><label style={{ ...labelStyle, color: '#3859D0' }}>NOME DO PRODUTO</label>
                  <input value={newProd.name} onChange={e => setNewProd(p => ({ ...p, name: e.target.value }))} placeholder="Nome do produto" style={inpStyle} />
                </div>
              </div>
            );
          })()}

          {/* ── Error ── */}
          {phase === 'error' && (
            <div style={{ fontSize: 12, color: '#ef4444', background: 'rgba(239,68,68,.06)', borderRadius: 8, padding: '10px 14px' }}>{errorMsg}</div>
          )}
        </div>

        {/* Footer */}
        {phase !== 'loading' && (
          <div style={{ padding: '14px 24px', borderTop: '1px solid #f1f5f9', display: 'flex', gap: 8, justifyContent: 'space-between', alignItems: 'center', flexShrink: 0 }}>
            {phase === 'pick' && (
              <>
                <button onClick={onClose} className="btn" style={{ fontSize: 12 }}>Cancelar</button>
                <button onClick={runExtract} disabled={totalSources === 0} className="btn btn-ai" style={{ fontSize: 12 }}>
                  Analisar {totalSources > 0 ? `${totalSources} fonte${totalSources > 1 ? 's' : ''}` : ''} →
                </button>
              </>
            )}
            {phase === 'done' && !addStep && (
              <>
                <button onClick={resetPick} className="btn" style={{ fontSize: 12 }}>Extrair novamente</button>
                <div style={{ display: 'flex', gap: 8 }}>
                  {!doneData?.product_id && (
                    <button onClick={handleAddProduct} className="btn" style={{ fontSize: 12 }} title="Só adiciona se confirmares que o produto está activo na tabela de preços">+ Produto (verificar primeiro)</button>
                  )}
                  <button onClick={handleOpenForm} disabled={openingForm} className="btn btn-ai" style={{ fontSize: 12 }}>
                    {openingForm ? 'A preparar...' : 'Abrir formulário →'}
                  </button>
                </div>
              </>
            )}
            {phase === 'done' && addStep && (
              <>
                <button onClick={() => setAddStep(false)} className="btn" style={{ fontSize: 12 }}>← Voltar</button>
                <button onClick={handleSaveProd} disabled={savingProd || !newProd.name.trim() || !newProd.brand_slug} className="btn btn-ai" style={{ fontSize: 12 }}>
                  {savingProd ? 'A guardar...' : 'Adicionar produto'}
                </button>
              </>
            )}
            {phase === 'error' && (
              <>
                <button onClick={onClose} className="btn" style={{ fontSize: 12 }}>Fechar</button>
                <button onClick={resetPick} className="btn btn-ai" style={{ fontSize: 12 }}>Tentar novamente</button>
              </>
            )}
          </div>
        )}
      </div>
    </div>
  );
};

// ─── Dev user helper ──────────────────────────────────────────────────────────

const getMktUser = (userTipo, userEmail) => {
  const p = new URLSearchParams(window.location.search);
  const dev = p.get('dev'), mkt = p.get('mkt_role');
  if (mkt === 'strategist') return { nome: 'Carlos Alves',  role: 'strategist', email: null };
  if (dev === 'rui')        return { nome: 'Rui Leitão',    role: 'board',      email: 'rui.leitao@digidelta.pt'   };
  if (dev === 'armando')    return { nome: 'Armando Mota',  role: 'ceo',        email: 'armando.mota@digidelta.pt' };
  if (dev === 'bruno')      return { nome: 'Bruno Rosa',    role: 'ceo',        email: 'bruno.rosa@digidelta.pt'   };
  if (dev === 'costa')      return { nome: 'Fábio Costa',   role: 'admin',      email: 'fabio.costa@digidelta.pt'  };
  const nome  = window.currentUser?.nome_apresentar || window.currentUser?.nome || null;
  const tipo  = userTipo || window.currentUser?.tipo || '';
  const sessao = window.currentUser?.email || null;
  const email = (!dev && !mkt && sessao) ? sessao : (userEmail || sessao || null);
  const aprovador = tipo === 'chairman' || tipo === 'c_suite' || tipo === 'director';
  const role = tipo === 'admin' ? 'admin' : (tipo === 'marketing' || !aprovador) ? 'strategist' : 'board';
  return { nome, role, email };
};

// ─── Orquestrador principal ────────────────────────────────────────────────────

const MktBriefingsScreen = ({ userName, userTipo, userEmail, onNavToSub }) => {
  const { brand: brandId } = window.useMktBrand ? window.useMktBrand() : { brand: 'todas' };
  const [view, setView] = React.useState(() => {
    try {
      if (window.__mktInicioNovoBriefing || localStorage.getItem('mkt-inicio-novo-briefing')) return 'form';
    } catch {}
    return 'list';
  }); // list | form | detail
  const [selected, setSelected] = React.useState(null);
  const [showImport, setShowImport] = React.useState(false);
  const [abrirAviso, setAbrirAviso] = React.useState(null);
  const pedidoBriefing = React.useRef(null);
  const mktUser = React.useMemo(() => getMktUser(userTipo, userEmail), [userTipo, userEmail]);

  const currentUserName = mktUser.nome || userName || null;
  const userRole = mktUser.role;

  const goList   = () => {
    let origem = null;
    try { origem = JSON.parse(sessionStorage.getItem('mkt-briefing-origem') || 'null'); } catch {}
    if (origem?.sub === 'conteudos' && origem.campanhaId) {
      try { sessionStorage.removeItem('mkt-briefing-origem'); } catch {}
      window.__mktAbrirPedido = null;
      try { localStorage.setItem('mkt-open-conteudo-id', String(origem.campanhaId)); } catch {}
      if (origem.tab) try { localStorage.setItem('mkt-inicio-conteudo-tab', String(origem.tab)); } catch {}
      if (origem.canal) try { localStorage.setItem('mkt-open-conteudo-canal', String(origem.canal)); } catch {}
      onNavToSub?.('conteudos');
      try {
        const prev = new URLSearchParams(String(window.location.hash || '').replace(/^#/, ''));
        const next = new URLSearchParams();
        next.set('screen', 'marketing');
        next.set('sub', 'conteudos');
        next.set('camp', String(origem.campanhaId));
        if (origem.tab) next.set('tab', String(origem.tab));
        if (origem.canal) next.set('canal', String(origem.canal));
        const marca = prev.get('marca');
        if (marca) next.set('marca', marca);
        window.location.hash = next.toString();
      } catch {}
      window.setTimeout(() => window.mktProducaoOpenCampanha?.(origem.campanhaId, origem), 0);
      return;
    }
    pedidoBriefing.current = null;
    setSelected(null);
    setView('list');
    try {
      const prev = new URLSearchParams(String(window.location.hash || '').replace(/^#/, ''));
      if (prev.get('sub') === 'briefings' && prev.get('id')) {
        prev.delete('id');
        const href = prev.toString();
        if (String(window.location.hash || '').replace(/^#/, '') !== href) history.replaceState(null, '', '#' + href);
      }
    } catch {}
  };
  const goNew    = (prefill) => {
    const data = prefill?._prefill ? { ...prefill._prefill, brand_slug: prefill._brand || undefined } : null;
    if (data && prefill?._origem) data._import_origin = prefill._origem;
    console.log('[goNew] prefill:', prefill, '→ data:', data, 'block1:', data?.block1);
    setSelected(data);
    setView('form');
  };
  const goEdit   = (b) => {
    try { sessionStorage.removeItem('mkt-briefing-origem'); } catch {}
    pedidoBriefing.current = null;
    if (b) setSelected(b);
    setView('form');
  };
  const goDetail = (b) => { setSelected(b);   setView('detail'); };

  React.useEffect(() => {
    let vivo = true;
    const abrir = (raw, seq) => {
      const id = String(raw || '').trim();
      const chave = id + ':' + String(seq || '');
      if (!id || !window.MktBriefingsAPI?.get || pedidoBriefing.current === chave) return;
      pedidoBriefing.current = chave;
      window.__mktBriefingLinkHandled = id;
      try { localStorage.removeItem('mkt-open-briefing-id'); } catch {}
      const cache = window.__mktBriefingCache && window.__mktBriefingCache[id];
      if (cache) {
        setAbrirAviso(null);
        setSelected(cache);
        setView('form');
      }
      window.MktBriefingsAPI.get(id).then(b => {
        if (!vivo || pedidoBriefing.current !== chave) return;
        if (!b) {
          pedidoBriefing.current = null;
          setAbrirAviso('Não foi possível abrir este briefing.');
          setSelected(null);
          setView('list');
          return;
        }
        window.__mktBriefingCache = Object.assign({}, window.__mktBriefingCache, { [id]: b });
        setAbrirAviso(null);
        setSelected(b);
        setView('form');
      }).catch(() => {
        if (pedidoBriefing.current === chave) pedidoBriefing.current = null;
      });
    };
    const ler = (ev) => {
      let id = null;
      let seq = '';
      try {
        const q = new URLSearchParams(String(window.location.hash || '').replace(/^#/, ''));
        if (q.get('sub') === 'briefings') {
          id = q.get('id');
          seq = q.get('abrir') || '';
        }
      } catch {}
      const pedido = ev?.detail?.id ? ev.detail : window.__mktAbrirPedido;
      if (pedido?.id) {
        id = String(pedido.id);
        if (pedido.seq) seq = String(pedido.seq);
      }
      if (!id) {
        try { id = localStorage.getItem('mkt-open-briefing-id'); } catch {}
      }
      if (id) {
        window.__mktInicioNovoBriefing = false;
        try { localStorage.removeItem('mkt-inicio-novo-briefing'); } catch {}
        abrir(id, seq);
        return;
      }
      if (window.__mktInicioNovoBriefing || localStorage.getItem('mkt-inicio-novo-briefing')) {
        setView('form');
        window.__mktInicioNovoBriefing = false;
        try { localStorage.removeItem('mkt-inicio-novo-briefing'); } catch {}
      }
    };
    ler();
    window.addEventListener('hashchange', ler);
    window.addEventListener('mkt-open-briefing', ler);
    return () => {
      vivo = false;
      window.removeEventListener('hashchange', ler);
      window.removeEventListener('mkt-open-briefing', ler);
    };
  }, []);

  const handleGenerateStrategy = async (b) => {
    if (b?.id) {
      const result = await window.MktBriefingsAPI.ensureStrategy(b.id);
      if (result?.id) {
        try { localStorage.setItem('mkt-pending-proposal', String(result.id)); } catch {}
      }
    }
    onNavToSub?.('conteudos');
  };

  const handleCriarCampanha = async (b) => {
    const n = Number(b.n_campanhas) || 0;
    let titulo = b.commercial_name || b.product_name || `Campanha #${b.id}`;
    if (n > 0) {
      const pedido = window.prompt('Nome desta campanha');
      if (!pedido || !pedido.trim()) return;
      titulo = pedido.trim();
    }
    try {
      const res = await fetch('/api/marketing/campanhas', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ briefing_id: b.id, titulo, created_by: mktUser.email }),
      });
      if (!res.ok) {
        const err = await res.json().catch(() => ({}));
        alert(`Erro ao criar campanha: ${err.error || res.status}`);
        return;
      }
      onNavToSub?.('campanhas');
    } catch (e) {
      alert(`Erro de rede: ${e.message}`);
    }
  };

  const handleVerCampanha = async (b) => {
    try {
      const list = await fetch('/api/marketing/campanhas').then(r => r.json());
      const camps = Array.isArray(list)
        ? list.filter(c => String(c.briefing_id) === String(b.id) && !['arquivada', 'cancelada'].includes(c.status || ''))
        : [];
      if (camps.length === 1 && camps[0]?.id) {
        try {
          localStorage.setItem('mkt-open-campanha-id', String(camps[0].id));
          localStorage.setItem('mkt-open-campanha-tab', 'briefing');
        } catch {}
        onNavToSub?.('campanhas');
        return;
      }
      try { localStorage.setItem('mkt-campanhas-briefing', String(b.id)); } catch {}
    } catch {}
    onNavToSub?.('campanhas');
  };

  const handleDuplicar = async (b) => {
    try {
      const res = await fetch(`/api/marketing/briefings/${b.id}`, { headers: { 'Content-Type': 'application/json' } });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      const full = await res.json();
      if (!full?.block1) throw new Error('Briefing incompleto');
      // Remove campos de sistema e define como novo rascunho
      const { id, version_id, status, created_at, updated_at, current_version_id, approvals, version, ai_apreciation, ...rest } = full;
      // Remove IDs dos blocos para evitar conflitos
      const cleanBlock = (b) => { if (!b) return null; const { id: _id, version_id: _vid, created_at: _ca, ...fields } = b; return fields; };
      setSelected({
        ...rest,
        block1: (() => { const x = cleanBlock(rest.block1) || {}; delete x.geo_markets; return x; })(),
        block2: cleanBlock(rest.block2),
        block3: cleanBlock(rest.block3),
        block4: { ...EMPTY_FORM.block4 },
        block5: cleanBlock(rest.block5),
        status: 'rascunho',  // força rascunho - sem ambiguidade
        // ai_apreciation herdada do original - já validada, re-corre só se alterar conteúdo
        _duplicate_of: b.id,
      });
      setView('form');
    } catch (e) {
      console.warn('[duplicar] erro:', e.message);
      alert(`Não foi possível duplicar o briefing: ${e.message}`);
    }
  };

  const handleImportDone = (data, sources) => {
    setShowImport(false);
    setSelected({ ...data, _import_origin: sources?.length ? { sources } : null });
    setView('form');
  };

  const marcarPropostaResultados = (importOrigin, briefingId) => {
    if (importOrigin?.origem !== 'resultados-analise' || !importOrigin.proposta_id || !briefingId) return;
    fetch(`/api/marketing/resultados/v2/analise/campanhas/${encodeURIComponent(importOrigin.proposta_id)}`, {
      method: 'PATCH', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ estado: 'briefing_criado', briefing_id: briefingId }),
    }).catch(e => console.warn('[resultados-analise] estado da proposta:', e.message));
  };

  const handleSave = async (formData, resolvedBrandId, apreciation) => {
    const bid = resolvedBrandId || brandId;
    const aprecPayload = apreciation ? { ai_apreciation: apreciation } : {};
    // Duplicados e novos briefings → sempre criar (nunca actualizar o original)
    const isExistingRecord = selected?.id && !selected?._duplicate_of;
    const payload = briefingParaGravar(formData);
    if (isExistingRecord) {
      const result = await window.MktBriefingsAPI.update(selected.id, { ...payload, ...aprecPayload });
      // Actualizar selected com o novo version_id para que edições seguintes façam PUT (não POST)
      if (result?.version_id) setSelected(s => ({
        ...s,
        ...result,
        commercial_name: payload.block1?.commercial_name ?? s?.commercial_name,
      }));
    } else {
      const importOrigin = selected?._import_origin || null;
      const created = await window.MktBriefingsAPI.create({ brand_id: bid, created_by_name: currentUserName, created_by_email: mktUser.email || null, import_origin: importOrigin, ...briefingParaGravar(formData, { incluirCampanha: true }), ...aprecPayload });
      marcarPropostaResultados(importOrigin, created?.id);
      // Após criar: id real + LIMPAR _duplicate_of (já existe em BD como registo próprio - próximos saves devem fazer PUT, não novo POST)
      if (created?.id) setSelected(s => {
        const next = { ...(s || {}), id: created.id, status: 'rascunho' };
        delete next._duplicate_of;
        return next;
      });
    }
    // Não fechar - mostrar confirmação inline no form
  };

  const handleSubmit = async (formData, resolvedBrandId, apreciation, submitterNotes) => {
    // Duplicados → sempre criar novo registo antes de submeter
    const isDuplicate = !!selected?._duplicate_of;
    let id = (!isDuplicate && selected?.id) ? selected.id : null;
    if (!id) {
      const bid = resolvedBrandId || brandId;
      const importOrigin = selected?._import_origin || null;
      const aprecPayload = apreciation ? { ai_apreciation: apreciation } : {};
      const created = await window.MktBriefingsAPI.create({ brand_id: bid, created_by_name: currentUserName, created_by_email: mktUser.email || null, import_origin: importOrigin, ...briefingParaGravar(formData, { incluirCampanha: true }), ...aprecPayload });
      marcarPropostaResultados(importOrigin, created?.id);
      id = created?.id;
      if (created?.id) setSelected(s => ({ ...(s || {}), id: created.id, _duplicate_of: undefined }));
    } else if (apreciation) {
      await window.MktBriefingsAPI.update(selected.id, { ...formData, ai_apreciation: apreciation });
    }
    if (id) await window.MktBriefingsAPI.submit(id, { submitter_email: mktUser?.email || null, submitter_notes: submitterNotes || null });
    goList();
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0 }}>
      {showImport && (
        <ImportModal
          onDone={handleImportDone}
          onClose={() => setShowImport(false)}
        />
      )}
      {abrirAviso && view === 'list' && (
        <div style={{ padding: '10px 24px', background: '#FEF2F2', color: '#991B1B', fontSize: 13, flexShrink: 0 }}>{abrirAviso}</div>
      )}
      {view === 'list' && (
        <BriefingsList
          brandId={brandId}
          onSelect={b => goEdit(b)}
          onNew={goNew}
          onEdit={goEdit}
          onImport={() => setShowImport(true)}
          onGenerateStrategy={handleGenerateStrategy}
          onCriarCampanha={handleCriarCampanha}
          onVerCampanha={handleVerCampanha}
          onDuplicar={handleDuplicar}
          userRole={userRole}
          userName={currentUserName}
          userEmail={mktUser.email}
        />
      )}
      {view === 'form' && (
        <BriefingForm
          briefing={selected}
          brandId={brandId}
          onSave={handleSave}
          onSubmit={handleSubmit}
          onCancel={goList}
          onApproved={goList}
          onRejected={goList}
          onGenerateStrategy={handleGenerateStrategy}
          onCriarCampanha={handleCriarCampanha}
          onVerCampanha={handleVerCampanha}
          userRole={userRole}
          userName={currentUserName}
          userEmail={mktUser.email}
          onCreated={(created, snapshot) => {
            if (!created?.id) return;
            setSelected(s => {
              const next = { ...(s || {}), ...(snapshot || {}), id: created.id, status: created.status || 'rascunho' };
              delete next._duplicate_of;
              return next;
            });
          }}
        />
      )}
      {view === 'detail' && (
        <BriefingDetail
          briefing={selected}
          onEdit={goEdit}
          onBack={goList}
          onApproved={goList}
          onRejected={goList}
          onGenerateStrategy={handleGenerateStrategy}
          onCriarCampanha={handleCriarCampanha}
          onVerCampanha={handleVerCampanha}
          userRole={userRole}
          userName={currentUserName}
          userEmail={mktUser.email}
        />
      )}
    </div>
  );
};

window.MktBriefingsScreen = MktBriefingsScreen;
window.BriefingDateRangePicker = BriefingDateRangePicker;
window.GeoMarketField = GeoMarketField;
window.Block4 = Block4;
