/* ============================================================
   Vazifalar doskasi (Trello uslubida) — ustunlar + kartochkalar + DnD
   ============================================================ */

/* ---------- Muddat formati (sana + soat) ---------- */
function fmtTaskDue(iso) {
  if (!iso) return null;
  const d = new Date(iso);
  const months = ['yan', 'fev', 'mar', 'apr', 'may', 'iyn', 'iyl', 'avg', 'sen', 'okt', 'noy', 'dek'];
  const p = n => (n < 10 ? '0' + n : '' + n);
  return d.getDate() + ' ' + months[d.getMonth()] + ', ' + p(d.getHours()) + ':' + p(d.getMinutes());
}
// faqat sana (kun + oy) — kartochkada koʻrsatish uchun
function fmtTaskDate(iso) {
  if (!iso) return null;
  const d = new Date(iso);
  const months = ['yan', 'fev', 'mar', 'apr', 'may', 'iyn', 'iyl', 'avg', 'sen', 'okt', 'noy', 'dek'];
  return d.getDate() + ' ' + months[d.getMonth()];
}
function startOfDay(d) { const x = new Date(d); x.setHours(0, 0, 0, 0); return x; }
// datetime-local input qiymati (mahalliy vaqt)
function toLocalInput(iso) {
  if (!iso) return '';
  const d = new Date(iso);
  const p = n => (n < 10 ? '0' + n : '' + n);
  return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate()) + 'T' + p(d.getHours()) + ':' + p(d.getMinutes());
}
// date input qiymati (faqat sana, mahalliy)
function toDateInput(iso) {
  if (!iso) return '';
  const d = new Date(iso);
  const p = n => (n < 10 ? '0' + n : '' + n);
  return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate());
}
// date input (faqat sana) → ISO; mahalliy yarim tun
function dateInputToIso(v) {
  if (!v) return null;
  const [y, m, d] = v.split('-').map(Number);
  return new Date(y, m - 1, d).toISOString();
}
// time input qiymati (HH:MM) — agar soat 00:00 bo'lsa bo'sh (soat ixtiyoriy)
function toTimeInput(iso) {
  if (!iso) return '';
  const d = new Date(iso);
  const p = n => (n < 10 ? '0' + n : '' + n);
  if (d.getHours() === 0 && d.getMinutes() === 0) return '';
  return p(d.getHours()) + ':' + p(d.getMinutes());
}
// sana (+ixtiyoriy soat) → ISO; soat bo'sh bo'lsa yarim tun
function dateTimeToIso(dateStr, timeStr) {
  if (!dateStr) return null;
  const [y, m, d] = dateStr.split('-').map(Number);
  let hh = 0, mm = 0;
  if (timeStr) { const t = timeStr.split(':').map(Number); hh = t[0] || 0; mm = t[1] || 0; }
  return new Date(y, m - 1, d, hh, mm).toISOString();
}
// kartochkada sana (+soat, agar belgilangan bo'lsa)
function fmtCardDate(iso) {
  if (!iso) return null;
  const d = new Date(iso);
  const months = ['yan', 'fev', 'mar', 'apr', 'may', 'iyn', 'iyl', 'avg', 'sen', 'okt', 'noy', 'dek'];
  const p = n => (n < 10 ? '0' + n : '' + n);
  const base = d.getDate() + ' ' + months[d.getMonth()];
  return (d.getHours() || d.getMinutes()) ? base + ' ' + p(d.getHours()) + ':' + p(d.getMinutes()) : base;
}

/* ---------- Bitta vazifa kartasi ---------- */
function TaskCard({ task, onOpen, canDrag, dragging, onDragStart, onDragEnd, onDuplicate, canDuplicate }) {
  const done = DB.taskCategory(task.listId) === 'done';
  const end = task.endDate ? new Date(task.endDate) : null;
  // Boshlanish va tugash sanasi (+soat) — diapazon ko'rinishida
  let dateLabel = null;
  if (task.startDate && task.endDate) dateLabel = fmtCardDate(task.startDate) + ' – ' + fmtCardDate(task.endDate);
  else if (task.endDate) dateLabel = fmtCardDate(task.endDate);
  else if (task.startDate) dateLabel = fmtCardDate(task.startDate);
  let dueState = null; // 'overdue' | 'soon'
  if (end && !done) {
    const diff = Math.round((startOfDay(end).getTime() - startOfDay(new Date()).getTime()) / 86400000);
    if (diff < 0) dueState = 'overdue';
    else if (diff <= 2) dueState = 'soon';
  }
  const overdue = dueState === 'overdue';
  const dueBg = overdue ? '#F6DCD6' : (dueState === 'soon' ? '#F6E7CF' : 'var(--bg-2)');
  const dueInk = overdue ? '#C2453A' : (dueState === 'soon' ? '#A6651E' : 'var(--ink-2)');
  const accent = overdue ? '#E8735F' : (dueState === 'soon' ? '#E3A23B' : null);
  const assignee = task.assigneeId ? DB.userName(task.assigneeId) : null;
  const labels = task.labels || [];
  const images = task.images || [];

  return (
    <div
      draggable={canDrag}
      onDragStart={(e) => onDragStart(e, task)}
      onDragEnd={onDragEnd}
      onClick={() => onOpen(task)}
      className="ocard"
      style={{
        background: 'var(--surface)', border: '1px solid var(--border)',
        borderLeft: accent ? ('3px solid ' + accent) : '1px solid var(--border)',
        borderRadius: 8,
        padding: '9px 12px', boxShadow: '0 1px 1px rgba(40,28,18,.12)', cursor: canDrag ? 'grab' : 'pointer',
        opacity: dragging ? .4 : 1, transition: 'box-shadow .15s, transform .12s, border-color .15s', position: 'relative',
      }}>
      {canDuplicate && (
        <button type="button" className="task-copy" title="Nusxa olish" aria-label="Vazifadan nusxa olish"
          onClick={(e) => { e.stopPropagation(); onDuplicate(task); }}
          onMouseDown={(e) => e.stopPropagation()}
          style={{ position: 'absolute', top: 7, right: 7, width: 26, height: 26, borderRadius: 7, padding: 0,
                   display: 'grid', placeItems: 'center', border: '1px solid var(--border)', background: 'var(--surface)',
                   color: 'var(--ink-3)', cursor: 'pointer', boxShadow: 'var(--sh-1)', zIndex: 2 }}>
          <Icon name="copy" size={13} stroke={2} />
        </button>
      )}
      {labels.length > 0 && (
        <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap', marginBottom: 8 }}>
          {labels.map((l, i) => (
            <span key={i} title={l.text || ''} style={{ height: 6, minWidth: 28, borderRadius: 100, background: l.color }} />
          ))}
        </div>
      )}
      <div style={{ fontWeight: 500, fontSize: 14, lineHeight: 1.35, color: 'var(--ink)',
                    paddingRight: canDuplicate ? 30 : 0 }}>
        {task.title}
      </div>
      {images.length > 0 && (
        <div style={{ display: 'flex', gap: 4, marginTop: 8, flexWrap: 'wrap' }}>
          {images.slice(0, 4).map((src, i) => (
            <div key={i} style={{ position: 'relative', width: 44, height: 44, borderRadius: 6, overflow: 'hidden',
                                  border: '1px solid var(--border)' }}>
              <img src={src} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
              {i === 3 && images.length > 4 && (
                <div style={{ position: 'absolute', inset: 0, background: 'rgba(0,0,0,.5)', color: '#fff',
                              display: 'grid', placeItems: 'center', fontWeight: 800, fontSize: 12 }}>
                  +{images.length - 4}
                </div>
              )}
            </div>
          ))}
        </div>
      )}
      {task.desc && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 5, marginTop: 7, color: 'var(--ink-4)', fontSize: 11.5 }}>
          <Icon name="layers" size={12} stroke={1.8} /> Tavsif bor
        </div>
      )}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 10, flexWrap: 'wrap' }}>
        {dateLabel && (
          <span style={{ display: 'flex', alignItems: 'center', gap: 5, fontSize: 11, fontWeight: 600, padding: '3px 7px',
                         borderRadius: 6, background: dueBg, color: dueInk }}>
            <Icon name={done ? 'check' : 'clock'} size={12} stroke={2} /> {dateLabel}
            {overdue && <span style={{ fontWeight: 800 }}>· kechikdi</span>}
          </span>
        )}
        <div style={{ flex: 1 }} />
        {assignee && (
          <span style={{ display: 'flex', alignItems: 'center', gap: 6, minWidth: 0 }}>
            {DB.userAvatar(task.assigneeId)
              ? <img src={DB.userAvatar(task.assigneeId)} title={assignee} alt={assignee}
                  style={{ width: 22, height: 22, borderRadius: '50%', objectFit: 'cover', flex: 'none', display: 'block' }} />
              : <span style={{ width: 22, height: 22, borderRadius: '50%', background: 'var(--brand-tint)', color: 'var(--brand-700)',
                           display: 'grid', placeItems: 'center', fontWeight: 800, fontSize: 9.5, flex: 'none' }}>
                  {assignee.split(' ').map(s => s[0]).join('').slice(0, 2)}
                </span>}
            <span style={{ fontSize: 11.5, fontWeight: 600, color: 'var(--ink-2)', whiteSpace: 'nowrap',
                           overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: 110 }}>{assignee}</span>
          </span>
        )}
      </div>
    </div>
  );
}

/* ---------- Ustun ---------- */
function TaskColumn({ col, tasks, perms, dnd, colDnd, onAddCard, onOpenCard, onEditCol, onDuplicateCard }) {
  const isOver = dnd.overCol === col.id;
  const active = dnd.dragging && perms.taskEdit;
  const colDragging = colDnd.dragging && colDnd.draggingId !== col.id;
  const colIsOver = colDnd.over === col.id && colDragging;
  const isColSource = colDnd.draggingId === col.id;

  const excluded = active ? tasks.filter(t => t.id !== dnd.draggingId) : [];
  const k = dnd.reorderCol === col.id ? dnd.reorderIndex : null;
  const barBeforeId = (active && k != null && k < excluded.length) ? excluded[k].id : null;
  const barAtEnd = active && k != null && k >= excluded.length;

  function cardDragOver(e, cardId) {
    if (!active) return;
    e.preventDefault(); e.stopPropagation();
    const rect = e.currentTarget.getBoundingClientRect();
    const before = (e.clientY - rect.top) < rect.height / 2;
    const j = excluded.findIndex(t => t.id === cardId);
    if (j < 0) return;
    dnd.setReorder(col.id, before ? j : j + 1);
  }

  const Bar = () => (
    <div style={{ height: 3, borderRadius: 3, background: col.color, margin: '-3px 2px', boxShadow: `0 0 0 3px ${col.color}22` }} />
  );

  return (
    <div
      onDragOver={(e) => { if (colDragging) { e.preventDefault(); colDnd.setOver(col.id); } }}
      onDrop={(e) => { if (colDnd.dragging) { e.preventDefault(); colDnd.onDrop(col.id); } }}
      style={{ flex: '0 0 288px', width: 288, display: 'flex', flexDirection: 'column', height: '100%',
               opacity: isColSource ? 0.4 : 1,
               background: 'var(--bg-2)', borderRadius: 14, padding: '10px 8px 8px',
               border: colIsOver ? `2px solid ${col.color}` : '2px solid transparent',
               boxShadow: 'var(--sh-1)', transition: 'opacity .15s, border-color .15s' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 7, padding: '2px 6px 10px' }}>
        {perms.taskManageBoard && (
          <span draggable
            onDragStart={(e) => { e.stopPropagation(); colDnd.onStart(e, col.id); }}
            onDragEnd={colDnd.onEnd}
            title="Ustunni sudrab joyini almashtiring" aria-label="Ustunni ko'chirish"
            style={{ display: 'inline-flex', alignItems: 'center', color: 'var(--ink-4)', cursor: 'grab', flex: 'none' }}>
            <Icon name="drag" size={14} />
          </span>
        )}
        <span className="dot" style={{ width: 9, height: 9, background: col.color }} />
        <span style={{ fontWeight: 700, fontSize: 14, letterSpacing: '-.01em', color: 'var(--ink)' }}>{col.title}</span>
        <span className="chip tnum" style={{ background: col.color + '1a', color: col.color, padding: '2px 8px', fontSize: 11.5 }}>{tasks.length}</span>
        <span className="chip" style={{ padding: '1px 7px', fontSize: 10, background: 'var(--bg-2)', color: 'var(--ink-4)' }}>
          {col.category === 'done' ? 'Bajarilgan' : 'Jarayonda'}
        </span>
        <div style={{ flex: 1 }} />
        {perms.taskManageBoard && (
          <button className="btn btn--icon btn--ghost" style={{ padding: 5, borderColor: 'transparent', color: 'var(--ink-4)' }}
            title="Ustunni tahrirlash" aria-label="Ustunni tahrirlash" onClick={() => onEditCol(col)}>
            <Icon name="gear" size={14} />
          </button>
        )}
      </div>

      <div
        onDragOver={(e) => { if (active) { e.preventDefault(); dnd.setReorder(col.id, excluded.length); } }}
        onDragLeave={(e) => { if (e.currentTarget === e.target) dnd.setOver(null); }}
        onDrop={(e) => { e.preventDefault(); dnd.onDrop(col.id); }}
        style={{
          flex: 1, overflowY: 'auto', overflowX: 'hidden', padding: '2px 4px 6px', display: 'flex', flexDirection: 'column', gap: 8,
          borderRadius: 'var(--r)', background: isOver && active ? col.color + '0f' : 'transparent',
          outline: isOver && active ? `2px dashed ${col.color}` : '2px dashed transparent', outlineOffset: -2,
          transition: 'background .15s, outline-color .15s',
        }}>
        {tasks.map(t => (
          <React.Fragment key={t.id}>
            {barBeforeId === t.id && <Bar />}
            <div onDragOver={(e) => cardDragOver(e, t.id)}>
              <TaskCard task={t} onOpen={onOpenCard} canDrag={perms.taskEdit}
                dragging={dnd.draggingId === t.id} onDragStart={dnd.onDragStart} onDragEnd={dnd.onDragEnd}
                canDuplicate={perms.taskAdd} onDuplicate={onDuplicateCard} />
            </div>
          </React.Fragment>
        ))}
        {barAtEnd && <Bar />}
        {tasks.length === 0 && !perms.taskAdd && (
          <div style={{ padding: '16px 8px', textAlign: 'center', color: 'var(--ink-4)', fontSize: 12, fontWeight: 500 }}>Bo'sh</div>
        )}
      </div>
      {perms.taskAdd && (
        <button className="add-order-btn" onClick={() => onAddCard(col.id)}
          style={{ display: 'flex', alignItems: 'center', gap: 7, padding: '8px 10px', marginTop: 2, width: '100%',
                   border: 'none', borderRadius: 8, background: 'transparent', cursor: 'pointer',
                   color: 'var(--ink-3)', fontWeight: 600, fontSize: 13, transition: 'background .15s' }}
          onMouseEnter={e => { e.currentTarget.style.background = 'rgba(40,28,18,.06)'; }}
          onMouseLeave={e => { e.currentTarget.style.background = 'transparent'; }}>
          <Icon name="plus" size={15} stroke={2.4} /> Vazifa qo'shish
        </button>
      )}
    </div>
  );
}

/* ---------- Doska ---------- */
function TaskBoard({ tasks, setTasks, taskLists, setTaskLists, perms, showToast, reload }) {
  const onApiError = (ex) => { showToast((ex && ex.message) || 'Server xatosi'); if (reload) reload(); };
  const [mode, setMode] = useState('board');          // 'board' | 'timeline'
  const [cardModal, setCardModal] = useState(null);  // {task} | {create:true, listId}
  const [colModal, setColModal] = useState(null);    // {col} | {create:true}
  const [draggingId, setDraggingId] = useState(null);
  const [overCol, setOverCol] = useState(null);
  const [reorderCol, setReorderCol] = useState(null);
  const [reorderIndex, setReorderIndex] = useState(null);
  const [colDragId, setColDragId] = useState(null);   // ustun reorder
  const [colOverId, setColOverId] = useState(null);
  const [sortBy, setSortBy] = useState('manual');     // 'manual' | 'due' | 'assignee'
  const [filterAssignee, setFilterAssignee] = useState(''); // '' = barchasi, 'none' = biriktirilmagan, yoki userId
  const dragRef = useRef(null);
  // Optimistik yaratilgan (tmp_) kartalar hali serverda yo'q — ular o'chirilsa/ko'chirilsa,
  // create javobi kelganda serverga qo'llash uchun shu yerda belgilanadi.
  const pendingDelRef = useRef(new Set());       // tmp_id
  const pendingMoveRef = useRef(new Map());       // tmp_id -> { listId, index }

  const cols = useMemo(() => [...taskLists].sort((a, b) => (a.order || 0) - (b.order || 0)), [taskLists]);
  // Eng chap (birinchi) ustun = "rejalanmagan" — faqat shu ustun muddatsiz vazifa qabul qiladi
  const backlogId = cols.length ? cols[0].id : null;

  function taskSort(a, b) {
    if (sortBy === 'due') {
      const av = a.endDate ? new Date(a.endDate).getTime() : Infinity;
      const bv = b.endDate ? new Date(b.endDate).getTime() : Infinity;
      if (av !== bv) return av - bv;
    } else if (sortBy === 'assignee') {
      const an = a.assigneeId ? DB.userName(a.assigneeId) : '￿';
      const bn = b.assigneeId ? DB.userName(b.assigneeId) : '￿';
      const c = an.localeCompare(bn); if (c) return c;
    }
    return (a.order || 0) - (b.order || 0);
  }

  const visibleTasks = useMemo(() => {
    if (!filterAssignee) return tasks;
    if (filterAssignee === 'none') return tasks.filter(t => !t.assigneeId);
    return tasks.filter(t => t.assigneeId === filterAssignee);
  }, [tasks, filterAssignee]);

  const byList = useMemo(() => {
    const m = {}; cols.forEach(c => m[c.id] = []);
    visibleTasks.forEach(t => { if (m[t.listId]) m[t.listId].push(t); });
    Object.values(m).forEach(arr => arr.sort(taskSort));
    return m;
  }, [visibleTasks, cols, sortBy]);

  function onDragStart(e, task) {
    if (!perms.taskEdit) return;
    dragRef.current = task; setDraggingId(task.id);
    e.dataTransfer.effectAllowed = 'move';
    try { e.dataTransfer.setData('text/plain', task.id); } catch (_) {}
  }
  function onDragEnd() { setDraggingId(null); setOverCol(null); setReorderCol(null); setReorderIndex(null); dragRef.current = null; }
  function setReorder(colId, idx) { setReorderCol(colId); setReorderIndex(idx); setOverCol(colId); }

  function applyMoveAt(taskId, targetListId, index) {
    setTasks(prev => {
      const moving = prev.find(t => t.id === taskId);
      if (!moving) return prev;
      const list = prev.filter(t => t.listId === targetListId && t.id !== taskId).sort((a, b) => (a.order || 0) - (b.order || 0));
      const idx = index == null ? list.length : Math.max(0, Math.min(index, list.length));
      const cat = DB.taskCategory(targetListId);
      const newMoving = { ...moving, listId: targetListId,
        completedAt: cat === 'done' ? (moving.completedAt || new Date().toISOString()) : null };
      list.splice(idx, 0, newMoving);
      const pos = {}; list.forEach((t, i) => pos[t.id] = i);
      return prev.map(t => {
        if (t.id === taskId) return { ...newMoving, order: pos[taskId] };
        if (t.listId === targetListId && pos[t.id] != null) return { ...t, order: pos[t.id] };
        return t;
      });
    });
  }

  function onDrop(targetListId) {
    const t = dragRef.current;
    const rIdx = (reorderCol === targetListId) ? reorderIndex : null;
    setOverCol(null); setDraggingId(null); setReorderCol(null); setReorderIndex(null);
    dragRef.current = null;
    if (!t || !perms.taskEdit) return;
    const sameSpot = t.listId === targetListId && rIdx == null;
    if (sameSpot) return;
    // Muddatsiz vazifani faqat backlog (birinchi ustun) qabul qiladi
    if (backlogId && targetListId !== backlogId && !t.endDate) {
      showToast('Sanasiz vazifa faqat birinchi ustunda turadi — avval tugash sanasini belgilang');
      return;
    }
    applyMoveAt(t.id, targetListId, rIdx);
    const col = DB.taskListById(targetListId);
    if (t.listId !== targetListId && col) showToast(`${t.title} → ${col.title}`);
    // Hali saqlanmagan (tmp_) karta — server chaqirmaymiz; create javobi kelganda ko'chiriladi.
    if (String(t.id).startsWith('tmp_')) {
      pendingMoveRef.current.set(t.id, { listId: targetListId, index: rIdx });
    } else {
      API.moveTask(t.id, targetListId, rIdx == null ? undefined : rIdx).catch(onApiError);
    }
  }

  const dnd = { draggingId, dragging: !!draggingId, overCol, setOver: setOverCol, onDragStart, onDragEnd, onDrop,
                reorderCol, reorderIndex, setReorder };

  /* ----- Ustun reorder (drag) ----- */
  function onColDragStart(e, colId) {
    if (!perms.taskManageBoard) return;
    setColDragId(colId);
    e.dataTransfer.effectAllowed = 'move';
    try { e.dataTransfer.setData('text/col', colId); } catch (_) {}
  }
  function onColDragEnd() { setColDragId(null); setColOverId(null); }
  function onColDrop(targetId) {
    const src = colDragId;
    setColDragId(null); setColOverId(null);
    if (!src || src === targetId || !perms.taskManageBoard) return;
    const ordered = cols.map(c => c.id);
    const from = ordered.indexOf(src), to = ordered.indexOf(targetId);
    if (from < 0 || to < 0) return;
    ordered.splice(to, 0, ordered.splice(from, 1)[0]);
    const pos = {}; ordered.forEach((id, i) => pos[id] = i);
    setTaskLists(prev => prev.map(c => (pos[c.id] != null ? { ...c, order: pos[c.id] } : c)));
    cols.forEach(c => { if ((c.order || 0) !== pos[c.id]) API.updateTaskList(c.id, { order: pos[c.id] }).catch(onApiError); });
  }
  const colDnd = { dragging: !!colDragId, draggingId: colDragId, over: colOverId, setOver: setColOverId,
                   onStart: onColDragStart, onEnd: onColDragEnd, onDrop: onColDrop };

  /* ----- Kartochka saqlash (OPTIMISTIK — modal darrov yopiladi, API fonda) ----- */
  async function saveCard(data) {
    const isEdit = !!cardModal.task;
    const listId = isEdit ? cardModal.task.listId : cardModal.listId;
    const asg = DB.USERS.find(u => u.id === data.assigneeId);
    setCardModal(null); // kutmasdan darrov yopamiz — tez his qilinishi uchun

    if (isEdit) {
      const id = cardModal.task.id;
      const prevTask = cardModal.task;
      // Darrov mahalliy qo'llaymiz
      const merged = {
        ...prevTask, title: data.title, desc: data.desc || '',
        assigneeId: data.assigneeId || null, assigneeName: asg ? asg.name : (prevTask.assigneeName || null),
        startDate: data.startDate || null, endDate: data.endDate || null,
        labels: data.labels || [], images: data.images || [],
      };
      setTasks(prev => prev.map(t => (t.id === id ? merged : t)));
      showToast('Vazifa yangilandi');
      try {
        const { task } = await API.updateTask(id, data);
        setTasks(prev => prev.map(t => (t.id === task.id ? task : t)));
      } catch (ex) {
        setTasks(prev => prev.map(t => (t.id === id ? prevTask : t))); // orqaga qaytarish
        showToast(ex.message || 'Saqlashda xatolik');
      }
      return;
    }

    // Yaratish — vaqtinchalik karta darrov ko'rinadi, server javobi kelganda almashtiriladi
    const tempId = 'tmp_' + Date.now() + '_' + Math.random().toString(36).slice(2, 7);
    const list = DB.taskListById(listId);
    const optimistic = {
      id: tempId, title: data.title, desc: data.desc || '', listId,
      order: 1e9, assigneeId: data.assigneeId || null, assigneeName: asg ? asg.name : null,
      dueDate: null, startDate: data.startDate || null, endDate: data.endDate || null,
      completedAt: list && list.category === 'done' ? new Date().toISOString() : null,
      labels: data.labels || [], images: data.images || [],
      createdBy: null, createdAt: new Date().toISOString(), _pending: true,
    };
    setTasks(prev => [...prev, optimistic]);
    showToast('Vazifa qoʼshildi');
    try {
      const { task } = await API.createTask({ ...data, listId });
      // Foydalanuvchi karta saqlanguncha uni o'chirgan bo'lsa — serverdan ham o'chiramiz.
      if (pendingDelRef.current.has(tempId)) {
        pendingDelRef.current.delete(tempId);
        pendingMoveRef.current.delete(tempId);
        API.deleteTask(task.id).catch(() => {});
      } else {
        // Saqlangunча boshqa ustunga ko'chirilgan bo'lsa — serverda ham ko'chiramiz.
        const mv = pendingMoveRef.current.get(tempId);
        pendingMoveRef.current.delete(tempId);
        setTasks(prev => prev.map(t => (t.id === tempId ? { ...task, listId: mv ? mv.listId : task.listId } : t)));
        if (mv) API.moveTask(task.id, mv.listId, mv.index == null ? undefined : mv.index).catch(onApiError);
      }
    } catch (ex) {
      pendingDelRef.current.delete(tempId);
      pendingMoveRef.current.delete(tempId);
      setTasks(prev => prev.filter(t => t.id !== tempId)); // muvaffaqiyatsiz — olib tashlash
      showToast(ex.message || 'Saqlashda xatolik');
    }
  }
  // OPTIMISTIK o'chirish — darrov UI'dan olib tashlaymiz, API fonda.
  async function deleteCard(id) {
    const removed = tasks.find(t => t.id === id);
    setTasks(prev => prev.filter(t => t.id !== id));
    setCardModal(null);
    showToast('Vazifa oʼchirildi');

    // Hali serverda saqlanmagan (optimistik) karta — create javobi kelganda o'chiriladi.
    if (String(id).startsWith('tmp_')) {
      pendingDelRef.current.add(id);
      return;
    }
    try {
      await API.deleteTask(id);
    } catch (ex) {
      if (removed) setTasks(prev => (prev.some(t => t.id === id) ? prev : [...prev, removed])); // qaytarish
      showToast(ex.message || 'Oʼchirishda xatolik');
    }
  }
  // Vazifadan nusxa olish — barcha maydonlarni nusxalab, shu ustunга yangi karta qo'shadi
  async function duplicateCard(task) {
    if (!perms.taskAdd || !task) return;
    try {
      const payload = {
        title: (task.title || '') + ' (nusxa)',
        desc: task.desc || '',
        assigneeId: task.assigneeId || null,
        dueDate: null,
        startDate: task.startDate || null,
        endDate: task.endDate || null,
        labels: task.labels || [],
        images: task.images || [],
        listId: task.listId,
      };
      const { task: created } = await API.createTask(payload);
      setTasks(prev => [...prev, created]);
      showToast('Vazifadan nusxa olindi');
    } catch (ex) { showToast(ex.message || 'Nusxalashda xatolik'); }
  }

  /* ----- Ustun saqlash ----- */
  async function saveCol(data) {
    try {
      if (colModal.col) {
        const { taskList } = await API.updateTaskList(colModal.col.id, data);
        setTaskLists(prev => prev.map(c => (c.id === taskList.id ? taskList : c)));
        showToast('Ustun yangilandi');
      } else {
        const { taskList } = await API.createTaskList(data);
        setTaskLists(prev => [...prev, taskList]);
        showToast('Ustun qoʼshildi');
      }
      setColModal(null);
    } catch (ex) { showToast(ex.message || 'Saqlashda xatolik'); }
  }
  async function deleteCol(id) {
    const has = tasks.some(t => t.listId === id);
    if (has) { showToast("Ustunda vazifalar bor — avval ularni koʻchiring"); return; }
    try {
      await API.deleteTaskList(id);
      setTaskLists(prev => prev.filter(c => c.id !== id));
      showToast('Ustun oʼchirildi');
      setColModal(null);
    } catch (ex) { showToast(ex.message || 'Oʼchirishda xatolik'); }
  }

  const stats = DB.taskStats(tasks);

  return (
    <>
      {/* Sarlavha + qisqa statistika */}
      <div className="tb-toolbar" style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '14px 22px 10px', flexWrap: 'wrap' }}>
        <div>
          <h2 style={{ fontSize: 18, fontWeight: 800, letterSpacing: '-.02em', margin: 0 }}>Vazifalar doskasi</h2>
          <div style={{ fontSize: 12, color: 'var(--ink-4)', marginTop: 2 }}>
            {stats.total} ta vazifa · {stats.doing} jarayonda · {stats.done} bajarilgan
          </div>
        </div>
        <div style={{ flex: 1 }} />
        {/* Doska / Timeline almashtirgich */}
        <div style={{ display: 'inline-flex', background: 'var(--bg-2)', borderRadius: 9, padding: 3, gap: 2 }}>
          {[['board', 'Doska', 'board'], ['timeline', 'Timeline', 'chart']].map(([m, label, icon]) => (
            <button key={m} onClick={() => setMode(m)}
              style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '6px 12px', border: 'none', borderRadius: 7,
                       fontWeight: 700, fontSize: 12.5, cursor: 'pointer',
                       background: mode === m ? 'var(--surface)' : 'transparent',
                       color: mode === m ? 'var(--brand-700)' : 'var(--ink-3)',
                       boxShadow: mode === m ? 'var(--sh-1)' : 'none' }}>
              <Icon name={icon} size={14} stroke={2} /> {label}
            </button>
          ))}
        </div>
        {perms.taskManageBoard && mode === 'board' && (
          <button className="btn btn--ghost" style={{ height: 38 }} onClick={() => setColModal({ create: true })}>
            <Icon name="plus" size={15} stroke={2.4} /> Ustun qo'shish
          </button>
        )}
      </div>

      {/* Saralash + filtr */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 9, padding: '0 22px 10px', flexWrap: 'wrap' }}>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12, fontWeight: 700, color: 'var(--ink-3)' }}>
          <Icon name="filter" size={14} /> Filtr:
        </span>
        <select className="select" style={{ width: 'auto', height: 34, padding: '6px 30px 6px 11px', fontSize: 12.5, fontWeight: 600 }}
          value={filterAssignee} onChange={e => setFilterAssignee(e.target.value)}>
          <option value="">Barcha ijrochilar</option>
          <option value="none">Biriktirilmagan</option>
          {DB.USERS.map(u => <option key={u.id} value={u.id}>{u.name}</option>)}
        </select>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12, fontWeight: 700, color: 'var(--ink-3)', marginLeft: 4 }}>
          Saralash:
        </span>
        <select className="select" style={{ width: 'auto', height: 34, padding: '6px 30px 6px 11px', fontSize: 12.5, fontWeight: 600 }}
          value={sortBy} onChange={e => setSortBy(e.target.value)}>
          <option value="manual">Standart tartib</option>
          <option value="due">Tugash sanasi</option>
          <option value="assignee">Ijrochi</option>
        </select>
        {(filterAssignee || sortBy !== 'manual') && (
          <button className="btn btn--ghost btn--sm" onClick={() => { setFilterAssignee(''); setSortBy('manual'); }}>
            <Icon name="x" size={13} /> Tozalash
          </button>
        )}
        <div style={{ flex: 1 }} />
        <span style={{ fontSize: 12, color: 'var(--ink-4)', fontWeight: 600 }}>{visibleTasks.length} ta koʻrsatilmoqda</span>
      </div>

      {mode === 'timeline' ? (
        <TaskTimeline tasks={visibleTasks} taskLists={taskLists} sortBy={sortBy} onOpenCard={(task) => setCardModal({ task })} />
      ) : (
      <div className="tb-cols" style={{ display: 'flex', gap: 14, padding: '4px 22px 18px', height: '100%', overflowX: 'auto' }}>
        {cols.map(col => (
          <TaskColumn key={col.id} col={col} tasks={byList[col.id] || []} perms={perms} dnd={dnd} colDnd={colDnd}
            onAddCard={(listId) => setCardModal({ create: true, listId })}
            onOpenCard={(task) => setCardModal({ task })}
            onDuplicateCard={duplicateCard}
            onEditCol={(c) => setColModal({ col: c })} />
        ))}
        {cols.length === 0 && (
          <div style={{ margin: 'auto', textAlign: 'center', color: 'var(--ink-4)' }}>
            <Empty icon="board" text={perms.taskManageBoard ? "Ustun qo'shing va vazifalarni boshqaring" : 'Doska hali sozlanmagan'} />
          </div>
        )}
      </div>
      )}

      {cardModal && (() => {
        const lid = cardModal.task ? cardModal.task.listId : cardModal.listId;
        return (
          <TaskModal initial={cardModal.task} listId={lid}
            requireDate={!!backlogId && lid !== backlogId}
            perms={perms} onClose={() => setCardModal(null)} onSave={saveCard} onDelete={deleteCard} />
        );
      })()}
      {colModal && (
        <ColumnModal initial={colModal.col} onClose={() => setColModal(null)} onSave={saveCol} onDelete={deleteCol} />
      )}
    </>
  );
}

/* ---------- Vazifa modal (yaratish / tahrirlash) ---------- */
function TaskModal({ initial, listId, perms, requireDate, onClose, onSave, onDelete }) {
  const editing = !!initial;
  const [title, setTitle] = useState(initial ? initial.title : '');
  const [desc, setDesc] = useState(initial ? (initial.desc || '') : '');
  const [assigneeId, setAssigneeId] = useState(initial ? (initial.assigneeId || '') : '');
  const [startDate, setStartDate] = useState(initial ? toDateInput(initial.startDate) : '');
  const [startTime, setStartTime] = useState(initial ? toTimeInput(initial.startDate) : '');
  const [endDate, setEndDate] = useState(initial ? toDateInput(initial.endDate) : '');
  const [endTime, setEndTime] = useState(initial ? toTimeInput(initial.endDate) : '');
  const [labels, setLabels] = useState(initial ? (initial.labels || []) : []);
  const [images, setImages] = useState(initial ? (initial.images || []) : []);
  const [imgBusy, setImgBusy] = useState(false);
  const col = DB.taskListById(listId);
  const dateOrderBad = startDate && endDate && endDate < startDate;
  const endMissing = !endDate;                 // tugash sanasi har doim majburiy
  const assigneeMissing = !assigneeId;         // ijrochi majburiy

  const canEdit = editing ? perms.taskEdit : perms.taskAdd;
  const invalid = !title.trim() || assigneeMissing || endMissing || dateOrderBad;

  function toggleLabel(color) {
    setLabels(prev => prev.some(l => l.color === color) ? prev.filter(l => l.color !== color) : [...prev, { color }]);
  }
  async function addImages(fileList) {
    const files = Array.from(fileList || []).filter(f => f.type.startsWith('image/'));
    if (!files.length) return;
    setImgBusy(true);
    try {
      const urls = [];
      for (const f of files) urls.push(await fileToDataURL(f, 1280, 'image/jpeg', 0.82));
      setImages(prev => [...prev, ...urls]);
    } catch (_) { /* noto'g'ri fayl — e'tiborsiz */ }
    setImgBusy(false);
  }
  function removeImage(idx) {
    setImages(prev => prev.filter((_, i) => i !== idx));
  }
  function submit() {
    if (!canEdit || invalid) return;
    onSave({
      title: title.trim(),
      desc: desc.trim(),
      assigneeId: assigneeId || null,
      dueDate: null,
      startDate: dateTimeToIso(startDate, startTime),
      endDate: dateTimeToIso(endDate, endTime),
      labels,
      images,
    });
  }

  return (
    <Modal onClose={onClose} width={520}>
      <ModalHeader title={editing ? 'Vazifani tahrirlash' : 'Yangi vazifa'}
        subtitle={col ? col.title + (col.category === 'done' ? ' · Bajarilgan' : ' · Jarayonda') : ''} onClose={onClose} />
      <div style={{ padding: '20px 22px', display: 'grid', gap: 14, overflowY: 'auto', flex: 1, minHeight: 0 }}>
        <div>
          <label className="field-label">Sarlavha</label>
          <input className="input" value={title} autoFocus disabled={!canEdit} onChange={e => setTitle(e.target.value)}
            onKeyDown={e => { if (e.key === 'Enter' && title.trim()) submit(); }} />
        </div>
        <div>
          <label className="field-label">Tavsif</label>
          <textarea className="input" rows={3} value={desc} disabled={!canEdit} onChange={e => setDesc(e.target.value)}
            style={{ resize: 'vertical', minHeight: 60, fontFamily: 'inherit' }} />
        </div>
        <div>
          <label className="field-label">Ijrochi (ma'sul shaxs)<span style={{ color: '#C2453A', fontWeight: 700 }}> *</span></label>
          <select className="select" value={assigneeId} disabled={!canEdit} onChange={e => setAssigneeId(e.target.value)}>
            <option value="">Tanlang…</option>
            {DB.USERS.map(u => <option key={u.id} value={u.id}>{u.name}</option>)}
          </select>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: '1.4fr 1fr', gap: 12 }}>
          <div>
            <label className="field-label">Boshlanish sanasi</label>
            <input className="input" type="date" value={startDate} disabled={!canEdit} onChange={e => setStartDate(e.target.value)} />
          </div>
          <div>
            <label className="field-label">Soat (ixtiyoriy)</label>
            <input className="input" type="time" value={startTime} disabled={!canEdit || !startDate} onChange={e => setStartTime(e.target.value)} />
          </div>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: '1.4fr 1fr', gap: 12 }}>
          <div>
            <label className="field-label">Tugash sanasi<span style={{ color: '#C2453A', fontWeight: 700 }}> *</span></label>
            <input className="input" type="date" value={endDate} disabled={!canEdit} min={startDate || undefined} onChange={e => setEndDate(e.target.value)} />
          </div>
          <div>
            <label className="field-label">Soat (ixtiyoriy)</label>
            <input className="input" type="time" value={endTime} disabled={!canEdit || !endDate} onChange={e => setEndTime(e.target.value)} />
          </div>
        </div>
        {dateOrderBad && (
          <div style={{ fontSize: 12, color: '#C2453A', fontWeight: 600, marginTop: -6 }}>
            Tugash sanasi boshlanishdan oldin boʻlmasligi kerak.
          </div>
        )}
        {endMissing && !dateOrderBad && (
          <div style={{ fontSize: 12, color: '#A6651E', fontWeight: 600, marginTop: -6 }}>
            Tugash sanasi majburiy.
          </div>
        )}
        {assigneeMissing && (
          <div style={{ fontSize: 12, color: '#A6651E', fontWeight: 600, marginTop: -6 }}>
            Ijrochini tanlash majburiy.
          </div>
        )}
        <div>
          <label className="field-label">Teglar</label>
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            {DB.TASK_LABEL_COLORS.map(c => {
              const on = labels.some(l => l.color === c);
              return (
                <button key={c} type="button" disabled={!canEdit} onClick={() => toggleLabel(c)}
                  title={on ? 'Olib tashlash' : 'Qoʻshish'}
                  style={{ width: 38, height: 24, borderRadius: 6, background: c, cursor: canEdit ? 'pointer' : 'default',
                           border: on ? '2px solid var(--ink)' : '2px solid transparent', outline: on ? '2px solid #fff' : 'none', outlineOffset: -4,
                           display: 'grid', placeItems: 'center' }}>
                  {on && <Icon name="check" size={13} stroke={3} style={{ color: '#fff' }} />}
                </button>
              );
            })}
          </div>
        </div>
        <div>
          <label className="field-label">Rasmlar{images.length > 0 ? ' · ' + images.length + ' ta' : ''}</label>
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            {images.map((src, i) => (
              <div key={i} style={{ position: 'relative', width: 72, height: 72, borderRadius: 8, overflow: 'hidden',
                                    border: '1px solid var(--border)', background: 'var(--bg-2)' }}>
                <img src={src} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
                {canEdit && (
                  <button type="button" onClick={() => removeImage(i)} title="O'chirish"
                    style={{ position: 'absolute', top: 3, right: 3, width: 20, height: 20, borderRadius: '50%', border: 'none',
                             background: 'rgba(0,0,0,.62)', color: '#fff', cursor: 'pointer', display: 'grid', placeItems: 'center', padding: 0 }}>
                    <Icon name="x" size={12} stroke={2.6} />
                  </button>
                )}
              </div>
            ))}
            {canEdit && (
              <label title="Rasm qoʻshish"
                style={{ width: 72, height: 72, borderRadius: 8, border: '1.5px dashed var(--border)', background: 'var(--bg-2)',
                         display: 'grid', placeItems: 'center', cursor: imgBusy ? 'default' : 'pointer', color: 'var(--ink-4)' }}>
                <input type="file" accept="image/*" multiple disabled={imgBusy} style={{ display: 'none' }}
                  onChange={e => { addImages(e.target.files); e.target.value = ''; }} />
                <Icon name={imgBusy ? 'clock' : 'plus'} size={20} stroke={2} />
              </label>
            )}
          </div>
        </div>
        {editing && initial.completedAt && (
          <div style={{ fontSize: 12, color: 'var(--ink-4)', background: 'var(--bg-2)', padding: '9px 12px', borderRadius: 8 }}>
            <Icon name="check" size={13} style={{ verticalAlign: '-2px', marginRight: 5, color: 'var(--st-delivered)' }} />
            Bajarilgan: {fmtTaskDue(initial.completedAt)}
            {initial.endDate && (startOfDay(initial.completedAt).getTime() <= startOfDay(initial.endDate).getTime()
              ? <span style={{ color: 'var(--st-delivered)', fontWeight: 700 }}> · muddatida</span>
              : <span style={{ color: '#C2453A', fontWeight: 700 }}> · kechikib</span>)}
          </div>
        )}
      </div>
      <div className="modal-foot" style={{ padding: '14px 22px', borderTop: '1px solid var(--border)', display: 'flex', gap: 10, alignItems: 'center' }}>
        {editing && perms.taskDelete && (
          <button className="btn btn--ghost" style={{ color: '#C2453A' }} onClick={() => onDelete(initial.id)}>
            <Icon name="trash" size={15} /> O'chirish
          </button>
        )}
        <div style={{ flex: 1 }} />
        <button className="btn btn--ghost" onClick={onClose}>Bekor qilish</button>
        {canEdit && <button className="btn btn--primary" disabled={invalid} onClick={submit}><Icon name="check" size={16} stroke={2.4} /> Saqlash</button>}
      </div>
    </Modal>
  );
}

/* ---------- Ustun modal (yaratish / tahrirlash) ---------- */
function ColumnModal({ initial, onClose, onSave, onDelete }) {
  const editing = !!initial;
  const [title, setTitle] = useState(initial ? initial.title : '');
  const [color, setColor] = useState(initial ? initial.color : '#8E74FF');
  const [category, setCategory] = useState(initial ? initial.category : 'doing');
  const palette = ['#8E74FF', '#E3A23B', '#3DBE9C', '#E8735F', '#5A9FD4', '#8E74FF', '#D9708F', '#7A7068'];

  return (
    <Modal onClose={onClose} width={460}>
      <ModalHeader title={editing ? 'Ustunni tahrirlash' : 'Yangi ustun'} onClose={onClose} />
      <div style={{ padding: '20px 22px', display: 'grid', gap: 14 }}>
        <div>
          <label className="field-label">Ustun nomi</label>
          <input className="input" value={title} autoFocus onChange={e => setTitle(e.target.value)}
            onKeyDown={e => { if (e.key === 'Enter' && title.trim()) onSave({ title: title.trim(), color, category }); }} />
        </div>
        <div>
          <label className="field-label">Kategoriya <span style={{ color: 'var(--ink-4)', fontWeight: 400 }}>(statistika uchun)</span></label>
          <select className="select" value={category} onChange={e => setCategory(e.target.value)}>
            <option value="doing">Jarayonda</option>
            <option value="done">Bajarilgan</option>
          </select>
          <div style={{ fontSize: 11.5, color: 'var(--ink-4)', marginTop: 6, lineHeight: 1.5 }}>
            "Bajarilgan" ustuniga tushgan vazifalar bajarilgan deb hisoblanadi. Statistika ustun nomi emas, shu kategoriya bo'yicha chiqadi.
          </div>
        </div>
        <div>
          <label className="field-label">Rang</label>
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            {palette.map(c => (
              <button key={c} type="button" onClick={() => setColor(c)}
                style={{ width: 30, height: 30, borderRadius: 8, background: c, cursor: 'pointer',
                         border: color === c ? '2px solid var(--ink)' : '2px solid transparent', outline: color === c ? '2px solid #fff' : 'none', outlineOffset: -4 }} />
            ))}
          </div>
        </div>
      </div>
      <div style={{ padding: '14px 22px', borderTop: '1px solid var(--border)', display: 'flex', gap: 10, alignItems: 'center' }}>
        {editing && (
          <button className="btn btn--ghost" style={{ color: '#C2453A' }} onClick={() => onDelete(initial.id)}>
            <Icon name="trash" size={15} /> O'chirish
          </button>
        )}
        <div style={{ flex: 1 }} />
        <button className="btn btn--ghost" onClick={onClose}>Bekor qilish</button>
        <button className="btn btn--primary" disabled={!title.trim()} onClick={() => onSave({ title: title.trim(), color, category })}>
          <Icon name="check" size={16} stroke={2.4} /> Saqlash
        </button>
      </div>
    </Modal>
  );
}

/* ---------- Timeline (Gantt) ko'rinishi ---------- */
const TL_DAY = 86400000;
function tlStartOfDay(d) { const x = new Date(d); x.setHours(0, 0, 0, 0); return x; }
function tlDaysBetween(a, b) { return Math.round((tlStartOfDay(b) - tlStartOfDay(a)) / TL_DAY); }

function TaskTimeline({ tasks, taskLists, sortBy = 'manual', onOpenCard }) {
  const months = ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'Iyn', 'Iyl', 'Avg', 'Sen', 'Okt', 'Noy', 'Dek'];
  const colorOf = (listId) => {
    const l = (taskLists || []).find(x => x.id === listId) || DB.taskListById(listId);
    return (l && l.color) || '#8E74FF';
  };
  const dated = tasks.filter(t => t.startDate || t.endDate);
  const undated = tasks.filter(t => !t.startDate && !t.endDate);

  if (dated.length === 0) {
    return (
      <div style={{ padding: '4px 22px 18px', height: '100%' }}>
        <div style={{ margin: '40px auto', textAlign: 'center', color: 'var(--ink-4)' }}>
          <Empty icon="chart" text="Sana belgilangan vazifa yoʻq — vazifaga boshlanish/tugash sanasini qoʻshing" />
        </div>
      </div>
    );
  }

  let minT = Infinity, maxT = -Infinity;
  dated.forEach(t => {
    const s = tlStartOfDay(t.startDate || t.endDate).getTime();
    const e = tlStartOfDay(t.endDate || t.startDate).getTime();
    minT = Math.min(minT, s); maxT = Math.max(maxT, e);
  });
  const PAD = 2, DAYW = 38, LEFTW = 200, ROWH = 40;
  const rangeStart = tlStartOfDay(new Date(minT - PAD * TL_DAY));
  const totalDays = tlDaysBetween(rangeStart, new Date(maxT)) + PAD + 1;
  const today = tlStartOfDay(new Date());
  const days = Array.from({ length: totalDays }, (_, i) => new Date(rangeStart.getTime() + i * TL_DAY));
  const sorted = [...dated].sort((a, b) => {
    if (sortBy === 'due') {
      const av = a.endDate ? new Date(a.endDate).getTime() : Infinity;
      const bv = b.endDate ? new Date(b.endDate).getTime() : Infinity;
      if (av !== bv) return av - bv;
    } else if (sortBy === 'assignee') {
      const an = a.assigneeId ? DB.userName(a.assigneeId) : '￿';
      const bn = b.assigneeId ? DB.userName(b.assigneeId) : '￿';
      const c = an.localeCompare(bn); if (c) return c;
    }
    return new Date(a.startDate || a.endDate) - new Date(b.startDate || b.endDate);
  });
  const fmt = d => d.getDate() + ' ' + months[d.getMonth()].toLowerCase();

  return (
    <div style={{ padding: '4px 22px 18px', height: '100%', display: 'flex', flexDirection: 'column', minHeight: 0 }}>
      <div style={{ overflow: 'auto', border: '1px solid var(--border)', borderRadius: 'var(--r)', background: 'var(--surface)', flex: 1, minHeight: 0 }}>
        <div style={{ minWidth: LEFTW + totalDays * DAYW, position: 'relative' }}>
          {/* Sarlavha qatori */}
          <div style={{ display: 'flex', position: 'sticky', top: 0, zIndex: 3, background: 'var(--surface)', borderBottom: '1px solid var(--border)' }}>
            <div style={{ width: LEFTW, flex: 'none', position: 'sticky', left: 0, zIndex: 4, background: 'var(--surface)',
                          borderRight: '1px solid var(--border)', padding: '10px 14px', fontWeight: 700, fontSize: 12, color: 'var(--ink-3)' }}>Vazifa</div>
            <div style={{ display: 'flex' }}>
              {days.map((d, i) => {
                const wk = d.getDay() === 0 || d.getDay() === 6;
                const isToday = d.getTime() === today.getTime();
                const showM = i === 0 || d.getDate() === 1;
                return (
                  <div key={i} style={{ width: DAYW, flex: 'none', textAlign: 'center', padding: '5px 0', borderRight: '1px solid var(--border)',
                                        background: isToday ? 'var(--brand-tint)' : (wk ? 'var(--bg-2)' : 'transparent') }}>
                    <div style={{ fontSize: 9, fontWeight: 700, height: 11, color: 'var(--ink-4)' }}>{showM ? months[d.getMonth()] : ''}</div>
                    <div style={{ fontSize: 11.5, fontWeight: isToday ? 800 : 600, color: isToday ? 'var(--brand-700)' : 'var(--ink-2)' }}>{d.getDate()}</div>
                  </div>
                );
              })}
            </div>
          </div>
          {/* Qatorlar */}
          {sorted.map(t => {
            const s = tlStartOfDay(t.startDate || t.endDate);
            const e = tlStartOfDay(t.endDate || t.startDate);
            const offset = tlDaysBetween(rangeStart, s);
            const span = Math.max(1, tlDaysBetween(s, e) + 1);
            const done = DB.taskCategory(t.listId) === 'done';
            const c = colorOf(t.listId);
            const range = t.startDate && t.endDate ? (fmt(new Date(t.startDate)) + ' – ' + fmt(new Date(t.endDate)))
              : fmt(new Date(t.startDate || t.endDate));
            return (
              <div key={t.id} style={{ display: 'flex', borderBottom: '1px solid var(--border)' }}>
                <div onClick={() => onOpenCard(t)} title={t.title}
                  style={{ width: LEFTW, flex: 'none', position: 'sticky', left: 0, zIndex: 2, background: 'var(--surface)',
                           borderRight: '1px solid var(--border)', padding: '0 14px', height: ROWH, display: 'flex', alignItems: 'center',
                           cursor: 'pointer', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', fontSize: 12.5, fontWeight: 700 }}>
                  {t.title}
                </div>
                <div style={{ position: 'relative', width: totalDays * DAYW, height: ROWH }}>
                  <div onClick={() => onOpenCard(t)} title={t.title + ' · ' + range}
                    style={{ position: 'absolute', top: 8, left: offset * DAYW + 3, width: span * DAYW - 6, height: ROWH - 16,
                             borderRadius: 7, background: c, opacity: done ? 0.72 : 1, display: 'flex', alignItems: 'center', padding: '0 9px',
                             cursor: 'pointer', boxShadow: 'var(--sh-1)', overflow: 'hidden' }}>
                    {done && <Icon name="check" size={12} stroke={2.6} style={{ color: '#fff', marginRight: 5, flex: 'none' }} />}
                    <span style={{ color: '#fff', fontSize: 11, fontWeight: 700, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{t.title}</span>
                  </div>
                </div>
              </div>
            );
          })}
        </div>
      </div>
      {undated.length > 0 && (
        <div style={{ marginTop: 12 }}>
          <div style={{ fontSize: 11.5, fontWeight: 700, color: 'var(--ink-4)', marginBottom: 7 }}>Sanasiz vazifalar ({undated.length})</div>
          <div style={{ display: 'flex', gap: 7, flexWrap: 'wrap' }}>
            {undated.map(t => (
              <button key={t.id} onClick={() => onOpenCard(t)}
                style={{ display: 'inline-flex', alignItems: 'center', gap: 6, padding: '5px 11px', borderRadius: 100,
                         background: 'var(--surface)', border: '1px solid var(--border)', fontSize: 12, fontWeight: 700, cursor: 'pointer' }}>
                <span className="dot" style={{ width: 7, height: 7, background: colorOf(t.listId) }} /> {t.title}
              </button>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

window.TaskBoard = TaskBoard;
