import React, { useState, useRef, useCallback, useMemo } from 'react';
import { MOCK_PROJECTS, MOCK_TASKS, MOCK_TEAM, getAllProjects, getAllTasks, getProjectById } from '../../services/mockData';
import { useProject } from '../../context/ProjectContext';
import { useAuth } from '../../context/AuthContext';

// ── Layout constants ─────────────────────────────────────────────────────────
const COL_W   = 38;
const ROW_H   = 40;
const LABEL_W = 310;
const HDR_H   = 52;

// ── Status / priority config ──────────────────────────────────────────────────
const STATUS_COLOR = { COMPLETADA:'#10B981', EN_PROCESO:'#2563EB', EN_VERIFICACION:'#8B5CF6', PENDIENTE:'#94a3b8', BLOQUEADA:'#EF4444' };
const STATUS_LABEL = { COMPLETADA:'Completada', EN_PROCESO:'En proceso', EN_VERIFICACION:'Verificación', PENDIENTE:'Pendiente', BLOQUEADA:'Bloqueada' };
const PRIORITY_COLOR = { CRITICO:'#EF4444', ALTO:'#F97316', MEDIO:'#F59E0B', BAJO:'#94a3b8' };
const DEP_TYPE_LABEL = { FS:'Fin→Inicio', SS:'Inicio→Inicio', FF:'Fin→Fin', SF:'Inicio→Fin' };

// Roles que pueden editar cualquier tarea
const ADMIN_ROLES = ['SUPER_ADMIN', 'DIRECTOR', 'GERENTE'];

// ── Permissions ───────────────────────────────────────────────────────────────
function getCanEdit(user, task) {
  if (!user) return false;
  if (ADMIN_ROLES.includes(user.role)) return true;
  // Jefe de área: edita tareas de su proyecto
  if (user.role === 'JEFE_AREA') {
    const member = MOCK_TEAM.find(m => m.email === user.email);
    if (!member) return false;
    return task.assignee?.id === member.id;
  }
  // Colaborador: solo sus propias tareas
  const member = MOCK_TEAM.find(m => m.email === user.email);
  if (!member) return false;
  return task.assignee?.id === member.id;
}

// ── Date utils ────────────────────────────────────────────────────────────────
const MS      = 86400000;
const diffDays = (a, b) => Math.round((new Date(b) - new Date(a)) / MS);
const addDays  = (d, n) => { const r = new Date(d); r.setDate(r.getDate() + n); return r; };
const fmtD     = d => new Date(d).toLocaleDateString('es-MX', { day:'2-digit', month:'short' });
const fmtM     = d => new Date(d).toLocaleString('es-MX', { month:'short', year:'2-digit' }).toUpperCase();
const fmtDT    = d => new Date(d).toLocaleString('es-MX', { day:'2-digit', month:'short', hour:'2-digit', minute:'2-digit' });

// ── CPM ───────────────────────────────────────────────────────────────────────
function computeCPM(tasks) {
  const map = {};
  tasks.forEach(t => { map[t.id] = { ...t, ES:0, EF:0, LS:0, LF:0, float:0 }; });

  let changed = true, iter = 0;
  while (changed && iter++ < 50) {
    changed = false;
    tasks.forEach(t => {
      const node = map[t.id];
      let maxEF = 0;
      (t.predecessors || []).forEach(p => {
        const pred = map[p.id];
        if (!pred) return;
        const lag = p.lag || 0;
        if (p.type === 'FS')      maxEF = Math.max(maxEF, pred.EF + lag);
        else if (p.type === 'SS') maxEF = Math.max(maxEF, pred.ES + lag);
        else if (p.type === 'FF') maxEF = Math.max(maxEF, pred.EF + lag - (node.dur || 14));
        else                      maxEF = Math.max(maxEF, pred.ES + lag - (node.dur || 14));
      });
      const dur    = t.isMilestone ? 0 : (t.estimatedHours ? Math.ceil(t.estimatedHours / 8) : 14);
      const newES  = maxEF;
      const newEF  = newES + dur;
      if (newES !== node.ES || newEF !== node.EF) { node.ES = newES; node.EF = newEF; node.dur = dur; changed = true; }
    });
  }

  const projectEnd = Math.max(...Object.values(map).map(n => n.EF));
  Object.values(map).forEach(n => { n.LF = projectEnd; n.LS = n.LF - n.dur; });
  changed = true; iter = 0;
  while (changed && iter++ < 50) {
    changed = false;
    [...tasks].reverse().forEach(t => {
      const node = map[t.id];
      let minLS = projectEnd;
      tasks.forEach(s => {
        (s.predecessors || []).forEach(p => {
          if (p.id !== t.id) return;
          const succ = map[s.id];
          const lag = p.lag || 0;
          if (p.type === 'FS')      minLS = Math.min(minLS, succ.LS - lag);
          else if (p.type === 'SS') minLS = Math.min(minLS, succ.LS - lag);
          else                      minLS = Math.min(minLS, succ.LF - lag);
        });
      });
      const newLF = minLS, newLS = newLF - node.dur;
      if (newLF !== node.LF || newLS !== node.LS) { node.LF = newLF; node.LS = newLS; changed = true; }
    });
  }
  Object.values(map).forEach(n => { n.float = n.LS - n.ES; n.isCritical = n.float <= 0; });
  return map;
}

function buildRows(project, tasks) {
  if (!tasks.length) return [];
  const cpm = computeCPM(tasks);
  const projectStart = new Date(project.startDate);
  return tasks.map(t => {
    const node = cpm[t.id] || { ES:0, EF:14, float:99, isCritical:false, dur:14 };
    const start = addDays(projectStart, node.ES);
    const end   = addDays(projectStart, node.EF);
    const pct   = t.progressPct ?? ({ COMPLETADA:100, EN_PROCESO:55, EN_VERIFICACION:80, BLOQUEADA:20, PENDIENTE:0 }[t.status] ?? 0);
    return { ...t, _start:start, _end:end, _dur:node.dur, _pct:pct, _float:node.float, _isCritical:node.isCritical || t.isCritical, _projectCode:project.code, _projectId:project.id };
  });
}

function getWindow(rows) {
  if (!rows.length) { const n=new Date(); return {start:new Date(n.getFullYear(),n.getMonth(),1), days:90}; }
  const mn = new Date(Math.min(...rows.map(r=>r._start))); mn.setDate(mn.getDate()-5); mn.setDate(1);
  const mx = new Date(Math.max(...rows.map(r=>r._end)));   mx.setDate(mx.getDate()+14);
  return { start:mn, days: Math.max(diffDays(mn, mx)+1, 30) };
}

function monthTicks(winStart, total) {
  const out=[]; let d=new Date(winStart); d.setDate(1);
  while(diffDays(winStart,d)<total){ const off=Math.max(0,diffDays(winStart,d)); const next=new Date(d.getFullYear(),d.getMonth()+1,1); const days=Math.min(diffDays(d,next),total-off); if(days>0) out.push({label:fmtM(d),off,days}); d=next; }
  return out;
}
function weekTicks(winStart, total) {
  const out=[]; const d=new Date(winStart); while(d.getDay()!==1) d.setDate(d.getDate()+1);
  while(diffDays(winStart,d)<total){ const off=diffDays(winStart,d); if(off>=0&&off<total) out.push({off,label:fmtD(d)}); d.setDate(d.getDate()+7); }
  return out;
}

// ── Dependency arrows ─────────────────────────────────────────────────────────
function DepArrows({ rows, winStart, showDeps }) {
  if (!showDeps) return null;
  const rowIndex = {};
  rows.forEach((r,i) => { rowIndex[r.id] = i; });
  const arrows = [];
  rows.forEach((t,ti) => {
    (t.predecessors || []).forEach((p,pi) => {
      const pi2 = rowIndex[p.id];
      if (pi2 === undefined) return;
      const pred = rows[pi2];
      const predEndX   = diffDays(winStart, pred._end)  * COL_W;
      const taskStartX = diffDays(winStart, t._start)   * COL_W;
      const predY = pi2 * ROW_H + ROW_H/2;
      const taskY = ti  * ROW_H + ROW_H/2;
      const color = t._isCritical && pred._isCritical ? '#EF4444' : '#94a3b8';
      const id  = `arr-${t.id}-${p.id}-${pi}`;
      const x1  = predEndX + 2, x2 = taskStartX - 2;
      const midX = x1 + Math.max((x2-x1)/2, 12);
      const dPath = `M ${x1} ${predY} L ${midX} ${predY} L ${midX} ${taskY} L ${x2} ${taskY}`;
      arrows.push(
        <g key={id}>
          <defs><marker id={`ah-${id}`} markerWidth="6" markerHeight="6" refX="5" refY="2" orient="auto"><path d="M0,0 L0,4 L6,2 z" fill={color}/></marker></defs>
          <path d={dPath} fill="none" stroke={color} strokeWidth={1.5} strokeDasharray={t._isCritical && pred._isCritical ? 'none' : '4 2'} markerEnd={`url(#ah-${id})`} opacity={0.7}/>
        </g>
      );
    });
  });
  return <>{arrows}</>;
}

// ── Hover tooltip ─────────────────────────────────────────────────────────────
function GanttTooltip({ t, x, y }) {
  if (!t) return null;
  return (
    <div className="fixed z-50 pointer-events-none bg-white dark:bg-surface-800 border border-slate-200 dark:border-surface-600 rounded-xl shadow-2xl p-3.5 w-64 text-xs"
      style={{ left:x+14, top:y-10, maxWidth:260 }}>
      <div className="flex items-start gap-2 mb-2">
        {t.isMilestone && <span className="text-amber-400 mt-0.5">◆</span>}
        <p className="font-semibold text-slate-900 dark:text-white leading-snug">{t.title}</p>
      </div>
      <p className="text-slate-400 dark:text-gray-500 font-mono mb-2">{t.wbsCode} · {t._projectCode}</p>
      <div className="space-y-1.5">
        <TRow k="Estado"      v={STATUS_LABEL[t.status]}/>
        <TRow k="Prioridad"   v={t.priority}/>
        <TRow k="ES / EF"     v={`${fmtD(t._start)} — ${fmtD(t._end)}`}/>
        <TRow k="Duración"    v={`${t._dur} días`}/>
        <TRow k="Float"       v={`${t._float} días`} cls={t._float<=0?'text-red-500':t._float<=3?'text-amber-500':'text-emerald-500'}/>
        <TRow k="Avance"      v={`${t._pct}%`} cls="font-bold" style={{color:STATUS_COLOR[t.status]}}/>
      </div>
      {t._isCritical && <p className="mt-1.5 text-red-500 font-semibold">⚡ Ruta crítica</p>}
    </div>
  );
}
function TRow({k,v,cls='text-slate-700 dark:text-gray-300'}) {
  return <div className="flex justify-between"><span className="text-slate-400">{k}</span><span className={cls}>{v}</span></div>;
}

// ── Status badges ─────────────────────────────────────────────────────────────
const STATUS_BG   = { COMPLETADA:'bg-emerald-100 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-300', EN_PROCESO:'bg-blue-100 text-blue-700 dark:bg-blue-500/20 dark:text-blue-300', EN_VERIFICACION:'bg-purple-100 text-purple-700 dark:bg-purple-500/20 dark:text-purple-300', PENDIENTE:'bg-slate-100 text-slate-600 dark:bg-gray-500/20 dark:text-gray-300', BLOQUEADA:'bg-red-100 text-red-700 dark:bg-red-500/20 dark:text-red-300' };
const PRIORITY_BG = { CRITICO:'bg-red-100 text-red-700 dark:bg-red-500/20 dark:text-red-300', ALTO:'bg-orange-100 text-orange-700 dark:bg-orange-500/20 dark:text-orange-300', MEDIO:'bg-amber-100 text-amber-700 dark:bg-amber-500/20 dark:text-amber-300', BAJO:'bg-slate-100 text-slate-600 dark:bg-gray-500/20 dark:text-gray-300' };

const ALLOWED_TRANSITIONS = {
  PENDIENTE:       ['EN_PROCESO'],
  EN_PROCESO:      ['EN_VERIFICACION','BLOQUEADA'],
  EN_VERIFICACION: ['COMPLETADA','EN_PROCESO'],
  BLOQUEADA:       ['PENDIENTE','EN_PROCESO'],
  COMPLETADA:      [],
};

const EVIDENCE_TYPES = ['DOCUMENTO','FOTO','VIDEO','INFORME','FIRMA','OTRO'];

// ── Task detail modal ─────────────────────────────────────────────────────────
function TaskDetailModal({ task:t, canEdit, onSave, onClose }) {
  const [editing, setEditing] = useState(false);
  const [form, setForm] = useState({
    status:         t.status,
    progressPct:    t.progressPct ?? t._pct ?? 0,
    actualHours:    t.actualHours ?? 0,
    actualCost:     t.actualCost ?? 0,
    assigneeId:     t.assignee?.id ?? '',
    blockedReason:  t.blockedReason ?? '',
    authorizedById: t.authorizedBy?.id ?? '',
    priority:       t.priority,
    closureNote:    t.closureNote ?? '',
  });
  const [evidence,    setEvidence]    = useState(t.evidence    ?? []);
  const [progressLog, setProgressLog] = useState(t.progressLog ?? []);
  const [newEvDesc,   setNewEvDesc]   = useState('');
  const [newEvType,   setNewEvType]   = useState('DOCUMENTO');
  const [newNote,     setNewNote]     = useState('');

  if (!t) return null;

  const set = (k,v) => setForm(f=>({...f,[k]:v}));
  const barColor = STATUS_COLOR[form.status] ?? '#94a3b8';
  const allowedNext = ALLOWED_TRANSITIONS[form.status] ?? [];
  const allStatuses = ['PENDIENTE','EN_PROCESO','EN_VERIFICACION','BLOQUEADA','COMPLETADA'];
  const visiblePct  = form.status === 'COMPLETADA' ? 100 : form.status === 'PENDIENTE' ? 0 : form.progressPct;

  // Evidence required when closing
  const wantsToComplete  = form.status === 'COMPLETADA' || form.status === 'EN_VERIFICACION';
  const evidenceOk = !wantsToComplete || (evidence.length > 0 && form.closureNote.trim().length > 0);
  const blockedOk  = form.status !== 'BLOQUEADA' || (form.blockedReason.trim() && form.authorizedById);
  const canSave    = evidenceOk && blockedOk;

  const addEvidence = () => {
    if (!newEvDesc.trim()) return;
    setEvidence(prev => [...prev, { id: Date.now(), type: newEvType, desc: newEvDesc.trim(), addedAt: new Date().toISOString() }]);
    setNewEvDesc(''); setNewEvType('DOCUMENTO');
  };

  const addProgressNote = () => {
    if (!newNote.trim()) return;
    setProgressLog(prev => [...prev, { id: Date.now(), pct: visiblePct, note: newNote.trim(), addedAt: new Date().toISOString() }]);
    setNewNote('');
  };

  const handleSave = () => {
    if (!canSave) return;
    const assignee    = MOCK_TEAM.find(m => m.id === form.assigneeId) || t.assignee || null;
    const authorizedBy = form.authorizedById ? MOCK_TEAM.find(m => m.id === form.authorizedById) || null : null;
    onSave({ ...t, ...form, progressPct: visiblePct, assignee, authorizedBy, evidence, progressLog });
    setEditing(false);
    onClose();
  };

  const EvidenceTypeBadge = ({ type }) => (
    <span className="text-xs font-mono bg-slate-100 dark:bg-surface-700 text-slate-500 dark:text-gray-400 px-1.5 py-0.5 rounded">{type}</span>
  );

  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center p-4">
      <div className="absolute inset-0 bg-black/40 backdrop-blur-sm" onClick={onClose}/>
      <div className="relative w-full max-w-lg bg-white dark:bg-surface-800 rounded-2xl shadow-2xl border border-slate-200 dark:border-surface-700 overflow-hidden max-h-[92vh] flex flex-col">

        {/* Header */}
        <div className="px-6 py-5 border-b border-slate-100 dark:border-surface-700 flex-shrink-0" style={{borderTop:`3px solid ${barColor}`}}>
          <div className="flex items-start justify-between gap-3">
            <div className="flex-1 min-w-0">
              <div className="flex items-center gap-2 mb-1 flex-wrap">
                {t.isMilestone && <span className="text-amber-400">◆</span>}
                <span className="text-xs font-mono text-slate-400 dark:text-gray-500">{t.wbsCode}</span>
                <span className="text-xs font-mono text-slate-300 dark:text-gray-600">{t._projectCode}</span>
                {t._isCritical && <span className="text-xs px-1.5 py-0.5 rounded-full bg-red-100 text-red-700 dark:bg-red-500/20 dark:text-red-400 font-medium">⚡ Ruta crítica</span>}
              </div>
              <h2 className="text-base font-bold text-slate-900 dark:text-white leading-snug">{t.title}</h2>
            </div>
            <div className="flex items-center gap-2 flex-shrink-0">
              {!t.isMilestone && !editing && canEdit && (
                <button onClick={() => setEditing(true)}
                  className="text-xs px-3 py-1.5 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-medium transition cursor-pointer">
                  Editar
                </button>
              )}
              {!t.isMilestone && !editing && !canEdit && (
                <span className="text-xs px-3 py-1.5 rounded-lg bg-slate-100 dark:bg-surface-700 text-slate-500 dark:text-gray-400 font-medium">
                  Solo lectura
                </span>
              )}
              <button onClick={onClose} className="p-1.5 rounded-lg text-slate-400 hover:text-slate-700 dark:hover:text-white hover:bg-black/5 transition cursor-pointer">
                <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}><path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
              </button>
            </div>
          </div>
          <div className="flex gap-2 mt-3 flex-wrap">
            <span className={`text-xs px-2 py-0.5 rounded-full font-medium ${STATUS_BG[form.status]||''}`}>{STATUS_LABEL[form.status]}</span>
            <span className={`text-xs px-2 py-0.5 rounded-full font-medium ${PRIORITY_BG[form.priority]||''}`}>{form.priority}</span>
            {t.phase && <span className="text-xs px-2 py-0.5 rounded-full bg-slate-100 dark:bg-surface-700 text-slate-500 dark:text-gray-400">{t.phase}</span>}
          </div>
        </div>

        {/* Body */}
        <div className="overflow-y-auto flex-1 p-6 space-y-5">

          {/* Progress bar */}
          {!t.isMilestone && (
            <div>
              <div className="flex justify-between text-xs mb-1.5">
                <span className="text-slate-500 dark:text-gray-400 font-medium">Avance</span>
                <span className="font-bold tabular-nums" style={{color:barColor}}>{visiblePct}%</span>
              </div>
              <div className="h-2.5 bg-slate-100 dark:bg-surface-700 rounded-full overflow-hidden">
                <div className="h-full rounded-full transition-all" style={{width:`${visiblePct}%`,background:barColor}}/>
              </div>
              {editing && form.status !== 'COMPLETADA' && form.status !== 'PENDIENTE' && (
                <input type="range" min={0} max={99} value={form.progressPct} onChange={e=>set('progressPct',Number(e.target.value))}
                  className="w-full mt-2 accent-blue-600 cursor-pointer"/>
              )}
            </div>
          )}

          {/* Edit section */}
          {editing && !t.isMilestone && (
            <div className="space-y-3 bg-blue-50 dark:bg-blue-500/10 border border-blue-100 dark:border-blue-500/20 rounded-xl p-4">
              <p className="text-xs font-bold text-blue-700 dark:text-blue-400 uppercase tracking-wider">Editar tarea</p>

              <div>
                <label className="field-label">Estado</label>
                <select value={form.status} onChange={e=>set('status',e.target.value)} className="field-input">
                  {allStatuses.map(s=>(
                    <option key={s} value={s} disabled={s!==form.status && !allowedNext.includes(s)}>
                      {STATUS_LABEL[s]}{s!==form.status&&!allowedNext.includes(s)?' (no permitido)':''}
                    </option>
                  ))}
                </select>
              </div>

              <div>
                <label className="field-label">Prioridad</label>
                <select value={form.priority} onChange={e=>set('priority',e.target.value)} className="field-input">
                  {['CRITICO','ALTO','MEDIO','BAJO'].map(p=><option key={p}>{p}</option>)}
                </select>
              </div>

              <div>
                <label className="field-label">Responsable</label>
                <select value={form.assigneeId} onChange={e=>set('assigneeId',e.target.value)} className="field-input">
                  <option value="">Sin asignar</option>
                  {MOCK_TEAM.map(m=><option key={m.id} value={m.id}>{m.firstName} {m.lastName} — {m.title}</option>)}
                </select>
              </div>

              <div className="grid grid-cols-2 gap-3">
                <div><label className="field-label">Horas reales</label><input type="number" min={0} value={form.actualHours} onChange={e=>set('actualHours',Number(e.target.value))} className="field-input"/></div>
                <div><label className="field-label">Costo real ($)</label><input type="number" min={0} value={form.actualCost} onChange={e=>set('actualCost',Number(e.target.value))} className="field-input"/></div>
              </div>

              {/* Nota de progreso inline */}
              <div>
                <label className="field-label">Nota de progreso (opcional)</label>
                <div className="flex gap-2">
                  <input value={newNote} onChange={e=>setNewNote(e.target.value)} placeholder="Describe el avance actual..."
                    className="field-input flex-1" onKeyDown={e=>e.key==='Enter'&&(e.preventDefault(),addProgressNote())}/>
                  <button onClick={addProgressNote} className="px-3 py-2 bg-slate-100 dark:bg-surface-700 text-slate-600 dark:text-gray-300 rounded-lg text-xs font-medium hover:bg-slate-200 dark:hover:bg-surface-600 transition cursor-pointer">Añadir</button>
                </div>
              </div>

              {/* Bloqueo */}
              {form.status === 'BLOQUEADA' && (
                <>
                  <div>
                    <label className="field-label">Motivo bloqueo <span className="text-red-500">*</span></label>
                    <textarea rows={2} value={form.blockedReason} onChange={e=>set('blockedReason',e.target.value)}
                      className="field-input resize-none" placeholder="Describe el motivo del bloqueo..."/>
                  </div>
                  <div>
                    <label className="field-label">Autorizado por <span className="text-red-500">*</span></label>
                    <select value={form.authorizedById} onChange={e=>set('authorizedById',e.target.value)} className="field-input">
                      <option value="">Seleccionar...</option>
                      {MOCK_TEAM.filter(m=>ADMIN_ROLES.includes(m.role)).map(m=><option key={m.id} value={m.id}>{m.firstName} {m.lastName}</option>)}
                    </select>
                  </div>
                </>
              )}

              {/* Evidencias — requerido para COMPLETADA / EN_VERIFICACION */}
              {wantsToComplete && (
                <div className="space-y-3 bg-emerald-50 dark:bg-emerald-500/10 border border-emerald-200 dark:border-emerald-500/20 rounded-xl p-3">
                  <p className="text-xs font-bold text-emerald-700 dark:text-emerald-400 uppercase tracking-wider">
                    Evidencias de cierre <span className="text-red-500">*</span>
                  </p>
                  {evidence.length === 0 && (
                    <p className="text-xs text-emerald-700 dark:text-emerald-400 opacity-70">Debes adjuntar al menos 1 evidencia para cerrar esta tarea.</p>
                  )}
                  {evidence.map(ev=>(
                    <div key={ev.id} className="flex items-start gap-2 text-xs">
                      <EvidenceTypeBadge type={ev.type}/>
                      <span className="flex-1 text-slate-700 dark:text-gray-300">{ev.desc}</span>
                      <button onClick={()=>setEvidence(p=>p.filter(e=>e.id!==ev.id))} className="text-red-400 hover:text-red-600 cursor-pointer">×</button>
                    </div>
                  ))}
                  <div className="flex gap-2">
                    <select value={newEvType} onChange={e=>setNewEvType(e.target.value)}
                      className="text-xs bg-white dark:bg-surface-700 border border-slate-200 dark:border-surface-600 rounded-lg px-2 py-1.5 text-slate-700 dark:text-gray-300 w-28 focus:outline-none focus:ring-1 focus:ring-emerald-400">
                      {EVIDENCE_TYPES.map(t=><option key={t}>{t}</option>)}
                    </select>
                    <input value={newEvDesc} onChange={e=>setNewEvDesc(e.target.value)} placeholder="Descripción de la evidencia..."
                      className="flex-1 text-xs bg-white dark:bg-surface-700 border border-slate-200 dark:border-surface-600 rounded-lg px-3 py-1.5 text-slate-800 dark:text-gray-200 placeholder-slate-400 focus:outline-none focus:ring-1 focus:ring-emerald-400"
                      onKeyDown={e=>e.key==='Enter'&&(e.preventDefault(),addEvidence())}/>
                    <button onClick={addEvidence} className="px-3 py-1.5 bg-emerald-600 hover:bg-emerald-700 text-white text-xs font-medium rounded-lg transition cursor-pointer">+</button>
                  </div>
                  <div>
                    <label className="field-label">Nota de cierre <span className="text-red-500">*</span></label>
                    <textarea rows={2} value={form.closureNote} onChange={e=>set('closureNote',e.target.value)}
                      className="field-input resize-none" placeholder="Describe lo que se entregó, condiciones de cierre..."/>
                  </div>
                </div>
              )}

              {!evidenceOk && wantsToComplete && (
                <p className="text-xs text-red-600 dark:text-red-400 font-medium">
                  {evidence.length===0 ? '⚠ Agrega al menos 1 evidencia.' : '⚠ Escribe una nota de cierre.'}
                </p>
              )}
            </div>
          )}

          {/* CPM info */}
          <div className="grid grid-cols-2 gap-3">
            {[
              { label:'Inicio planif. (ES)', val: fmtD(t._start) },
              { label:'Fin planif. (EF)',    val: fmtD(t._end) },
              { label:'Duración',            val: t.isMilestone ? 'Hito' : `${t._dur} días` },
              { label:'Float / holgura',     val: t._isCritical ? '0 días (crítica)' : `${t._float} días`,
                cls: t._isCritical ? 'text-red-500 font-bold' : t._float<=3 ? 'text-amber-500 font-semibold' : 'text-emerald-600 dark:text-emerald-400 font-semibold' },
            ].map(({label,val,cls})=>(
              <div key={label} className="bg-slate-50 dark:bg-surface-700 rounded-xl p-3">
                <p className="text-xs text-slate-400 dark:text-gray-500 mb-0.5">{label}</p>
                <p className={`text-sm font-semibold text-slate-800 dark:text-gray-200 ${cls||''}`}>{val}</p>
              </div>
            ))}
          </div>

          {/* Hours / cost read mode */}
          {!t.isMilestone && !editing && (
            <div className="grid grid-cols-2 gap-3">
              <div className="bg-slate-50 dark:bg-surface-700 rounded-xl p-3">
                <p className="text-xs text-slate-400 dark:text-gray-500 mb-1">Horas est. / real</p>
                <p className="text-sm font-semibold text-slate-800 dark:text-gray-200 tabular-nums">{t.estimatedHours??'—'}h / {form.actualHours}h</p>
              </div>
              <div className="bg-slate-50 dark:bg-surface-700 rounded-xl p-3">
                <p className="text-xs text-slate-400 dark:text-gray-500 mb-1">Costo plan. / real</p>
                <p className="text-sm font-semibold text-slate-800 dark:text-gray-200 tabular-nums">${(t.plannedCost??0).toLocaleString()} / ${form.actualCost.toLocaleString()}</p>
              </div>
            </div>
          )}

          {/* Assignee read mode */}
          {!editing && (()=>{
            const assignee = form.assigneeId ? MOCK_TEAM.find(m=>m.id===form.assigneeId) : t.assignee;
            return assignee ? (
              <div className="flex items-center gap-3">
                <div className="w-8 h-8 rounded-full flex items-center justify-center text-xs font-bold flex-shrink-0"
                  style={{background:(assignee.color||'#2563eb')+'22',color:assignee.color||'#2563eb'}}>
                  {assignee.avatar}
                </div>
                <div>
                  <p className="text-sm font-medium text-slate-800 dark:text-gray-200">{assignee.firstName} {assignee.lastName}</p>
                  <p className="text-xs text-slate-400 dark:text-gray-500">{assignee.title}</p>
                </div>
              </div>
            ) : null;
          })()}

          {/* Blocked reason */}
          {!editing && form.status === 'BLOQUEADA' && form.blockedReason && (
            <div className="bg-red-50 dark:bg-red-500/10 border border-red-200 dark:border-red-500/20 rounded-xl p-3">
              <p className="text-xs font-bold text-red-600 dark:text-red-400 mb-1">Motivo bloqueo</p>
              <p className="text-sm text-red-700 dark:text-red-300">{form.blockedReason}</p>
            </div>
          )}

          {/* Evidence list (read mode) */}
          {evidence.length > 0 && (
            <div>
              <p className="text-xs font-bold text-slate-500 dark:text-gray-400 uppercase tracking-wider mb-2">Evidencias ({evidence.length})</p>
              <div className="space-y-1.5">
                {evidence.map(ev=>(
                  <div key={ev.id} className="flex items-start gap-2 text-xs bg-slate-50 dark:bg-surface-700 rounded-lg px-3 py-2">
                    <EvidenceTypeBadge type={ev.type}/>
                    <span className="flex-1 text-slate-700 dark:text-gray-300">{ev.desc}</span>
                    <span className="text-slate-400 dark:text-gray-500 whitespace-nowrap">{fmtDT(ev.addedAt)}</span>
                  </div>
                ))}
              </div>
            </div>
          )}

          {/* Closure note read mode */}
          {form.closureNote && (
            <div className="bg-emerald-50 dark:bg-emerald-500/10 border border-emerald-200 dark:border-emerald-500/20 rounded-xl p-3">
              <p className="text-xs font-bold text-emerald-600 dark:text-emerald-400 mb-1">Nota de cierre</p>
              <p className="text-sm text-emerald-700 dark:text-emerald-300">{form.closureNote}</p>
            </div>
          )}

          {/* Progress log */}
          {progressLog.length > 0 && (
            <div>
              <p className="text-xs font-bold text-slate-500 dark:text-gray-400 uppercase tracking-wider mb-2">Registro de avances ({progressLog.length})</p>
              <div className="space-y-1.5">
                {[...progressLog].reverse().map(entry=>(
                  <div key={entry.id} className="flex items-start gap-2 text-xs bg-slate-50 dark:bg-surface-700 rounded-lg px-3 py-2">
                    <span className="font-mono font-bold tabular-nums w-10 text-blue-600 dark:text-blue-400">{entry.pct}%</span>
                    <span className="flex-1 text-slate-700 dark:text-gray-300">{entry.note}</span>
                    <span className="text-slate-400 dark:text-gray-500 whitespace-nowrap">{fmtDT(entry.addedAt)}</span>
                  </div>
                ))}
              </div>
            </div>
          )}

          {/* Predecessors */}
          {(t.predecessors||[]).length > 0 && (
            <div>
              <p className="text-xs font-bold text-slate-500 dark:text-gray-400 uppercase tracking-wider mb-2">Predecesoras</p>
              <div className="flex flex-wrap gap-1.5">
                {t.predecessors.map(p=>(
                  <span key={p.id} className="text-xs px-2.5 py-1 bg-slate-100 dark:bg-surface-700 text-slate-600 dark:text-gray-300 rounded-lg font-mono">
                    {p.id} — {DEP_TYPE_LABEL[p.type]}{p.lag?` +${p.lag}d`:''}
                  </span>
                ))}
              </div>
            </div>
          )}
        </div>

        {/* Footer */}
        {editing && (
          <div className="px-6 py-4 border-t border-slate-100 dark:border-surface-700 flex gap-3 justify-end flex-shrink-0 bg-white dark:bg-surface-800">
            <button onClick={()=>setEditing(false)}
              className="px-4 py-2 text-sm font-medium rounded-xl border border-slate-200 dark:border-surface-600 text-slate-600 dark:text-gray-300 hover:bg-slate-50 dark:hover:bg-surface-700 transition cursor-pointer">
              Cancelar
            </button>
            <button onClick={handleSave} disabled={!canSave}
              className="px-4 py-2 text-sm font-semibold rounded-xl bg-blue-600 hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed text-white transition cursor-pointer">
              Guardar cambios
            </button>
          </div>
        )}
      </div>
    </div>
  );
}

// ── Project color palette ─────────────────────────────────────────────────────
const PROJECT_COLORS = ['#2563EB','#10B981','#8B5CF6','#F59E0B','#EF4444','#06B6D4'];

// ── Main ──────────────────────────────────────────────────────────────────────
export default function GanttPage() {
  const { selectedProjectId } = useProject();
  const { user } = useAuth();

  const [statusF,     setStatusF]     = useState('all');
  const [showDeps,    setShowDeps]    = useState(true);
  const [showCP,      setShowCP]      = useState(true);
  const [tooltip,     setTooltip]     = useState(null);
  const [detailTask,  setDetailTask]  = useState(null);
  const [localTasks,  setLocalTasks]  = useState(() => {
    const allTasks = getAllTasks();
    const clone = {};
    Object.entries(allTasks).forEach(([k,v]) => { clone[k] = v.map(t=>({...t})); });
    return clone;
  });

  const scrollRef = useRef(null);
  const headerRef = useRef(null);
  const labelRef  = useRef(null);
  const syncing   = useRef(false);

  // Build rows: single project or all projects
  const { rows, projectsInView } = useMemo(() => {
    const statusFilter = t => statusF === 'all' || t.status === statusF;

    if (selectedProjectId) {
      const project = getProjectById(selectedProjectId || getAllProjects()[0]?.id);
      const tasks   = (localTasks[project.id] || []).filter(statusFilter);
      return { rows: buildRows(project, tasks), projectsInView: [project] };
    }

    // All projects — combine rows
    const allRows = [];
    getAllProjects().forEach(proj => {
      const tasks = (localTasks[proj.id] || []).filter(statusFilter);
      if (tasks.length > 0) allRows.push(...buildRows(proj, tasks));
    });
    return { rows: allRows, projectsInView: getAllProjects() };
  }, [selectedProjectId, localTasks, statusF]);

  const onSaveTask = useCallback(updated => {
    setLocalTasks(prev => {
      const projectId = updated._projectId || selectedProjectId || getAllProjects()[0]?.id;
      const list = (prev[projectId] || []).map(t => t.id === updated.id ? updated : t);
      return { ...prev, [projectId]: list };
    });
    setDetailTask(null);
  }, [selectedProjectId]);

  const win    = getWindow(rows);
  const months = monthTicks(win.start, win.days);
  const weeks  = weekTicks(win.start, win.days);
  const todayOff = diffDays(win.start, new Date());
  const totalW   = win.days * COL_W;
  const totalH   = rows.length * ROW_H;

  const critCount  = rows.filter(r=>r._isCritical&&!r.isMilestone).length;
  const milestones = rows.filter(r=>r.isMilestone).length;

  const onChartScroll = useCallback(e => {
    if (headerRef.current) headerRef.current.scrollLeft = e.target.scrollLeft;
    if (syncing.current) return;
    syncing.current = true;
    if (labelRef.current) labelRef.current.scrollTop = e.target.scrollTop;
    syncing.current = false;
  }, []);
  const onLabelScroll = useCallback(e => {
    if (syncing.current) return;
    syncing.current = true;
    if (scrollRef.current) scrollRef.current.scrollTop = e.target.scrollTop;
    syncing.current = false;
  }, []);

  // Project color by code
  const projColorMap = useMemo(() => {
    const m = {};
    getAllProjects().forEach((p,i) => { m[p.id] = PROJECT_COLORS[i % PROJECT_COLORS.length]; });
    return m;
  }, []);

  const isAllProjects = !selectedProjectId;
  const currentProject = isAllProjects ? null : getProjectById(selectedProjectId || getAllProjects()[0]?.id);

  return (
    <div className="flex flex-col h-[calc(100vh-112px)] gap-3">

      {/* Toolbar */}
      <div className="flex flex-wrap items-center justify-between gap-3 flex-shrink-0">
        <div>
          <h1 className="text-lg font-bold text-slate-900 dark:text-white">Diagrama de Gantt</h1>
          <p className="text-sm text-slate-400 dark:text-gray-500 mt-0.5">
            {rows.length} tareas · {critCount} en ruta crítica · {milestones} hitos
            {isAllProjects && projectsInView.length > 0 && ` · ${projectsInView.filter(p=>(localTasks[p.id]||[]).length>0).length} proyectos`}
          </p>
        </div>
        <div className="flex gap-2 flex-wrap items-center">
          {isAllProjects
            ? <span className="text-sm bg-slate-100 dark:bg-surface-700 border border-slate-200 dark:border-surface-700 rounded-lg px-3 py-1.5 text-slate-500 dark:text-gray-400 font-medium">Todos los proyectos</span>
            : <span className="text-sm bg-slate-100 dark:bg-surface-700 border border-slate-200 dark:border-surface-700 rounded-lg px-3 py-1.5 text-slate-700 dark:text-gray-300 font-medium">{currentProject?.code}</span>
          }
          <select value={statusF} onChange={e=>setStatusF(e.target.value)}
            className="text-sm bg-white dark:bg-surface-800 border border-slate-200 dark:border-surface-700 rounded-lg px-3 py-1.5 text-slate-700 dark:text-gray-300 cursor-pointer">
            <option value="all">Todos los estados</option>
            {Object.entries(STATUS_LABEL).map(([k,v])=><option key={k} value={k}>{v}</option>)}
          </select>
          <button onClick={()=>setShowDeps(v=>!v)}
            className={`text-xs px-3 py-1.5 rounded-lg border transition cursor-pointer font-medium ${showDeps?'bg-blue-50 dark:bg-blue-500/10 border-blue-300 dark:border-blue-500/50 text-blue-700 dark:text-blue-400':'border-slate-200 dark:border-surface-700 text-slate-500 dark:text-gray-400'}`}>
            ↗ Dependencias
          </button>
          <button onClick={()=>setShowCP(v=>!v)}
            className={`text-xs px-3 py-1.5 rounded-lg border transition cursor-pointer font-medium ${showCP?'bg-red-50 dark:bg-red-500/10 border-red-300 dark:border-red-500/50 text-red-700 dark:text-red-400':'border-slate-200 dark:border-surface-700 text-slate-500 dark:text-gray-400'}`}>
            ⚡ Ruta crítica
          </button>
        </div>
      </div>

      {/* Role indicator */}
      {user && !ADMIN_ROLES.includes(user.role) && (
        <div className="flex items-center gap-2 bg-amber-50 dark:bg-amber-500/10 border border-amber-200 dark:border-amber-500/20 rounded-xl px-4 py-2 flex-shrink-0">
          <svg className="w-3.5 h-3.5 text-amber-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}><path strokeLinecap="round" strokeLinejoin="round" d="M12 9v2m0 4h.01M10.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"/></svg>
          <p className="text-xs text-amber-700 dark:text-amber-300">
            <strong>Vista restringida:</strong> Puedes editar solo las tareas asignadas a ti. Las demás son de solo lectura.
          </p>
        </div>
      )}

      {/* Legend */}
      <div className="flex gap-4 flex-wrap text-xs flex-shrink-0">
        {Object.entries(STATUS_LABEL).map(([k,v])=>(
          <button key={k} onClick={()=>setStatusF(f=>f===k?'all':k)}
            className={`flex items-center gap-1.5 transition cursor-pointer ${statusF===k?'opacity-100 font-semibold':'opacity-60 hover:opacity-90'}`}>
            <span className="w-2.5 h-2.5 rounded-sm" style={{background:STATUS_COLOR[k]}}/><span className="text-slate-600 dark:text-gray-300">{v}</span>
          </button>
        ))}
        {isAllProjects && getAllProjects().filter(p=>(localTasks[p.id]||[]).length>0).map((p,i)=>(
          <span key={p.id} className="flex items-center gap-1.5 text-slate-500 dark:text-gray-400">
            <span className="w-2.5 h-2.5 rounded-full" style={{background:PROJECT_COLORS[i%PROJECT_COLORS.length]}}/>
            {p.code}
          </span>
        ))}
        <span className="flex items-center gap-1.5 text-slate-400 dark:text-gray-500"><span className="w-px h-3 bg-red-400"/>Hoy</span>
        <span className="flex items-center gap-1.5 text-slate-400 dark:text-gray-500"><span className="w-3 h-3 rotate-45 inline-block bg-amber-400 rounded-sm"/>Hito</span>
      </div>

      {/* Empty state */}
      {rows.length === 0 && (
        <div className="flex-1 bg-white dark:bg-surface-800 rounded-2xl border border-slate-200 dark:border-surface-700 shadow-sm flex flex-col items-center justify-center gap-3">
          <svg className="w-12 h-12 text-slate-300 dark:text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1}><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>
          <p className="text-sm font-semibold text-slate-500 dark:text-gray-400">Sin tareas para mostrar</p>
          <p className="text-xs text-slate-400 dark:text-gray-500">
            {isAllProjects ? 'No hay tareas registradas en ningún proyecto.' : `El proyecto ${currentProject?.code} no tiene tareas o no hay datos disponibles.`}
          </p>
        </div>
      )}

      {/* Gantt panel */}
      {rows.length > 0 && (
        <div className="flex-1 min-h-0 bg-white dark:bg-surface-800 rounded-2xl border border-slate-200 dark:border-surface-700 shadow-sm overflow-hidden flex flex-col">

          {/* Header */}
          <div className="flex flex-shrink-0 border-b border-slate-200 dark:border-surface-700 bg-white dark:bg-surface-800 z-20">
            <div className="flex-shrink-0 border-r border-slate-200 dark:border-surface-700 flex items-end px-4 pb-2" style={{width:LABEL_W}}>
              <span className="text-xs font-bold text-slate-400 dark:text-gray-500 uppercase tracking-wider">WBS · Tarea</span>
            </div>
            <div ref={headerRef} className="flex-1 overflow-hidden" style={{height:HDR_H}}>
              <div style={{width:totalW, position:'relative', height:HDR_H}}>
                <div className="flex border-b border-slate-100 dark:border-surface-700" style={{height:26}}>
                  {months.map((m,i)=>(
                    <div key={i} className="flex-shrink-0 px-2 flex items-center border-r border-slate-100 dark:border-surface-700" style={{width:m.days*COL_W}}>
                      <span className="text-xs font-bold text-slate-500 dark:text-gray-400 tracking-wider">{m.label}</span>
                    </div>
                  ))}
                </div>
                <div style={{height:26, position:'relative'}}>
                  {weeks.map((w,i)=>(
                    <div key={i} className="absolute flex items-center" style={{left:w.off*COL_W, width:7*COL_W, height:'100%'}}>
                      <div className="w-px h-2 bg-slate-200 dark:bg-surface-600 self-start"/>
                      <span className="text-xs text-slate-300 dark:text-gray-600 ml-0.5 whitespace-nowrap">{w.label}</span>
                    </div>
                  ))}
                  {todayOff>=0 && todayOff<=win.days && (
                    <div className="absolute top-0 bottom-0 w-0.5 bg-red-400/70" style={{left:todayOff*COL_W}}/>
                  )}
                </div>
              </div>
            </div>
          </div>

          {/* Body */}
          <div className="flex flex-1 min-h-0">
            {/* Label column */}
            <div ref={labelRef} onScroll={onLabelScroll}
              className="flex-shrink-0 overflow-y-auto border-r border-slate-200 dark:border-surface-700 scrollbar-thin"
              style={{width:LABEL_W}}>
              {rows.map((t,i) => {
                const cp = showCP && t._isCritical;
                const projColor = projColorMap[t._projectId];
                const editable = getCanEdit(user, t);
                return (
                  <div key={t.id} style={{height:ROW_H}} onDoubleClick={()=>setDetailTask(t)}
                    className={`flex items-center gap-2 px-3 border-b border-slate-50 dark:border-surface-700 hover:bg-slate-50 dark:hover:bg-surface-700/40 transition-colors cursor-pointer select-none ${cp?'bg-red-50/30 dark:bg-red-500/5':''}`}>
                    {isAllProjects && <span className="w-1 h-4 rounded-full flex-shrink-0" style={{background:projColor}}/>}
                    {t.isMilestone
                      ? <span className="text-amber-400 text-xs flex-shrink-0">◆</span>
                      : <span className="w-1.5 h-1.5 rounded-full flex-shrink-0" style={{background:PRIORITY_COLOR[t.priority]}}/>
                    }
                    <span className="text-xs font-mono text-slate-400 dark:text-gray-500 flex-shrink-0 w-8">{t.wbsCode}</span>
                    <span className={`text-xs truncate leading-tight flex-1 ${t.isMilestone?'font-semibold text-amber-600 dark:text-amber-400':cp?'text-red-600 dark:text-red-400':'text-slate-700 dark:text-gray-300'}`}>
                      {t.title}
                    </span>
                    {cp && <span className="text-red-400 text-xs flex-shrink-0">⚡</span>}
                    {!editable && !t.isMilestone && (
                      <svg className="w-3 h-3 text-slate-300 dark:text-gray-600 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0110 0v4"/></svg>
                    )}
                  </div>
                );
              })}
            </div>

            {/* Chart */}
            <div ref={scrollRef} onScroll={onChartScroll} className="flex-1 overflow-auto">
              <div style={{width:totalW, height:totalH, position:'relative'}}>

                {/* Weekend shading */}
                {Array.from({length:Math.ceil(win.days/7)}).map((_,wi)=>{
                  const d0=new Date(win.start), dow=d0.getDay();
                  const satOff=((6-dow)+7)%7+wi*7;
                  return [satOff,satOff+1].filter(d=>d<win.days).map(d=>(
                    <div key={`we-${wi}-${d}`} className="absolute top-0 bg-slate-50 dark:bg-surface-700/30 pointer-events-none" style={{left:d*COL_W,width:COL_W,height:totalH}}/>
                  ));
                })}

                {/* Grid lines */}
                {weeks.map((w,i)=>(
                  <div key={i} className="absolute top-0 bottom-0 w-px bg-slate-100 dark:bg-surface-700/60 pointer-events-none" style={{left:w.off*COL_W,height:totalH}}/>
                ))}

                {/* Today line */}
                {todayOff>=0 && todayOff<=win.days && (
                  <div className="absolute top-0 w-0.5 bg-red-400/60 z-10 pointer-events-none" style={{left:todayOff*COL_W,height:totalH}}/>
                )}

                {/* Dependency arrows */}
                <svg className="absolute top-0 left-0 z-10 pointer-events-none" style={{width:totalW,height:totalH}}>
                  <DepArrows rows={rows} winStart={win.start} showDeps={showDeps}/>
                </svg>

                {/* Task bars */}
                {rows.map((t,i) => {
                  const left  = Math.max(0, diffDays(win.start, t._start)) * COL_W;
                  const wPx   = Math.max(t.isMilestone ? 0 : t._dur * COL_W, t.isMilestone ? 0 : 20);
                  const color = isAllProjects ? projColorMap[t._projectId] : (STATUS_COLOR[t.status]);
                  const statusColor = STATUS_COLOR[t.status];
                  const cp    = showCP && t._isCritical;
                  const barH  = t.isMilestone ? 0 : 20;
                  const top   = i * ROW_H + (ROW_H - barH) / 2;
                  const editable = getCanEdit(user, t);

                  return (
                    <div key={t.id}>
                      <div className={`absolute w-full ${cp?'bg-red-50/20 dark:bg-red-500/5':''}`} style={{top:i*ROW_H,height:ROW_H}}/>

                      {t.isMilestone ? (
                        <div className="absolute z-20 cursor-pointer"
                          style={{left:left-10, top:i*ROW_H+ROW_H/2-10, width:20, height:20}}
                          onMouseEnter={e=>setTooltip({t,x:e.clientX,y:e.clientY})}
                          onMouseMove={e=>setTooltip(tt=>tt?{...tt,x:e.clientX,y:e.clientY}:null)}
                          onMouseLeave={()=>setTooltip(null)}
                          onDoubleClick={()=>setDetailTask(t)}>
                          <div className="w-4 h-4 rotate-45 rounded-sm m-0.5"
                            style={{background:cp?'#EF4444':'#F59E0B',boxShadow:`0 0 6px ${cp?'#EF444466':'#F59E0B66'}`}}/>
                        </div>
                      ) : (
                        <div className={`absolute z-20 rounded-lg overflow-hidden ${editable?'cursor-pointer':'cursor-default'}`}
                          style={{left,top,width:wPx,height:barH,background:`${statusColor}22`,border:`1.5px solid ${cp?statusColor+'cc':statusColor+'66'}`,boxShadow:cp?`0 0 0 1px ${statusColor}44`:'none'}}
                          onMouseEnter={e=>setTooltip({t,x:e.clientX,y:e.clientY})}
                          onMouseMove={e=>setTooltip(tt=>tt?{...tt,x:e.clientX,y:e.clientY}:null)}
                          onMouseLeave={()=>setTooltip(null)}
                          onDoubleClick={()=>setDetailTask(t)}>
                          {/* Progress fill */}
                          <div className="h-full rounded-md" style={{width:`${t._pct}%`,background:statusColor}}/>
                          {/* Project color stripe in multi-project mode */}
                          {isAllProjects && <div className="absolute left-0 top-0 bottom-0 w-1" style={{background:color}}/>}
                          {wPx > 50 && (
                            <span className="absolute inset-y-0 left-2 flex items-center text-white font-medium pointer-events-none" style={{fontSize:9}}>
                              {t._pct > 0 ? `${t._pct}%` : ''}
                            </span>
                          )}
                        </div>
                      )}

                      {/* Float bar */}
                      {!t.isMilestone && t._float > 0 && t._float < 30 && (
                        <div className="absolute z-10 rounded-r-lg opacity-30"
                          style={{left:left+wPx,top:top+barH/4,width:t._float*COL_W,height:barH/2,background:'#94a3b8',borderTop:'1px dashed #94a3b8'}}/>
                      )}

                      <span className="absolute text-slate-300 dark:text-gray-600 whitespace-nowrap pointer-events-none"
                        style={{left:left+wPx+4,top:i*ROW_H+ROW_H/2-6,fontSize:9}}>
                        {fmtD(t._end)}
                      </span>
                    </div>
                  );
                })}
              </div>
            </div>
          </div>
        </div>
      )}

      {tooltip && <GanttTooltip t={tooltip.t} x={tooltip.x} y={tooltip.y}/>}
      {detailTask && (
        <TaskDetailModal
          task={detailTask}
          canEdit={getCanEdit(user, detailTask)}
          onSave={onSaveTask}
          onClose={()=>setDetailTask(null)}
        />
      )}
    </div>
  );
}
