// bulkactions.jsx — sticky bottom action bar for multi-selected tasks.
//
// Renders only when selected.size > 0. Every action delegates to the
// same updateTask / deleteTask handlers the rest of the app uses, so
// the existing optimistic-update + SSE echo-suppression + toast paths
// all keep working untouched. No new write surface.
//
// Actions:
//   Status   — set status on every selected row
//   Priority — set priority on every selected row
//   Owner    — replace owners on every selected row (with a person, or unassign all)
//   Epic     — move every row to a different epic (or "no epic")
//   Sprint   — move every row to a different sprint (or "no sprint")
//   Due      — set / clear the due date for every row
//   Delete   — soft-delete every row (move to Bin)
//   Clear    — drop the multi-selection
//
// Wiring (mounted from FlowboardApp):
//   <BulkActionBar
//     selected={selectedSet}
//     allTasks={tasks}              // FlowboardApp's React state
//     people={people}
//     epics={EPICS}                 // global, all epics across projects
//     sprints={SPRINTS}
//     onUpdate={updateTask}
//     onDelete={deleteTask}
//     onClear={() => setSelected(new Set())}
//   />

function BulkActionBar({ selected, allTasks, people, epics, sprints, onUpdate, onDelete, onClear }) {
  // CRITICAL: every hook (useState / useMemo / useEffect / useRef …) must
  // be called in the SAME order on every render. The previous version had
  // an early `return null` for empty selection sandwiched between two
  // useMemo calls — going from 0 → 1 selected changed the hook count and
  // React threw "Rendered more hooks than during the previous render",
  // which propagated up to the nearest error boundary and gave a white
  // screen. Keep all hooks above any conditional return.

  const [menu, setMenu] = React.useState(null); // {type, anchor}
  // Multi-select buffer for the Owner popover. `null` means the
  // popover hasn't been opened yet (or was closed/reset). When the
  // user opens it we hydrate from the union of owners across the
  // currently-selected tasks so "Apply" feels like an edit, not a
  // forced clear-and-replace.
  const [ownerSel, setOwnerSel] = React.useState(null);
  function close() {
    setMenu(null);
    setOwnerSel(null);  // reset so next open starts fresh
  }

  const ids = React.useMemo(
    () => (selected ? Array.from(selected) : []),
    [selected]
  );

  // Map selected ids back to task objects so the action menus can show
  // the project context and pick the right epic / sprint sets.
  const tasks = React.useMemo(() => {
    if (!ids.length) return [];
    const byId = new Map();
    for (const t of (allTasks || [])) byId.set(t.id, t);
    return ids.map(id => byId.get(id)).filter(Boolean);
  }, [ids, allTasks]);

  // ── All hooks declared. Now safe to bail. ───────────────────────────
  const count = ids.length;
  if (count === 0) return null;

  // If every selected task lives in the same project, scope epics / sprints
  // to that project for cleaner pickers. Mixed-project selection falls back
  // to the full lists (epic moves across projects rarely make sense, but we
  // still allow it — server doesn't enforce same-project epics).
  const projectIds = Array.from(new Set(tasks.map(t => t.projectId).filter(Boolean)));
  const sameProject = projectIds.length === 1 ? projectIds[0] : null;
  const scopedEpics = (epics || []).filter(e =>
    !sameProject || (e.project_id || e.projectId) === sameProject
  );
  const scopedSprints = (sprints || []).filter(s =>
    !sameProject || !s.project_id || s.project_id === sameProject
  );
  // Stories live in the global window.USER_STORIES (same pattern as
  // STATUSES / PRIORITIES read above). Scope to the selection's project
  // when they all share one, so the picker isn't cluttered with other
  // projects' stories.
  const allStories = (typeof window !== "undefined" && Array.isArray(window.USER_STORIES)) ? window.USER_STORIES : [];
  const scopedStories = allStories.filter(s =>
    !sameProject || !s.projectId || s.projectId === sameProject
  );

  function bulkPatch(patch) {
    for (const id of ids) onUpdate && onUpdate(id, patch);
    close();
  }

  function bulkDelete() {
    const msg = `Move ${count} task${count === 1 ? "" : "s"} to Bin?\n` +
                `They'll stay there for 30 days — restore anytime from the Bin.`;
    if (!window.confirm(msg)) return;
    // Collapse the selection to its topmost ancestors. If the user has
    // selected a parent AND its descendants (which happens automatically
    // when EpicGroup cascades the parent's checkbox down), DELETEing the
    // parent will FK-cascade every descendant on the server. Firing extra
    // DELETEs for the children then produces a 404 storm — visible to the
    // user as a wave of "Couldn't save change" toasts on the first try
    // before deleteTask's 404-as-success guard kicks in. So we filter the
    // descendants out before dispatching: O(N) ancestor walk per id.
    const selSet = selected instanceof Set ? selected : new Set(ids);
    const byId   = new Map();
    for (const t of (allTasks || [])) byId.set(t.id, t);
    function hasSelectedAncestor(id) {
      let cur = byId.get(id);
      // Walk up via parentTaskId. Cap at 12 hops to avoid pathological
      // cycles — depth is capped at 4 server-side, so 12 is plenty.
      let hops = 0;
      while (cur && cur.parentTaskId && hops < 12) {
        if (selSet.has(cur.parentTaskId)) return true;
        cur = byId.get(cur.parentTaskId);
        hops++;
      }
      return false;
    }
    const topmost = ids.filter(id => !hasSelectedAncestor(id));
    for (const id of topmost) onDelete && onDelete(id);
    onClear && onClear();
    close();
  }

  function setEpic(epic) {
    bulkPatch({
      epicId    : epic ? epic.id    : null,
      epicTitle : epic ? epic.title : null,
      epicColor : epic ? epic.color : null,
    });
  }

  function setSprint(sprint) {
    bulkPatch({
      sprint     : sprint ? sprint.id    : null,
      sprintLabel: sprint ? sprint.label : null,
    });
  }

  // Assign / clear the user story on every selected task. updateTask
  // maps userStoryId → user_story_id and re-groups the table row under
  // the story automatically.
  function setStory(story) {
    bulkPatch({ userStoryId: story ? story.id : null });
  }

  return (
    <div className="bulk-bar" role="toolbar" aria-label="Bulk actions">
      <div className="bulk-bar-count">
        <span className="bulk-bar-count-num">{count}</span>
        <span className="bulk-bar-count-label">selected</span>
      </div>

      <div className="bulk-bar-divider"/>

      <button className="bulk-bar-btn"
              onClick={(e) => setMenu({ type: "status", anchor: e.currentTarget })}>
        <Icons.Sort size={13}/> Status
      </button>
      <button className="bulk-bar-btn"
              onClick={(e) => setMenu({ type: "prio", anchor: e.currentTarget })}>
        <Icons.Alert size={13}/> Priority
      </button>
      <button className="bulk-bar-btn"
              onClick={(e) => setMenu({ type: "owner", anchor: e.currentTarget })}>
        <Icons.Users size={13}/> Owner
      </button>
      <button className="bulk-bar-btn"
              onClick={(e) => setMenu({ type: "epic", anchor: e.currentTarget })}>
        <Icons.Folder size={13}/> Epic
      </button>
      <button className="bulk-bar-btn"
              onClick={(e) => setMenu({ type: "sprint", anchor: e.currentTarget })}>
        <Icons.Lightning size={13}/> Sprint
      </button>
      <button className="bulk-bar-btn"
              onClick={(e) => setMenu({ type: "story", anchor: e.currentTarget })}>
        <Icons.Note size={13}/> Story
      </button>
      <button className="bulk-bar-btn"
              onClick={(e) => setMenu({ type: "due", anchor: e.currentTarget })}>
        <Icons.Calendar size={13}/> Due date
      </button>

      <div className="bulk-bar-divider"/>

      <button className="bulk-bar-btn bulk-bar-btn-danger"
              onClick={bulkDelete}>
        <Icons.Trash size={13}/> Delete
      </button>
      <button className="bulk-bar-btn bulk-bar-btn-ghost"
              onClick={() => onClear && onClear()}>
        <Icons.Close size={12}/> Clear
      </button>

      {/* ── Popovers ─────────────────────────────────────────────── */}

      {menu && menu.type === "status" && (
        <Popover anchor={menu.anchor} onClose={close}>
          <div style={{ minWidth: 180 }}>
            {STATUSES.map(s => (
              <div key={s.id} className="popover-item"
                   onClick={() => bulkPatch({ status: s.id })}>
                <span className={`pill pill-sm ${s.cls}`} style={{ minWidth: 90 }}>{s.label}</span>
              </div>
            ))}
          </div>
        </Popover>
      )}

      {menu && menu.type === "prio" && (
        <Popover anchor={menu.anchor} onClose={close}>
          <div style={{ minWidth: 160 }}>
            {PRIORITIES.map(s => (
              <div key={s.id} className="popover-item"
                   onClick={() => bulkPatch({ prio: s.id })}>
                <span className={`pill pill-sm ${s.cls}`} style={{ minWidth: 80 }}>{s.label}</span>
              </div>
            ))}
            <div className="popover-item" style={{ borderTop: "1px solid var(--border)", color: "var(--ink-muted)" }}
                 onClick={() => bulkPatch({ prio: "none" })}>
              <Icons.Close size={12}/> Clear priority
            </div>
          </div>
        </Popover>
      )}

      {menu && menu.type === "owner" && (() => {
        // Hydrate the multi-select buffer from the UNION of current
        // owners across every selected task — gives the admin a
        // realistic starting point ("these people are already on at
        // least one of the picked tasks; uncheck them or add more").
        // We hydrate inside this IIFE so the buffer is initialized
        // exactly once per popover open without needing a layout
        // effect that would fire after the first render.
        if (ownerSel === null) {
          const union = new Set();
          for (const t of tasks) for (const o of (t.owners || [])) union.add(o);
          // Defer the setState by one tick so React sees a fresh
          // render rather than warning about state update during
          // render. queueMicrotask is enough to escape the current
          // commit phase.
          queueMicrotask(() => setOwnerSel(union));
        }
        const cur = ownerSel || new Set();
        const ppl = (people || PEOPLE || []);
        const me = (typeof window !== "undefined" && window.api && window.api.getUser && window.api.getUser()) || null;
        function toggle(id) {
          setOwnerSel(prev => {
            const next = new Set(prev || []);
            next.has(id) ? next.delete(id) : next.add(id);
            return next;
          });
        }
        function applyOwners() {
          bulkPatch({ owners: Array.from(cur) });
        }
        return (
          <Popover anchor={menu.anchor} onClose={close}>
            <div style={{ minWidth: 260, maxHeight: 380, display: "flex", flexDirection: "column" }}>
              <div style={{
                padding: "6px 10px", fontSize: 11, color: "var(--ink-muted)",
                fontWeight: 700, textTransform: "uppercase", letterSpacing: ".04em",
                borderBottom: "1px solid var(--border)",
              }}>
                Owners — pick any number, then Apply
              </div>

              {me && (
                <div className="popover-item"
                     style={{ color: "var(--brand)", fontWeight: 600 }}
                     onClick={() => toggle(me.id)}>
                  <Icons.Plus size={13}/>
                  <span style={{ flex: 1 }}>{cur.has(me.id) ? "Remove me" : "Assign to me"}</span>
                  {cur.has(me.id) && <Icons.Check size={13}/>}
                </div>
              )}

              <div style={{ overflowY: "auto", flex: 1 }}>
                {ppl.map(p => {
                  const on = cur.has(p.id);
                  return (
                    <div key={p.id}
                         className="popover-item"
                         onClick={() => toggle(p.id)}
                         style={{ background: on ? "rgba(0,115,234,.06)" : undefined }}>
                      <Avatar person={p} size="sm"/>
                      <span style={{ flex: 1 }}>{p.name}</span>
                      {on && <Icons.Check size={13} style={{ color: "var(--brand)" }}/>}
                    </div>
                  );
                })}
              </div>

              <div style={{
                display: "flex", alignItems: "center", gap: 8,
                padding: "8px 10px",
                borderTop: "1px solid var(--border)",
                background: "var(--bg-subtle, #fafbfd)",
              }}>
                <span style={{ fontSize: 11, color: "var(--ink-muted)" }}>
                  {cur.size === 0 ? "No one selected" : `${cur.size} selected`}
                </span>
                <span style={{ flex: 1 }}/>
                <button className="btn-ghost"
                        onClick={() => bulkPatch({ owners: [] })}
                        title="Clear owners on every selected task">
                  Unassign all
                </button>
                <button className="btn-primary"
                        disabled={cur.size === 0}
                        onClick={applyOwners}>
                  Apply to {count} task{count === 1 ? "" : "s"}
                </button>
              </div>
            </div>
          </Popover>
        );
      })()}

      {menu && menu.type === "epic" && (
        <Popover anchor={menu.anchor} onClose={close}>
          <div style={{ minWidth: 240, maxHeight: 320, overflowY: "auto" }}>
            {sameProject == null && (
              <div style={{
                padding: "6px 10px", fontSize: 11, color: "var(--ink-muted)",
                background: "rgba(253,171,61,.08)",
                borderBottom: "1px solid var(--border)",
              }}>
                Tasks span multiple projects — moving them to a single epic
                may put them in a different project's backlog.
              </div>
            )}
            {scopedEpics.length === 0 && (
              <div style={{ padding: 12, fontSize: 12, color: "var(--ink-muted)" }}>
                No epics in this project yet.
              </div>
            )}
            {scopedEpics.map(e => (
              <div key={e.id} className="popover-item"
                   onClick={() => setEpic(e)}>
                <span style={{
                  width: 10, height: 10, borderRadius: 999,
                  background: e.color || "var(--brand)",
                }}/>
                <span style={{ flex: 1 }}>{e.title}</span>
              </div>
            ))}
            <div className="popover-item"
                 style={{ borderTop: "1px solid var(--border)", color: "var(--ink-muted)" }}
                 onClick={() => setEpic(null)}>
              <Icons.Close size={12}/> No epic
            </div>
          </div>
        </Popover>
      )}

      {menu && menu.type === "sprint" && (
        <Popover anchor={menu.anchor} onClose={close}>
          <div style={{ minWidth: 240, maxHeight: 320, overflowY: "auto" }}>
            {scopedSprints.length === 0 && (
              <div style={{ padding: 12, fontSize: 12, color: "var(--ink-muted)" }}>
                No sprints found.
              </div>
            )}
            {scopedSprints.map(s => (
              <div key={s.id} className="popover-item"
                   onClick={() => setSprint(s)}>
                <Icons.Lightning size={12} style={{
                  color: s.active ? "var(--brand)" : "var(--ink-faint)",
                }}/>
                <div style={{ flex: 1 }}>
                  <div style={{ fontWeight: 600 }}>{s.label}</div>
                  <div style={{ fontSize: 11, color: "var(--ink-muted)" }}>
                    {s.active ? "Active · " : s.completed ? "Completed · " : "Upcoming · "}
                    {s.dates || "—"}
                  </div>
                </div>
              </div>
            ))}
            <div className="popover-item"
                 style={{ borderTop: "1px solid var(--border)", color: "var(--ink-muted)" }}
                 onClick={() => setSprint(null)}>
              <Icons.Close size={12}/> Move to backlog (no sprint)
            </div>
          </div>
        </Popover>
      )}

      {menu && menu.type === "story" && (
        <Popover anchor={menu.anchor} onClose={close}>
          <div style={{ minWidth: 260, maxHeight: 340, overflowY: "auto" }}>
            {sameProject == null && (
              <div style={{
                padding: "6px 10px", fontSize: 11, color: "var(--ink-muted)",
                background: "rgba(253,171,61,.08)",
                borderBottom: "1px solid var(--border)",
              }}>
                Tasks span multiple projects — a story belongs to one project,
                so this may move some tasks across projects.
              </div>
            )}
            {scopedStories.length === 0 && (
              <div style={{ padding: 12, fontSize: 12, color: "var(--ink-muted)" }}>
                No user stories in this project yet.
              </div>
            )}
            {scopedStories.map(s => (
              <div key={s.id} className="popover-item"
                   onClick={() => setStory(s)}>
                <span aria-hidden="true">📖</span>
                <span style={{ flex: 1 }}>{s.title || "(Untitled story)"}</span>
              </div>
            ))}
            <div className="popover-item"
                 style={{ borderTop: "1px solid var(--border)", color: "var(--ink-muted)" }}
                 onClick={() => setStory(null)}>
              <Icons.Close size={12}/> No story
            </div>
          </div>
        </Popover>
      )}

      {menu && menu.type === "due" && (
        <Popover anchor={menu.anchor} onClose={close}>
          <MiniCalendar value={tasks[0]?.due || ""}
                        onChange={(d) => bulkPatch({ due: d })}
                        onClear={() => bulkPatch({ due: "—" })}/>
        </Popover>
      )}
    </div>
  );
}

Object.assign(window, { BulkActionBar });
