// OrgChart — Organigrama: "Organigrama real" y "Organigrama aprobado"
// Fuente de datos: window.OrgChartData (components/orgchart-data.jsx), derivada de
// "Organigrama real y conciliacion de headcount 11 sep 2026.xlsx" (corte 2026-09-11).
// Este módulo es independiente de window.OrgDataStore (components/data-providers.jsx):
// OrgDataStore sigue siendo la fuente para Solicitudes de personal, Crear empleado y ATS,
// sin ningún cambio. El Organigrama visualiza el snapshot real/aprobado del Excel.

const ORG_ROOT_CODE = 'POS-000074'; // única raíz aprobada del Excel (C.E.O Chief Executive Office)

const ESTRUCTURAL_META = {
  'Activa':                       { label: 'Activa',                       pill: 'green' },
  'Pendiente de inactivación':    { label: 'Pendiente de inactivación',    pill: 'amber-soft' },
  'Inactiva':                     { label: 'Inactiva',                     pill: '' },
  'No aprobada':                  { label: 'No aprobada',                  pill: 'rose' },
};

const OCUPACION_META = {
  'Ocupada':                          { label: 'Ocupada',             pill: 'green' },
  'Vacante':                          { label: 'Vacante',             pill: '' },
  'Ocupada con cobertura temporal':   { label: 'Cobertura temporal',  pill: 'amber-soft' },
};

const CONCILIACION_META = {
  'Conciliada':              { pill: 'green' },
  'Sin posición aprobada':   { pill: 'rose' },
  'Homologación pendiente':  { pill: 'amber-soft' },
  'Revisión requerida':      { pill: 'amber-soft' },
  '—':                       { pill: '' },
};

const _ORGPOS_MESES = ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'];
const formatFechaLarga = (iso) => { if (!iso) return '—'; const [y, m, d] = iso.split('-').map(Number); return `${d} de ${_ORGPOS_MESES[m - 1]} de ${y}`; };

const getInitials = (nombre) => {
  if (!nombre) return '';
  const parts = nombre.trim().split(/\s+/);
  const first = parts[0] ? parts[0][0] : '';
  const last = parts.length > 1 ? parts[parts.length - 1][0] : '';
  return (first + last).toUpperCase();
};

// ─── Árbol genérico: sirve tanto para la jerarquía aprobada (jefeAprobadoCodigo)
// como para la real (jefeRealCodigo, con caída a jefeAprobadoCodigo en vacantes) ──

const getAprobadoParent = (p) => p.jefeAprobadoCodigo || null;
const getRealParent = (p) => p.jefeRealCodigo || p.jefeAprobadoCodigo || null;

const buildOrgCodeIndex = (positions) => {
  const byCode = {};
  positions.forEach(p => { byCode[p.codigo] = p; });
  return byCode;
};

const buildOrgChartTree = (positions, getParentCode) => {
  const byCode = {};
  positions.forEach(p => { byCode[p.codigo] = { ...p, children: [] }; });
  let root = null;
  positions.forEach(p => {
    const parentCode = getParentCode(p);
    if (parentCode && byCode[parentCode]) byCode[parentCode].children.push(byCode[p.codigo]);
    else if (!parentCode) root = byCode[p.codigo];
  });
  return root;
};

// `byCode` es un índice código→posición ya construido (ver buildOrgCodeIndex),
// para no reconstruirlo en cada llamada cuando se recorre en bucle.
const getOrgAncestorCodes = (byCode, codigo, getParentCode) => {
  const codes = [];
  let current = byCode[codigo];
  while (current) {
    const parentCode = getParentCode(current);
    if (!parentCode || !byCode[parentCode]) break;
    codes.push(parentCode);
    current = byCode[parentCode];
  }
  return codes;
};

// Conserva las posiciones que coinciden con el filtro/búsqueda más sus ancestros,
// para que la jerarquía se vea conectada hasta la raíz.
const pruneOrgWithAncestors = (allPositions, byCode, matchList, getParentCode) => {
  const keep = new Set();
  matchList.forEach(p => {
    let current = p;
    while (current) {
      keep.add(current.codigo);
      const parentCode = getParentCode(current);
      current = parentCode ? byCode[parentCode] : null;
    }
  });
  return allPositions.filter(p => keep.has(p.codigo));
};

const OrgFilterSelect = ({ label, value, isDefault, onChange, options }) => (
  <div className={`filter-select ${isDefault ? '' : 'active'}`}>
    <span className="filter-label">{label}</span>
    <select value={value} onChange={e => onChange(e.target.value)}>
      {options.map(o => <option key={o.value} value={o.value}>{o.label}</option>)}
    </select>
    <IconChevronDown size={13} />
  </div>
);

const ORG_FILTROS_VACIOS = { sociedad: 'todas', proceso: 'todos', subproceso: 'todos', planta: 'todas' };

// ─── Tarjeta + rama recursiva del árbol (compartida por real y aprobado) ───

const ORG_CHILDREN_PAGE = 24;

const OrgBranch = ({ node, variant, expandedIds, toggle, matchedCodes, filtrosActivos, highlightCode, onSelect }) => {
  const [visibleCount, setVisibleCount] = React.useState(ORG_CHILDREN_PAGE);
  const hasChildren = node.children.length > 0;
  const expanded = expandedIds.has(node.codigo);
  const isHighlighted = highlightCode === node.codigo;
  const isContextOnly = filtrosActivos && matchedCodes && !matchedCodes.has(node.codigo);
  const ocupMeta = OCUPACION_META[node.estadoOcupacion] || { label: node.estadoOcupacion, pill: '' };
  const isVacante = node.estadoOcupacion === 'Vacante';

  let cardClass = 'orgpos-card';
  if (isHighlighted) cardClass += ' highlighted';
  if (isContextOnly) cardClass += ' dim';
  if (variant === 'real' && isVacante) cardClass += ' orgreal-vacante';
  if (variant === 'real' && node.esExt) cardClass += ' orgreal-ext';

  return (
    <div className="orgpos-branch">
      <div id={`orgpos-${node.codigo}`} className={cardClass} onClick={() => onSelect(node.codigo)} role="button" tabIndex={0}>
        {variant === 'real' && node.esExt && <span className="orgreal-ext-badge" title="Colaborador real sin posición aprobada conciliada — requiere revisión de GH">Fuera de HC aprobado</span>}
        <div className="orgpos-code mono">{node.codigo}</div>
        <div className="orgpos-cargo">{node.cargo}</div>

        {variant === 'aprobado' && (
          <>
            <div className="orgpos-meta">{node.sociedad} · {node.planta}</div>
            <span className={`pill ${ocupMeta.pill}`} style={{ marginTop: 6 }}>{ocupMeta.label}</span>
          </>
        )}

        {variant === 'real' && (
          isVacante ? (
            <span className="pill" style={{ marginTop: 6 }}>Vacante</span>
          ) : (
            <>
              <div className="orgreal-person">
                <div className="avatar" style={{ width: 30, height: 30, fontSize: 11.5 }}>{getInitials(node.nombreEmpleado)}</div>
                <div className="orgreal-person-name">{node.nombreEmpleado}</div>
              </div>
              <div className="orgpos-meta">{node.sociedad} · {node.planta}</div>
              <span className={`pill ${ocupMeta.pill}`} style={{ marginTop: 6 }}>{ocupMeta.label}</span>
            </>
          )
        )}

        {hasChildren && (
          <button className="orgpos-expand-btn" onClick={(e) => { e.stopPropagation(); toggle(node.codigo); }}>
            <IconChevronDown size={12} style={{ transform: expanded ? 'rotate(180deg)' : 'none', transition: 'transform .15s' }} />
            {node.children.length} posición{node.children.length === 1 ? '' : 'es'}
          </button>
        )}
      </div>
      {hasChildren && expanded && (() => {
        // Prioriza en la primera página: el nodo resaltado o cualquier rama ya
        // abierta (ancestro de un resaltado/foco), luego los resultados exactos
        // del filtro — así una posición nunca queda enterrada detrás de cientos
        // de hermanos en un nodo con muchos hijos directos.
        const priority = (child) => {
          if (child.codigo === highlightCode || expandedIds.has(child.codigo)) return 0;
          if (filtrosActivos && matchedCodes && matchedCodes.has(child.codigo)) return 1;
          return 2;
        };
        const orderedChildren = [...node.children].sort((a, b) => priority(a) - priority(b));
        const visibleChildren = orderedChildren.slice(0, visibleCount);
        const remaining = orderedChildren.length - visibleChildren.length;
        return (
          <>
            <div className="orgpos-line-v" />
            <div className="orgpos-children-row">
              {visibleChildren.map(child => (
                <OrgBranch key={child.codigo} node={child} variant={variant} expandedIds={expandedIds} toggle={toggle}
                  matchedCodes={matchedCodes} filtrosActivos={filtrosActivos} highlightCode={highlightCode} onSelect={onSelect} />
              ))}
              {remaining > 0 && (
                <button className="orgpos-more-btn" onClick={(e) => { e.stopPropagation(); setVisibleCount(v => v + ORG_CHILDREN_PAGE); }}>
                  <IconChevronDown size={12} /> {remaining} más
                </button>
              )}
              {visibleCount > ORG_CHILDREN_PAGE && remaining === 0 && (
                <button className="orgpos-more-btn" onClick={(e) => { e.stopPropagation(); setVisibleCount(ORG_CHILDREN_PAGE); }}>
                  Mostrar menos
                </button>
              )}
            </div>
          </>
        );
      })()}
    </div>
  );
};

// ─── Detalle de posición ───

const OrgDetailModal = ({ codigo, variant, onClose, onVerEnDiagrama }) => {
  const p = window.OrgChartData.getByCodigo(codigo);
  if (!p) return null;
  const ocupMeta = OCUPACION_META[p.estadoOcupacion] || { label: p.estadoOcupacion, pill: '' };
  const estMeta = ESTRUCTURAL_META[p.estadoEstructural] || { label: p.estadoEstructural, pill: '' };
  const jefeCode = variant === 'aprobado' ? getAprobadoParent(p) : getRealParent(p);
  const jefe = jefeCode ? window.OrgChartData.getByCodigo(jefeCode) : null;

  return (
    <Modal open onClose={onClose} title={p.codigo} subtitle={p.cargo} width={540}
      footer={<>
        <button className="btn" onClick={onClose}>Cerrar</button>
        {onVerEnDiagrama && <button className="btn primary" onClick={onVerEnDiagrama}><IconSitemap size={14} /> Ver en el diagrama</button>}
      </>}>
      {variant === 'real' && p.esExt && (
        <div className="orgpos-alert orgreal-alert-warning">
          <div className="orgpos-alert-icon"><IconAlert size={18} /></div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div className="orgpos-alert-title">Fuera de HC aprobado</div>
            <div className="orgpos-alert-msg">
              Colaborador real sin una posición aprobada conciliada. Es un registro analítico para hacer visible al colaborador —
              no constituye una ampliación de headcount aprobada. Requiere revisión de Gestión Humana.
            </div>
          </div>
        </div>
      )}
      <div className="amp-resumen-grid">
        <div><span className="muted-xs">Código</span><div className="mono">{p.codigo}</div></div>
        <div><span className="muted-xs">Cargo</span><div>{p.cargo}</div></div>
        <div><span className="muted-xs">Sociedad</span><div>{p.sociedad}</div></div>
        <div><span className="muted-xs">Regional</span><div>{p.regional}</div></div>
        <div><span className="muted-xs">Planta</span><div>{p.planta}</div></div>
        <div><span className="muted-xs">Proceso</span><div>{p.proceso}</div></div>
        <div><span className="muted-xs">Subproceso</span><div>{p.subproceso}</div></div>
        <div><span className="muted-xs">Posición jefe</span><div className="mono">{jefeCode || '—'}{jefe ? ` · ${jefe.cargo}` : ''}</div></div>
        <div><span className="muted-xs">Estado estructural</span><div><span className={`pill ${estMeta.pill}`}>{estMeta.label}</span></div></div>
        <div><span className="muted-xs">Estado de ocupación</span><div><span className={`pill ${ocupMeta.pill}`}>{ocupMeta.label}</span></div></div>

        {variant === 'real' && (
          <>
            <div><span className="muted-xs">Nombre del empleado</span><div style={{ fontWeight: 600 }}>{p.nombreEmpleado || '—'}</div></div>
            <div><span className="muted-xs">Documento simulado</span><div className="mono">{p.documentoSimulado || '—'}</div></div>
            <div><span className="muted-xs">Jefe informado</span><div>{p.codigo === ORG_ROOT_CODE ? '—' : (p.jefeInformado || '—')}</div></div>
            <div><span className="muted-xs">Tipo de ocupación</span><div>{p.tipoOcupacion}</div></div>
            <div><span className="muted-xs">Estado de conciliación</span><div><span className={`pill ${(CONCILIACION_META[p.conciliacionHC] || {}).pill || ''}`}>{p.conciliacionHC}</span></div></div>
          </>
        )}
      </div>

      {variant === 'real' && p.coberturaTemporal && (
        <div style={{ marginTop: 18, paddingTop: 16, borderTop: '1px solid var(--border)' }}>
          <div className="muted-xs" style={{ marginBottom: 8 }}>
            Cobertura temporal {p.coberturaTemporal.demo && <span className="pill amber-soft" style={{ marginLeft: 6 }}>Demo</span>}
          </div>
          <div className="amp-resumen-grid">
            <div><span className="muted-xs">Titular</span><div>{p.coberturaTemporal.titular}</div></div>
            <div><span className="muted-xs">Estado de ausencia</span><div>{p.coberturaTemporal.estadoAusencia}</div></div>
            <div><span className="muted-xs">Reemplazo temporal</span><div>{p.coberturaTemporal.reemplazoTemporal}</div></div>
            <div><span className="muted-xs">Fecha estimada de finalización</span><div className="mono">{formatFechaLarga(p.coberturaTemporal.fechaFinEstimada)}</div></div>
          </div>
        </div>
      )}
    </Modal>
  );
};

// ─── Exportar (Excel / PDF / Imagen) — sin dependencias externas ───

const ORG_APROBADO_COLUMNS = [
  ['codigo', 'Código de posición'], ['cargo', 'Cargo'], ['sociedad', 'Sociedad'], ['regional', 'Regional'], ['planta', 'Planta'],
  ['jefeLabel', 'Posición jefe'], ['estadoEstructuralLabel', 'Estado estructural'], ['estadoOcupacionLabel', 'Estado de ocupación'],
];

const ORG_REAL_COLUMNS = [
  ['codigo', 'Código de posición'], ['cargo', 'Cargo'], ['sociedad', 'Sociedad'], ['regional', 'Regional'], ['planta', 'Planta'],
  ['jefeLabel', 'Posición jefe'], ['estadoEstructuralLabel', 'Estado estructural'], ['estadoOcupacionLabel', 'Estado de ocupación'],
  ['nombreEmpleadoLabel', 'Nombre del empleado'], ['documentoLabel', 'Número de documento simulado'],
  ['tipoOcupacion', 'Tipo de ocupación'], ['conciliacionHC', 'Conciliación HC'],
];

const _svgEscape = (s) => String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');

const _orgposDescargarBlob = (blob, filename) => {
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url; a.download = filename;
  document.body.appendChild(a); a.click(); document.body.removeChild(a);
  setTimeout(() => URL.revokeObjectURL(url), 1000);
};

const _orgBuildExportRows = (dataset, variant) => {
  const getParentCode = variant === 'aprobado' ? getAprobadoParent : getRealParent;
  return dataset.map(p => {
    const jefeCode = getParentCode(p);
    const jefe = jefeCode ? window.OrgChartData.getByCodigo(jefeCode) : null;
    return {
      ...p,
      jefeLabel: jefeCode ? `${jefeCode} — ${jefe ? jefe.cargo : ''}` : '—',
      estadoEstructuralLabel: (ESTRUCTURAL_META[p.estadoEstructural] || {}).label || p.estadoEstructural,
      estadoOcupacionLabel: (OCUPACION_META[p.estadoOcupacion] || {}).label || p.estadoOcupacion,
      nombreEmpleadoLabel: p.nombreEmpleado || '—',
      documentoLabel: p.documentoSimulado || '—',
    };
  });
};

const filtrosResumenTexto = (filtros) => {
  const parts = [];
  if (filtros.sociedad !== 'todas') parts.push(`Sociedad: ${filtros.sociedad}`);
  if (filtros.proceso !== 'todos') parts.push(`Proceso: ${filtros.proceso}`);
  if (filtros.subproceso !== 'todos') parts.push(`Subproceso: ${filtros.subproceso}`);
  if (filtros.planta !== 'todas') parts.push(`Planta: ${filtros.planta}`);
  return parts.length ? parts.join(' · ') : 'Sin filtros aplicados';
};

const exportarOrgExcel = (dataset, variant) => {
  const columns = variant === 'aprobado' ? ORG_APROBADO_COLUMNS : ORG_REAL_COLUMNS;
  const rows = _orgBuildExportRows(dataset, variant);
  const headHtml = columns.map(([, label]) => `<th>${label}</th>`).join('');
  const bodyHtml = rows.map(r => `<tr>${columns.map(([key]) => `<td>${_svgEscape(r[key] ?? '—')}</td>`).join('')}</tr>`).join('');
  const html = `<html><head><meta charset="UTF-8"></head><body><table border="1"><thead><tr>${headHtml}</tr></thead><tbody>${bodyHtml}</tbody></table></body></html>`;
  _orgposDescargarBlob(new Blob([html], { type: 'application/vnd.ms-excel' }), `organigrama-${variant}-${Date.now()}.xls`);
};

const exportarOrgPDF = (dataset, scope, variant, filtros) => {
  const columns = variant === 'aprobado' ? ORG_APROBADO_COLUMNS : ORG_REAL_COLUMNS;
  const rows = _orgBuildExportRows(dataset, variant);
  const bodyRows = rows.map(r => `<tr>${columns.map(([key]) => `<td>${_svgEscape(r[key] ?? '—')}</td>`).join('')}</tr>`).join('');
  const win = window.open('', '_blank', 'width=1100,height=800');
  if (!win) return;
  const titulo = variant === 'aprobado' ? 'Organigrama aprobado' : 'Organigrama real';
  win.document.write(`<html><head><title>${titulo}</title><style>
    body { font-family: Arial, sans-serif; padding: 24px; color: #1B1030; }
    h1 { font-size: 18px; margin-bottom: 2px; }
    .sub { font-size: 12px; color: #5B5468; margin-bottom: 4px; }
    table { width: 100%; border-collapse: collapse; font-size: 9.5px; margin-top: 14px; }
    th, td { border: 1px solid #E7E4EC; padding: 5px 6px; text-align: left; }
    th { background: #F8FAFC; }
  </style></head><body>
    <h1>${titulo} — Atica SOMOS</h1>
    <div class="sub">${scope === 'completo' ? 'Organigrama completo' : 'Organigrama filtrado'} · ${rows.length} posiciones · Generado ${new Date().toLocaleDateString('es-CO')}</div>
    <div class="sub">Filtros: ${_svgEscape(filtrosResumenTexto(filtros))}</div>
    <table><thead><tr>${columns.map(([, l]) => `<th>${l}</th>`).join('')}</tr></thead><tbody>${bodyRows}</tbody></table>
  </body></html>`);
  win.document.close();
  win.focus();
  setTimeout(() => win.print(), 300);
};

// Layout genérico de árbol → SVG (reutilizado tal cual entre real y aprobado).
const buildOrgTreeSVG = (root, accessors) => {
  const { getCode, getParentCode, getTitle, getBadgeLabel, getBadgeColor, getBorderColor, getFillColor } = accessors;
  const NODE_W = 150, NODE_H = 58, H_GAP = 16, V_GAP = 50;
  let leafCounter = 0;
  const positioned = [];

  const layout = (node, depth) => {
    let x;
    if (!node.children || node.children.length === 0) {
      x = leafCounter * (NODE_W + H_GAP);
      leafCounter++;
    } else {
      const childXs = node.children.map(c => layout(c, depth + 1));
      x = (childXs[0] + childXs[childXs.length - 1]) / 2;
    }
    positioned.push({ node, x, y: depth * (NODE_H + V_GAP) });
    return x;
  };
  layout(root, 0);

  const maxX = Math.max(...positioned.map(p => p.x)) + NODE_W;
  const maxY = Math.max(...positioned.map(p => p.y)) + NODE_H;
  const byCode = {}; positioned.forEach(p => { byCode[getCode(p.node)] = p; });

  const lines = positioned
    .filter(p => getParentCode(p.node) && byCode[getParentCode(p.node)])
    .map(p => {
      const parent = byCode[getParentCode(p.node)];
      const x1 = parent.x + NODE_W / 2, y1 = parent.y + NODE_H;
      const x2 = p.x + NODE_W / 2, y2 = p.y;
      return `<path d="M ${x1} ${y1} V ${y1 + V_GAP / 2} H ${x2} V ${y2}" fill="none" stroke="#E7E4EC" stroke-width="1.5" />`;
    }).join('');

  const boxes = positioned.map(p => {
    const n = p.node;
    return `<g>
      <rect x="${p.x}" y="${p.y}" width="${NODE_W}" height="${NODE_H}" rx="10" fill="${getFillColor(n)}" stroke="${getBorderColor(n)}" stroke-width="1.5" />
      <text x="${p.x + NODE_W / 2}" y="${p.y + 16}" font-family="JetBrains Mono, monospace" font-size="9.5" fill="#2E1A46" text-anchor="middle" font-weight="700">${_svgEscape(getCode(n))}</text>
      <text x="${p.x + NODE_W / 2}" y="${p.y + 30}" font-family="Arial, sans-serif" font-size="10.5" fill="#1B1030" text-anchor="middle" font-weight="600">${_svgEscape(getTitle(n))}</text>
      <text x="${p.x + NODE_W / 2}" y="${p.y + 43}" font-family="Arial, sans-serif" font-size="8.5" fill="${getBadgeColor(n)}" text-anchor="middle" font-weight="700">${_svgEscape(getBadgeLabel(n))}</text>
    </g>`;
  }).join('');

  return `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="${maxX + 20}" height="${maxY + 20}" viewBox="-10 -10 ${maxX + 20} ${maxY + 20}">
  <rect x="-10" y="-10" width="${maxX + 20}" height="${maxY + 20}" fill="#F8FAFC" />
  ${lines}
  ${boxes}
</svg>`;
};

const HEADER_H = 74;
const _wrapSvgWithHeader = (svg, { title, subtitle }) => {
  const m = svg.match(/viewBox="([-\d.]+) ([-\d.]+) ([\d.]+) ([\d.]+)"/);
  if (!m) return svg;
  const vx = Number(m[1]), vy = Number(m[2]), vw = Number(m[3]), vh = Number(m[4]);
  const inner = svg.replace(/<\?xml[^>]*\?>\s*/, '').replace(/^<svg[^>]*>/, '').replace(/<\/svg>\s*$/, '');
  return `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="${vw}" height="${vh + HEADER_H}" viewBox="${vx} ${vy - HEADER_H} ${vw} ${vh + HEADER_H}">
  <rect x="${vx}" y="${vy - HEADER_H}" width="${vw}" height="${vh + HEADER_H}" fill="#F8FAFC" />
  <rect x="${vx}" y="${vy - HEADER_H}" width="${vw}" height="${HEADER_H - 4}" fill="#FFFFFF" />
  <text x="${vx + 14}" y="${vy - HEADER_H + 24}" font-family="Arial, sans-serif" font-size="15" font-weight="700" fill="#2E1A46">${_svgEscape(title)}</text>
  <text x="${vx + 14}" y="${vy - HEADER_H + 44}" font-family="Arial, sans-serif" font-size="10" fill="#5B5468">${_svgEscape(subtitle)}</text>
  ${inner}
</svg>`;
};

const exportarOrgImagen = (dataset, allPositions, variant, scope, filtros) => {
  const getParentCode = variant === 'aprobado' ? getAprobadoParent : getRealParent;
  const withAncestors = pruneOrgWithAncestors(allPositions, buildOrgCodeIndex(allPositions), dataset, getParentCode);
  const root = buildOrgChartTree(withAncestors, getParentCode);
  if (!root) return;
  const svg = buildOrgTreeSVG(root, {
    getCode: n => n.codigo,
    getParentCode: n => getParentCode(n),
    getTitle: n => n.cargo,
    getBadgeLabel: n => (OCUPACION_META[n.estadoOcupacion] || {}).label || n.estadoOcupacion,
    getBadgeColor: n => n.estadoOcupacion === 'Ocupada' ? '#2AB36E' : (n.estadoOcupacion === 'Vacante' ? '#5B5468' : '#B5730A'),
    getBorderColor: n => (variant === 'real' && n.esExt) ? '#EF435C' : '#E7E4EC',
    getFillColor: n => (variant === 'real' && n.esExt) ? '#FEF1F3' : '#FFFFFF',
  });
  const titulo = variant === 'aprobado' ? 'Organigrama aprobado — Atica SOMOS' : 'Organigrama real — Atica SOMOS';
  const subtitulo = `${scope === 'completo' ? 'Organigrama completo' : 'Organigrama filtrado'} · ${dataset.length} posiciones · ${filtrosResumenTexto(filtros)} · Generado ${new Date().toLocaleDateString('es-CO')}`;
  const finalSvg = _wrapSvgWithHeader(svg, { title: titulo, subtitle: subtitulo });
  _orgposDescargarBlob(new Blob([finalSvg], { type: 'image/svg+xml' }), `organigrama-${variant}-${Date.now()}.svg`);
};

const ExportOrganigramaModal = ({ allItems, filteredItems, filtrosActivos, filtrosResumen, unitLabel = 'posiciones', onExcel, onPDF, onImagen, onClose }) => {
  const [scope, setScope] = React.useState(filtrosActivos ? 'filtrado' : 'completo');
  const [formato, setFormato] = React.useState('excel');

  const dataset = scope === 'completo' ? allItems : filteredItems;

  const exportar = () => {
    if (formato === 'excel') onExcel(dataset);
    else if (formato === 'pdf') onPDF(dataset, scope);
    else onImagen(dataset, scope);
    onClose();
  };

  return (
    <Modal open onClose={onClose} title="Exportar organigrama" subtitle="Elige el alcance y el formato" width={480}
      footer={<>
        <button className="btn" onClick={onClose}>Cancelar</button>
        <button className="btn primary" onClick={exportar}><IconExternal size={14} /> Exportar</button>
      </>}>
      <div className="muted-xs" style={{ marginBottom: 8 }}>Alcance</div>
      <div className="range-list" style={{ marginBottom: 18 }}>
        <label className={`range-row ${scope === 'completo' ? 'active' : ''}`}>
          <input type="radio" name="orgpos-export-scope" checked={scope === 'completo'} onChange={() => setScope('completo')} />
          <div><div style={{ fontSize: 13, fontWeight: 500 }}>Organigrama completo</div><div className="muted" style={{ fontSize: 11.5 }}>{allItems.length} {unitLabel}</div></div>
        </label>
        <label className={`range-row ${scope === 'filtrado' ? 'active' : ''} ${!filtrosActivos ? 'disabled' : ''}`}>
          <input type="radio" name="orgpos-export-scope" checked={scope === 'filtrado'} disabled={!filtrosActivos} onChange={() => setScope('filtrado')} />
          <div>
            <div style={{ fontSize: 13, fontWeight: 500 }}>Organigrama filtrado</div>
            <div className="muted" style={{ fontSize: 11.5 }}>
              {filtrosActivos ? `${filteredItems.length} ${unitLabel} · ${filtrosResumen}` : 'Aplica al menos un filtro para habilitar esta opción'}
            </div>
          </div>
        </label>
      </div>
      <div className="muted-xs" style={{ marginBottom: 8 }}>Formato</div>
      <div className="seg-pill" style={{ width: '100%' }}>
        <button className={`seg-pill-btn ${formato === 'excel' ? 'active' : ''}`} style={{ flex: 1 }} onClick={() => setFormato('excel')}>Excel</button>
        <button className={`seg-pill-btn ${formato === 'pdf' ? 'active' : ''}`} style={{ flex: 1 }} onClick={() => setFormato('pdf')}>PDF</button>
        <button className={`seg-pill-btn ${formato === 'imagen' ? 'active' : ''}`} style={{ flex: 1 }} onClick={() => setFormato('imagen')}>Imagen</button>
      </div>
    </Modal>
  );
};

// ─── Sección de una pestaña principal (real | aprobado): filtros + Diagrama/Maestro ───

const ORG_PER_PAGE = 25;

const OrgTabSection = ({ variant, focusCode, onConsumedFocus }) => {
  const dataset = React.useMemo(() => (
    variant === 'aprobado' ? window.OrgChartData.getAprobadas() : window.OrgChartData.getAll()
  ), [variant]);
  const getParentCode = variant === 'aprobado' ? getAprobadoParent : getRealParent;
  const byCode = React.useMemo(() => buildOrgCodeIndex(dataset), [dataset]);

  const [view, setView] = React.useState('diagrama');
  const [filtros, setFiltros] = React.useState(ORG_FILTROS_VACIOS);
  const [search, setSearch] = React.useState('');
  const [page, setPage] = React.useState(1);
  const [expandedIds, setExpandedIds] = React.useState(() => new Set([ORG_ROOT_CODE]));
  const [highlightCode, setHighlightCode] = React.useState(null);
  const [detalleCode, setDetalleCode] = React.useState(null);
  const [exportOpen, setExportOpen] = React.useState(false);

  const sociedadesOpts = React.useMemo(() => [...new Set(dataset.map(p => p.sociedad))].sort(), [dataset]);
  const procesosOpts = React.useMemo(() => [...new Set(dataset.map(p => p.proceso))].sort(), [dataset]);
  const subprocesosOpts = React.useMemo(() => [...new Set(
    dataset.filter(p => filtros.proceso === 'todos' || p.proceso === filtros.proceso).map(p => p.subproceso)
  )].sort(), [dataset, filtros.proceso]);
  const plantasOpts = React.useMemo(() => [...new Set(
    dataset.filter(p => filtros.sociedad === 'todas' || p.sociedad === filtros.sociedad).map(p => p.planta)
  )].sort(), [dataset, filtros.sociedad]);

  const filtrosActivos = filtros.sociedad !== 'todas' || filtros.proceso !== 'todos' || filtros.subproceso !== 'todos' || filtros.planta !== 'todas';

  const setFiltro = (k, v) => {
    setFiltros(f => {
      const next = { ...f, [k]: v };
      if (k === 'proceso') next.subproceso = 'todos';
      if (k === 'sociedad') next.planta = 'todas';
      return next;
    });
    setPage(1);
  };
  const limpiarFiltros = () => { setFiltros(ORG_FILTROS_VACIOS); setPage(1); };

  const filtered = React.useMemo(() => dataset
    .filter(p => filtros.sociedad === 'todas' || p.sociedad === filtros.sociedad)
    .filter(p => filtros.proceso === 'todos' || p.proceso === filtros.proceso)
    .filter(p => filtros.subproceso === 'todos' || p.subproceso === filtros.subproceso)
    .filter(p => filtros.planta === 'todas' || p.planta === filtros.planta)
  , [dataset, filtros]);

  const searched = React.useMemo(() => {
    const q = search.trim().toLowerCase();
    if (!q) return filtered;
    return filtered.filter(p => {
      if (p.codigo.toLowerCase().includes(q)) return true;
      if (p.cargo.toLowerCase().includes(q)) return true;
      if (variant === 'real') {
        if (p.nombreEmpleado && p.nombreEmpleado.toLowerCase().includes(q)) return true;
        if (p.documentoSimulado && p.documentoSimulado.includes(q)) return true;
      }
      return false;
    });
  }, [filtered, search, variant]);

  const matchedCodes = React.useMemo(() => new Set(filtered.map(p => p.codigo)), [filtered]);

  // El árbol se construye completo (barato, es solo enlazar padres/hijos) pero solo
  // se renderiza lo que está en expandedIds — así nunca se pinta de una vez el árbol
  // completo de miles de posiciones.
  const treeSource = filtrosActivos ? pruneOrgWithAncestors(dataset, byCode, filtered, getParentCode) : dataset;
  const root = React.useMemo(() => buildOrgChartTree(treeSource, getParentCode), [treeSource, variant]);

  // Al activar filtros, despliega automáticamente las ramas necesarias para ver los resultados.
  React.useEffect(() => {
    if (!filtrosActivos) return;
    const ancestorCodes = [];
    filtered.forEach(p => ancestorCodes.push(...getOrgAncestorCodes(byCode, p.codigo, getParentCode)));
    setExpandedIds(prev => new Set([...prev, ...ancestorCodes, ORG_ROOT_CODE]));
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [filtrosActivos, filtered]);

  // Expande las ramas necesarias, resalta y hace scroll hasta una posición del
  // Diagrama — usado tanto por la navegación entre módulos (focusCode) como por
  // la acción "Ver en el diagrama" desde el Maestro o el detalle.
  const goToNode = (codigo) => {
    const ancestors = getOrgAncestorCodes(byCode, codigo, getParentCode);
    setExpandedIds(prev => new Set([...prev, ...ancestors, codigo, ORG_ROOT_CODE]));
    setHighlightCode(codigo);
    setView('diagrama');
    setTimeout(() => {
      const el = document.getElementById(`orgpos-${codigo}`);
      if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' });
    }, 150);
  };

  React.useEffect(() => {
    if (!focusCode) return;
    const p = window.OrgChartData.getByCodigo(focusCode);
    if (!p || (variant === 'aprobado' && !p.enHCAprobado)) return;
    goToNode(focusCode);
    onConsumedFocus && onConsumedFocus();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [focusCode]);

  const toggle = (codigo) => {
    setExpandedIds(prev => {
      const next = new Set(prev);
      if (next.has(codigo)) next.delete(codigo); else next.add(codigo);
      return next;
    });
  };

  const columns = variant === 'aprobado' ? ORG_APROBADO_COLUMNS : ORG_REAL_COLUMNS;
  const totalPages = Math.max(1, Math.ceil(searched.length / ORG_PER_PAGE));
  const startIdx = (page - 1) * ORG_PER_PAGE;
  const pageRows = searched.slice(startIdx, startIdx + ORG_PER_PAGE);

  return (
    <div className="card">
      <div className="card-head">
        <div>
          <h3>{variant === 'real' ? 'Organigrama real' : 'Organigrama aprobado'}</h3>
          <div className="sub">
            {variant === 'real'
              ? 'Posiciones aprobadas junto con quién las ocupa hoy, más los colaboradores EXT sin posición aprobada conciliada.'
              : 'Solo posiciones estructurales aprobadas — nunca muestra empleados ni códigos EXT.'}
          </div>
        </div>
      </div>

      <div className="orgpos-toolbar">
        <div className="orgpos-filters">
          <IconFilter size={14} className="orgpos-filters-icon" />
          <OrgFilterSelect
            label="Sociedad" value={filtros.sociedad} isDefault={filtros.sociedad === 'todas'}
            onChange={v => setFiltro('sociedad', v)}
            options={[{ value: 'todas', label: 'Todas' }, ...sociedadesOpts.map(s => ({ value: s, label: s }))]}
          />
          <OrgFilterSelect
            label="Proceso" value={filtros.proceso} isDefault={filtros.proceso === 'todos'}
            onChange={v => setFiltro('proceso', v)}
            options={[{ value: 'todos', label: 'Todos' }, ...procesosOpts.map(p => ({ value: p, label: p }))]}
          />
          <OrgFilterSelect
            label="Subproceso" value={filtros.subproceso} isDefault={filtros.subproceso === 'todos'}
            onChange={v => setFiltro('subproceso', v)}
            options={[{ value: 'todos', label: 'Todos' }, ...subprocesosOpts.map(s => ({ value: s, label: s }))]}
          />
          <OrgFilterSelect
            label="Planta" value={filtros.planta} isDefault={filtros.planta === 'todas'}
            onChange={v => setFiltro('planta', v)}
            options={[{ value: 'todas', label: 'Todas' }, ...plantasOpts.map(s => ({ value: s, label: s }))]}
          />
          {filtrosActivos && (
            <button className="orgpos-clear-filters" onClick={limpiarFiltros}>
              <IconAlert size={12} style={{ transform: 'rotate(45deg)' }} /> Limpiar filtros
            </button>
          )}
          <span className="muted" style={{ fontSize: 12 }}>{filtered.length} posiciones</span>
        </div>
        <div className="orgpos-toolbar-actions">
          <button className="btn" onClick={() => setExportOpen(true)}>
            <IconExternal size={14} /> Exportar
          </button>
          <div className="seg-pill">
            <button className={`seg-pill-btn ${view === 'diagrama' ? 'active' : ''}`} onClick={() => setView('diagrama')}>Diagrama</button>
            <button className={`seg-pill-btn ${view === 'maestro' ? 'active' : ''}`} onClick={() => setView('maestro')}>Maestro</button>
          </div>
        </div>
      </div>

      {view === 'diagrama' ? (
        filtered.length === 0 ? (
          <div className="muted" style={{ fontSize: 12.5, padding: '24px 4px', textAlign: 'center' }}>Ninguna posición coincide con los filtros aplicados.</div>
        ) : (
          <div className="org-tree-scroll">
            {root && (
              <OrgBranch node={root} variant={variant} expandedIds={expandedIds} toggle={toggle}
                matchedCodes={matchedCodes} filtrosActivos={filtrosActivos} highlightCode={highlightCode}
                onSelect={setDetalleCode} />
            )}
          </div>
        )
      ) : (
        <>
          <div className="sst-quickfilter" style={{ marginBottom: 14 }}>
            <div className="search" style={{ flex: 'none', width: 320, margin: 0 }}>
              <span className="icon"><IconSearch size={14} /></span>
              <input
                placeholder={variant === 'real' ? 'Buscar por código, cargo, empleado o documento' : 'Buscar por código o cargo'}
                value={search} onChange={e => { setSearch(e.target.value); setPage(1); }}
              />
            </div>
          </div>
          <div className="doc-list-card">
            <table className="data-table">
              <thead>
                <tr>{columns.map(([key, label]) => <th key={key}>{label}</th>)}<th></th></tr>
              </thead>
              <tbody>
                {pageRows.map(p => {
                  const ocupMeta = OCUPACION_META[p.estadoOcupacion] || { label: p.estadoOcupacion, pill: '' };
                  const estMeta = ESTRUCTURAL_META[p.estadoEstructural] || { label: p.estadoEstructural, pill: '' };
                  const jefeCode = getParentCode(p);
                  const jefe = jefeCode ? window.OrgChartData.getByCodigo(jefeCode) : null;
                  const concMeta = CONCILIACION_META[p.conciliacionHC] || { pill: '' };
                  return (
                    <tr key={p.codigo} className={highlightCode === p.codigo ? 'orgpos-row-highlighted' : ''} onClick={() => setDetalleCode(p.codigo)} style={{ cursor: 'pointer' }}>
                      <td className="mono" style={{ fontWeight: 700 }}>{p.codigo}</td>
                      <td>{p.cargo}</td>
                      <td>{p.sociedad}</td>
                      <td>{p.regional}</td>
                      <td>{p.planta}</td>
                      <td className="mono muted">{jefeCode || '—'}{jefe ? <span className="muted"> · {jefe.cargo}</span> : ''}</td>
                      <td><span className={`pill ${estMeta.pill}`}>{estMeta.label}</span></td>
                      <td><span className={`pill ${ocupMeta.pill}`}>{ocupMeta.label}</span></td>
                      {variant === 'real' && (
                        <>
                          <td style={{ fontWeight: p.nombreEmpleado ? 500 : 400 }}>{p.nombreEmpleado || '—'}</td>
                          <td className="mono muted">{p.documentoSimulado || '—'}</td>
                          <td>{p.tipoOcupacion}</td>
                          <td><span className={`pill ${concMeta.pill}`}>{p.conciliacionHC}</span></td>
                        </>
                      )}
                      <td>
                        <button className="icon-action" title="Ver en el diagrama" onClick={(e) => { e.stopPropagation(); goToNode(p.codigo); }}>
                          <IconSitemap size={14} />
                        </button>
                      </td>
                    </tr>
                  );
                })}
                {pageRows.length === 0 && (
                  <tr><td colSpan={columns.length + 1} className="muted" style={{ textAlign: 'center', padding: 20, fontSize: 12.5 }}>Ninguna posición coincide con los filtros y la búsqueda.</td></tr>
                )}
              </tbody>
            </table>
            <div className="emp-pagination">
              <div className="muted" style={{ fontSize: 12.5 }}>
                Mostrando <b>{searched.length === 0 ? 0 : startIdx + 1}–{Math.min(startIdx + ORG_PER_PAGE, searched.length)}</b> de <b>{searched.length}</b>
              </div>
              <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 4 }}>
                <button className="btn" disabled={page === 1} style={{ padding: '6px 10px' }} onClick={() => setPage(p => Math.max(1, p - 1))}>
                  <IconChevronLeft size={14} />
                </button>
                {Array.from({ length: Math.min(totalPages, 5) }, (_, i) => i + 1).map(n => (
                  <button key={n} className={`btn ${page === n ? 'primary' : ''}`} style={{ padding: '6px 11px', minWidth: 32 }} onClick={() => setPage(n)}>{n}</button>
                ))}
                <button className="btn" disabled={page === totalPages} style={{ padding: '6px 10px' }} onClick={() => setPage(p => Math.min(totalPages, p + 1))}>
                  <IconChevronRight size={14} />
                </button>
              </div>
            </div>
          </div>
        </>
      )}

      {detalleCode && (
        <OrgDetailModal codigo={detalleCode} variant={variant} onClose={() => setDetalleCode(null)}
          onVerEnDiagrama={() => { const codigo = detalleCode; setDetalleCode(null); goToNode(codigo); }} />
      )}
      {exportOpen && (
        <ExportOrganigramaModal
          allItems={dataset} filteredItems={filtered} filtrosActivos={filtrosActivos}
          filtrosResumen={filtrosResumenTexto(filtros)} unitLabel="posiciones"
          onExcel={(ds) => exportarOrgExcel(ds, variant)}
          onPDF={(ds, scope) => exportarOrgPDF(ds, scope, variant, filtros)}
          onImagen={(ds, scope) => exportarOrgImagen(ds, dataset, variant, scope, filtros)}
          onClose={() => setExportOpen(false)}
        />
      )}
    </div>
  );
};

// ─── Raíz del módulo ───

const OrgChart = () => {
  const [mainTab, setMainTab] = React.useState('real');
  const [focusHint, setFocusHint] = React.useState(null);

  // Consume una sola vez la pista de navegación dejada por otro módulo
  // (ej. window.__navigateToModule('orgchart', { tab: 'aprobado', focusCode })).
  React.useEffect(() => {
    if (window.__pendingModuleHint) {
      const hint = window.__pendingModuleHint;
      delete window.__pendingModuleHint;
      setMainTab(hint.tab === 'aprobado' ? 'aprobado' : 'real');
      setFocusHint(hint);
    }
  }, []);

  return (
    <div className="main">
      <div className="page-head">
        <div>
          <h1 className="page-title">Organigrama</h1>
          <div className="page-subtitle">Estructura jerárquica de la compañía · corte de conciliación de headcount al 11 de septiembre de 2026</div>
        </div>
      </div>

      <div className="tabs">
        <button className={`tab ${mainTab === 'real' ? 'active' : ''}`} onClick={() => setMainTab('real')}>Organigrama real</button>
        <button className={`tab ${mainTab === 'aprobado' ? 'active' : ''}`} onClick={() => setMainTab('aprobado')}>Organigrama aprobado</button>
      </div>

      {mainTab === 'real' && (
        <OrgTabSection variant="real" focusCode={focusHint ? focusHint.focusCode : null} onConsumedFocus={() => setFocusHint(null)} />
      )}
      {mainTab === 'aprobado' && (
        <OrgTabSection variant="aprobado" focusCode={focusHint ? focusHint.focusCode : null} onConsumedFocus={() => setFocusHint(null)} />
      )}
    </div>
  );
};

window.OrgChart = OrgChart;
