// ═══ PORT ACTIVITIES · Relatório diário de atividades do porto ═════════════
// Portado do IMT "Port Activities Module". O parser do Excel da empresa e os
// exports são port 1:1 — NÃO alterar sem reler a referência.
// Estado corrente no servidor: GET/PUT /api/portact/current (PUT com debounce
// de 400ms na digitação, imediato nas operações estruturais).

const PA_STATUSES = ['TO DO','DOING','DONE','CANCELADO'];
const PA_BADGE = { 'TO DO':{bg:'#eef1f6',fg:'#7a8697',bar:'#C3CDD8'},
                   'DOING':{bg:'#fdf1e0',fg:'#a8681a',bar:'#E8B87A'},
                   'DONE':{bg:'#e6f5ec',fg:'#1e7a45',bar:'#86C79A'},
                   'CANCELADO':{bg:'#fbe9e9',fg:'#b5433f',bar:'#D48A8A'} };
const PA_STATUS_MAP = {'não iniciado':'TO DO','nao iniciado':'TO DO','a iniciar':'TO DO','pendente':'TO DO',
  'em progresso':'DOING','em andamento':'DOING','iniciado':'DOING',
  'completo':'DONE','concluído':'DONE','concluido':'DONE','finalizado':'DONE','cancelado':'CANCELADO'};
const PA_META_DEFAULT = { reportNo:'', dateLabel:'', port:'AÇU PORT - RJ', vessel:'Genesis I' };

// escapa aspas também — vai para value="" de inputs e para o markup do PDF
const _paEsc = (s) => String(s == null ? '' : s).replace(/[&<>"]/g, (m) => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[m]));

// ── SheetJS vendored (mesmo padrão do helideck/npt — NUNCA CDN) ──
const _paLoadScript = (src) => new Promise((resolve, reject) => {
  const s = document.createElement('script');
  s.src = src; s.onload = () => resolve(true); s.onerror = () => reject(new Error('load fail: ' + src));
  document.head.appendChild(s);
});
const _paEnsureXLSX = async () => {
  if (window.XLSX) return;
  await _paLoadScript('vendor/xlsx.full.min.js'); // vendored local — sem CDN (a internet de bordo derruba CDN)
};
// logo OSM Thome (base64) usado no cabeçalho do PDF — carregado sob demanda
const _paEnsureAssets = async () => {
  if (window.PA_ASSETS) return;
  await _paLoadScript('tidal/portact-assets.js?v=1');
};

const paUid = () => 'a' + Math.random().toString(36).slice(2, 9);

/* ---------- Excel parsing (port 1:1 do IMT) ---------- */
const paNormHdr = (h) => String(h == null ? '' : h).trim().toLowerCase();
const paFindCol = (map, names) => { for (let i = 0; i < names.length; i++) { if (Object.prototype.hasOwnProperty.call(map, names[i])) return map[names[i]]; } return -1; };
const paToDate = (v) => { if (v == null || v === '') return null; if (typeof v === 'number') return new Date(Math.round((v - 25569) * 86400 * 1000)); if (v instanceof Date) return v; const d = new Date(v); return isNaN(d.getTime()) ? null : d; };
const paPad = (n) => String(n).padStart(2, '0');
const paFmtDT = (d) => { if (!d) return ''; return paPad(d.getUTCDate()) + '/' + paPad(d.getUTCMonth() + 1) + ', ' + paPad(d.getUTCHours()) + ':' + paPad(d.getUTCMinutes()); };
const paFmtDur = (h) => { if (h == null || isNaN(h) || h <= 0) return ''; let m = Math.round(h * 60); const days = Math.floor(m / 1440); m -= days * 1440; const hrs = Math.floor(m / 60), min = m - hrs * 60; const p = []; if (days > 0) p.push(days + 'd'); if (hrs > 0) p.push(hrs + 'h'); if (days === 0 && min > 0) p.push(min + 'min'); return p.join(' ') || '0h'; };
const paDurHours = (cell, start, end) => { if (start && end) { const h = (end - start) / 3600000; if (h > 0) return h; } if (typeof cell === 'number') return cell * 24; const d = paToDate(cell); if (d) { const ep = Date.UTC(1899, 11, 30, 0, 0, 0); const h2 = (d.getTime() - ep) / 3600000; if (h2 > 0 && h2 < 24 * 400) return h2; } return null; };
const paMapStatus = (s, prog) => { const k = paNormHdr(s); if (PA_STATUS_MAP[k]) return PA_STATUS_MAP[k]; if (prog >= 100) return 'DONE'; if (prog > 0) return 'DOING'; return 'TO DO'; };

const paParseWorkbook = (wb) => {
  const out = { activities: [], sections: [], meta: {}, warnings: [] };
  const names = wb.SheetNames || [];
  let pick = null, hdrIdx = -1, colmap = null, rows = null;
  for (let s = 0; s < names.length; s++) {
    const rr = XLSX.utils.sheet_to_json(wb.Sheets[names[s]], { header: 1, raw: true, defval: null, blankrows: false });
    for (let r = 0; r < Math.min(rr.length, 15); r++) {
      const line = rr[r] || [], m = {};
      for (let c = 0; c < line.length; c++) m[paNormHdr(line[c])] = c;
      if (paFindCol(m, ['atividade']) >= 0 && paFindCol(m, ['empresa']) >= 0) { pick = names[s]; hdrIdx = r; colmap = m; rows = rr; break; }
    }
    if (pick) break;
  }
  if (!pick) { out.warnings.push('Nenhum cabeçalho com Empresa/Atividade encontrado.'); return out; }
  for (let r0 = 0; r0 < hdrIdx; r0++) {
    const l = rows[r0] || [];
    for (let c0 = 0; c0 < l.length; c0++) {
      const t = String(l[c0] == null ? '' : l[c0]);
      if (/Embarca/i.test(t)) out.meta.vessel = (t.replace(/.*:\s*/, '').trim()) || 'Genesis I';
      if (/Início/i.test(t) && l[c0 + 1] != null) { const d = paToDate(l[c0 + 1]); if (d) out.meta.pocStart = paFmtDT(d); }
      if (/Término/i.test(t) && l[c0 + 1] != null) { const d2 = paToDate(l[c0 + 1]); if (d2) out.meta.pocEnd = paFmtDT(d2); }
    }
  }
  const cComp = paFindCol(colmap, ['empresa']), cAct = paFindCol(colmap, ['atividade']),
    cIni = paFindCol(colmap, ['data início','data inicio','início','inicio']),
    cFim = paFindCol(colmap, ['data final','data fim','término','termino','fim']),
    cDur = paFindCol(colmap, ['duração','duracao']), cPro = paFindCol(colmap, ['progresso','progress']), cSt = paFindCol(colmap, ['status']);
  let topBand = 'B — PORT';
  for (let rb = 0; rb < hdrIdx; rb++) { const lb = rows[rb] || []; for (let cb = 0; cb < lb.length; cb++) { if (/^B\s*[-–—]\s*Port$/i.test(String(lb[cb] == null ? '' : lb[cb]).trim())) topBand = 'B — PORT'; } }
  let cur = topBand;
  const seen = [];
  for (let r = hdrIdx + 1; r < rows.length; r++) {
    const row = rows[r] || [];
    let company = cComp >= 0 ? row[cComp] : null, activity = cAct >= 0 ? row[cAct] : null;
    const start = cIni >= 0 ? paToDate(row[cIni]) : null, end = cFim >= 0 ? paToDate(row[cFim]) : null;
    if (company != null) company = String(company).replace(/\s+$/, '').trim();
    if (activity != null) activity = String(activity).trim();
    if ((!company || company === '') && (!activity || activity === '') && !start && !end) continue;
    if (activity && !company && !start && !end) { cur = activity; if (seen.indexOf(cur) === -1) seen.push(cur); continue; }
    if (!activity && !company) continue;
    const progRaw = cPro >= 0 ? row[cPro] : null;
    let progress = (progRaw == null || progRaw === '') ? 0 : Math.round((+progRaw <= 1 ? +progRaw * 100 : +progRaw));
    if (isNaN(progress)) progress = 0; progress = Math.max(0, Math.min(100, progress));
    const durH = paDurHours(cDur >= 0 ? row[cDur] : null, start, end);
    const status = paMapStatus(cSt >= 0 ? row[cSt] : null, progress);
    if (seen.indexOf(cur) === -1) seen.push(cur);
    out.activities.push({ id: paUid(),
      section: cur, company: company || '', activity: activity || '', start: start ? paFmtDT(start) : '', end: end ? paFmtDT(end) : '',
      duration: paFmtDur(durH), progress: progress, status: status });
  }
  out.sections = seen;
  return out;
};

/* ---------- Atividades ordenadas por seção (compartilhado pelos exports) ---------- */
const paOrdered = (st) => {
  const ordered = [], secs = st.sections.slice();
  st.activities.forEach((a) => { if (secs.indexOf(a.section) === -1) secs.push(a.section); });
  secs.forEach((sec) => { st.activities.forEach((a) => { if (a.section === sec) ordered.push(a); }); });
  return ordered;
};

/* ---------- EXPORT: Excel (Resumo + Atividades) — port 1:1 ---------- */
const _paExportExcel = async (st, toast, lang) => {
  try { await _paEnsureXLSX(); }
  catch (e) { toast.push({ icon: 'x', title: (lang === 'pt' ? 'SheetJS não carregou' : 'SheetJS failed to load') }); return; }
  const meta = st.meta || {}, ord = paOrdered(st);
  if (!ord.length) { toast.push({ icon: 'x', title: (lang === 'pt' ? 'Nenhuma atividade para exportar' : 'No activities to export') }); return; }
  // Sheet 1: activities
  const rows = [['Seção','Atividade','Empresa / Resp.','Início','Término','Duração','Progresso (%)','Status']];
  ord.forEach((a) => { rows.push([a.section || '', a.activity || '', a.company || '', a.start || '', a.end || '', a.duration || '', (a.progress != null ? +a.progress : 0), a.status || '']); });
  const ws1 = XLSX.utils.aoa_to_sheet(rows);
  ws1['!cols'] = [{wch:16},{wch:46},{wch:22},{wch:14},{wch:14},{wch:10},{wch:12},{wch:12}];
  // Sheet 2: summary by status + by section
  const dist = {'TO DO':0,'DOING':0,'DONE':0,'CANCELADO':0};
  ord.forEach((a) => { dist[a.status] = (dist[a.status] || 0) + 1; });
  const sum = [['Relatório Diário de Atividades — MPSV Genesis I'],
    ['Nº do relatório', meta.reportNo || ''], ['Data', meta.dateLabel || ''], ['Porto', meta.port || ''], ['Embarcação', meta.vessel || 'Genesis I'], [],
    ['Total de atividades', ord.length],
    ['DONE (concluídas)', dist['DONE']],
    ['DOING (em andamento)', dist['DOING']],
    ['TO DO (a iniciar)', dist['TO DO']],
    ['CANCELADO', dist['CANCELADO']], [],
    ['Atividades por seção']];
  const bySec = {}; ord.forEach((a) => { bySec[a.section] = (bySec[a.section] || 0) + 1; });
  st.sections.forEach((s) => { if (bySec[s] != null) sum.push([s, bySec[s]]); });
  Object.keys(bySec).forEach((s) => { if (st.sections.indexOf(s) === -1) sum.push([s, bySec[s]]); });
  const ws2 = XLSX.utils.aoa_to_sheet(sum);
  ws2['!cols'] = [{wch:30},{wch:20}];
  const wb = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(wb, ws2, 'Resumo');
  XLSX.utils.book_append_sheet(wb, ws1, 'Atividades');
  const stamp = (meta.reportNo ? ('_' + String(meta.reportNo).replace(/[^\w-]/g, '')) : '');
  XLSX.writeFile(wb, 'Port_Activities_Genesis' + stamp + '.xlsx');
  toast.push({ icon: 'check', title: (lang === 'pt' ? 'Excel do Port Activities exportado' : 'Port Activities Excel exported') });
};

/* ---------- EXPORT: PDF (A4 landscape, print) — markup portado 1:1 ---------- */
const _paExportPDF = async (st, toast, lang) => {
  const meta = st.meta || {}, ord = paOrdered(st);
  if (!ord.length) { toast.push({ icon: 'x', title: (lang === 'pt' ? 'Nenhuma atividade para exportar' : 'No activities to export') }); return; }
  try { await _paEnsureAssets(); } catch (e) { /* sem logo, segue sem a imagem */ }
  const logo = (window.PA_ASSETS && window.PA_ASSETS.logo) || '';
  const PA_BADGE_PDF = {'TO DO':{bg:'#C3CDD8',fg:'#fff'},'DOING':{bg:'#E8B87A',fg:'#fff'},'DONE':{bg:'#86C79A',fg:'#fff'},'CANCELADO':{bg:'#D48A8A',fg:'#fff'}};
  const PA_BAR = {'TO DO':'#C3CDD8','DOING':'#E8B87A','DONE':'#86C79A','CANCELADO':'#D48A8A'};
  // group in section order
  const secs = [], groups = {};
  ord.forEach((a) => { if (!groups[a.section]) { groups[a.section] = []; secs.push(a.section); } groups[a.section].push(a); });
  let bodyRows = '';
  secs.forEach((sec) => {
    bodyRows += '<tr><td colspan="7" class="sec">' + _paEsc(sec) + '</td></tr>';
    groups[sec].forEach((a) => {
      const b = PA_BADGE_PDF[a.status] || PA_BADGE_PDF['TO DO'];
      const pct = Math.max(0, Math.min(100, +a.progress || 0));
      const barClr = PA_BAR[a.status] || '#C3CDD8';
      bodyRows += '<tr>'
        + '<td class="act">' + _paEsc(a.activity || '') + '</td>'
        + '<td class="comp">' + _paEsc(a.company || '') + '</td>'
        + '<td class="ctr">' + _paEsc(a.start || '—') + '</td>'
        + '<td class="ctr">' + _paEsc(a.end || '—') + '</td>'
        + '<td class="ctr">' + _paEsc(a.duration || '—') + '</td>'
        + '<td class="prog"><div class="track"><div class="fill" style="width:' + pct + '%;background:' + barClr + '"></div></div><span class="pct">' + pct + '%</span></td>'
        + '<td class="ctr"><span class="badge" style="background:' + b.bg + ';color:' + b.fg + '">' + _paEsc(a.status || '') + '</span></td>'
        + '</tr>';
    });
  });
  const dist = {'TO DO':0,'DOING':0,'DONE':0,'CANCELADO':0};
  ord.forEach((a) => { dist[a.status] = (dist[a.status] || 0) + 1; });
  const dt = new Date().toLocaleString('pt-BR');
  const w = window.open('', '_blank');
  if (!w) { toast.push({ icon: 'x', title: (lang === 'pt' ? 'Libere os pop-ups' : 'Allow pop-ups'), body: (lang === 'pt' ? 'O relatório abre numa aba nova' : 'The report opens in a new tab') }); return; }
  w.document.write('<!DOCTYPE html><html><head><meta charset="utf-8"><title>Relatório Diário — Genesis I</title><style>'
   + '@page{size:A4 landscape;margin:8mm 10mm;}'
   + '*{box-sizing:border-box;margin:0;padding:0;}'
   + 'body{font-family:Arial,Helvetica,sans-serif;color:#2a2a33;font-size:9px;}'
   + '.hdr{display:flex;align-items:center;justify-content:space-between;border-bottom:2.5px solid #2C2849;padding-bottom:6px;margin-bottom:8px;}'
   + '.hdr img{height:34px;vertical-align:middle;margin-right:8px;}'
   + '.t1{font-size:15px;font-weight:800;color:#2C2849;}'
   + '.t1 .num{color:#c0392b;}'
   + '.t2{font-size:9px;color:#6a8090;margin-top:1px;}'
   + '.meta{text-align:right;font-size:9px;color:#444;line-height:1.5;}'
   + '.meta .big{font-size:13px;font-weight:800;color:#c0392b;}'
   + '.chips{display:flex;gap:6px;margin:6px 0 8px;flex-wrap:wrap;}'
   + '.chip{font-size:9px;font-weight:700;padding:3px 9px;border-radius:10px;color:#fff;}'
   + 'table{width:100%;border-collapse:collapse;}'
   + 'th{background:#F0F5FA;color:#5A6473;font-size:8px;text-transform:uppercase;letter-spacing:.5px;text-align:left;padding:5px 6px;border-bottom:1px solid #d8e0ea;}'
   + 'td{padding:4px 6px;border-bottom:1px solid #eef1f6;font-size:9px;vertical-align:middle;}'
   + 'td.sec{background:#eaf6fc;color:#0e8fc0;font-weight:800;font-size:10px;letter-spacing:1px;text-transform:uppercase;padding:5px 8px;}'
   + 'td.comp{color:#5A93C9;font-weight:700;}'
   + 'td.ctr{color:#6a7480;}'
   + 'td.prog{white-space:nowrap;}'
   + '.track{display:inline-block;width:78px;height:7px;background:#E9EEF3;border-radius:4px;overflow:hidden;vertical-align:middle;}'
   + '.fill{height:100%;border-radius:4px;}'
   + '.pct{font-size:8px;color:#6a7480;margin-left:5px;vertical-align:middle;}'
   + '.badge{display:inline-block;font-size:8px;font-weight:700;padding:2px 8px;border-radius:9px;}'
   + '.foot{margin-top:10px;padding-top:5px;border-top:1px solid #d8e0ea;display:flex;justify-content:space-between;font-size:8px;color:#8593a8;}'
   + '</style></head><body>'
   + '<div class="hdr">'
   + '<div style="display:flex;align-items:center">' + (logo ? '<img src="' + logo + '">' : '') + '<div><div class="t1">Relatório Diário <span class="num">#' + _paEsc(meta.reportNo || '—') + '</span></div><div class="t2">MPSV Genesis I · ' + _paEsc(meta.vessel || 'Genesis I') + ' · Port Activities</div></div></div>'
   + '<div class="meta"><div class="big">' + _paEsc(meta.dateLabel || '') + '</div>' + _paEsc(meta.port || '') + '</div>'
   + '</div>'
   + '<div class="chips">'
   + '<span class="chip" style="background:#86C79A">DONE ' + dist['DONE'] + '</span>'
   + '<span class="chip" style="background:#E8B87A">DOING ' + dist['DOING'] + '</span>'
   + '<span class="chip" style="background:#C3CDD8;color:#4a5560">TO DO ' + dist['TO DO'] + '</span>'
   + (dist['CANCELADO'] ? '<span class="chip" style="background:#D48A8A">CANCELADO ' + dist['CANCELADO'] + '</span>' : '')
   + '<span class="chip" style="background:#2C2849">TOTAL ' + ord.length + '</span>'
   + '</div>'
   + '<table><thead><tr><th style="width:30%">Atividade</th><th style="width:14%">Empresa / Resp.</th><th style="width:11%">Início</th><th style="width:11%">Término</th><th style="width:8%">Duração</th><th style="width:14%">Progresso</th><th style="width:10%">Status</th></tr></thead>'
   + '<tbody>' + bodyRows + '</tbody></table>'
   + '<div class="foot"><span>MPSV Genesis I · Relatório Diário de Atividades do Porto</span><span>Gerado: ' + dt + '</span></div>'
   + '<scr' + 'ipt>window.onload=function(){setTimeout(function(){window.print();},350);}</scr' + 'ipt>'
   + '</body></html>');
  w.document.close();
};

/* ════════════════════════════════════════════════════════════════════════
   COMPONENTE REACT (padrão Tidal)
   ════════════════════════════════════════════════════════════════════════ */
const PortAct = ({state, setState, setPage}) => {
  const _tt = useT();
  const lang = _tt.lang;
  const toast = useToast();
  const isMobile = useMobile();
  const token = () => localStorage.getItem('genesis_token');
  const me = state.currentUser || {};
  const canManage = me.role === 'admin' || me.is_staff === true || (me.permissoes || []).indexOf('portact.gerir') >= 0;

  const [st, setSt] = React.useState(null);        // null = carregando
  const [status, setStatus] = React.useState(null); // {msg, kind:'ok'|'err'|null}
  const [busy, setBusy] = React.useState(false);    // gerando PPTX
  const [drag, setDrag] = React.useState(false);
  const fileRef = React.useRef(null);
  const stRef = React.useRef(null);
  const saveT = React.useRef(null);

  const H = () => ({'Authorization':`Bearer ${token()}`});

  const load = () => fetch('/api/portact/current', { headers: H() }).then((r) => r.ok ? r.json() : null).then((d) => {
    const s = (d && d.meta) ? d : { meta: {}, sections: [], activities: [] };
    s.meta = { ...PA_META_DEFAULT, ...s.meta };
    if (!Array.isArray(s.sections)) s.sections = [];
    if (!Array.isArray(s.activities)) s.activities = [];
    s.activities.forEach((a) => { if (!a.id) a.id = paUid(); });
    stRef.current = s; setSt(s);
  }).catch(() => { const s = { meta: { ...PA_META_DEFAULT }, sections: [], activities: [] }; stRef.current = s; setSt(s); });
  React.useEffect(() => { load(); }, []);

  /* ---------- persistência (PUT do estado inteiro) ---------- */
  const save = (s) => {
    const cur = s || stRef.current; if (!cur) return;
    fetch('/api/portact/current', { method: 'PUT', headers: { ...H(), 'Content-Type': 'application/json' },
      body: JSON.stringify({ meta: cur.meta, sections: cur.sections, activities: cur.activities }) })
      .then((r) => { if (!r.ok) toast.push({ icon: 'x', title: (lang === 'pt' ? 'Falha ao salvar' : 'Save failed') }); })
      .catch(() => toast.push({ icon: 'x', title: (lang === 'pt' ? 'Falha ao salvar' : 'Save failed') }));
  };
  const saveSoon = () => { clearTimeout(saveT.current); saveT.current = setTimeout(() => save(), 400); }; // digitação: debounce do write do estado inteiro
  const commit = (next, immediate) => { stRef.current = next; setSt(next); if (immediate) save(next); else saveSoon(); };

  /* ---------- Editing ops (port 1:1 do IMT) ---------- */
  const paSetMeta = (field, val) => commit({ ...st, meta: { ...st.meta, [field]: val } });
  const paSetField = (id, field, val) => {
    const acts = st.activities.map((a) => {
      if (a.id !== id) return a;
      const na = { ...a, [field]: val };
      if (field === 'progress') na.progress = Math.max(0, Math.min(100, parseInt(val) || 0));
      return na;
    });
    commit({ ...st, activities: acts });
  };
  const paSetStatusVal = (id, val) => {
    const acts = st.activities.map((a) => {
      if (a.id !== id) return a;
      const na = { ...a, status: val };
      if (val === 'DONE' && na.progress < 100) na.progress = 100;
      if (val === 'TO DO') na.progress = 0;
      return na;
    });
    commit({ ...st, activities: acts }, true);
  };
  const paAddActivity = (section) => commit({ ...st, activities: st.activities.concat([{ id: paUid(), section: section, company: '', activity: (lang === 'pt' ? 'Nova atividade' : 'New activity'), start: '', end: '', duration: '', progress: 0, status: 'TO DO' }]) }, true);
  const paDelActivity = (id) => {
    if (!window.confirm(lang === 'pt' ? 'Remover esta atividade?' : 'Remove this activity?')) return;
    commit({ ...st, activities: st.activities.filter((a) => a.id !== id) }, true);
  };
  const paAddSection = () => {
    let name = window.prompt(lang === 'pt' ? 'Nome da nova seção:' : 'New section name:', 'NOVA SEÇÃO');
    if (!name) return; name = name.trim(); if (!name) return;
    const secs = st.sections.indexOf(name) === -1 ? st.sections.concat([name]) : st.sections.slice();
    commit({ ...st, sections: secs }, true);
  };
  const paRenameSection = (old) => {
    let name = window.prompt(lang === 'pt' ? 'Renomear seção:' : 'Rename section:', old);
    if (!name) return; name = name.trim(); if (!name || name === old) return;
    const secs = st.sections.map((s) => s === old ? name : s);
    const acts = st.activities.map((a) => a.section === old ? { ...a, section: name } : a);
    commit({ ...st, sections: secs, activities: acts }, true);
  };
  const paDelSection = (name) => {
    const n = st.activities.filter((a) => a.section === name).length;
    if (!window.confirm((lang === 'pt' ? 'Remover a seção "' : 'Remove section "') + name + '"' + (n ? (lang === 'pt' ? ' e suas ' : ' and its ') + n + (lang === 'pt' ? ' atividade(s)' : ' activitie(s)') : '') + '?')) return;
    commit({ ...st, sections: st.sections.filter((s) => s !== name), activities: st.activities.filter((a) => a.section !== name) }, true);
  };
  const paMoveSection = (name, dir) => {
    const i = st.sections.indexOf(name); if (i < 0) return;
    const j = i + dir; if (j < 0 || j >= st.sections.length) return;
    const secs = st.sections.slice(); const t = secs[i]; secs[i] = secs[j]; secs[j] = t;
    commit({ ...st, sections: secs }, true);
  };
  const paMoveActivity = (id, dir) => {
    const a = st.activities.find((x) => x.id === id); if (!a) return;
    const sameSec = st.activities.filter((x) => x.section === a.section);
    const pos = sameSec.indexOf(a); const swapWith = sameSec[pos + dir]; if (!swapWith) return;
    const acts = st.activities.slice();
    const i1 = acts.indexOf(a), i2 = acts.indexOf(swapWith);
    const t = acts[i1]; acts[i1] = acts[i2]; acts[i2] = t;
    commit({ ...st, activities: acts }, true);
  };
  const paClearAll = () => {
    if (!window.confirm(lang === 'pt' ? 'Limpar TODAS as atividades e seções?' : 'Clear ALL activities and sections?')) return;
    commit({ meta: { ...PA_META_DEFAULT }, sections: [], activities: [] }, true);
  };

  /* ---------- Import do Excel da empresa (port 1:1) ---------- */
  const paHandleFile = async (file) => {
    if (!file) return;
    try { await _paEnsureXLSX(); }
    catch (e) { setStatus({ msg: (lang === 'pt' ? 'Leitor de Excel indisponível (SheetJS não carregou).' : 'Excel reader unavailable (SheetJS did not load).'), kind: 'err' }); return; }
    setStatus({ msg: (lang === 'pt' ? 'Lendo ' : 'Reading ') + file.name + '…', kind: null });
    const reader = new FileReader();
    reader.onerror = () => setStatus({ msg: (lang === 'pt' ? 'Não consegui ler o arquivo.' : 'Could not read the file.'), kind: 'err' });
    reader.onload = (e) => {
      try {
        const wb = XLSX.read(new Uint8Array(e.target.result), { type: 'array' });
        const res = paParseWorkbook(wb);
        if (!res.activities.length) { setStatus({ msg: (lang === 'pt' ? 'Nenhuma atividade encontrada. Confira as colunas Empresa/Atividade/Status.' : 'No activities found. Check the Company/Activity/Status columns.'), kind: 'err' }); return; }
        const cur = stRef.current;
        const mode = (cur.activities.length && !window.confirm(
          (lang === 'pt' ? 'Importar ' : 'Import ') + res.activities.length + (lang === 'pt' ? ' atividade(s).\n\nOK = SUBSTITUIR a lista atual\nCancelar = ADICIONAR ao que já existe' : ' activitie(s).\n\nOK = REPLACE the current list\nCancel = APPEND to what exists'))) ? 'append' : 'replace';
        let next;
        if (mode === 'replace' || !cur.activities.length) {
          next = { ...cur, activities: res.activities, sections: res.sections.slice() };
        } else {
          const secs = cur.sections.slice();
          res.sections.forEach((s) => { if (secs.indexOf(s) === -1) secs.push(s); });
          next = { ...cur, sections: secs, activities: cur.activities.concat(res.activities) };
        }
        if (!next.meta.vessel && res.meta.vessel) next.meta = { ...next.meta, vessel: res.meta.vessel };
        commit(next, true);
        setStatus({ msg: res.activities.length + (lang === 'pt' ? ' atividade(s) · ' : ' activitie(s) · ') + res.sections.length + (lang === 'pt' ? ' seção(ões) importada(s) de ' : ' section(s) imported from ') + file.name + '.', kind: 'ok' });
        toast.push({ icon: 'check', title: '✓ ' + res.activities.length + (lang === 'pt' ? ' atividade(s) importada(s).' : ' activitie(s) imported.') });
      } catch (err) { console.error(err); setStatus({ msg: (lang === 'pt' ? 'Erro ao processar: ' : 'Processing error: ') + err.message, kind: 'err' }); }
    };
    reader.readAsArrayBuffer(file);
  };

  /* ---------- Generate PPTX (server) ---------- */
  const paGeneratePPTX = async () => {
    if (!st.activities.length) { toast.push({ icon: 'x', title: (lang === 'pt' ? 'Nenhuma atividade para gerar' : 'No activities to generate') }); return; }
    setBusy(true);
    try {
      const ordered = paOrdered(st);
      const payload = { meta: st.meta, activities: ordered.map((a) => ({ section: a.section, company: a.company, activity: a.activity, start: a.start, end: a.end, duration: a.duration, progress: a.progress, status: a.status })) };
      const r = await fetch('/api/portact/pptx', { method: 'POST', headers: { ...H(), 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
      const ct = (r.headers.get('Content-Type') || '');
      if (!r.ok || ct.indexOf('presentation') === -1) {
        toast.push({ icon: 'x', title: (lang === 'pt' ? 'Não foi possível gerar o PowerPoint' : 'Could not generate the PowerPoint') });
        setBusy(false); return;
      }
      const blob = await r.blob();
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a'); a.href = url;
      a.download = 'Relatorio_Diario_Genesis' + (st.meta.reportNo ? ('_' + st.meta.reportNo) : '') + '.pptx';
      document.body.appendChild(a); a.click(); a.remove();
      setTimeout(() => { URL.revokeObjectURL(url); }, 1500);
      toast.push({ icon: 'check', title: (lang === 'pt' ? '✓ Relatório gerado.' : '✓ Report generated.') });
    } catch (e) { toast.push({ icon: 'x', title: (lang === 'pt' ? 'Não foi possível gerar o PowerPoint' : 'Could not generate the PowerPoint'), body: String(e && e.message ? e.message : e) }); }
    setBusy(false);
  };

  /* ---------- render ---------- */
  if (st === null) return <main style={{maxWidth:1080,margin:'0 auto',padding:isMobile?'16px 14px 60px':'26px 30px 80px'}}><div style={{fontFamily:'var(--fm)',fontSize:13,color:'var(--muted)',padding:20}}>{lang==='pt'?'Carregando…':'Loading…'}</div></main>;

  const total = st.activities.length;
  const dist = {'TO DO':0,'DOING':0,'DONE':0,'CANCELADO':0};
  st.activities.forEach((a) => { dist[a.status] = (dist[a.status] || 0) + 1; });
  // atividade cuja seção não está na lista ordenada -> a seção entra no fim (só na render)
  const secs = st.sections.slice();
  st.activities.forEach((a) => { if (secs.indexOf(a.section) === -1) secs.push(a.section); });

  const gridCols = '2.6fr 1.1fr 1fr 1fr 0.7fr 1.3fr 1.2fr 0.5fr';
  const inp = { width:'100%', padding:'4px 6px', border:'1px solid transparent', borderRadius:4, fontSize:11, background:'transparent' };
  const inpFocus = (e) => { e.target.style.borderColor = 'var(--line-soft)'; e.target.style.background = 'var(--surface)'; };
  const inpBlur = (e) => { e.target.style.borderColor = 'transparent'; e.target.style.background = 'transparent'; };
  const lbl = { display:'block', fontSize:9, letterSpacing:1, textTransform:'uppercase', color:'var(--muted)', marginBottom:3 };
  const metaInp = { padding:'6px 8px', border:'1px solid var(--line-soft)', borderRadius:6, fontSize:13, background:'var(--surface)', color:'var(--ink)' };

  return (
    <main style={{maxWidth:1080,margin:'0 auto',padding:isMobile?'16px 14px 60px':'26px 30px 80px'}}>

      {/* toolbar */}
      <div style={{display:'flex',gap:6,alignItems:'center',flexWrap:'wrap',marginBottom:12}}>
        {canManage && <button className="btn btn-sm" onClick={paAddSection}>{lang==='pt'?'＋ Seção':'＋ Section'}</button>}
        {canManage && <button className="btn btn-sm" onClick={paGeneratePPTX} disabled={busy}>{busy ? '⏳ PPTX…' : '📊 PPTX'}</button>}
        {canManage && <button className="btn btn-sm" onClick={()=>_paExportPDF(st, toast, lang)}>📄 PDF</button>}
        {canManage && <button className="btn btn-sm" onClick={()=>_paExportExcel(st, toast, lang)}>📗 Excel</button>}
        {canManage && total > 0 && <button className="btn btn-sm" onClick={paClearAll}>↺ {lang==='pt'?'Limpar':'Clear'}</button>}
      </div>

      {/* meta bar (relatório # / data / porto / embarcação) */}
      <div style={{display:'flex',gap:14,flexWrap:'wrap',alignItems:'flex-end',background:'var(--surface)',border:'1px solid var(--line-soft)',borderRadius:10,padding:'12px 16px',marginBottom:12,boxShadow:'var(--shadow-sm)'}}>
        <div><label style={lbl}>{lang==='pt'?'Relatório Diário #':'Daily Report #'}</label>
          <input type="text" value={st.meta.reportNo||''} readOnly={!canManage} onChange={(e)=>paSetMeta('reportNo', e.target.value)} placeholder="001" style={{...metaInp,width:90,fontWeight:700}}/></div>
        <div><label style={lbl}>{lang==='pt'?'Data':'Date'}</label>
          <input type="text" value={st.meta.dateLabel||''} readOnly={!canManage} onChange={(e)=>paSetMeta('dateLabel', e.target.value)} placeholder="04/08/2026" style={{...metaInp,width:120}}/></div>
        <div><label style={lbl}>{lang==='pt'?'Porto':'Port'}</label>
          <input type="text" value={st.meta.port||''} readOnly={!canManage} onChange={(e)=>paSetMeta('port', e.target.value)} placeholder="AÇU PORT - RJ" style={{...metaInp,width:180}}/></div>
        <div><label style={lbl}>{lang==='pt'?'Embarcação':'Vessel'}</label>
          <input type="text" value={st.meta.vessel||''} readOnly={!canManage} onChange={(e)=>paSetMeta('vessel', e.target.value)} placeholder="Genesis I" style={{...metaInp,width:140}}/></div>
        <div style={{flex:1,textAlign:'right',fontSize:11,color:'var(--muted)',alignSelf:'center',minWidth:180}}>
          {total} {lang==='pt'?'atividade(s)':'activitie(s)'} · <span style={{color:'#1e7a45'}}>{dist['DONE']} DONE</span> · <span style={{color:'#a8681a'}}>{dist['DOING']} DOING</span> · <span style={{color:'#7a8697'}}>{dist['TO DO']} TO DO</span>{dist['CANCELADO'] ? <span> · <span style={{color:'#b5433f'}}>{dist['CANCELADO']} CANC</span></span> : null}
        </div>
      </div>

      {/* drop zone (só gestor) */}
      {canManage && (
        <React.Fragment>
          <div onClick={()=>fileRef.current && fileRef.current.click()}
               onDragEnter={(e)=>{e.preventDefault();setDrag(true);}} onDragOver={(e)=>{e.preventDefault();setDrag(true);}}
               onDragLeave={(e)=>{e.preventDefault();setDrag(false);}}
               onDrop={(e)=>{e.preventDefault();setDrag(false);const f=e.dataTransfer.files&&e.dataTransfer.files[0];paHandleFile(f);}}
               style={{border:'1.5px dashed '+(drag?'#5A93C9':'var(--line-soft)'),borderRadius:10,background:'var(--surface)',padding:18,textAlign:'center',cursor:'pointer',transition:'all .15s',marginBottom:6}}>
            <div style={{fontSize:22,marginBottom:4}}>📄</div>
            <div style={{fontWeight:600,fontSize:13,marginBottom:3}}>{lang==='pt'?'Solte aqui a planilha de atividades do porto (Excel da empresa), ou clique para escolher':'Drop the port activities spreadsheet here (company Excel), or click to choose'}</div>
            <div style={{fontSize:11,color:'var(--muted)'}}>{lang==='pt'?'.xls / .xlsx · lê as colunas Empresa, Atividade, Data Início/Final, Duração, Progresso e Status · seções detectadas automaticamente':'.xls / .xlsx · reads Company, Activity, Start/End Date, Duration, Progress and Status columns · sections auto-detected'}</div>
            <input type="file" ref={fileRef} accept=".xls,.xlsx" style={{display:'none'}} onChange={(e)=>{paHandleFile(e.target.files[0]);e.target.value='';}}/>
          </div>
          <div style={{fontFamily:'var(--fm)',fontSize:11,minHeight:15,marginBottom:14,color:status?(status.kind==='err'?'#b03030':status.kind==='ok'?'#2a7a4a':'var(--muted)'):'var(--muted)'}}>{status?status.msg:''}</div>
        </React.Fragment>
      )}

      {/* seções + atividades */}
      {!total && !secs.length ? (
        <div style={{background:'var(--surface)',border:'1px solid var(--line-soft)',borderRadius:14,padding:'28px 20px',textAlign:'center',color:'var(--muted)',fontSize:13,lineHeight:1.7}}>
          <strong>{lang==='pt'?'Nenhuma atividade ainda.':'No activities yet.'}</strong><br/>
          {lang==='pt'?(canManage?'Solte a planilha da empresa acima, ou use ＋ Seção para montar manualmente.':'Aguarde um gestor importar a planilha da empresa.'):(canManage?'Drop the company spreadsheet above, or use ＋ Section to build it manually.':'Wait for a manager to import the company spreadsheet.')}
        </div>
      ) : secs.map((sec) => {
        const acts = st.activities.filter((a) => a.section === sec);
        return (
          <div key={sec} style={{marginBottom:16,border:'1px solid var(--line-soft)',borderRadius:10,overflow:'hidden',background:'var(--surface)',boxShadow:'var(--shadow-sm)'}}>
            {/* section header */}
            <div style={{display:'flex',alignItems:'center',gap:8,background:'var(--surface-2)',padding:'8px 12px'}}>
              <span style={{fontFamily:'var(--fs)',fontSize:14,fontWeight:800,letterSpacing:1,color:'#0e8fc0',textTransform:'uppercase',flex:1}}>{sec}</span>
              <span style={{fontSize:10,color:'var(--muted)'}}>{acts.length} item(s)</span>
              {canManage && <button className="btn btn-sm" title={lang==='pt'?'Subir seção':'Move section up'} onClick={()=>paMoveSection(sec,-1)} style={{padding:'2px 7px'}}>▲</button>}
              {canManage && <button className="btn btn-sm" title={lang==='pt'?'Descer seção':'Move section down'} onClick={()=>paMoveSection(sec,1)} style={{padding:'2px 7px'}}>▼</button>}
              {canManage && <button className="btn btn-sm" title={lang==='pt'?'Renomear':'Rename'} onClick={()=>paRenameSection(sec)} style={{padding:'2px 7px'}}>✎</button>}
              {canManage && <button className="btn btn-sm" title={lang==='pt'?'Adicionar atividade':'Add activity'} onClick={()=>paAddActivity(sec)} style={{padding:'2px 8px'}}>＋</button>}
              {canManage && <button className="btn btn-sm" title={lang==='pt'?'Remover seção':'Remove section'} onClick={()=>paDelSection(sec)} style={{padding:'2px 7px',color:'#b03030'}}>✕</button>}
            </div>
            {/* column header */}
            <div style={{display:'grid',gridTemplateColumns:gridCols,gap:6,padding:'5px 12px',borderBottom:'1px solid var(--line-soft)',fontSize:9,letterSpacing:.5,textTransform:'uppercase',color:'var(--muted)'}}>
              <div>{lang==='pt'?'Atividade':'Activity'}</div><div>{lang==='pt'?'Empresa':'Company'}</div><div>{lang==='pt'?'Início':'Start'}</div><div>{lang==='pt'?'Término':'End'}</div><div>{lang==='pt'?'Duração':'Duration'}</div><div>{lang==='pt'?'Progresso':'Progress'}</div><div>Status</div><div></div>
            </div>
            {!acts.length && <div style={{padding:'10px 12px',fontSize:11,color:'var(--muted)'}}>{lang==='pt'?'Sem atividades. Use ＋ para adicionar.':'No activities. Use ＋ to add.'}</div>}
            {acts.map((a, ai) => {
              const b = PA_BADGE[a.status] || PA_BADGE['TO DO'];
              return (
                <div key={a.id} style={{display:'grid',gridTemplateColumns:gridCols,gap:6,padding:'6px 12px',alignItems:'center',borderBottom:'1px solid var(--line-soft)',background:ai%2?'rgba(0,0,0,.015)':'transparent'}}>
                  <input value={a.activity} readOnly={!canManage} onChange={(e)=>paSetField(a.id,'activity',e.target.value)} onFocus={inpFocus} onBlur={inpBlur} style={inp}/>
                  <input value={a.company} readOnly={!canManage} onChange={(e)=>paSetField(a.id,'company',e.target.value)} onFocus={inpFocus} onBlur={inpBlur} style={{...inp,fontWeight:600,color:'#5A93C9'}}/>
                  <input value={a.start} readOnly={!canManage} onChange={(e)=>paSetField(a.id,'start',e.target.value)} placeholder="—" onFocus={inpFocus} onBlur={inpBlur} style={{...inp,fontSize:10,color:'var(--muted)'}}/>
                  <input value={a.end} readOnly={!canManage} onChange={(e)=>paSetField(a.id,'end',e.target.value)} placeholder="—" onFocus={inpFocus} onBlur={inpBlur} style={{...inp,fontSize:10,color:'var(--muted)'}}/>
                  <input value={a.duration} readOnly={!canManage} onChange={(e)=>paSetField(a.id,'duration',e.target.value)} placeholder="—" onFocus={inpFocus} onBlur={inpBlur} style={{...inp,fontSize:10,color:'var(--muted)'}}/>
                  <div style={{display:'flex',alignItems:'center',gap:6}}>
                    <div style={{flex:1,height:7,background:'#E9EEF3',borderRadius:4,overflow:'hidden'}}>
                      <div style={{width:a.progress+'%',height:'100%',background:b.bar,borderRadius:4}}></div>
                    </div>
                    <input type="number" min="0" max="100" value={a.progress} readOnly={!canManage} onChange={(e)=>paSetField(a.id,'progress',e.target.value)} style={{width:44,padding:'3px 4px',border:'1px solid var(--line-soft)',borderRadius:4,fontSize:10,textAlign:'center',background:'var(--surface)',color:'var(--ink)'}}/>
                  </div>
                  <select value={a.status} disabled={!canManage} onChange={(e)=>paSetStatusVal(a.id,e.target.value)} style={{width:'100%',padding:'4px 6px',border:'1px solid var(--line-soft)',borderRadius:5,fontSize:10,fontWeight:700,background:b.bg,color:b.fg}}>
                    {PA_STATUSES.map((s)=><option key={s} value={s}>{s}</option>)}
                  </select>
                  <div style={{display:'flex',gap:2,justifyContent:'flex-end'}}>
                    {canManage && <button className="btn btn-sm" title={lang==='pt'?'Subir':'Up'} onClick={()=>paMoveActivity(a.id,-1)} style={{padding:'1px 4px',fontSize:9}}>▲</button>}
                    {canManage && <button className="btn btn-sm" title={lang==='pt'?'Descer':'Down'} onClick={()=>paMoveActivity(a.id,1)} style={{padding:'1px 4px',fontSize:9}}>▼</button>}
                    {canManage && <button className="btn btn-sm" title={lang==='pt'?'Remover':'Remove'} onClick={()=>paDelActivity(a.id)} style={{padding:'1px 5px',fontSize:9,color:'#b03030'}}>✕</button>}
                  </div>
                </div>
              );
            })}
          </div>
        );
      })}
    </main>
  );
};
