// Gantt chart — canvas rendering (two-canvas: sticky header + body)

const DAY_WIDTH_DEFAULT = 36;
const HEADER_H = 36;
const ROW_H_DEFAULT = 48;
const BAR_H_DEFAULT = 32;

const dayMs = 86400000;
const startOfDay = (d) => { const x = new Date(d); x.setHours(0, 0, 0, 0); return x; };
const addDays = (d, n) => new Date(startOfDay(d).getTime() + n * dayMs);
const daysBetween = (a, b) => Math.round((startOfDay(b) - startOfDay(a)) / dayMs);
const fmtDate = (d) => {
  const dt = new Date(d);
  return `${String(dt.getDate()).padStart(2,'0')}/${String(dt.getMonth()+1).padStart(2,'0')}`;
};
const fmtDateFull = (d) => {
  const dt = new Date(d);
  return `${String(dt.getDate()).padStart(2,'0')}/${String(dt.getMonth()+1).padStart(2,'0')}/${dt.getFullYear()}`;
};
const fmtISO = (d) => {
  const dt = startOfDay(d);
  return `${dt.getFullYear()}-${String(dt.getMonth()+1).padStart(2,'0')}-${String(dt.getDate()).padStart(2,'0')}`;
};
const parseISO = (s) => { const [y,m,d] = s.split('-').map(Number); return new Date(y, m-1, d); };

const MONTH_NAMES = ['Gen','Feb','Mar','Apr','Mag','Giu','Lug','Ago','Set','Ott','Nov','Dic'];

function getISOWeek(d) {
  const dt = new Date(+startOfDay(d));
  dt.setDate(dt.getDate() + 4 - (dt.getDay() || 7));
  const y0 = new Date(dt.getFullYear(), 0, 1);
  return Math.ceil(((dt - y0) / dayMs + 1) / 7);
}

function computeWeeks(rangeStart, rangeEnd) {
  const result = [];
  const dow = rangeStart.getDay() || 7;
  let cursor = addDays(startOfDay(rangeStart), 1 - dow);
  while (cursor <= rangeEnd) {
    const nextMon = addDays(cursor, 7);
    const segStart = cursor < rangeStart ? rangeStart : cursor;
    const segEnd   = nextMon > rangeEnd  ? rangeEnd  : addDays(nextMon, -1);
    result.push({
      weekNum:    getISOWeek(segStart),
      offsetDays: daysBetween(rangeStart, segStart),
      lengthDays: daysBetween(segStart, nextMon > rangeEnd ? addDays(rangeEnd, 1) : nextMon),
      label:      `Sett ${getISOWeek(segStart)}`,
      dateRange:  `${segStart.getDate()}–${segEnd.getDate()} ${MONTH_NAMES[segEnd.getMonth()]}`,
    });
    cursor = nextMon;
  }
  return result;
}

function computeRange(tasks, paddingBefore = 5, paddingAfter = 5) {
  if (tasks.length === 0) {
    const today = startOfDay(new Date());
    return { start: addDays(today, -paddingBefore), end: addDays(today, paddingAfter) };
  }
  let minD = startOfDay(tasks[0].start), maxD = startOfDay(tasks[0].end);
  tasks.forEach((t) => {
    const s = startOfDay(t.start), e = startOfDay(t.end);
    if (s < minD) minD = s;
    if (e > maxD) maxD = e;
  });
  return { start: addDays(minD, -paddingBefore), end: addDays(maxD, paddingAfter) };
}

function computeMonths(rangeStart, rangeEnd) {
  const result = [];
  let cursor = startOfDay(rangeStart);
  while (cursor <= rangeEnd) {
    const ms = new Date(cursor);
    const mec = new Date(ms.getFullYear(), ms.getMonth() + 1, 1);
    const me = mec > rangeEnd ? new Date(rangeEnd.getTime() + dayMs) : mec;
    result.push({
      label: `${MONTH_NAMES[ms.getMonth()]} ${ms.getFullYear()}`,
      short: MONTH_NAMES[ms.getMonth()],
      offsetDays: Math.max(0, daysBetween(rangeStart, cursor)),
      lengthDays: daysBetween(cursor, me),
    });
    cursor = mec;
  }
  return result;
}

function getThemeVars(el, barH = BAR_H_DEFAULT) {
  const cs = getComputedStyle(el);
  const get = (v) => cs.getPropertyValue(v).trim();
  return {
    bgElev:       get('--bg-elev')       || '#ffffff',
    bgRail:       get('--bg-rail')       || '#ecebe5',
    fg:           get('--fg')            || '#1a1816',
    fgMute:       get('--fg-mute')       || '#6b6863',
    border:       get('--border')        || '#d8d5cc',
    borderStrong: get('--border-strong') || '#1a1816',
    grid:         get('--grid')          || '#e3e0d6',
    gridStrong:   get('--grid-strong')   || '#c9c5b9',
    today:        get('--today')         || '#d97757',
    weekend:      get('--weekend')       || 'rgba(0,0,0,0.025)',
    fontUi:           get('--font-ui')            || "'Inter Tight', system-ui, sans-serif",
    barRadius:        Math.min(parseFloat(get('--bar-radius')) || 3, barH / 2),
    monthSepW:        parseFloat(get('--month-sep-w'))  || 2,
    monthLabelColor:  get('--month-label-color')  || get('--fg') || '#1a1816',
    barLabelDark:     get('--bar-label-dark')     || '#1a1816',
    barLabelLight:    get('--bar-label-light')    || '#ffffff',
  };
}

function drawArrow(ctx, x1, y1, x2, y2, color) {
  const GAP = 12, AH = 7, AW = 5;
  const cornerX = Math.max(x1 + GAP, x2 - GAP);
  ctx.strokeStyle = color;
  ctx.lineWidth = 1.5;
  ctx.setLineDash([5, 4]);
  ctx.beginPath();
  ctx.moveTo(x1, y1);
  ctx.lineTo(cornerX, y1);
  ctx.lineTo(cornerX, y2);
  ctx.lineTo(x2, y2);
  ctx.stroke();
  ctx.setLineDash([]);
  ctx.fillStyle = color;
  ctx.beginPath();
  ctx.moveTo(x2, y2);
  ctx.lineTo(x2 - AH, y2 - AW);
  ctx.lineTo(x2 - AH, y2 + AW);
  ctx.closePath();
  ctx.fill();
}

function taskBounds(task, i, range, DAY_WIDTH, rowH, barH) {
  const startOff = daysBetween(range.start, task.start);
  const midY = i * rowH + rowH / 2;
  if (task.type === 'milestone') {
    const s = Math.min(barH / 2, rowH / 2 - 2, 13);
    const cx = startOff * DAY_WIDTH + DAY_WIDTH / 2;
    return { isMilestone: true, cx, cy: midY, s, midY, rightX: cx + s + 1, leftX: cx - s - 1 };
  }
  const dur = Math.max(1, daysBetween(task.start, task.end) + 1);
  const bL = startOff * DAY_WIDTH + 2;
  const bW = Math.max(8, dur * DAY_WIDTH - 4);
  const bT = i * rowH + Math.round((rowH - barH) / 2);
  return { isMilestone: false, bL, bT, bW, bH: barH, midY, rightX: bL + bW, leftX: bL };
}

// Draws a rounded-rect path (no fill/stroke — caller decides)
function roundRect(ctx, x, y, w, h, r) {
  const rr = Math.max(0, Math.min(r, w / 2, h / 2));
  ctx.beginPath();
  if (rr <= 0) { ctx.rect(x, y, w, h); return; }
  ctx.moveTo(x + rr, y);
  ctx.lineTo(x + w - rr, y);   ctx.arcTo(x+w, y,   x+w, y+rr,   rr);
  ctx.lineTo(x + w, y + h - rr); ctx.arcTo(x+w, y+h, x+w-rr, y+h, rr);
  ctx.lineTo(x + rr, y + h);   ctx.arcTo(x,   y+h, x,   y+h-rr, rr);
  ctx.lineTo(x, y + rr);       ctx.arcTo(x,   y,   x+rr, y,     rr);
  ctx.closePath();
}

function needsDarkText(hex) {
  const c = hex.replace('#','');
  if (c.length < 6) return false;
  const r = parseInt(c.slice(0,2),16), g = parseInt(c.slice(2,4),16), b = parseInt(c.slice(4,6),16);
  return (0.299*r + 0.587*g + 0.114*b) / 255 > 0.62;
}

// Sets canvas backing-store size for DPR, returns scaled ctx
function setupCanvas(canvas, cssW, cssH) {
  const dpr = window.devicePixelRatio || 1;
  canvas.width  = Math.round(cssW * dpr);
  canvas.height = Math.round(cssH * dpr);
  canvas.style.width  = cssW + 'px';
  canvas.style.height = cssH + 'px';
  const ctx = canvas.getContext('2d');
  ctx.scale(dpr, dpr);
  return ctx;
}

function drawHeader(canvas, { months, weeks, DAY_WIDTH, canvasW, vars, timelineView }) {
  if (!canvas || canvasW <= 0) return;
  const ctx = setupCanvas(canvas, canvasW, HEADER_H);

  ctx.fillStyle = vars.bgElev;
  ctx.fillRect(0, 0, canvasW, HEADER_H);

  if (timelineView === 'weeks') {
    weeks.forEach((w, i) => {
      const x = w.offsetDays * DAY_WIDTH;
      const ww = w.lengthDays * DAY_WIDTH;
      if (i > 0) {
        ctx.strokeStyle = vars.gridStrong;
        ctx.lineWidth = vars.monthSepW;
        ctx.beginPath();
        ctx.moveTo(Math.round(x) + 0.5, 0);
        ctx.lineTo(Math.round(x) + 0.5, HEADER_H - 1);
        ctx.stroke();
      }
      const pad = 6;
      ctx.save();
      ctx.beginPath();
      ctx.rect(x + pad, 0, Math.max(0, ww - pad - 2), HEADER_H);
      ctx.clip();
      ctx.fillStyle = vars.monthLabelColor;
      ctx.font = `600 10px ${vars.fontUi}`;
      ctx.textBaseline = 'top';
      ctx.fillText(w.label, x + pad, 5);
      ctx.fillStyle = vars.fgMute;
      ctx.font = `400 9px ${vars.fontUi}`;
      ctx.textBaseline = 'bottom';
      ctx.fillText(w.dateRange, x + pad, HEADER_H - 5);
      ctx.restore();
    });
  } else {
    months.forEach((m, i) => {
      const x = m.offsetDays * DAY_WIDTH;
      const w = m.lengthDays * DAY_WIDTH;
      if (i > 0) {
        ctx.strokeStyle = vars.gridStrong;
        ctx.lineWidth = vars.monthSepW;
        ctx.beginPath();
        ctx.moveTo(Math.round(x) + 0.5, 0);
        ctx.lineTo(Math.round(x) + 0.5, HEADER_H - 1);
        ctx.stroke();
      }
      const compact = w < 90 || DAY_WIDTH < 14;
      const label = (compact ? m.short : m.label).toUpperCase();
      const pad = i > 0 ? 12 : 10;
      ctx.save();
      ctx.beginPath();
      ctx.rect(x + pad, 0, Math.max(0, w - pad - 4), HEADER_H);
      ctx.clip();
      ctx.fillStyle = vars.monthLabelColor;
      ctx.font = `600 11px ${vars.fontUi}`;
      ctx.textBaseline = 'middle';
      ctx.fillText(label, x + pad, HEADER_H / 2);
      ctx.restore();
    });
  }

  // Bottom border
  ctx.strokeStyle = vars.border;
  ctx.lineWidth = 1;
  ctx.beginPath();
  ctx.moveTo(0, HEADER_H - 0.5);
  ctx.lineTo(canvasW, HEADER_H - 0.5);
  ctx.stroke();
}

function buildBarLabel(task, barContent) {
  const parts = [];
  if (barContent.includes('title')) parts.push(task.name);
  if (barContent.includes('start')) parts.push(fmtDate(task.start));
  if (barContent.includes('end'))   parts.push(fmtDate(task.end));
  return parts.join(' · ');
}

function drawBody(canvas, { tasks, days, range, DAY_WIDTH, canvasW, selectedId, drag,
                             showWeekends, showDayGrid, showTodayLine, showRowLines, months, weeks, barContent, rowH, barH, vars, timelineView }) {
  if (!canvas || canvasW <= 0 || tasks.length === 0) return;
  const canvasH = tasks.length * rowH;
  const ctx = setupCanvas(canvas, canvasW, canvasH);

  // Background
  ctx.fillStyle = vars.bgElev;
  ctx.fillRect(0, 0, canvasW, canvasH);

  // Selected-row highlight
  tasks.forEach((task, i) => {
    if (task.id !== selectedId) return;
    ctx.fillStyle = vars.bgRail;
    ctx.fillRect(0, i * rowH, canvasW, rowH);
  });

  // Weekend columns
  if (showWeekends) {
    days.forEach((d, i) => {
      if (!d.isWeekend) return;
      ctx.fillStyle = vars.weekend;
      ctx.fillRect(i * DAY_WIDTH, 0, DAY_WIDTH, canvasH);
    });
  }

  // Day grid lines
  if (showDayGrid) {
    ctx.strokeStyle = vars.grid;
    ctx.lineWidth = 0.5;
    days.forEach((_, i) => {
      const x = Math.round((i + 1) * DAY_WIDTH) + 0.5;
      ctx.beginPath();
      ctx.moveTo(x, 0);
      ctx.lineTo(x, canvasH);
      ctx.stroke();
    });
  }

  // Row borders
  if (showRowLines) {
    ctx.strokeStyle = vars.border;
    ctx.lineWidth = 1;
    tasks.forEach((_, i) => {
      const y = Math.round((i + 1) * rowH) - 0.5;
      ctx.beginPath();
      ctx.moveTo(0, y);
      ctx.lineTo(canvasW, y);
      ctx.stroke();
    });
  }

  // Separator lines (month or week)
  const separators = timelineView === 'weeks' ? weeks.slice(1) : months.slice(1);
  separators.forEach((seg) => {
    const x = Math.round(seg.offsetDays * DAY_WIDTH) + 0.5;
    ctx.strokeStyle = vars.gridStrong;
    ctx.lineWidth = vars.monthSepW;
    ctx.beginPath();
    ctx.moveTo(x, 0);
    ctx.lineTo(x, canvasH);
    ctx.stroke();
  });

  // Today line
  if (showTodayLine) {
    const today = startOfDay(new Date());
    if (today >= range.start && today <= range.end) {
      const x = Math.round(daysBetween(range.start, today) * DAY_WIDTH + DAY_WIDTH / 2) + 0.5;
      ctx.strokeStyle = vars.today;
      ctx.lineWidth = 1;
      ctx.beginPath();
      ctx.moveTo(x, 0);
      ctx.lineTo(x, canvasH);
      ctx.stroke();
    }
  }

  // Pre-compute task bounds (used for arrows and bar drawing)
  const boundsById = {};
  tasks.forEach((task, i) => { boundsById[task.id] = taskBounds(task, i, range, DAY_WIDTH, rowH, barH); });

  // Dependency arrows — drawn before bars so they appear behind
  tasks.forEach((task) => {
    if (!task.deps || task.deps.length === 0) return;
    const tgt = boundsById[task.id];
    task.deps.forEach((depId) => {
      const src = boundsById[depId];
      if (!src) return;
      ctx.globalAlpha = 0.58;
      drawArrow(ctx, src.rightX, src.midY, tgt.leftX, tgt.midY, vars.fgMute);
      ctx.globalAlpha = 1;
    });
  });

  // Task bars and milestones (drawn on top of arrows)
  tasks.forEach((task, i) => {
    const b          = boundsById[task.id];
    const isDragging = drag && drag.id === task.id;
    const isSelected = task.id === selectedId;

    ctx.globalAlpha = isDragging ? 0.88 : 1;

    if (b.isMilestone) {
      const { cx, cy, s } = b;
      const isBrutalist = vars.barRadius === 0;

      ctx.fillStyle = task.color;
      ctx.beginPath();
      ctx.moveTo(cx, cy - s); ctx.lineTo(cx + s, cy);
      ctx.lineTo(cx, cy + s); ctx.lineTo(cx - s, cy);
      ctx.closePath();
      ctx.fill();

      ctx.strokeStyle = isBrutalist ? vars.borderStrong : 'rgba(0,0,0,0.14)';
      ctx.lineWidth   = isBrutalist ? 2 : 1;
      ctx.stroke();

      ctx.globalAlpha = 1;

      // Label to the right of diamond
      const mParts = [];
      if (barContent.includes('title')) mParts.push(task.name);
      if (barContent.includes('start') || barContent.includes('end')) mParts.push(fmtDate(task.start));
      if (mParts.length > 0) {
        ctx.save();
        ctx.beginPath(); ctx.rect(cx + s + 4, cy - rowH / 2, canvasW - cx - s - 4, rowH); ctx.clip();
        ctx.fillStyle = vars.fg;
        ctx.font = `600 11px ${vars.fontUi}`;
        ctx.textBaseline = 'middle';
        ctx.fillText(mParts.join(' · '), cx + s + 8, cy);
        ctx.restore();
      }

      if (isSelected) {
        ctx.strokeStyle = vars.fg;
        ctx.lineWidth = 2;
        const ss = s + 4;
        ctx.beginPath();
        ctx.moveTo(cx, cy - ss); ctx.lineTo(cx + ss, cy);
        ctx.lineTo(cx, cy + ss); ctx.lineTo(cx - ss, cy);
        ctx.closePath();
        ctx.stroke();
      }
    } else {
      const { bL, bT, bW, bH } = b;
      const r          = vars.barRadius;
      const isBrutalist = r === 0;

      if (task.color2) {
        const grad = ctx.createLinearGradient(bL, bT, bL + bW, bT);
        grad.addColorStop(0, task.color);
        grad.addColorStop(1, task.color2);
        ctx.fillStyle = grad;
      } else {
        ctx.fillStyle = task.color;
      }
      roundRect(ctx, bL, bT, bW, bH, r);
      ctx.fill();

      if (task.progress > 0) {
        ctx.save();
        roundRect(ctx, bL, bT, bW, bH, r);
        ctx.clip();
        ctx.fillStyle = 'rgba(255,255,255,0.22)';
        ctx.fillRect(bL, bT, bW * task.progress / 100, bH);
        const px = bL + bW * task.progress / 100;
        ctx.strokeStyle = 'rgba(255,255,255,0.4)';
        ctx.lineWidth = 1;
        ctx.beginPath(); ctx.moveTo(px + 0.5, bT); ctx.lineTo(px + 0.5, bT + bH); ctx.stroke();
        ctx.restore();
      }

      ctx.strokeStyle = isBrutalist ? vars.borderStrong : 'rgba(0,0,0,0.08)';
      ctx.lineWidth   = isBrutalist ? 2 : 1;
      roundRect(ctx, bL, bT, bW, bH, r);
      ctx.stroke();

      ctx.globalAlpha = 1;

      const labelColor = needsDarkText(task.color) ? vars.barLabelDark : vars.barLabelLight;
      ctx.save();
      ctx.beginPath(); ctx.rect(bL + 2, bT, bW - 4, bH); ctx.clip();
      ctx.fillStyle = labelColor;
      ctx.font = `600 12px ${vars.fontUi}`;
      ctx.textBaseline = 'middle';
      ctx.fillText(buildBarLabel(task, barContent), bL + 10, bT + bH / 2);
      ctx.restore();

      if (isSelected) {
        ctx.strokeStyle = vars.fg;
        ctx.lineWidth = 2;
        roundRect(ctx, bL - 3, bT - 3, bW + 6, bH + 6, r + 3);
        ctx.stroke();
      }
    }
  });
}

function Gantt({ tasks, selectedId, onSelectTask, onUpdateTask, onAddTask, onEditTask, onReorderTasks, exportRef,
                 showSidebar = true, paddingBefore = 5, paddingAfter = 5,
                 showTodayLine = false, showWeekends = true, showDayGrid = true, showRowLines = true,
                 barContent = ['title'], rowHeight = ROW_H_DEFAULT, barHeight = BAR_H_DEFAULT,
                 theme = '', timelineView = 'days' }) {
  const headerRef  = React.useRef(null);
  const bodyRef    = React.useRef(null);
  const wrapperRef = React.useRef(null);
  const [canvasW, setCanvasW] = React.useState(0);
  const [drag, setDrag]       = React.useState(null);
  const [hoverMode, setHoverMode] = React.useState(null);
  const [rowDragId, setRowDragId]   = React.useState(null);
  const [dropIndex, setDropIndex]   = React.useState(null);

  React.useLayoutEffect(() => {
    if (!wrapperRef.current) return;
    const update = () => setCanvasW(wrapperRef.current.clientWidth);
    update();
    const ro = new ResizeObserver(update);
    ro.observe(wrapperRef.current);
    window.addEventListener('resize', update);
    return () => { ro.disconnect(); window.removeEventListener('resize', update); };
  }, []);

  const range     = React.useMemo(() => computeRange(tasks, paddingBefore, paddingAfter), [tasks, paddingBefore, paddingAfter]);
  const totalDays = daysBetween(range.start, range.end) + 1;
  const DAY_WIDTH = canvasW > 0 ? canvasW / totalDays : DAY_WIDTH_DEFAULT;

  const days = React.useMemo(() => {
    const arr = [];
    for (let i = 0; i < totalDays; i++) {
      const d = addDays(range.start, i);
      arr.push({ date: d, isWeekend: d.getDay() === 0 || d.getDay() === 6 });
    }
    return arr;
  }, [range.start.getTime(), totalDays]);

  const months = React.useMemo(() => computeMonths(range.start, range.end), [range.start.getTime(), range.end.getTime()]);
  const weeks  = React.useMemo(() => computeWeeks(range.start, range.end),  [range.start.getTime(), range.end.getTime()]);

  // Redraw whenever anything visual changes
  React.useEffect(() => {
    if (canvasW <= 0 || !wrapperRef.current) return;
    const vars = getThemeVars(wrapperRef.current, barHeight);
    drawHeader(headerRef.current, { months, weeks, DAY_WIDTH, canvasW, vars, timelineView });
    drawBody(bodyRef.current, { tasks, days, range, DAY_WIDTH, canvasW, selectedId, drag,
                                showWeekends, showDayGrid, showTodayLine, showRowLines, months, weeks, barContent,
                                rowH: rowHeight, barH: barHeight, vars, timelineView });
  }, [tasks, selectedId, drag, canvasW, showWeekends, showDayGrid, showTodayLine,
      range.start.getTime(), range.end.getTime(), DAY_WIDTH, days, months, weeks, barContent,
      rowHeight, barHeight, showRowLines, theme, timelineView]);

  // Hit test on the body canvas (y is relative to body top)
  const hitTest = React.useCallback((x, y) => {
    for (let i = 0; i < tasks.length; i++) {
      const task = tasks[i];
      if (task.type === 'milestone') {
        const startOff = daysBetween(range.start, task.start);
        const s = Math.min(barHeight / 2, rowHeight / 2 - 2, 13) + 4;
        const cx = startOff * DAY_WIDTH + DAY_WIDTH / 2;
        const cy = i * rowHeight + rowHeight / 2;
        if (Math.abs(x - cx) + Math.abs(y - cy) <= s) return { task, mode: 'move' };
      } else {
        const startOff = daysBetween(range.start, task.start);
        const dur      = Math.max(1, daysBetween(task.start, task.end) + 1);
        const bL = startOff * DAY_WIDTH + 2;
        const bW = Math.max(8, dur * DAY_WIDTH - 4);
        const bT = i * rowHeight + Math.round((rowHeight - barHeight) / 2);
        if (y >= bT && y <= bT + barHeight && x >= bL && x <= bL + bW) {
          let mode = 'move';
          if (x <= bL + 8) mode = 'resize-left';
          else if (x >= bL + bW - 8) mode = 'resize-right';
          return { task, mode };
        }
      }
    }
    return null;
  }, [tasks, range, DAY_WIDTH, rowHeight, barHeight]);

  const bodyPos = (e) => {
    const rect = bodyRef.current.getBoundingClientRect();
    return { x: e.clientX - rect.left, y: e.clientY - rect.top };
  };

  const onBodyMouseDown = React.useCallback((e) => {
    e.preventDefault();
    if (!bodyRef.current) return;
    const { x, y } = bodyPos(e);
    const hit = hitTest(x, y);
    if (hit) {
      onSelectTask(hit.task.id);
      setDrag({
        id: hit.task.id, mode: hit.mode,
        startX: e.clientX, dayWidth: DAY_WIDTH,
        originalStart: new Date(hit.task.start),
        originalEnd:   new Date(hit.task.end),
      });
    } else {
      onSelectTask(null);
    }
  }, [hitTest, onSelectTask, DAY_WIDTH]);

  const onBodyDoubleClick = React.useCallback((e) => {
    if (!bodyRef.current) return;
    const { x, y } = bodyPos(e);
    const hit = hitTest(x, y);
    if (hit) onEditTask(hit.task.id);
  }, [hitTest, onEditTask]);

  const onBodyMouseMove = React.useCallback((e) => {
    if (drag || !bodyRef.current) return;
    const { x, y } = bodyPos(e);
    const hit = hitTest(x, y);
    setHoverMode(hit ? hit.mode : null);
  }, [drag, hitTest]);

  const onBodyMouseLeave = () => setHoverMode(null);

  // Global drag move / release
  React.useEffect(() => {
    if (!drag) return;
    const onMove = (e) => {
      const daysDelta = Math.round((e.clientX - drag.startX) / drag.dayWidth);
      const task = tasks.find((t) => t.id === drag.id);
      if (!task) return;
      let ns = drag.originalStart, ne = drag.originalEnd;
      if (drag.mode === 'move') {
        ns = addDays(drag.originalStart, daysDelta);
        ne = task.type === 'milestone' ? ns : addDays(drag.originalEnd, daysDelta);
      } else if (drag.mode === 'resize-left') {
        ns = addDays(drag.originalStart, daysDelta);
        if (ns >= drag.originalEnd) ns = addDays(drag.originalEnd, -1);
      } else {
        ne = addDays(drag.originalEnd, daysDelta);
        if (ne <= drag.originalStart) ne = addDays(drag.originalStart, 1);
      }
      onUpdateTask(drag.id, { start: fmtISO(ns), end: fmtISO(ne) });
    };
    const onUp = () => setDrag(null);
    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseup', onUp);
    return () => { window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
  }, [drag, tasks, onUpdateTask]);

  const cursor = drag
    ? (drag.mode === 'move' ? 'grabbing' : 'ew-resize')
    : hoverMode === 'move' ? 'grab'
    : (hoverMode === 'resize-left' || hoverMode === 'resize-right') ? 'ew-resize'
    : 'default';

  const ganttStyle = { '--row-h': `${rowHeight}px` };

  if (tasks.length === 0) {
    return (
      <div className={`gantt ${showSidebar ? '' : 'no-sidebar'}`} ref={exportRef} style={ganttStyle}>
        {showSidebar && <div className="gantt-sidebar"><div className="sidebar-header">Attività</div></div>}
        <div className="gantt-chart" ref={wrapperRef}><EmptyState onAddTask={onAddTask} /></div>
      </div>
    );
  }

  return (
    <div className={`gantt ${showSidebar ? '' : 'no-sidebar'}`} ref={exportRef} style={ganttStyle}>
      {showSidebar && (
        <div
          className="gantt-sidebar"
          onDragOver={(e) => {
            if (!rowDragId) return;
            e.preventDefault();
            e.dataTransfer.dropEffect = 'move';
            const rows = [...e.currentTarget.querySelectorAll('.sidebar-row')];
            let idx = rows.length;
            for (let i = 0; i < rows.length; i++) {
              const rect = rows[i].getBoundingClientRect();
              if (e.clientY < rect.top + rect.height / 2) { idx = i; break; }
              if (e.clientY < rect.bottom) { idx = i + 1; break; }
            }
            setDropIndex(idx);
          }}
          onDragLeave={(e) => {
            if (!e.currentTarget.contains(e.relatedTarget)) setDropIndex(null);
          }}
          onDrop={(e) => {
            e.preventDefault();
            if (rowDragId && dropIndex !== null && onReorderTasks) {
              onReorderTasks(rowDragId, dropIndex);
            }
            setRowDragId(null);
            setDropIndex(null);
          }}
        >
          <div className="sidebar-header">Attività · {tasks.length}</div>
          {tasks.map((t, idx) => (
            <React.Fragment key={t.id}>
              {dropIndex === idx && rowDragId && <div className="row-drop-indicator" />}
              <div
                className={`sidebar-row${selectedId === t.id ? ' selected' : ''}${rowDragId === t.id ? ' row-dragging' : ''}`}
                onClick={() => onSelectTask(t.id)}
              >
                <span
                  className="row-drag-handle"
                  draggable="true"
                  onDragStart={(e) => {
                    e.dataTransfer.effectAllowed = 'move';
                    e.dataTransfer.setData('text/plain', t.id);
                    setRowDragId(t.id);
                  }}
                  onDragEnd={() => { setRowDragId(null); setDropIndex(null); }}
                  onClick={(e) => e.stopPropagation()}
                >
                  <DragIcon />
                </span>
                <span className={`row-dot${t.type === 'milestone' ? ' row-dot--milestone' : ''}`} style={{ '--row-dot-color': t.color }}></span>
                <span className="row-name">{t.name}</span>
                {t.type === 'milestone'
                  ? <span className="row-dates">◆ {fmtDate(t.start)}</span>
                  : <span className="row-dates">{fmtDate(t.start)} – {fmtDate(t.end)}</span>
                }
                <button
                  className="row-edit-btn"
                  type="button"
                  title="Proprietà"
                  aria-label="Proprietà"
                  onClick={(e) => { e.stopPropagation(); onEditTask(t.id); }}
                >
                  <EditIcon />
                </button>
              </div>
            </React.Fragment>
          ))}
          {dropIndex === tasks.length && rowDragId && <div className="row-drop-indicator" />}
        </div>
      )}
      <div className="gantt-chart" ref={wrapperRef}>
        <canvas ref={headerRef} className="gantt-header-canvas" />
        <canvas
          ref={bodyRef}
          className="gantt-body-canvas"
          style={{ cursor }}
          onMouseDown={onBodyMouseDown}
          onDoubleClick={onBodyDoubleClick}
          onMouseMove={onBodyMouseMove}
          onMouseLeave={onBodyMouseLeave}
        />
      </div>
    </div>
  );
}

function EditIcon() {
  return (
    <svg width="12" height="12" viewBox="0 0 12 12" fill="none">
      <path d="M8.5 1.5L10.5 3.5L4 10H2V8L8.5 1.5Z" stroke="currentColor" strokeWidth="1.3" strokeLinejoin="round" />
    </svg>
  );
}

function DragIcon() {
  return (
    <svg width="8" height="12" viewBox="0 0 8 12" fill="currentColor">
      <circle cx="2" cy="2"  r="1.2" /><circle cx="6" cy="2"  r="1.2" />
      <circle cx="2" cy="6"  r="1.2" /><circle cx="6" cy="6"  r="1.2" />
      <circle cx="2" cy="10" r="1.2" /><circle cx="6" cy="10" r="1.2" />
    </svg>
  );
}

function PlusIcon() {
  return (
    <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
      <path d="M7 1.5V12.5M1.5 7H12.5" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" />
    </svg>
  );
}

function DownloadIcon() {
  return (
    <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
      <path d="M7 1V9M7 9L3.5 5.5M7 9L10.5 5.5M2 12H12" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

function CopyIcon() {
  return (
    <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
      <rect x="4.5" y="1.5" width="8" height="8" rx="1" stroke="currentColor" strokeWidth="1.5"/>
      <rect x="1.5" y="4.5" width="8" height="8" rx="1" stroke="currentColor" strokeWidth="1.5"/>
    </svg>
  );
}

function EmptyState({ onAddTask }) {
  return (
    <div className="empty-state">
      <div className="empty-illu">
        <div className="ill-bar" style={{ top: 20, left: 30, width: 180, background: 'var(--swatch-1)' }}></div>
        <div className="ill-bar" style={{ top: 50, left: 90, width: 140, background: 'var(--swatch-2)' }}></div>
        <div className="ill-bar" style={{ top: 80, left: 160, width: 160, background: 'var(--swatch-3)' }}></div>
        <div className="ill-bar" style={{ top: 110, left: 50, width: 100, background: 'var(--swatch-4)' }}></div>
      </div>
      <div className="empty-title">Costruisci il tuo Gantt.</div>
      <div className="empty-sub">Aggiungi la prima attività per generare automaticamente la timeline. Trascina le barre per spostarle, i bordi per ridimensionarle.</div>
      <button className="btn btn-primary" type="button" onClick={onAddTask}><PlusIcon /> Aggiungi prima attività</button>
    </div>
  );
}

window.Gantt = Gantt;
window.PlusIcon = PlusIcon;
window.DownloadIcon = DownloadIcon;
window.CopyIcon = CopyIcon;
window.fmtDate = fmtDate;
window.fmtDateFull = fmtDateFull;
window.fmtISO = fmtISO;
window.parseISO = parseISO;
window.addDays = addDays;
window.startOfDay = startOfDay;
