/* ============================================================
   Tasks — Asana-style task tracker (app.mesper.de/approvals)
   v1: Board view. List + Timeline (with leave overlay) come next.
   Projects = clients or internal work. Everyone can read/write.
   ============================================================ */
const { useState, useEffect, useRef } = React;

const TASK_STATUSES = [
  { id: "todo",        label: "To Do",       color: "var(--ink-3)" },
  { id: "in_progress", label: "In Progress", color: "var(--accent)" },
  { id: "blocked",     label: "Blocked",     color: "var(--st-declined)" },
  { id: "done",        label: "Done",        color: "var(--st-approved)" },
];
const PRIORITIES = {
  low:    { label: "Low",    color: "var(--ink-4)" },
  medium: { label: "Medium", color: "var(--st-pending)" },
  high:   { label: "High",   color: "var(--st-declined)" },
};
let DRAG_TASK_ID = null; // current dragged task (module-level, survives re-renders)

function taskDueLabel(due) {
  if (!due) return null;
  try { const d = parseISO(due); return `${MONTHS_SHORT[d.getMonth()]} ${d.getDate()}`; } catch (e) { return due; }
}
function dueState(due, status) {
  if (!due || status === "done") return "neutral";
  const tmr = toISO(addDays(parseISO(TODAY), 1));
  if (due < TODAY) return "overdue";
  if (due === TODAY || due === tmr) return "soon";
  return "neutral";
}
const DUE_COLORS = {
  neutral: { c: "var(--ink-4)", b: "transparent" },
  soon: { c: "var(--st-pending)", b: "var(--st-pending-soft)" },
  overdue: { c: "var(--st-declined)", b: "var(--st-declined-soft)" },
};

/* ---- completion circle (mark complete) ---- */
function CompleteCircle({ done, onToggle, size = 20 }) {
  const [hover, setHover] = useState(false);
  return (
    <button type="button" title={done ? "Mark incomplete" : "Mark complete"}
      onClick={(e) => { e.stopPropagation(); onToggle(); }}
      onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      className="vp-focusable"
      style={{
        width: size, height: size, borderRadius: "50%", flex: "0 0 auto", padding: 0,
        display: "grid", placeItems: "center", cursor: "pointer", transition: "all .12s",
        border: `2px solid ${done ? "var(--st-approved)" : "var(--line-strong)"}`,
        background: done ? "var(--st-approved)" : hover ? "var(--accent-soft)" : "transparent",
        color: done ? "#fff" : hover ? "var(--accent)" : "transparent",
      }}>
      <Icon name="check" size={size * 0.62} stroke={2.6} />
    </button>
  );
}

/* ---- priority + due pills ---- */
function PriorityPill({ p }) {
  const pr = PRIORITIES[p];
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 5, fontSize: 11.5, fontWeight: 700, color: pr.color, background: `color-mix(in srgb, ${pr.color} 13%, var(--surface))`, padding: "2px 8px", borderRadius: "var(--r-pill)", whiteSpace: "nowrap" }}>
      <span style={{ width: 6, height: 6, borderRadius: "50%", background: pr.color }} />{pr.label}
    </span>
  );
}
function DuePill({ due, status }) {
  if (!due) return null;
  const s = DUE_COLORS[dueState(due, status)];
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 4, fontSize: 11.5, fontWeight: 600, color: s.c, background: s.b, padding: s.b === "transparent" ? 0 : "2px 7px", borderRadius: "var(--r-pill)", whiteSpace: "nowrap" }}>
      <Icon name="calendar" size={12} /> {taskDueLabel(due)}
    </span>
  );
}

/* ---- small chip for a task's project ---- */
function ProjectChip({ project, size = "sm" }) {
  if (!project) return null;
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: size === "sm" ? 11.5 : 13, color: "var(--ink-3)", fontWeight: 600, whiteSpace: "nowrap", maxWidth: 160, overflow: "hidden", textOverflow: "ellipsis" }}>
      <span style={{ width: 8, height: 8, borderRadius: 3, background: project.color, flex: "0 0 auto" }} />
      {project.name}
    </span>
  );
}

/* ---- one task card ---- */
function TaskCard({ task, project, assignee, onOpen, onToggleComplete, onDropBefore }) {
  const [hover, setHover] = useState(false);
  const [dropOver, setDropOver] = useState(false);
  const [dragging, setDragging] = useState(false);
  const done = task.status === "done";
  return (
    <div>
      {dropOver && <div style={{ height: 2, background: "var(--accent)", borderRadius: 2, margin: "0 2px 7px" }} />}
      <div draggable
        onDragStart={() => { DRAG_TASK_ID = task.id; setDragging(true); }}
        onDragEnd={() => { DRAG_TASK_ID = null; setDragging(false); setDropOver(false); }}
        onDragOver={(e) => { if (DRAG_TASK_ID && DRAG_TASK_ID !== task.id) { e.preventDefault(); e.stopPropagation(); if (!dropOver) setDropOver(true); } }}
        onDragLeave={() => setDropOver(false)}
        onDrop={(e) => { e.preventDefault(); e.stopPropagation(); setDropOver(false); onDropBefore && onDropBefore(); }}
        onClick={() => onOpen(task)}
        onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
        style={{
          background: "var(--surface)", border: "1px solid var(--line)", borderRadius: "var(--r-md)",
          padding: "12px", cursor: "grab", opacity: dragging ? 0.4 : 1,
          boxShadow: hover ? "var(--sh-md)" : "var(--sh-sm)", transform: hover && !dragging ? "translateY(-1px)" : "none",
          transition: "box-shadow .14s, transform .14s", display: "flex", flexDirection: "column", gap: 10,
        }}>
        <div style={{ display: "flex", alignItems: "flex-start", gap: 9 }}>
          <div style={{ flex: "0 0 auto", marginTop: 1, opacity: done || hover ? 1 : 0, transition: "opacity .12s" }}>
            <CompleteCircle done={done} onToggle={() => onToggleComplete(task)} size={18} />
          </div>
          <div style={{ fontWeight: 600, fontSize: 14, color: "var(--ink)", lineHeight: 1.35, textDecoration: done ? "line-through" : "none", opacity: done ? 0.55 : 1 }}>{task.title}</div>
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
          <ProjectChip project={project} />
          <div style={{ flex: 1 }} />
          {task.priority !== "medium" && <PriorityPill p={task.priority} />}
          <DuePill due={task.due} status={task.status} />
          {assignee ? <Avatar person={assignee} size={22} /> : <div style={{ width: 22, height: 22, borderRadius: "50%", background: "var(--paper-2)", display: "grid", placeItems: "center", color: "var(--ink-4)" }}><Icon name="users" size={12} /></div>}
        </div>
      </div>
    </div>
  );
}

/* ---- inline quick-add (per board column / list group) ---- */
function InlineAdd({ status, onAdd }) {
  const [active, setActive] = useState(false);
  const [val, setVal] = useState("");
  const ref = useRef(null);
  useEffect(() => { if (active && ref.current) ref.current.focus(); }, [active]);
  function commit() { const t = val.trim(); if (t) { onAdd(status, t); setVal(""); if (ref.current) ref.current.focus(); } }
  if (!active) {
    return (
      <button type="button" onClick={() => setActive(true)} className="vp-focusable"
        style={{ display: "flex", alignItems: "center", gap: 7, width: "100%", padding: "8px 10px", border: "none", background: "transparent", color: "var(--ink-4)", cursor: "pointer", fontWeight: 600, fontSize: 13, borderRadius: "var(--r-sm)" }}>
        <Icon name="plus" size={15} /> Add task
      </button>
    );
  }
  return (
    <input ref={ref} value={val} onChange={(e) => setVal(e.target.value)}
      onBlur={() => { if (!val.trim()) setActive(false); }}
      onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); commit(); } else if (e.key === "Escape") { setVal(""); setActive(false); } }}
      placeholder="Task name — Enter to add"
      style={{ width: "100%", padding: "9px 11px", fontSize: 13.5, border: "1px solid var(--accent)", borderRadius: "var(--r-sm)", outline: "none", boxShadow: "0 0 0 3px var(--accent-soft)", color: "var(--ink)" }} />
  );
}

/* ---- board view (drag between columns + reorder within a column) ---- */
function BoardView({ tasks, projectsById, onMove, onOpen, onToggleComplete, onQuickAdd }) {
  const [overCol, setOverCol] = useState(null);
  const ordered = (status) => tasks.filter((t) => t.status === status).sort((a, b) => a.sort - b.sort);

  function dropBefore(status, target, list) {
    if (!DRAG_TASK_ID || DRAG_TASK_ID === target.id) return;
    const idx = list.findIndex((t) => t.id === target.id);
    const prev = list[idx - 1];
    const newSort = prev ? (prev.sort + target.sort) / 2 : target.sort - 1;
    onMove(DRAG_TASK_ID, status, newSort);
  }
  function dropEnd(status, list) {
    if (!DRAG_TASK_ID) return;
    const last = list[list.length - 1];
    onMove(DRAG_TASK_ID, status, last ? last.sort + 1 : 0);
  }

  return (
    <div style={{ display: "flex", gap: 14, alignItems: "flex-start", overflowX: "auto", paddingBottom: 8 }}>
      {TASK_STATUSES.map((col) => {
        const list = ordered(col.id);
        const isOver = overCol === col.id;
        return (
          <div key={col.id}
            onDragOver={(e) => { e.preventDefault(); if (overCol !== col.id) setOverCol(col.id); }}
            onDragLeave={(e) => { if (e.currentTarget === e.target) setOverCol(null); }}
            onDrop={(e) => { e.preventDefault(); setOverCol(null); dropEnd(col.id, list); }}
            style={{
              flex: "1 0 256px", minWidth: 256, maxWidth: 320, background: isOver ? "var(--accent-soft)" : "var(--surface-2)",
              border: `1px solid ${isOver ? "var(--accent)" : "var(--line)"}`, borderRadius: "var(--r-lg)",
              padding: 10, transition: "background .12s, border-color .12s", display: "flex", flexDirection: "column", gap: 9, maxHeight: "calc(100vh - 230px)",
            }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "4px 6px 2px" }}>
              <span style={{ width: 9, height: 9, borderRadius: "50%", background: col.color }} />
              <span style={{ fontWeight: 700, fontSize: 13.5, color: "var(--ink-2)" }}>{col.label}</span>
              <span style={{ fontSize: 12, color: "var(--ink-4)", fontWeight: 600 }}>{list.length}</span>
            </div>
            <div style={{ display: "flex", flexDirection: "column", gap: 9, overflowY: "auto" }}>
              {list.map((t) => (
                <TaskCard key={t.id} task={t} project={projectsById[t.projectId]} assignee={t.assignee ? VP_DATA.byId[t.assignee] : null} onOpen={onOpen} onToggleComplete={onToggleComplete} onDropBefore={() => dropBefore(col.id, t, list)} />
              ))}
              <InlineAdd status={col.id} onAdd={onQuickAdd} />
            </div>
          </div>
        );
      })}
    </div>
  );
}

/* ---- list view (grouped by status) ---- */
function ListRow({ t, project, first, onOpen }) {
  const [hover, setHover] = useState(false);
  const assignee = t.assignee ? VP_DATA.byId[t.assignee] : null;
  const overdue = t.due && t.status !== "done" && t.due < TODAY;
  return (
    <div onClick={() => onOpen(t)} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      style={{ display: "grid", gridTemplateColumns: "1fr 160px 150px 92px 96px", gap: 12, alignItems: "center", padding: "11px 16px", cursor: "pointer", borderTop: first ? "none" : "1px solid var(--line)", background: hover ? "var(--surface-2)" : "transparent", transition: "background .12s" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 9, minWidth: 0 }}>
        <span style={{ width: 7, height: 7, borderRadius: "50%", background: PRIORITIES[t.priority].color, flex: "0 0 auto" }} />
        <span style={{ fontWeight: 600, fontSize: 14, color: "var(--ink)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{t.title}</span>
      </div>
      <ProjectChip project={project} />
      <div style={{ display: "flex", alignItems: "center", gap: 8, minWidth: 0 }}>
        {assignee ? <><Avatar person={assignee} size={24} /><span style={{ fontSize: 13, color: "var(--ink-2)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{assignee.first}</span></> : <span style={{ fontSize: 12.5, color: "var(--ink-4)" }}>Unassigned</span>}
      </div>
      <span style={{ fontSize: 12.5, fontWeight: 600, color: PRIORITIES[t.priority].color }}>{PRIORITIES[t.priority].label}</span>
      <span style={{ fontSize: 12.5, fontWeight: 600, color: overdue ? "var(--st-declined)" : "var(--ink-3)", display: "flex", alignItems: "center", gap: 5 }}>
        {t.due ? <><Icon name="calendar" size={12} /> {taskDueLabel(t.due)}</> : "—"}
      </span>
    </div>
  );
}
function ListView({ tasks, projectsById, onOpen }) {
  if (!tasks.length) return <div style={{ color: "var(--ink-4)", fontSize: 14, textAlign: "center", padding: "40px 0" }}>No tasks here yet.</div>;
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 20 }}>
      {TASK_STATUSES.map((col) => {
        const rows = tasks.filter((t) => t.status === col.id);
        if (!rows.length) return null;
        return (
          <div key={col.id}>
            <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 8, padding: "0 2px" }}>
              <span style={{ width: 9, height: 9, borderRadius: "50%", background: col.color }} />
              <span style={{ fontWeight: 700, fontSize: 13.5, color: "var(--ink-2)" }}>{col.label}</span>
              <span style={{ fontSize: 12, color: "var(--ink-4)", fontWeight: 600 }}>{rows.length}</span>
            </div>
            <Card pad="2px 0">
              {rows.map((t, i) => <ListRow key={t.id} t={t} project={projectsById[t.projectId]} first={i === 0} onOpen={onOpen} />)}
            </Card>
          </div>
        );
      })}
    </div>
  );
}

/* ---- timeline (Gantt) with approved-leave overlay ---- */
function dDiff(aISO, bISO) { return Math.round((parseISO(bISO) - parseISO(aISO)) / 86400000); }
function startOfWeekISO(iso) { const d = parseISO(iso); return toISO(addDays(d, -((d.getDay() + 6) % 7))); }
function endOfWeekISO(iso) { const d = parseISO(iso); return toISO(addDays(d, 6 - ((d.getDay() + 6) % 7))); }
const TL_LABEL_W = 176;

function TimelineRow({ label, sub, color, bars, totalDays, spanStart, onClick }) {
  return (
    <div onClick={onClick} style={{ display: "flex", alignItems: "stretch", borderTop: "1px solid var(--line)", minHeight: 42, cursor: onClick ? "pointer" : "default" }}>
      <div style={{ width: TL_LABEL_W, flex: "0 0 auto", padding: "8px 12px", display: "flex", alignItems: "center", gap: 9, borderRight: "1px solid var(--line)", background: "var(--surface)", position: "sticky", left: 0, zIndex: 2 }}>
        {color && <span style={{ width: 8, height: 8, borderRadius: "50%", background: color, flex: "0 0 auto" }} />}
        <div style={{ minWidth: 0 }}>
          <div style={{ fontWeight: 600, fontSize: 13, color: "var(--ink)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{label}</div>
          {sub && <div style={{ fontSize: 11, color: "var(--ink-4)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{sub}</div>}
        </div>
      </div>
      <div style={{ flex: 1, position: "relative" }}>
        {bars.map((b, i) => (
          <div key={i} title={b.title}
            style={{ position: "absolute", top: 8, height: 24, left: `${(dDiff(spanStart, b.from) / totalDays) * 100}%`, width: `calc(${((dDiff(b.from, b.to) + 1) / totalDays) * 100}% - 3px)`, margin: "0 1.5px", borderRadius: 6, background: b.soft ? b.soft : b.color, border: b.soft ? `1.5px solid ${b.color}` : "none", color: b.soft ? b.color : "#fff", display: "flex", alignItems: "center", padding: "0 8px", fontSize: 11.5, fontWeight: 600, overflow: "hidden", whiteSpace: "nowrap" }}>
            {b.label}
          </div>
        ))}
      </div>
    </div>
  );
}

function TimelineView({ tasks, projectsById, leave, onOpen }) {
  const dated = tasks.filter((t) => t.due);
  const leaveItems = (leave || []).filter((r) => r.from && r.to);
  if (!dated.length && !leaveItems.length) {
    return <div style={{ color: "var(--ink-4)", fontSize: 14, textAlign: "center", padding: "48px 0" }}>Add due dates to tasks to see them on the timeline.</div>;
  }
  const starts = [...dated.map((t) => t.start || t.due), ...leaveItems.map((r) => r.from), TODAY];
  const ends = [...dated.map((t) => t.due), ...leaveItems.map((r) => r.to), TODAY];
  const minISO = starts.reduce((a, b) => (b < a ? b : a));
  const maxISO = ends.reduce((a, b) => (b > a ? b : a));
  const spanStart = startOfWeekISO(minISO);
  let spanEnd = endOfWeekISO(maxISO);
  if (dDiff(spanStart, spanEnd) < 41) spanEnd = endOfWeekISO(toISO(addDays(parseISO(spanStart), 41)));
  const totalDays = dDiff(spanStart, spanEnd) + 1;
  const weeks = Math.ceil(totalDays / 7);
  const trackMin = Math.max(640, weeks * 116);
  const todayLeft = (dDiff(spanStart, TODAY) >= 0 && dDiff(spanStart, TODAY) <= totalDays) ? (dDiff(spanStart, TODAY) / totalDays) * 100 : null;

  // group leave by person
  const byPerson = {};
  leaveItems.forEach((r) => { (byPerson[r.person] = byPerson[r.person] || []).push(r); });
  const peopleOut = Object.keys(byPerson).map((pid) => VP_DATA.byId[pid]).filter(Boolean);

  const weekTicks = [];
  for (let w = 0; w < weeks; w++) {
    const wISO = toISO(addDays(parseISO(spanStart), w * 7));
    const d = parseISO(wISO);
    weekTicks.push({ left: ((w * 7) / totalDays) * 100, label: `${MONTHS_SHORT[d.getMonth()]} ${d.getDate()}` });
  }

  const GroupHeader = ({ children }) => (
    <div style={{ display: "flex", borderTop: "1px solid var(--line)", background: "var(--surface-2)" }}>
      <div style={{ width: TL_LABEL_W, flex: "0 0 auto", padding: "7px 12px", fontSize: 11.5, fontWeight: 700, letterSpacing: "0.05em", textTransform: "uppercase", color: "var(--ink-4)", borderRight: "1px solid var(--line)", position: "sticky", left: 0, zIndex: 2 }}>{children}</div>
      <div style={{ flex: 1 }} />
    </div>
  );

  return (
    <Card pad="0" style={{ overflow: "hidden" }}>
      <div style={{ overflowX: "auto" }}>
        <div style={{ minWidth: TL_LABEL_W + trackMin, position: "relative" }}>
          {/* axis header */}
          <div style={{ display: "flex", borderBottom: "1px solid var(--line)" }}>
            <div style={{ width: TL_LABEL_W, flex: "0 0 auto", borderRight: "1px solid var(--line)", background: "var(--surface)", position: "sticky", left: 0, zIndex: 3 }} />
            <div style={{ flex: 1, position: "relative", height: 30 }}>
              {weekTicks.map((t, i) => (
                <div key={i} style={{ position: "absolute", left: `${t.left}%`, top: 0, bottom: 0, paddingLeft: 6, borderLeft: "1px solid var(--line)", fontSize: 11, color: "var(--ink-4)", fontWeight: 600, display: "flex", alignItems: "center" }}>{t.label}</div>
              ))}
            </div>
          </div>

          {/* vertical week gridlines + today line across the whole body */}
          <div style={{ position: "absolute", left: TL_LABEL_W, right: 0, top: 30, bottom: 0, pointerEvents: "none", zIndex: 0 }}>
            {weekTicks.map((t, i) => <div key={i} style={{ position: "absolute", left: `${t.left}%`, top: 0, bottom: 0, borderLeft: "1px solid var(--line)", opacity: 0.5 }} />)}
            {todayLeft != null && <div style={{ position: "absolute", left: `${todayLeft}%`, top: 0, bottom: 0, width: 2, background: "var(--accent)", opacity: 0.6 }} />}
          </div>

          {/* Team out */}
          {peopleOut.length > 0 && <>
            <GroupHeader>Team out</GroupHeader>
            {peopleOut.map((p) => (
              <TimelineRow key={p.id} label={`${p.first} ${p.last}`} color={p.color} totalDays={totalDays} spanStart={spanStart}
                bars={byPerson[p.id].map((r) => { const lt = VP_DATA.leaveTypes[r.type] || {}; return { from: r.from, to: r.to, color: lt.color || "var(--ink-3)", label: lt.short || "Off", title: `${lt.label || "Leave"} · ${fmtRange(r.from, r.to)}` }; })} />
            ))}
          </>}

          {/* Tasks */}
          <GroupHeader>Tasks</GroupHeader>
          {dated.length === 0 && <div style={{ display: "flex", borderTop: "1px solid var(--line)" }}><div style={{ width: TL_LABEL_W, flex: "0 0 auto", borderRight: "1px solid var(--line)" }} /><div style={{ flex: 1, padding: "12px", fontSize: 12.5, color: "var(--ink-4)" }}>No tasks with due dates yet.</div></div>}
          {dated.map((t) => {
            const project = projectsById[t.projectId];
            return <TimelineRow key={t.id} label={t.title} sub={project ? project.name : ""} color={project ? project.color : "var(--accent)"} totalDays={totalDays} spanStart={spanStart}
              onClick={onOpen ? () => onOpen(t) : undefined}
              bars={[{ from: t.start || t.due, to: t.due, color: project ? project.color : "var(--accent)", label: t.title, title: `${t.title} · ${fmtRange(t.start || t.due, t.due)}` }]} />;
          })}
        </div>
      </div>
    </Card>
  );
}

/* ---- task create / edit modal ---- */
const selectStyle = {
  width: "100%", padding: "9px 11px", fontSize: 14, border: "1px solid var(--line-2)",
  borderRadius: "var(--r-sm)", background: "var(--surface)", color: "var(--ink)", fontFamily: "var(--font-body)",
};
function TaskModal({ task, projects, defaultProjectId, user, onSave, onDelete, onClose }) {
  const editing = !!task;
  const [title, setTitle] = useState(task?.title || "");
  const [projectId, setProjectId] = useState(task?.projectId || defaultProjectId || (projects[0] && projects[0].id) || "");
  const [status, setStatus] = useState(task?.status || "todo");
  const [priority, setPriority] = useState(task?.priority || "medium");
  const [assignee, setAssignee] = useState(task?.assignee || "");
  const [start, setStart] = useState(task?.start || "");
  const [due, setDue] = useState(task?.due || "");
  const [description, setDescription] = useState(task?.description || "");
  const [err, setErr] = useState("");
  const [comments, setComments] = useState([]);
  const [cLoading, setCLoading] = useState(editing);
  const [newComment, setNewComment] = useState("");

  useEffect(() => {
    if (!task?.id) return;
    let active = true;
    MesperDB.loadTaskComments(task.id)
      .then((cs) => { if (active) { setComments(cs); setCLoading(false); } })
      .catch(() => { if (active) setCLoading(false); });
    return () => { active = false; };
  }, [task && task.id]);

  async function postComment() {
    const body = newComment.trim();
    if (!body) return;
    try { const c = await MesperDB.addTaskComment(task.id, body); setComments((cs) => [...cs, c]); setNewComment(""); }
    catch (e) { setErr((e && e.message) || "Could not add comment."); }
  }

  function save() {
    if (!title.trim()) { setErr("Give the task a title."); return; }
    if (!projectId) { setErr("Pick a project."); return; }
    if (start && due && due < start) { setErr("Due date is before the start date."); return; }
    onSave(task?.id, { title: title.trim(), projectId, status, priority, assignee: assignee || null, start: start || null, due: due || null, description: description.trim() });
  }

  return (
    <div>
      <div style={{ padding: "20px 24px", borderBottom: "1px solid var(--line)", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
        <h2 style={{ fontSize: 20 }}>{editing ? "Edit task" : "New task"}</h2>
        <button type="button" onClick={onClose} className="vp-focusable" style={{ ...navBtn, border: "none", background: "var(--paper-2)" }}><Icon name="x" size={18} /></button>
      </div>
      <div style={{ padding: "20px 24px", display: "grid", gap: 16 }}>
        <div>
          <div style={fieldLabel}>Title</div>
          <input value={title} onChange={(e) => { setTitle(e.target.value); setErr(""); }} autoFocus
            placeholder="What needs doing?" className="vp-focusable"
            style={{ ...selectStyle, fontSize: 15, fontWeight: 600 }} />
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
          <div>
            <div style={fieldLabel}>Project</div>
            <select value={projectId} onChange={(e) => setProjectId(e.target.value)} className="vp-focusable" style={selectStyle}>
              {projects.map((p) => <option key={p.id} value={p.id}>{p.name}</option>)}
            </select>
          </div>
          <div>
            <div style={fieldLabel}>Status</div>
            <select value={status} onChange={(e) => setStatus(e.target.value)} className="vp-focusable" style={selectStyle}>
              {TASK_STATUSES.map((s) => <option key={s.id} value={s.id}>{s.label}</option>)}
            </select>
          </div>
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
          <div>
            <div style={fieldLabel}>Assignee</div>
            <ColleagueSelect value={assignee} onChange={setAssignee} colleagues={VP_DATA.people} selfId={user.id} placeholder="Unassigned" />
          </div>
          <div>
            <div style={fieldLabel}>Priority</div>
            <select value={priority} onChange={(e) => setPriority(e.target.value)} className="vp-focusable" style={selectStyle}>
              {Object.entries(PRIORITIES).map(([id, p]) => <option key={id} value={id}>{p.label}</option>)}
            </select>
          </div>
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
          <div>
            <div style={fieldLabel}>Start date</div>
            <input type="date" value={start || ""} onChange={(e) => setStart(e.target.value)} className="vp-focusable" style={selectStyle} />
          </div>
          <div>
            <div style={fieldLabel}>Due date</div>
            <input type="date" value={due || ""} onChange={(e) => setDue(e.target.value)} className="vp-focusable" style={selectStyle} />
          </div>
        </div>
        <div>
          <div style={fieldLabel}>Description <span style={{ color: "var(--ink-4)", fontWeight: 500 }}>· optional</span></div>
          <textarea value={description} onChange={(e) => setDescription(e.target.value)} rows={3}
            placeholder="Details, links, acceptance criteria…" className="vp-focusable"
            style={{ ...selectStyle, resize: "vertical", lineHeight: 1.5 }} />
        </div>
        {editing && (
          <div style={{ borderTop: "1px solid var(--line)", paddingTop: 16 }}>
            <div style={fieldLabel}>Comments</div>
            <div style={{ display: "flex", flexDirection: "column", gap: 12, marginBottom: 12 }}>
              {cLoading && <div style={{ fontSize: 13, color: "var(--ink-4)" }}>Loading…</div>}
              {!cLoading && comments.length === 0 && <div style={{ fontSize: 13, color: "var(--ink-4)" }}>No comments yet.</div>}
              {comments.map((c) => {
                const author = c.author ? VP_DATA.byId[c.author] : null;
                return (
                  <div key={c.id} style={{ display: "flex", gap: 10 }}>
                    {author ? <Avatar person={author} size={28} /> : <div style={{ width: 28, height: 28, borderRadius: "50%", background: "var(--paper-2)" }} />}
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ display: "flex", alignItems: "baseline", gap: 8 }}>
                        <span style={{ fontWeight: 600, fontSize: 13 }}>{author ? `${author.first} ${author.last}` : "Someone"}</span>
                        <span style={{ fontSize: 11.5, color: "var(--ink-4)" }}>{logRelTime(c.createdAt)}</span>
                      </div>
                      <div style={{ fontSize: 13.5, color: "var(--ink-2)", lineHeight: 1.45, marginTop: 2, whiteSpace: "pre-wrap" }}>{c.body}</div>
                    </div>
                  </div>
                );
              })}
            </div>
            <div style={{ display: "flex", gap: 8 }}>
              <input value={newComment} onChange={(e) => setNewComment(e.target.value)}
                onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); postComment(); } }}
                placeholder="Write a comment…" className="vp-focusable" style={{ ...selectStyle, flex: 1 }} />
              <Button size="md" variant="outline" onClick={postComment} disabled={!newComment.trim()}>Send</Button>
            </div>
          </div>
        )}
        {err && <div className="vp-fade" style={{ display: "flex", gap: 9, alignItems: "center", color: "var(--st-declined)", fontSize: 13.5, fontWeight: 500 }}><Icon name="info" size={17} /> {err}</div>}
      </div>
      <div style={{ padding: "14px 24px", borderTop: "1px solid var(--line)", display: "flex", justifyContent: "space-between", alignItems: "center", gap: 12 }}>
        {editing ? <button type="button" onClick={() => onDelete(task.id)} className="vp-focusable" style={{ border: "none", background: "transparent", color: "var(--st-declined)", fontWeight: 600, fontSize: 13.5, cursor: "pointer" }}>Delete</button> : <span />}
        <div style={{ display: "flex", gap: 10 }}>
          <Button variant="ghost" onClick={onClose}>Cancel</Button>
          <Button icon="check" onClick={save}>{editing ? "Save" : "Create task"}</Button>
        </div>
      </div>
    </div>
  );
}

/* ---- new project modal ---- */
function ProjectModal({ user, onSave, onClose }) {
  const [name, setName] = useState("");
  const [kind, setKind] = useState("client");
  const [client, setClient] = useState("");
  const [color, setColor] = useState("#5A58E7");
  const [err, setErr] = useState("");
  const COLORS = ["#5A58E7", "#3490EB", "#7D50CA", "#1FA36B", "#E0792B", "#C2410C"];
  function save() {
    if (!name.trim()) { setErr("Name the project."); return; }
    onSave({ name: name.trim(), kind, client: kind === "client" ? (client.trim() || name.trim()) : null, color });
  }
  return (
    <div>
      <div style={{ padding: "20px 24px", borderBottom: "1px solid var(--line)", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
        <h2 style={{ fontSize: 20 }}>New project</h2>
        <button type="button" onClick={onClose} className="vp-focusable" style={{ ...navBtn, border: "none", background: "var(--paper-2)" }}><Icon name="x" size={18} /></button>
      </div>
      <div style={{ padding: "20px 24px", display: "grid", gap: 16 }}>
        <div>
          <div style={fieldLabel}>Project name</div>
          <input value={name} onChange={(e) => { setName(e.target.value); setErr(""); }} autoFocus placeholder="e.g. Acme — Website" className="vp-focusable" style={{ ...selectStyle, fontWeight: 600 }} />
        </div>
        <div>
          <div style={fieldLabel}>Type</div>
          <div style={{ display: "flex", gap: 8 }}>
            {[{ id: "client", label: "Client" }, { id: "internal", label: "Internal" }].map((k) => {
              const on = kind === k.id;
              return <button key={k.id} type="button" onClick={() => setKind(k.id)} className="vp-focusable"
                style={{ flex: 1, padding: "9px 0", borderRadius: "var(--r-sm)", cursor: "pointer", fontWeight: 600, fontSize: 13.5, border: `1px solid ${on ? "transparent" : "var(--line-2)"}`, background: on ? "var(--accent-soft)" : "var(--surface)", color: on ? "var(--accent-ink)" : "var(--ink-2)", boxShadow: on ? "inset 0 0 0 1px var(--accent)" : "none" }}>{k.label}</button>;
            })}
          </div>
        </div>
        {kind === "client" && (
          <div>
            <div style={fieldLabel}>Client name <span style={{ color: "var(--ink-4)", fontWeight: 500 }}>· optional</span></div>
            <input value={client} onChange={(e) => setClient(e.target.value)} placeholder="Defaults to the project name" className="vp-focusable" style={selectStyle} />
          </div>
        )}
        <div>
          <div style={fieldLabel}>Color</div>
          <div style={{ display: "flex", gap: 8 }}>
            {COLORS.map((c) => (
              <button key={c} type="button" onClick={() => setColor(c)} className="vp-focusable"
                style={{ width: 30, height: 30, borderRadius: "50%", background: c, cursor: "pointer", border: color === c ? "3px solid var(--surface)" : "3px solid transparent", boxShadow: color === c ? `0 0 0 2px ${c}` : "none" }} />
            ))}
          </div>
        </div>
        {err && <div className="vp-fade" style={{ display: "flex", gap: 9, alignItems: "center", color: "var(--st-declined)", fontSize: 13.5, fontWeight: 500 }}><Icon name="info" size={17} /> {err}</div>}
      </div>
      <div style={{ padding: "14px 24px", borderTop: "1px solid var(--line)", display: "flex", justifyContent: "flex-end", gap: 10 }}>
        <Button variant="ghost" onClick={onClose}>Cancel</Button>
        <Button icon="check" onClick={save}>Create project</Button>
      </div>
    </div>
  );
}

/* ---- generic styled picker (status / priority / project) ---- */
function PillPicker({ value, options, onChange, placeholder }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  useEffect(() => { const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; document.addEventListener("mousedown", h); return () => document.removeEventListener("mousedown", h); }, []);
  const sel = options.find((o) => o.value === value);
  return (
    <div ref={ref} style={{ position: "relative", display: "inline-block" }}>
      <button type="button" onClick={() => setOpen((o) => !o)} className="vp-focusable"
        style={{ display: "inline-flex", alignItems: "center", gap: 8, padding: "6px 11px", borderRadius: "var(--r-sm)", border: `1px solid ${open ? "var(--accent)" : "var(--line-2)"}`, background: "var(--surface)", cursor: "pointer", fontWeight: 600, fontSize: 13.5, color: sel ? "var(--ink)" : "var(--ink-4)", boxShadow: open ? "0 0 0 3px var(--accent-soft)" : "none" }}>
        {sel && sel.dot && <span style={{ width: 9, height: 9, borderRadius: "50%", background: sel.dot, flex: "0 0 auto" }} />}
        <span style={{ whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", maxWidth: 220 }}>{sel ? sel.label : (placeholder || "Select…")}</span>
        <Icon name="chevDown" size={15} style={{ color: "var(--ink-4)" }} />
      </button>
      {open && (
        <div className="vp-pop" style={{ position: "absolute", top: "calc(100% + 6px)", left: 0, zIndex: 20, minWidth: 190, maxHeight: 260, overflowY: "auto", background: "var(--surface)", border: "1px solid var(--line)", borderRadius: "var(--r-md)", boxShadow: "var(--sh-lg)", padding: 6 }}>
          {options.map((o) => (
            <button key={o.value} type="button" onClick={() => { onChange(o.value); setOpen(false); }}
              style={{ width: "100%", display: "flex", alignItems: "center", gap: 9, padding: "8px 10px", border: "none", background: value === o.value ? "var(--accent-soft)" : "transparent", borderRadius: "var(--r-sm)", cursor: "pointer", textAlign: "left", fontSize: 13.5, fontWeight: 600, color: "var(--ink-2)" }}>
              {o.dot && <span style={{ width: 9, height: 9, borderRadius: "50%", background: o.dot, flex: "0 0 auto" }} />}
              <span style={{ flex: 1 }}>{o.label}</span>
              {value === o.value && <Icon name="check" size={15} style={{ color: "var(--accent)" }} />}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

function PanelRow({ label, children }) {
  return (
    <div style={{ display: "grid", gridTemplateColumns: "92px 1fr", alignItems: "center", gap: 12, minHeight: 38 }}>
      <div style={{ fontSize: 12.5, fontWeight: 600, color: "var(--ink-3)" }}>{label}</div>
      <div style={{ minWidth: 0 }}>{children}</div>
    </div>
  );
}

/* ---- Asana-style right slide-over task detail ---- */
function TaskPanel({ task, projects, user, onPatch, onDelete, onToggleComplete, onClose }) {
  const narrow = useIsNarrow(820);
  const [title, setTitle] = useState(task.title);
  const [editingDesc, setEditingDesc] = useState(false);
  const [desc, setDesc] = useState(task.description || "");
  const [comments, setComments] = useState([]);
  const [cLoading, setCLoading] = useState(true);
  const [newComment, setNewComment] = useState("");
  const [menuOpen, setMenuOpen] = useState(false);
  const done = task.status === "done";

  useEffect(() => { setTitle(task.title); setDesc(task.description || ""); setEditingDesc(false); }, [task.id]);
  useEffect(() => {
    let active = true; setCLoading(true);
    MesperDB.loadTaskComments(task.id).then((cs) => { if (active) { setComments(cs); setCLoading(false); } }).catch(() => { if (active) setCLoading(false); });
    return () => { active = false; };
  }, [task.id]);
  useEffect(() => { const onKey = (e) => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [onClose]);

  async function postComment() { const body = newComment.trim(); if (!body) return; try { const c = await MesperDB.addTaskComment(task.id, body); setComments((cs) => [...cs, c]); setNewComment(""); } catch (e) {} }
  function commitTitle() { const t = title.trim(); if (t && t !== task.title) onPatch(task.id, { title: t }); else if (!t) setTitle(task.title); }
  function saveDesc() { onPatch(task.id, { description: desc.trim() }); setEditingDesc(false); }

  const statusOpts = TASK_STATUSES.map((s) => ({ value: s.id, label: s.label, dot: s.color }));
  const prioOpts = Object.entries(PRIORITIES).map(([id, p]) => ({ value: id, label: p.label, dot: p.color }));
  const projOpts = projects.map((p) => ({ value: p.id, label: p.name, dot: p.color }));

  return (
    <div style={{ position: "fixed", inset: 0, zIndex: 60 }}>
      <div className="vp-scrim" onClick={onClose} style={{ position: "absolute", inset: 0, background: "rgba(26,33,56,0.28)" }} />
      <div className="vp-slide" onClick={(e) => e.stopPropagation()} style={{ position: "absolute", top: 0, right: 0, bottom: 0, width: narrow ? "100vw" : "min(560px, 100vw)", background: "var(--surface)", boxShadow: "var(--sh-pop)", display: "flex", flexDirection: "column" }}>
        {/* header */}
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, padding: "12px 20px", borderBottom: "1px solid var(--line)", flex: "0 0 auto" }}>
          <button type="button" onClick={() => onToggleComplete(task)} className="vp-focusable"
            style={{ display: "inline-flex", alignItems: "center", gap: 9, padding: "6px 13px 6px 7px", borderRadius: "var(--r-pill)", cursor: "pointer", fontWeight: 600, fontSize: 13.5, border: `1px solid ${done ? "transparent" : "var(--line-2)"}`, background: done ? "var(--st-approved-soft)" : "var(--surface)", color: done ? "var(--st-approved)" : "var(--ink-2)" }}>
            <CompleteCircle done={done} onToggle={() => onToggleComplete(task)} size={18} />
            {done ? "Completed" : "Mark complete"}
          </button>
          <div style={{ display: "flex", alignItems: "center", gap: 6, position: "relative" }}>
            <button type="button" onClick={() => setMenuOpen((o) => !o)} className="vp-focusable" style={{ ...navBtn, border: "none", background: "transparent", gap: 2 }} title="More">
              <span style={{ width: 4, height: 4, borderRadius: "50%", background: "var(--ink-3)" }} /><span style={{ width: 4, height: 4, borderRadius: "50%", background: "var(--ink-3)" }} /><span style={{ width: 4, height: 4, borderRadius: "50%", background: "var(--ink-3)" }} />
            </button>
            {menuOpen && <div className="vp-pop" style={{ position: "absolute", top: "calc(100% + 6px)", right: 40, zIndex: 5, background: "var(--surface)", border: "1px solid var(--line)", borderRadius: "var(--r-md)", boxShadow: "var(--sh-lg)", padding: 6 }}>
              <button type="button" onClick={() => { setMenuOpen(false); onDelete(task.id); }} style={{ display: "flex", alignItems: "center", gap: 8, padding: "8px 12px", border: "none", background: "transparent", color: "var(--st-declined)", fontWeight: 600, fontSize: 13.5, cursor: "pointer", borderRadius: "var(--r-sm)", whiteSpace: "nowrap" }}><Icon name="x" size={15} /> Delete task</button>
            </div>}
            <button type="button" onClick={onClose} className="vp-focusable" style={{ ...navBtn, border: "none", background: "var(--paper-2)" }}><Icon name="x" size={18} /></button>
          </div>
        </div>

        {/* body */}
        <div style={{ flex: 1, overflowY: "auto", padding: "20px 24px", display: "flex", flexDirection: "column", gap: 18 }}>
          <input value={title} onChange={(e) => setTitle(e.target.value)} onBlur={commitTitle}
            onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); e.target.blur(); } }}
            style={{ border: "none", outline: "none", width: "100%", fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 23, color: "var(--ink)", background: "transparent", textDecoration: done ? "line-through" : "none", opacity: done ? 0.55 : 1, padding: 0 }} />

          <div style={{ display: "grid", gap: 4 }}>
            <PanelRow label="Assignee"><ColleagueSelect value={task.assignee || ""} onChange={(id) => onPatch(task.id, { assignee: id || null })} colleagues={VP_DATA.people} selfId={user.id} placeholder="No assignee" /></PanelRow>
            <PanelRow label="Due date">
              <input type="date" value={task.due || ""} onChange={(e) => onPatch(task.id, { due: e.target.value || null })}
                style={{ padding: "7px 10px", fontSize: 13.5, border: "1px solid var(--line-2)", borderRadius: "var(--r-sm)", background: "var(--surface)", color: task.due ? "var(--ink)" : "var(--ink-4)", fontFamily: "var(--font-body)" }} />
            </PanelRow>
            <PanelRow label="Project"><PillPicker value={task.projectId} options={projOpts} onChange={(v) => onPatch(task.id, { projectId: v })} /></PanelRow>
            <PanelRow label="Priority"><PillPicker value={task.priority} options={prioOpts} onChange={(v) => onPatch(task.id, { priority: v })} /></PanelRow>
            <PanelRow label="Status"><PillPicker value={task.status} options={statusOpts} onChange={(v) => onPatch(task.id, { status: v })} /></PanelRow>
          </div>

          {/* description */}
          <div>
            <div style={{ fontSize: 12.5, fontWeight: 600, color: "var(--ink-3)", marginBottom: 8, display: "flex", justifyContent: "space-between", alignItems: "center" }}>
              Description
              {!editingDesc && <button type="button" onClick={() => { setDesc(task.description || ""); setEditingDesc(true); }} className="vp-focusable" style={{ border: "none", background: "transparent", color: "var(--accent)", fontWeight: 600, fontSize: 12.5, cursor: "pointer" }}>Edit</button>}
            </div>
            {editingDesc ? (
              <div>
                <textarea value={desc} onChange={(e) => setDesc(e.target.value)} rows={5} autoFocus placeholder="Add details…  **bold**  *italic*  - list  [link](url)  ==highlight=="
                  style={{ ...selectStyle, resize: "vertical", lineHeight: 1.5, fontFamily: "var(--font-body)" }} />
                <div style={{ display: "flex", gap: 8, marginTop: 8, alignItems: "center" }}>
                  <Button size="sm" icon="check" onClick={saveDesc}>Save</Button>
                  <Button size="sm" variant="ghost" onClick={() => setEditingDesc(false)}>Cancel</Button>
                  <span style={{ fontSize: 11.5, color: "var(--ink-4)" }}>Markdown supported</span>
                </div>
              </div>
            ) : task.description ? <RichText md={task.description} /> :
              <button type="button" onClick={() => { setDesc(""); setEditingDesc(true); }} style={{ border: "none", background: "transparent", color: "var(--ink-4)", fontSize: 13.5, cursor: "pointer", padding: 0 }}>Add description…</button>}
          </div>

          {/* comments */}
          <div style={{ borderTop: "1px solid var(--line)", paddingTop: 16 }}>
            <div style={{ fontSize: 12.5, fontWeight: 600, color: "var(--ink-3)", marginBottom: 12 }}>Comments</div>
            <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
              {cLoading && <div style={{ fontSize: 13, color: "var(--ink-4)" }}>Loading…</div>}
              {!cLoading && comments.length === 0 && <div style={{ fontSize: 13, color: "var(--ink-4)" }}>No comments yet.</div>}
              {comments.map((c) => {
                const author = c.author ? VP_DATA.byId[c.author] : null;
                return (
                  <div key={c.id} style={{ display: "flex", gap: 10 }}>
                    {author ? <Avatar person={author} size={28} /> : <div style={{ width: 28, height: 28, borderRadius: "50%", background: "var(--paper-2)" }} />}
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ display: "flex", alignItems: "baseline", gap: 8 }}>
                        <span style={{ fontWeight: 600, fontSize: 13 }}>{author ? `${author.first} ${author.last}` : "Someone"}</span>
                        <span style={{ fontSize: 11.5, color: "var(--ink-4)" }}>{logRelTime(c.createdAt)}</span>
                      </div>
                      <RichText md={c.body} style={{ marginTop: 2 }} />
                    </div>
                  </div>
                );
              })}
            </div>
          </div>
        </div>

        {/* composer */}
        <div style={{ flex: "0 0 auto", borderTop: "1px solid var(--line)", padding: "12px 20px", display: "flex", gap: 8 }}>
          <input value={newComment} onChange={(e) => setNewComment(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); postComment(); } }}
            placeholder="Write a comment…" className="vp-focusable" style={{ ...selectStyle, flex: 1 }} />
          <Button onClick={postComment} disabled={!newComment.trim()}>Send</Button>
        </div>
      </div>
    </div>
  );
}

/* ---- the tasks app ---- */
function TasksApp({ user, onHome }) {
  const narrow = useIsNarrow(820);
  const [phase, setPhase] = useState("loading"); // loading | ready | error
  const [err, setErr] = useState("");
  const [projects, setProjects] = useState([]);
  const [tasks, setTasks] = useState([]);
  const [activeProject, setActiveProject] = useState("all");
  const [activeAssignee, setActiveAssignee] = useState("all"); // all | me | <personId>
  const [view, setView] = useState("board"); // board | list | timeline
  const [modal, setModal] = useState(null); // {mode:'task'|'newTask'|'newProject', task?}
  const [toast, setToast] = useState(null);
  const toastTimer = useRef(null);

  function flash(msg, opts = {}) { setToast({ msg, ...opts }); clearTimeout(toastTimer.current); toastTimer.current = setTimeout(() => setToast(null), 2400); }

  async function load() {
    setPhase("loading"); setErr("");
    try { const { projects, tasks } = await MesperDB.loadTasksData(); setProjects(projects); setTasks(tasks); setPhase("ready"); }
    catch (e) { setErr((e && e.message) || "Could not load tasks."); setPhase("error"); }
  }
  useEffect(() => { load(); }, []);

  const projectsById = Object.fromEntries(projects.map((p) => [p.id, p]));
  const visibleTasks = tasks.filter((t) =>
    (activeProject === "all" || t.projectId === activeProject) &&
    (activeAssignee === "all" || (activeAssignee === "me" ? t.assignee === user.id : t.assignee === activeAssignee))
  );

  async function moveTask(id, status, sort) {
    const prev = tasks;
    setTasks((ts) => ts.map((t) => (t.id === id ? { ...t, status, ...(sort != null ? { sort } : {}) } : t)));
    try { await MesperDB.updateTask(id, sort != null ? { status, sort } : { status }); }
    catch (e) { setTasks(prev); flash((e && e.message) || "Could not move task.", { tone: "declined", icon: "x" }); }
  }
  async function saveTask(id, patch) {
    try {
      if (id) { const saved = await MesperDB.updateTask(id, patch); setTasks((ts) => ts.map((t) => (t.id === id ? saved : t))); flash("Task saved."); }
      else { const saved = await MesperDB.createTask(patch); setTasks((ts) => [...ts, saved]); flash("Task created."); }
      setModal(null);
    } catch (e) { flash((e && e.message) || "Could not save task.", { tone: "declined", icon: "x" }); }
  }
  async function removeTask(id) {
    try { await MesperDB.deleteTask(id); setTasks((ts) => ts.filter((t) => t.id !== id)); setModal(null); flash("Task deleted.", { tone: "declined", icon: "x" }); }
    catch (e) { flash((e && e.message) || "Could not delete task.", { tone: "declined", icon: "x" }); }
  }
  // Inline field edits from the slide-over panel: optimistic, no toast/close.
  async function patchTask(id, patch) {
    const prev = tasks;
    setTasks((ts) => ts.map((t) => (t.id === id ? { ...t, ...patch } : t)));
    try { const saved = await MesperDB.updateTask(id, patch); setTasks((ts) => ts.map((t) => (t.id === id ? saved : t))); }
    catch (e) { setTasks(prev); flash((e && e.message) || "Could not save.", { tone: "declined", icon: "x" }); }
  }
  async function toggleComplete(task) {
    const patch = task.status !== "done" ? { status: "done", prevStatus: task.status } : { status: task.prevStatus || "in_progress", prevStatus: null };
    await patchTask(task.id, patch);
  }
  async function quickAdd(status, title) {
    if (!defaultProjectId) { flash("Create a project first.", { tone: "declined", icon: "x" }); return; }
    try { const saved = await MesperDB.createTask({ projectId: defaultProjectId, status, title }); setTasks((ts) => [...ts, saved]); }
    catch (e) { flash((e && e.message) || "Could not add task.", { tone: "declined", icon: "x" }); }
  }
  async function saveProject(p) {
    try { const saved = await MesperDB.createProject(p); setProjects((ps) => [...ps, saved]); setActiveProject(saved.id); setModal(null); flash("Project created."); }
    catch (e) { flash((e && e.message) || "Could not create project.", { tone: "declined", icon: "x" }); }
  }

  const VIEWS = [{ id: "board", label: "Board" }, { id: "list", label: "List" }, { id: "timeline", label: "Timeline" }];
  const defaultProjectId = activeProject !== "all" ? activeProject : (projects[0] && projects[0].id);

  return (
    <div style={{ minHeight: "100%", display: "flex", flexDirection: "column", height: "100%", overflow: "hidden" }}>
      {/* top bar */}
      <header style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 14, padding: narrow ? "11px 16px" : "13px 28px", borderBottom: "1px solid var(--line)", background: "var(--surface)", flex: "0 0 auto" }}>
        <button type="button" onClick={onHome} className="vp-focusable" title="All tools" style={{ display: "flex", alignItems: "center", gap: 9, background: "transparent", border: "none", cursor: "pointer" }}>
          <Icon name="arrowLeft" size={18} style={{ color: "var(--ink-4)" }} />
          <BrandLockup height={22} sub="Tasks" />
        </button>
        <AccountMenu user={user} onLogout={() => MesperDB.signOut()} />
      </header>

      {/* toolbar */}
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, padding: narrow ? "12px 16px" : "16px 28px", flexWrap: "wrap", flex: "0 0 auto" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
          <select value={activeProject} onChange={(e) => setActiveProject(e.target.value)} className="vp-focusable" style={{ ...selectStyle, width: "auto", fontWeight: 600 }}>
            <option value="all">All projects</option>
            {projects.map((p) => <option key={p.id} value={p.id}>{p.name}</option>)}
          </select>
          <select value={activeAssignee} onChange={(e) => setActiveAssignee(e.target.value)} className="vp-focusable" style={{ ...selectStyle, width: "auto", fontWeight: 600 }}>
            <option value="all">All assignees</option>
            <option value="me">My tasks</option>
            {VP_DATA.people.map((p) => <option key={p.id} value={p.id}>{p.first} {p.last}</option>)}
          </select>
          <Button size="sm" variant="outline" icon="plus" onClick={() => setModal({ mode: "newProject" })}>Project</Button>
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
          <div style={{ display: "inline-flex", background: "var(--surface-2)", border: "1px solid var(--line)", borderRadius: "var(--r-pill)", padding: 3 }}>
            {VIEWS.map((v) => {
              const on = view === v.id;
              return <button key={v.id} type="button" onClick={() => setView(v.id)} className="vp-focusable"
                style={{ padding: "6px 14px", borderRadius: "var(--r-pill)", border: "none", cursor: "pointer", fontWeight: 600, fontSize: 13, background: on ? "var(--surface)" : "transparent", color: on ? "var(--ink)" : "var(--ink-3)", boxShadow: on ? "var(--sh-sm)" : "none" }}>{v.label}</button>;
            })}
          </div>
          <Button size="md" icon="plus" onClick={() => projects.length ? setModal({ mode: "newTask" }) : setModal({ mode: "newProject" })}>New task</Button>
        </div>
      </div>

      {/* body */}
      <main style={{ flex: 1, overflow: "auto", padding: narrow ? "4px 16px 24px" : "4px 28px 28px" }}>
        {phase === "loading" && <p style={{ color: "var(--ink-4)", fontSize: 14, padding: "40px 0", textAlign: "center" }}>Loading tasks…</p>}
        {phase === "error" && (
          <div style={{ padding: "40px 0", textAlign: "center" }}>
            <p style={{ color: "var(--st-declined)", fontSize: 14, marginBottom: 12 }}>{err}</p>
            <Button variant="outline" onClick={load}>Try again</Button>
          </div>
        )}
        {phase === "ready" && projects.length === 0 && (
          <div className="vp-fade-up" style={{ maxWidth: 420, margin: "60px auto", textAlign: "center" }}>
            <h2 style={{ fontSize: 21, marginBottom: 8 }}>Start with a project</h2>
            <p style={{ color: "var(--ink-3)", fontSize: 14.5, marginBottom: 18, lineHeight: 1.5 }}>Projects are your clients or internal work. Create one, then add tasks to it.</p>
            <Button icon="plus" onClick={() => setModal({ mode: "newProject" })}>New project</Button>
          </div>
        )}
        {phase === "ready" && projects.length > 0 && view === "board" && (
          <BoardView tasks={visibleTasks} projectsById={projectsById} onMove={moveTask} onOpen={(t) => setModal({ mode: "task", task: t })} onToggleComplete={toggleComplete} onQuickAdd={quickAdd} />
        )}
        {phase === "ready" && projects.length > 0 && view === "list" && (
          <ListView tasks={visibleTasks} projectsById={projectsById} onOpen={(t) => setModal({ mode: "task", task: t })} />
        )}
        {phase === "ready" && projects.length > 0 && view === "timeline" && (
          <TimelineView tasks={visibleTasks} projectsById={projectsById} leave={(VP_DATA.requests || []).filter((r) => r.status === "approved")} onOpen={(t) => setModal({ mode: "task", task: t })} />
        )}
      </main>

      {modal?.mode === "newProject" && <Modal onClose={() => setModal(null)} width={460}><ProjectModal user={user} onSave={saveProject} onClose={() => setModal(null)} /></Modal>}
      {modal?.mode === "newTask" && <Modal onClose={() => setModal(null)} width={560}><TaskModal projects={projects} defaultProjectId={defaultProjectId} user={user} onSave={saveTask} onDelete={removeTask} onClose={() => setModal(null)} /></Modal>}
      {modal?.mode === "task" && (() => {
        const live = tasks.find((t) => t.id === modal.task.id);
        return live ? <TaskPanel task={live} projects={projects} user={user} onPatch={patchTask} onDelete={removeTask} onToggleComplete={toggleComplete} onClose={() => setModal(null)} /> : null;
      })()}

      <Toast toast={toast} />
    </div>
  );
}

Object.assign(window, { TasksApp });
