import React, { Suspense, useDeferredValue, useEffect, useMemo, useRef, useState } from "react";

const AccountsTab = React.lazy(() => import("./tabs/AccountsTab.jsx"));
const LogsTab = React.lazy(() => import("./tabs/LogsTab.jsx"));
const EventsTab = React.lazy(() => import("./tabs/EventsTab.jsx"));
const SettingsTab = React.lazy(() => import("./tabs/SettingsTab.jsx"));

const emptySettings = {
  competitorGroups: [""],
  ourGroup: "",
  minIntervalMinutes: 5,
  maxIntervalMinutes: 10,
  dailyLimit: 100,
  historyLimit: 200,
  accountMaxGroups: 10,
  accountDailyLimit: 50,
  floodCooldownMinutes: 1440,
  queueTtlHours: 24
};

  const emptyTaskForm = {
    id: null,
    name: "",
  ourGroup: "",
  minIntervalMinutes: 1,
  maxIntervalMinutes: 3,
  dailyLimit: 15,
  historyLimit: 35,
  maxInvitesPerCycle: 1,
  maxCompetitorBots: 1,
  maxOurBots: 1,
  randomAccounts: false,
  multiAccountsPerRun: false,
  retryOnFail: true,
  autoJoinCompetitors: true,
  autoJoinOurGroup: true,
  separateBotRoles: false,
  requireSameBotInBoth: true,
  parseParticipants: false,
  inviteViaAdmins: false,
  inviteAdminMasterId: 0,
  inviteAdminAllowFlood: false,
  inviteAdminAnonymous: true,
  separateConfirmRoles: false,
  maxConfirmBots: 1,
  useWatcherInviteNoUsername: true,
  warmupEnabled: true,
  warmupStartLimit: 3,
  warmupDailyIncrease: 2,
  cycleCompetitors: false,
  competitorCursor: 0,
  inviteLinkOnFail: false,
    stopOnBlocked: true,
    stopBlockedPercent: 25,
    notes: "",
    enabled: true,
    autoAssignAccounts: true,
    allowStartWithoutInviteRights: true
  };

  const normalizeTask = (row) => ({
  id: row.id,
  name: row.name || "",
  ourGroup: row.our_group || "",
  minIntervalMinutes: Number(row.min_interval_minutes || 1),
  maxIntervalMinutes: Number(row.max_interval_minutes || 3),
  dailyLimit: Number(row.daily_limit || 15),
  historyLimit: Number(row.history_limit || 35),
  maxInvitesPerCycle: Number(row.max_invites_per_cycle || 1),
  maxCompetitorBots: Number(row.max_competitor_bots || 1),
  maxOurBots: Number(row.max_our_bots || 1),
  randomAccounts: Boolean(row.random_accounts),
  multiAccountsPerRun: Boolean(row.multi_accounts_per_run),
  retryOnFail: Boolean(row.retry_on_fail),
  autoJoinCompetitors: Boolean(row.auto_join_competitors),
  autoJoinOurGroup: Boolean(row.auto_join_our_group),
  separateBotRoles: Boolean(row.separate_bot_roles),
  requireSameBotInBoth: Boolean(row.require_same_bot_in_both),
  parseParticipants: Boolean(row.parse_participants),
  inviteViaAdmins: Boolean(row.invite_via_admins),
  inviteAdminMasterId: Number(row.invite_admin_master_id || 0),
  inviteAdminAllowFlood: Boolean(row.invite_admin_allow_flood),
  inviteAdminAnonymous: row.invite_admin_anonymous == null ? true : Boolean(row.invite_admin_anonymous),
  separateConfirmRoles: Boolean(row.separate_confirm_roles),
  maxConfirmBots: Number(row.max_confirm_bots || 1),
  useWatcherInviteNoUsername: row.use_watcher_invite_no_username == null ? true : Boolean(row.use_watcher_invite_no_username),
  warmupEnabled: row.warmup_enabled == null ? true : Boolean(row.warmup_enabled),
  warmupStartLimit: Number(row.warmup_start_limit || 3),
  warmupDailyIncrease: Number(row.warmup_daily_increase || 2),
  cycleCompetitors: Boolean(row.cycle_competitors),
  competitorCursor: Number(row.competitor_cursor || 0),
  inviteLinkOnFail: Boolean(row.invite_link_on_fail),
    stopOnBlocked: Boolean(row.stop_on_blocked),
    stopBlockedPercent: Number(row.stop_blocked_percent || 25),
    notes: row.notes || "",
    enabled: Boolean(row.enabled),
    allowStartWithoutInviteRights: row.allow_start_without_invite_rights == null ? true : Boolean(row.allow_start_without_invite_rights),
    autoAssignAccounts: true
  });

const normalizeIntervals = (form) => {
  const minValue = Number(form.minIntervalMinutes);
  const maxValue = Number(form.maxIntervalMinutes);
  const min = Number.isFinite(minValue) && minValue > 0 ? minValue : 1;
  let max = Number.isFinite(maxValue) && maxValue > 0 ? maxValue : 1;
  if (max < min) max = min;
  return { ...form, minIntervalMinutes: min, maxIntervalMinutes: max };
};

const sanitizeTaskForm = (form) => {
  let normalized = { ...form };
  normalized = normalizeIntervals(normalized);
  if (normalized.requireSameBotInBoth) {
    normalized.separateBotRoles = false;
    normalized.maxOurBots = normalized.maxCompetitorBots;
  }
  if (!normalized.separateBotRoles) {
    normalized.separateConfirmRoles = false;
  }
  return normalized;
};

export default function App() {
  const [settings, setSettings] = useState(emptySettings);
  const [accounts, setAccounts] = useState([]);
  const [accountStats, setAccountStats] = useState([]);
  const [accountAssignments, setAccountAssignments] = useState([]);
  const [globalStatus, setGlobalStatus] = useState({ connectedSessions: 0, totalAccounts: 0 });
  const [logs, setLogs] = useState([]);
  const [invites, setInvites] = useState([]);
  const [fallbackList, setFallbackList] = useState([]);
  const [tasks, setTasks] = useState([]);
  const [selectedTaskId, setSelectedTaskId] = useState(null);
  const [taskForm, setTaskForm] = useState(emptyTaskForm);
  const [competitorText, setCompetitorText] = useState("");
  const [selectedAccountIds, setSelectedAccountIds] = useState([]);
  const [taskAccountRoles, setTaskAccountRoles] = useState({});
  const [activePreset, setActivePreset] = useState("");
  const presetSignatureRef = useRef("");
  const [taskStatus, setTaskStatus] = useState({
    running: false,
    queueCount: 0,
    dailyRemaining: 0,
    dailyUsed: 0,
    dailyLimit: 0,
    cycleCompetitors: false,
    competitorCursor: 0,
    monitorInfo: { monitoring: false, accountId: 0, accountIds: [], groups: [], lastMessageAt: "", lastSource: "" },
    nextRunAt: "",
    nextInviteAccountId: 0,
    lastInviteAccountId: 0,
    pendingStats: { total: 0, withUsername: 0, withAccessHash: 0, withoutData: 0 },
    warnings: [],
    lastStopReason: "",
    lastStopAt: ""
  });
  const [taskStatusMap, setTaskStatusMap] = useState({});
  const [membershipStatus, setMembershipStatus] = useState({});
  const [groupVisibility, setGroupVisibility] = useState([]);
  const [accessStatus, setAccessStatus] = useState([]);
  const [inviteAccessStatus, setInviteAccessStatus] = useState([]);
  const [accountEvents, setAccountEvents] = useState([]);
  const [taskAudit, setTaskAudit] = useState([]);
  const [loginForm, setLoginForm] = useState({
    apiId: "",
    apiHash: "",
    phone: "",
    code: "",
    password: ""
  });
  const [tdataForm, setTdataForm] = useState({
    apiId: "2040",
    apiHash: "b18441a1ff607e10a989891a5462e627"
  });
  const [tdataResult, setTdataResult] = useState(null);
  const [tdataLoading, setTdataLoading] = useState(false);
  const [fileImportForm, setFileImportForm] = useState({
    onlyIds: false,
    sourceChat: ""
  });
  const [fileImportResult, setFileImportResult] = useState(null);
  const [taskActionLoading, setTaskActionLoading] = useState(false);
  const [loginId, setLoginId] = useState("");
  const [loginStatus, setLoginStatus] = useState("");
  const [taskNotice, setTaskNotice] = useState(null);
  const [autosaveNote, setAutosaveNote] = useState("");
  const [settingsNotice, setSettingsNotice] = useState(null);
  const [tdataNotice, setTdataNotice] = useState(null);
  const [toasts, setToasts] = useState([]);
  const toastTimers = useRef(new Map());
  const [notifications, setNotifications] = useState([]);
  const [notificationsOpen, setNotificationsOpen] = useState(false);
  const notificationsModalRef = useRef(null);
  const [manualLoginOpen, setManualLoginOpen] = useState(false);
  const [taskSearch, setTaskSearch] = useState("");
  const [taskFilter, setTaskFilter] = useState("all");
  const [notificationFilter, setNotificationFilter] = useState("all");
  const [infoOpen, setInfoOpen] = useState(false);
  const [infoTab, setInfoTab] = useState("usage");
  const [activeTab, setActiveTab] = useState("task");
  const [logsTab, setLogsTab] = useState("logs");
  const [logSearch, setLogSearch] = useState("");
  const [inviteSearch, setInviteSearch] = useState("");
  const [fallbackSearch, setFallbackSearch] = useState("");
  const [auditSearch, setAuditSearch] = useState("");
  const [logPage, setLogPage] = useState(1);
  const [invitePage, setInvitePage] = useState(1);
  const [fallbackPage, setFallbackPage] = useState(1);
  const [auditPage, setAuditPage] = useState(1);
  const [inviteFilter, setInviteFilter] = useState("all");
  const [taskSort, setTaskSort] = useState("activity");
  const [expandedInviteId, setExpandedInviteId] = useState(null);
  const [now, setNow] = useState(Date.now());
  const [isVisible, setIsVisible] = useState(!document.hidden);
  const bellRef = useRef(null);
  const settingsAutosaveReady = useRef(false);
  const taskAutosaveReady = useRef(false);
  const taskAutosaveTimer = useRef(null);
  const autosaveNoteTimer = useRef(null);
  const tasksPollInFlight = useRef(false);
  const accountsPollInFlight = useRef(false);
  const logsPollInFlight = useRef(false);
  const eventsPollInFlight = useRef(false);
  const deferredTaskSearch = useDeferredValue(taskSearch);
  const deferredLogSearch = useDeferredValue(logSearch);
  const deferredInviteSearch = useDeferredValue(inviteSearch);
  const deferredAuditSearch = useDeferredValue(auditSearch);

  const competitorGroups = useMemo(() => {
    return competitorText
      .split("\n")
      .map((line) => line.trim())
      .filter((line) => line.length > 0);
  }, [competitorText]);
  const hasSelectedTask = selectedTaskId != null;
  const selectedTask = tasks.find((task) => task.id === selectedTaskId) || null;
  const selectedTaskName = selectedTask ? (selectedTask.name || `Задача #${selectedTask.id}`) : "—";
  const roleMode = taskForm.requireSameBotInBoth ? "same" : taskForm.separateBotRoles ? "split" : "shared";
  const canSaveTask = Boolean(
    taskForm.name.trim() &&
    taskForm.ourGroup.trim() &&
    competitorGroups.length > 0
  );
  const accountById = useMemo(() => {
    const map = new Map();
    accounts.forEach((account) => {
      map.set(account.id, account);
    });
    return map;
  }, [accounts]);
  const formatAccountLabel = (account) => {
    if (!account) return "—";
    const base = account.phone || account.user_id || String(account.id);
    const username = account.username ? `@${account.username}` : "";
    return username ? `${base} (${username})` : base;
  };
  const copyToClipboard = async (text) => {
    if (!text) return false;
    try {
      if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
        await navigator.clipboard.writeText(text);
        return true;
      }
    } catch (error) {
      // ignore and fallback
    }
    try {
      const el = document.createElement("textarea");
      el.value = text;
      el.setAttribute("readonly", "");
      el.style.position = "absolute";
      el.style.left = "-9999px";
      document.body.appendChild(el);
      el.select();
      const ok = document.execCommand("copy");
      document.body.removeChild(el);
      return ok;
    } catch (error) {
      return false;
    }
  };
  const accountStatsMap = useMemo(() => {
    const map = new Map();
    (accountStats || []).forEach((item) => {
      map.set(item.id, item);
    });
    return map;
  }, [accountStats]);
  const roleSummary = useMemo(() => {
    const monitor = [];
    const invite = [];
    const confirm = [];
    Object.entries(taskAccountRoles).forEach(([id, roles]) => {
      const accountId = Number(id);
      if (roles.monitor) monitor.push(accountId);
      if (roles.invite) invite.push(accountId);
      if (roles.confirm) confirm.push(accountId);
    });
    return { monitor, invite, confirm };
  }, [taskAccountRoles]);
  const roleIntersectionCount = useMemo(() => {
    let count = 0;
    Object.values(taskAccountRoles).forEach((roles) => {
      if (roles.monitor && roles.invite) count += 1;
    });
    return count;
  }, [taskAccountRoles]);
  const assignedAccountCount = useMemo(() => {
    const ids = new Set([...roleSummary.monitor, ...roleSummary.invite, ...roleSummary.confirm]);
    return ids.size;
  }, [roleSummary]);
  const assignedAccountMap = useMemo(() => {
    const map = new Map();
    accountAssignments.forEach((row) => {
      const list = map.get(row.account_id) || [];
      list.push({
        taskId: row.task_id,
        roleMonitor: Boolean(row.role_monitor),
        roleInvite: Boolean(row.role_invite),
        roleConfirm: row.role_confirm != null ? Boolean(row.role_confirm) : Boolean(row.role_invite)
      });
      map.set(row.account_id, list);
    });
    return map;
  }, [accountAssignments]);
  const filterFreeAccounts = tasks.length > 1;
  const accountBuckets = useMemo(() => {
    const selected = selectedTaskId;
    const freeOrSelected = [];
    const busy = [];
    const taskNameMap = new Map();
    tasks.forEach((task) => {
      taskNameMap.set(task.id, task.name || `Задача #${task.id}`);
    });
    accounts.forEach((account) => {
      const assignedTasks = assignedAccountMap.get(account.id) || [];
      const assignedToSelected = selected != null && assignedTasks.some((item) => item.taskId === selected);
      const isFree = assignedTasks.length === 0;
      if (filterFreeAccounts && !isFree && !assignedToSelected) {
        busy.push(account);
      } else {
        freeOrSelected.push(account);
      }
    });
    return { freeOrSelected, busy, taskNameMap };
  }, [accounts, assignedAccountMap, selectedTaskId, filterFreeAccounts, tasks]);

  const loadTasks = async () => {
    const tasksData = await window.api.listTasks();
    setTasks(tasksData);
    if (!tasksData.length) {
      setSelectedTaskId(null);
      return tasksData;
    }
    if (selectedTaskId == null) {
      setSelectedTaskId(tasksData[0].id);
      return tasksData;
    }
    if (!tasksData.some((task) => task.id === selectedTaskId)) {
      setSelectedTaskId(tasksData[0].id);
    }
    return tasksData;
  };

  const loadAccountAssignments = async () => {
    if (!window.api) return;
    const assignments = await window.api.listAccountAssignments();
    setAccountAssignments(assignments || []);
  };

  const loadTaskStatuses = async (tasksData) => {
    const entries = await Promise.all(
      (tasksData || []).map(async (task) => {
        const status = await window.api.taskStatus(task.id);
        return [task.id, status];
      })
    );
    const map = {};
    entries.forEach(([id, status]) => {
      map[id] = status;
    });
    setTaskStatusMap(map);
  };

  const loadSelectedTask = async (taskId) => {
    taskAutosaveReady.current = false;
    if (!taskId) {
      setTaskForm(emptyTaskForm);
      setCompetitorText("");
      setSelectedAccountIds([]);
      setTaskAccountRoles({});
      setLogs([]);
      setInvites([]);
      setGroupVisibility([]);
      setTaskStatus({
        running: false,
        queueCount: 0,
        dailyRemaining: 0,
        dailyUsed: 0,
        dailyLimit: 0,
        monitorInfo: { monitoring: false, accountId: 0, accountIds: [], groups: [], lastMessageAt: "", lastSource: "" },
        nextRunAt: "",
        nextInviteAccountId: 0,
        lastInviteAccountId: 0,
        pendingStats: { total: 0, withUsername: 0, withAccessHash: 0, withoutData: 0 }
      });
      taskAutosaveReady.current = true;
      return;
    }
    const details = await window.api.getTask(taskId);
    if (!details) {
      taskAutosaveReady.current = true;
      return;
    }
    setTaskForm(sanitizeTaskForm({ ...emptyTaskForm, ...normalizeTask(details.task) }));
    setCompetitorText((details.competitors || []).join("\n"));
    const roleMap = {};
    if (details.accountRoles && details.accountRoles.length) {
      details.accountRoles.forEach((item) => {
        const roleConfirm = item.roleConfirm != null ? item.roleConfirm : item.roleInvite;
        roleMap[item.accountId] = {
          monitor: Boolean(item.roleMonitor),
          invite: Boolean(item.roleInvite),
          confirm: Boolean(roleConfirm)
        };
      });
    } else {
      (details.accountIds || []).forEach((accountId) => {
        roleMap[accountId] = { monitor: true, invite: true, confirm: true };
      });
    }
    setTaskAccountRoles(roleMap);
    setSelectedAccountIds(Object.keys(roleMap).map((id) => Number(id)));
    setLogs(await window.api.listLogs({ limit: 100, taskId }));
    setInvites(await window.api.listInvites({ limit: 200, taskId }));
    setFallbackList(await window.api.listFallback({ limit: 500, taskId }));
    setGroupVisibility([]);
    setTaskStatus(await window.api.taskStatus(taskId));
    taskAutosaveReady.current = true;
  };

  const loadBase = async () => {
    const [settingsData, accountsData, eventsData, statusData] = await Promise.all([
      window.api.getSettings(),
      window.api.listAccounts(),
      window.api.listAccountEvents(200),
      window.api.getStatus()
    ]);
    setSettings(settingsData);
    setAccounts(accountsData);
    setAccountEvents(eventsData);
    setAccountStats(statusData.accountStats || []);
    setGlobalStatus({
      connectedSessions: statusData.connectedSessions || 0,
      totalAccounts: statusData.totalAccounts || 0
    });
    const tasksData = await loadTasks();
    await loadAccountAssignments();
    await loadTaskStatuses(tasksData);
  };

  useEffect(() => {
    loadBase();
  }, []);

  useEffect(() => {
    loadSelectedTask(selectedTaskId);
    setAccessStatus([]);
    setInviteAccessStatus([]);
    setMembershipStatus({});
    setTaskNotice(null);
    setActivePreset("");
    if (selectedTaskId != null) {
      checkAccess("auto", true);
      checkInviteAccess("auto", true);
    }
  }, [selectedTaskId]);

  const buildPresetSignature = (form, roles) => {
    const roleEntries = Object.entries(roles || {})
      .map(([id, value]) => ({
        id: Number(id),
        monitor: Boolean(value && value.monitor),
        invite: Boolean(value && value.invite),
        confirm: Boolean(value && value.confirm)
      }))
      .sort((a, b) => a.id - b.id);
    const snapshot = {
      form: {
        warmupEnabled: Boolean(form.warmupEnabled),
        historyLimit: Number(form.historyLimit || 0),
        separateBotRoles: Boolean(form.separateBotRoles),
        requireSameBotInBoth: Boolean(form.requireSameBotInBoth),
        maxCompetitorBots: Number(form.maxCompetitorBots || 0),
        maxOurBots: Number(form.maxOurBots || 0),
        separateConfirmRoles: Boolean(form.separateConfirmRoles),
        maxConfirmBots: Number(form.maxConfirmBots || 0),
        inviteViaAdmins: Boolean(form.inviteViaAdmins),
        inviteAdminAnonymous: Boolean(form.inviteAdminAnonymous),
        inviteAdminMasterId: Number(form.inviteAdminMasterId || 0)
      },
      roles: roleEntries
    };
    return JSON.stringify(snapshot);
  };

  useEffect(() => {
    if (!activePreset) return;
    const currentSignature = buildPresetSignature(taskForm, taskAccountRoles);
    if (currentSignature !== presetSignatureRef.current) {
      setActivePreset("");
    }
  }, [taskForm, taskAccountRoles, activePreset]);

  const taskSummary = useMemo(() => {
    const totals = {
      total: tasks.length,
      running: 0,
      queue: 0,
      dailyUsed: 0,
      dailyLimit: 0
    };
    tasks.forEach((task) => {
      const status = taskStatusMap[task.id];
      if (status && status.running) totals.running += 1;
      if (status) {
        totals.queue += Number(status.queueCount || 0);
        totals.dailyUsed += Number(status.dailyUsed || 0);
        totals.dailyLimit += Number(status.dailyLimit || 0);
      }
    });
    return totals;
  }, [tasks, taskStatusMap]);

  const filteredTasks = useMemo(() => {
    const query = deferredTaskSearch.trim().toLowerCase();
    const filtered = tasks.filter((task) => {
      const name = (task.name || "").toLowerCase();
      const group = (task.our_group || "").toLowerCase();
      const matchesQuery = !query || name.includes(query) || group.includes(query) || String(task.id).includes(query);
      if (!matchesQuery) return false;
      const status = taskStatusMap[task.id];
      if (taskFilter === "running") return Boolean(status && status.running);
      if (taskFilter === "stopped") return Boolean(status && !status.running);
      return true;
    });
    const sorted = [...filtered].sort((a, b) => {
      const statusA = taskStatusMap[a.id];
      const statusB = taskStatusMap[b.id];
      if (taskSort === "queue") {
        return (statusB ? statusB.queueCount : 0) - (statusA ? statusA.queueCount : 0);
      }
      if (taskSort === "limit") {
        return (statusB ? statusB.dailyLimit : 0) - (statusA ? statusA.dailyLimit : 0);
      }
      if (taskSort === "lastMessage") {
        const dateA = statusA && statusA.monitorInfo && statusA.monitorInfo.lastMessageAt
          ? Date.parse(statusA.monitorInfo.lastMessageAt) || 0
          : 0;
        const dateB = statusB && statusB.monitorInfo && statusB.monitorInfo.lastMessageAt
          ? Date.parse(statusB.monitorInfo.lastMessageAt) || 0
          : 0;
        return dateB - dateA;
      }
      if (taskSort === "activity") {
        const aActive = statusA && statusA.running ? 1 : 0;
        const bActive = statusB && statusB.running ? 1 : 0;
        if (bActive !== aActive) return bActive - aActive;
      }
      if (taskSort === "id") {
        return b.id - a.id;
      }
      return b.id - a.id;
    });
    return sorted;
  }, [tasks, deferredTaskSearch, taskFilter, taskSort, taskStatusMap]);

  useEffect(() => {
    if (!window.api) return undefined;
    const load = async () => {
      if (!isVisible || tasksPollInFlight.current) return;
      tasksPollInFlight.current = true;
      try {
        const tasksData = await window.api.listTasks();
        setTasks(tasksData);
        await loadTaskStatuses(tasksData);
        if (selectedTaskId != null) {
          setTaskStatus(await window.api.taskStatus(selectedTaskId));
        }
      } finally {
        tasksPollInFlight.current = false;
      }
    };
    load();
    const interval = setInterval(async () => {
      await load();
    }, 5000);
    return () => clearInterval(interval);
  }, [selectedTaskId, isVisible]);

  useEffect(() => {
    if (!window.api) return undefined;
    const load = async () => {
      if (!isVisible || accountsPollInFlight.current) return;
      accountsPollInFlight.current = true;
      try {
        setAccounts(await window.api.listAccounts());
        setAccountAssignments(await window.api.listAccountAssignments());
        const statusData = await window.api.getStatus();
        setAccountStats(statusData.accountStats || []);
        setGlobalStatus({
          connectedSessions: statusData.connectedSessions || 0,
          totalAccounts: statusData.totalAccounts || 0
        });
      } finally {
        accountsPollInFlight.current = false;
      }
    };
    load();
    const interval = setInterval(load, 15000);
    return () => clearInterval(interval);
  }, [isVisible]);

  useEffect(() => {
    if (!window.api || activeTab !== "logs" || selectedTaskId == null) return undefined;
    const load = async () => {
      if (!isVisible || logsPollInFlight.current) return;
      logsPollInFlight.current = true;
      try {
        setLogs(await window.api.listLogs({ limit: 100, taskId: selectedTaskId }));
        setInvites(await window.api.listInvites({ limit: 200, taskId: selectedTaskId }));
        setFallbackList(await window.api.listFallback({ limit: 500, taskId: selectedTaskId }));
        setTaskAudit(await window.api.listTaskAudit(selectedTaskId));
      } finally {
        logsPollInFlight.current = false;
      }
    };
    load();
    const interval = setInterval(load, 5000);
    return () => clearInterval(interval);
  }, [activeTab, selectedTaskId, isVisible]);

  useEffect(() => {
    if (!window.api || activeTab !== "events") return undefined;
    const load = async () => {
      if (!isVisible || eventsPollInFlight.current) return;
      eventsPollInFlight.current = true;
      try {
        setAccountEvents(await window.api.listAccountEvents(200));
      } finally {
        eventsPollInFlight.current = false;
      }
    };
    load();
    const interval = setInterval(load, 10000);
    return () => clearInterval(interval);
  }, [activeTab, isVisible]);

  useEffect(() => {
    const timer = setInterval(() => setNow(Date.now()), 1000);
    return () => clearInterval(timer);
  }, []);

  useEffect(() => {
    const handleVisibility = () => {
      setIsVisible(!document.hidden);
    };
    document.addEventListener("visibilitychange", handleVisibility);
    window.addEventListener("focus", handleVisibility);
    window.addEventListener("blur", handleVisibility);
    return () => {
      document.removeEventListener("visibilitychange", handleVisibility);
      window.removeEventListener("focus", handleVisibility);
      window.removeEventListener("blur", handleVisibility);
    };
  }, []);

  useEffect(() => {
    if (selectedTaskId == null) return;
    setTaskStatusMap((prev) => ({
      ...prev,
      [selectedTaskId]: taskStatus
    }));
  }, [selectedTaskId, taskStatus]);

  useEffect(() => {
    if (!hasSelectedTask) return;
    if (taskForm.requireSameBotInBoth) {
      const nextValue = Math.max(1, roleIntersectionCount || 0);
      if (taskForm.maxCompetitorBots !== nextValue || taskForm.maxOurBots !== nextValue) {
        setTaskForm((prev) => sanitizeTaskForm({
          ...prev,
          maxCompetitorBots: nextValue,
          maxOurBots: nextValue
        }));
      }
      return;
    }
    if (taskForm.separateBotRoles) {
      const nextCompetitors = Math.max(1, roleSummary.monitor.length || 0);
      const nextOur = Math.max(1, roleSummary.invite.length || 0);
      const hasConfirmRoles = roleSummary.confirm.length > 0;
      const nextConfirm = taskForm.separateConfirmRoles && hasConfirmRoles
        ? Math.max(1, roleSummary.confirm.length)
        : taskForm.maxConfirmBots;
      if (taskForm.maxCompetitorBots !== nextCompetitors || taskForm.maxOurBots !== nextOur || (taskForm.separateConfirmRoles && hasConfirmRoles && taskForm.maxConfirmBots !== nextConfirm)) {
        setTaskForm((prev) => ({
          ...prev,
          maxCompetitorBots: nextCompetitors,
          maxOurBots: nextOur,
          maxConfirmBots: nextConfirm
        }));
      }
    }
  }, [
    hasSelectedTask,
    roleIntersectionCount,
    roleSummary.monitor.length,
    roleSummary.invite.length,
    roleSummary.confirm.length,
    taskForm.requireSameBotInBoth,
    taskForm.separateBotRoles,
    taskForm.separateConfirmRoles,
    taskForm.maxConfirmBots
  ]);

  const formatAccountStatus = (status) => {
    if (status === "limited") return "В спаме";
    if (status === "error") return "Ошибка";
    if (status === "ok") return "ОК";
    return status || "Неизвестно";
  };

  const formatTimestamp = (value) => {
    if (!value) return "—";
    const date = value instanceof Date ? value : new Date(value);
    if (Number.isNaN(date.getTime())) return "—";
    return date.toLocaleString("ru-RU");
  };

  const formatCountdown = (target) => {
    if (!target) return "—";
    const targetTime = new Date(target).getTime();
    if (!Number.isFinite(targetTime)) return "—";
    const diff = Math.max(0, Math.floor((targetTime - now) / 1000));
    const minutes = Math.floor(diff / 60);
    const seconds = diff % 60;
    return `${minutes}:${String(seconds).padStart(2, "0")}`;
  };
  const formatTargetType = (value) => {
    if (!value) return "";
    if (value === "channel") return "канал";
    if (value === "megagroup") return "супергруппа";
    if (value === "group") return "группа";
    return value;
  };

  const explainInviteError = useMemo(() => (error) => {
    if (!error) return "";
    if (error === "USER_ID_INVALID") {
      return "Пользователь удален/скрыт; access_hash невалиден для этой сессии; приглашение в канал/чат без валидной сущности.";
    }
    if (error === "CHAT_WRITE_FORBIDDEN") {
      return "Аккаунт не может приглашать: нет прав или он не участник группы.";
    }
    if (error === "USER_NOT_MUTUAL_CONTACT") {
      return "Пользователь не взаимный контакт для добавляющего аккаунта. Обычно это происходит, когда в группе/канале включена опция «добавлять могут только контакты» или у пользователя закрыт приём инвайтов. Решение: использовать аккаунт, который уже в контактах у пользователя, или поменять настройки группы.";
    }
    if (error === "USER_PRIVACY_RESTRICTED") {
      return "Приглашение запрещено пользователем: приватность не позволяет добавлять в группы.";
    }
    if (error === "USER_NOT_PARTICIPANT") {
      return "Аккаунт не состоит в целевой группе или канал приватный.";
    }
    if (error === "USER_BANNED_IN_CHANNEL") {
      return "Пользователь заблокирован в группе или канале назначения.";
    }
    if (error === "USER_BOT") {
      return "Бота нельзя приглашать как обычного пользователя.";
    }
    if (error === "USER_KICKED") {
      return "Пользователь был удален из группы ранее.";
    }
    if (error === "CHAT_ADMIN_REQUIRED") {
      return "Для добавления участников нужны права администратора.";
    }
    if (error === "CHANNEL_INVALID") {
      return "Цель не распознана как группа/канал для этого аккаунта (ссылка недоступна или сущность устарела).";
    }
    if (error === "USER_ALREADY_PARTICIPANT") {
      return "Пользователь уже состоит в целевой группе.";
    }
    if (error === "CHAT_MEMBER_ADD_FAILED") {
      return "Telegram отклонил добавление участника (ограничения приватности, антиспам или запрет инвайтов).";
    }
    if (error === "INVITE_HASH_EXPIRED" || error === "INVITE_HASH_INVALID") {
      return "Инвайт-ссылка недействительна или истекла.";
    }
    if (error === "CHANNEL_PRIVATE") {
      return "Целевая группа/канал приватные и недоступны по ссылке.";
    }
    if (error === "AUTH_KEY_DUPLICATED") {
      return "Сессия используется в другом месте, Telegram отозвал ключ.";
    }
    if (error.includes("FLOOD") || error.includes("PEER_FLOOD")) {
      return "Ограничение Telegram по частоте действий.";
    }
    return "";
  }, []);

  const explainTdataError = useMemo(() => (error) => {
    if (!error) return "";
    if (error.includes("AUTH_KEY_DUPLICATED")) {
      return "Эта сессия уже используется в другом месте. Выйдите из аккаунта на других устройствах и пересоберите tdata.";
    }
    if (error === "DUPLICATE_ACCOUNT") {
      return "Аккаунт уже добавлен в приложение.";
    }
    return "";
  }, []);

  const showNotification = (text, tone = "info") => {
    if (!text) return;
    const key = `${tone}|${text}`;
    const now = Date.now();
    setNotifications((prev) => {
      const existingIndex = prev.findIndex((item) => item.key === key);
      if (existingIndex >= 0) {
        const updated = { ...prev[existingIndex], count: (prev[existingIndex].count || 1) + 1, lastAt: now };
        const next = [updated, ...prev.filter((_, index) => index !== existingIndex)];
        return next.slice(0, 20);
      }
      const entry = { text, tone, id: `${now}-${Math.random().toString(36).slice(2)}`, key, count: 1, lastAt: now };
      return [entry, ...prev].slice(0, 20);
    });
    setToasts((prev) => {
      const existingIndex = prev.findIndex((item) => item.key === key);
      if (existingIndex >= 0) {
        const updated = { ...prev[existingIndex], count: (prev[existingIndex].count || 1) + 1, lastAt: now };
        const next = [updated, ...prev.filter((_, index) => index !== existingIndex)];
        return next.slice(0, 4);
      }
      const entry = { text, tone, id: `${now}-${Math.random().toString(36).slice(2)}`, key, count: 1, lastAt: now };
      return [entry, ...prev].slice(0, 4);
    });
    if (toastTimers.current.has(key)) {
      clearTimeout(toastTimers.current.get(key));
    }
    const timeoutId = setTimeout(() => {
      setToasts((prev) => prev.filter((item) => item.key !== key));
      toastTimers.current.delete(key);
    }, 6000);
    toastTimers.current.set(key, timeoutId);
  };

  useEffect(() => {
    if (!taskNotice) return;
    showNotification(taskNotice.text, taskNotice.tone || "info");
    setTaskNotice(null);
  }, [taskNotice]);

  useEffect(() => {
    if (!settingsNotice) return;
    showNotification(settingsNotice.text, settingsNotice.tone || "info");
    setSettingsNotice(null);
  }, [settingsNotice]);

  useEffect(() => {
    if (!tdataNotice) return;
    showNotification(tdataNotice.text, tdataNotice.tone || "info");
    setTdataNotice(null);
  }, [tdataNotice]);

  useEffect(() => {
    const handleClickOutside = (event) => {
      if (!notificationsOpen) return;
      if (!bellRef.current) return;
      if (notificationsModalRef.current && notificationsModalRef.current.contains(event.target)) {
        return;
      }
      if (!bellRef.current.contains(event.target)) {
        setNotificationsOpen(false);
      }
    };
    document.addEventListener("mousedown", handleClickOutside);
    return () => document.removeEventListener("mousedown", handleClickOutside);
  }, [notificationsOpen]);

  const filteredNotifications = useMemo(() => {
    if (notificationFilter === "all") return notifications;
    return notifications.filter((item) => item.tone === notificationFilter);
  }, [notifications, notificationFilter]);

  const criticalEvents = useMemo(() => {
    const criticalTypes = new Set([
      "connect_failed",
      "invite_failed",
      "invite_user_invalid",
      "monitor_handler_error",
      "flood"
    ]);
    const now = Date.now();
    return (accountEvents || []).filter((event) => {
      const ts = new Date(event.createdAt).getTime();
      const recent = Number.isFinite(ts) ? (now - ts) < 24 * 60 * 60 * 1000 : true;
      return criticalTypes.has(event.eventType) && recent;
    });
  }, [accountEvents]);
  const criticalErrorAccounts = useMemo(() => {
    return accounts.filter((account) => account.status && account.status !== "ok");
  }, [accounts]);

  const filteredLogs = useMemo(() => {
    const query = deferredLogSearch.trim().toLowerCase();
    if (!query) return logs;
    return logs.filter((log) => {
      const text = [
        log.startedAt,
        log.finishedAt,
        String(log.invitedCount),
        (log.successIds || []).join(","),
        (log.errors || []).join("|")
      ]
        .join(" ")
        .toLowerCase();
      return text.includes(query);
    });
  }, [logs, deferredLogSearch]);

  const filteredInvites = useMemo(() => {
    const query = deferredInviteSearch.trim().toLowerCase();
    return invites.filter((invite) => {
      if (inviteFilter === "success" && invite.status !== "success") return false;
      if (inviteFilter === "error" && invite.status !== "failed") return false;
      if (inviteFilter === "skipped" && invite.status !== "skipped") return false;
      if (inviteFilter === "unconfirmed" && invite.status !== "unconfirmed") return false;
      const text = [
        invite.invitedAt,
        invite.userId,
        invite.username,
        invite.sourceChat,
        invite.accountPhone,
        invite.watcherPhone,
        invite.strategy,
        invite.strategyMeta,
        invite.error,
        invite.skippedReason,
        invite.confirmError
      ]
        .join(" ")
        .toLowerCase();
      if (!query) return true;
      return text.includes(query);
    });
  }, [invites, deferredInviteSearch, inviteFilter]);
  const filteredFallback = useMemo(() => {
    const query = fallbackSearch.trim().toLowerCase();
    return fallbackList.filter((item) => {
      const text = [
        item.userId,
        item.username,
        item.reason,
        item.route,
        item.sourceChat,
        item.targetChat,
        item.status,
        item.createdAt
      ]
        .join(" ")
        .toLowerCase();
      if (!query) return true;
      return text.includes(query);
    });
  }, [fallbackList, fallbackSearch]);
  const mutualContactDiagnostics = useMemo(() => {
    const items = invites
      .filter((invite) => invite.error === "USER_NOT_MUTUAL_CONTACT")
      .slice()
      .sort((a, b) => (b.invitedAt || "").localeCompare(a.invitedAt || ""));
    return {
      count: items.length,
      recent: items.slice(0, 5)
    };
  }, [invites]);

  const filteredAudit = useMemo(() => {
    const query = deferredAuditSearch.trim().toLowerCase();
    if (!query) return taskAudit;
    return taskAudit.filter((item) => {
      const text = [item.action, item.details, item.createdAt].join(" ").toLowerCase();
      return text.includes(query);
    });
  }, [taskAudit, deferredAuditSearch]);

  const inviteStrategyStats = useMemo(() => {
    let success = 0;
    let failed = 0;
    invites.forEach((invite) => {
      if (!invite.strategyMeta) return;
      try {
        const parsed = JSON.parse(invite.strategyMeta);
        if (!Array.isArray(parsed) || !parsed.length) return;
        const hasOk = parsed.some((item) => item.ok);
        if (hasOk) success += 1;
        else failed += 1;
      } catch (error) {
        // ignore parse errors
      }
    });
    return { success, failed };
  }, [invites]);
  const inviteStats = useMemo(() => {
    const stats = {
      total: invites.length,
      success: 0,
      failed: 0,
      skipped: 0,
      unconfirmed: 0
    };
    invites.forEach((invite) => {
      switch (invite.status) {
        case "success":
          stats.success += 1;
          break;
        case "failed":
          stats.failed += 1;
          break;
        case "skipped":
          stats.skipped += 1;
          break;
        case "unconfirmed":
          stats.unconfirmed += 1;
          break;
        default:
          break;
      }
    });
    return stats;
  }, [invites]);

  const logPageSize = 20;
  const invitePageSize = 20;
  const fallbackPageSize = 20;
  const auditPageSize = 20;
  const logPageCount = Math.max(1, Math.ceil(filteredLogs.length / logPageSize));
  const invitePageCount = Math.max(1, Math.ceil(filteredInvites.length / invitePageSize));
  const fallbackPageCount = Math.max(1, Math.ceil(filteredFallback.length / fallbackPageSize));
  const auditPageCount = Math.max(1, Math.ceil(filteredAudit.length / auditPageSize));
  const pagedLogs = filteredLogs.slice((logPage - 1) * logPageSize, logPage * logPageSize);
  const pagedInvites = filteredInvites.slice((invitePage - 1) * invitePageSize, invitePage * invitePageSize);
  const pagedFallback = filteredFallback.slice((fallbackPage - 1) * fallbackPageSize, fallbackPage * fallbackPageSize);
  const pagedAudit = filteredAudit.slice((auditPage - 1) * auditPageSize, auditPage * auditPageSize);

  const onSettingsChange = (field, value) => {
    setSettings((prev) => ({
      ...prev,
      [field]: value
    }));
  };

  const updateIntervals = (nextMin, nextMax) => {
    const updated = normalizeIntervals({
      ...taskForm,
      minIntervalMinutes: nextMin,
      maxIntervalMinutes: nextMax
    });
    setTaskForm(updated);
  };

  const applyRoleMode = (mode) => {
    if (mode === "same") {
      setTaskForm(sanitizeTaskForm({ ...taskForm, requireSameBotInBoth: true, separateBotRoles: false }));
      return;
    }
    if (mode === "split") {
      setTaskForm({ ...taskForm, requireSameBotInBoth: false, separateBotRoles: true });
      return;
    }
    setTaskForm({ ...taskForm, requireSameBotInBoth: false, separateBotRoles: false });
  };

  const resetCooldown = async (accountId) => {
    if (!window.api) {
      showNotification("Electron API недоступен. Откройте приложение в Electron.", "error");
      return;
    }
    try {
      await window.api.resetAccountCooldown(accountId);
      const updated = await window.api.listAccounts();
      setAccounts(updated);
      setTaskNotice({ text: "Аккаунт снова активен.", tone: "success", source: "accounts" });
    } catch (error) {
      setTaskNotice({ text: error.message || String(error), tone: "error", source: "accounts" });
      showNotification(error.message || String(error), "error");
    }
  };

  const deleteAccount = async (accountId) => {
    if (!window.api) {
      showNotification("Electron API недоступен. Откройте приложение в Electron.", "error");
      return;
    }
    try {
      await window.api.deleteAccount(accountId);
      setAccounts(await window.api.listAccounts());
      setTaskNotice({ text: "Аккаунт удален.", tone: "success", source: "accounts" });
    } catch (error) {
      setTaskNotice({ text: error.message || String(error), tone: "error", source: "accounts" });
      showNotification(error.message || String(error), "error");
    }
  };

  const refreshIdentity = async () => {
    if (!window.api) {
      showNotification("Electron API недоступен. Откройте приложение в Electron.", "error");
      return;
    }
    try {
      await window.api.refreshAccountIdentity();
      setAccounts(await window.api.listAccounts());
      setTaskNotice({ text: "ID аккаунтов обновлены.", tone: "success", source: "accounts" });
    } catch (error) {
      setTaskNotice({ text: error.message || String(error), tone: "error", source: "accounts" });
      showNotification(error.message || String(error), "error");
    }
  };

  const saveSettings = async () => {
    if (!window.api) {
      setSettingsNotice({ text: "Electron API недоступен. Откройте приложение в Electron.", tone: "error" });
      showNotification("Electron API недоступен. Откройте приложение в Electron.", "error");
      return;
    }
    try {
      showNotification("Сохраняем настройки...", "info");
      const updated = await window.api.saveSettings(settings);
      setSettings(updated);
      setSettingsNotice({ text: "Настройки сохранены.", tone: "success" });
    } catch (error) {
      const message = error.message || String(error);
      setSettingsNotice({ text: message, tone: "error" });
      showNotification(message, "error");
    }
  };

  useEffect(() => {
    if (!settingsAutosaveReady.current) {
      settingsAutosaveReady.current = true;
      return;
    }
    if (!window.api) return;
    const timer = setTimeout(async () => {
      try {
        const updated = await window.api.saveSettings(settings);
        setSettings(updated);
      } catch (error) {
        showNotification(error.message || String(error), "error");
      }
    }, 600);
    return () => clearTimeout(timer);
  }, [settings]);

  useEffect(() => {
    if (!taskAutosaveReady.current) return;
    if (!canSaveTask) return;
    if (taskAutosaveTimer.current) {
      clearTimeout(taskAutosaveTimer.current);
    }
    taskAutosaveTimer.current = setTimeout(() => {
      saveTask("autosave", { silent: true });
    }, 800);
    return () => clearTimeout(taskAutosaveTimer.current);
  }, [taskForm, competitorText, taskAccountRoles, selectedAccountIds, canSaveTask]);

  const createTask = () => {
    taskAutosaveReady.current = false;
    setSelectedTaskId(null);
    setTaskForm(emptyTaskForm);
    setCompetitorText("");
    setSelectedAccountIds([]);
    setTaskAccountRoles({});
    setAccessStatus([]);
    setMembershipStatus({});
    taskAutosaveReady.current = true;
  };

  const selectTask = (taskId) => {
    if (taskId === selectedTaskId) return;
    setSelectedTaskId(taskId);
  };

  const saveTask = async (source = "editor", options = {}) => {
    const silent = Boolean(options.silent);
    if (!window.api) {
      if (!silent) {
        showNotification("Electron API недоступен. Откройте приложение в Electron.", "error");
      }
      return;
    }
    try {
      if (!silent) {
        showNotification("Сохраняем задачу...", "info");
      }
      const nextForm = sanitizeTaskForm(taskForm);
      setTaskForm(nextForm);
      const validateLink = (value) => {
        const trimmed = String(value || "").trim();
        if (!trimmed) return false;
        if (trimmed.startsWith("@")) return true;
        if (trimmed.startsWith("https://t.me/")) return true;
        if (trimmed.startsWith("http://t.me/")) return true;
        return false;
      };
      const invalidCompetitors = competitorGroups.filter((link) => !validateLink(link));
      if (!validateLink(nextForm.ourGroup)) {
        showNotification("Наша группа должна быть ссылкой t.me или @username.", "error");
        return;
      }
      if (invalidCompetitors.length) {
        showNotification(`Некорректные ссылки конкурентов: ${invalidCompetitors.join(", ")}`, "error");
        return;
      }
      let accountRolesMap = { ...taskAccountRoles };
      let accountIds = Object.keys(accountRolesMap).map((id) => Number(id));
      if (nextForm.requireSameBotInBoth) {
        const required = Math.max(1, Number(nextForm.maxCompetitorBots || 1));
        const pool = (selectedAccountIds && selectedAccountIds.length ? selectedAccountIds : accounts.map((account) => account.id))
          .filter((id) => Number.isFinite(id));
        const chosen = pool.slice(0, required);
        accountRolesMap = {};
        chosen.forEach((accountId) => {
        accountRolesMap[accountId] = { monitor: true, invite: true, confirm: true };
        });
        accountIds = chosen;
        setTaskAccountRoles(accountRolesMap);
        setSelectedAccountIds(chosen);
      }
      if (nextForm.autoAssignAccounts && (!accountIds || accountIds.length === 0)) {
        accountIds = accounts.map((account) => account.id);
        accountRolesMap = {};
        accountIds.forEach((accountId) => {
          accountRolesMap[accountId] = { monitor: true, invite: true, confirm: true };
        });
        setTaskAccountRoles(accountRolesMap);
        setSelectedAccountIds(accountIds);
        if (accountIds.length && !silent) {
          setTaskNotice({ text: `Автоназначены аккаунты: ${accountIds.length}`, tone: "success", source });
        }
      }
      if (!accountIds.length) {
        if (!silent) {
          showNotification("Нет аккаунтов для этой задачи.", "error");
        }
        return;
      }
      const roleEntries = Object.values(accountRolesMap);
      if (roleEntries.length) {
        const hasMonitor = roleEntries.some((item) => item.monitor);
        const hasInvite = roleEntries.some((item) => item.invite);
        const hasConfirm = roleEntries.some((item) => item.confirm);
        if (!hasMonitor) {
          if (!silent) {
            showNotification("Нужен хотя бы один аккаунт с ролью мониторинга.", "error");
          }
          return;
        }
        if (!hasInvite) {
          if (!silent) {
            showNotification("Нужен хотя бы один аккаунт с ролью инвайта.", "error");
          }
          return;
        }
        if (nextForm.separateConfirmRoles && !hasConfirm) {
          if (!silent) {
            showNotification("Нужен хотя бы один аккаунт с ролью подтверждения.", "error");
          }
          return;
        }
      } else {
        const requiredAccounts = nextForm.requireSameBotInBoth
          ? Math.max(1, Number(nextForm.maxCompetitorBots || 1))
          : nextForm.separateBotRoles
            ? Math.max(1, Number(nextForm.maxCompetitorBots || 1))
              + Math.max(1, Number(nextForm.maxOurBots || 1))
              + (nextForm.separateConfirmRoles ? Math.max(1, Number(nextForm.maxConfirmBots || 1)) : 0)
            : 1;
        if (accountIds.length < requiredAccounts) {
          if (!silent) {
            showNotification(`Нужно минимум ${requiredAccounts} аккаунтов для выбранного режима.`, "error");
          }
          return;
        }
      }
      const accountRoles = Object.entries(accountRolesMap).map(([id, roles]) => ({
        accountId: Number(id),
        roleMonitor: Boolean(roles.monitor),
        roleInvite: Boolean(roles.invite),
        roleConfirm: Boolean(roles.confirm != null ? roles.confirm : roles.invite)
      }));
      const result = await window.api.saveTask({
        task: nextForm,
        competitors: competitorGroups,
        accountIds,
        accountRoles
      });
      if (result.ok) {
        if (!silent) {
          setTaskNotice({ text: "Задача сохранена.", tone: "success", source });
        } else {
          setAutosaveNote("Автосохранено");
          if (autosaveNoteTimer.current) {
            clearTimeout(autosaveNoteTimer.current);
          }
          autosaveNoteTimer.current = setTimeout(() => {
            setAutosaveNote("");
          }, 1500);
        }
        await loadTasks();
        await loadAccountAssignments();
        setSelectedTaskId(result.taskId);
      } else {
        if (!silent) {
          showNotification(result.error || "Не удалось сохранить задачу", "error");
        }
      }
    } catch (error) {
      if (!silent) {
        showNotification(error.message || String(error), "error");
      }
    }
  };

  const deleteTask = async () => {
    if (!window.api || selectedTaskId == null) {
      return;
    }
    try {
      await window.api.deleteTask(selectedTaskId);
      setTaskNotice({ text: "Задача удалена.", tone: "success", source: "tasks" });
      const tasksData = await loadTasks();
      await loadAccountAssignments();
      if (!tasksData.length) {
        createTask();
        setActiveTab("task");
      }
    } catch (error) {
      showNotification(error.message || String(error), "error");
    }
  };

  const startTask = async (source = "sidebar") => {
    if (!window.api || selectedTaskId == null) {
      showNotification("Сначала выберите задачу.", "error");
      return;
    }
    if (taskActionLoading) return;
    setTaskActionLoading(true);
    showNotification("Запуск...", "info");
    const withTimeout = (promise, ms) => (
      Promise.race([
        promise,
        new Promise((_, reject) => setTimeout(() => reject(new Error("TIMEOUT")), ms))
      ])
    );
    try {
      const result = await withTimeout(window.api.startTaskById(selectedTaskId), 15000);
      if (result && result.ok) {
        setTaskNotice({ text: "Запущено.", tone: "success", source });
        if (result.warnings && result.warnings.length) {
          showNotification(`Предупреждения: ${result.warnings.join(" | ")}`, "info");
        }
        checkInviteAccess("auto", true);
      } else {
        showNotification(result.error || "Не удалось запустить", "error");
      }
    } catch (error) {
      const message = error.message === "TIMEOUT"
        ? "Запуск не ответил за 15 секунд. Проверьте логи/события и попробуйте снова."
        : (error.message || String(error));
      setTaskNotice({ text: message, tone: "error", source });
      showNotification(message, "error");
    } finally {
      setTaskActionLoading(false);
    }
  };

  const startAllTasks = async () => {
    if (!window.api) {
      showNotification("Electron API недоступен. Откройте приложение в Electron.", "error");
      return;
    }
    if (!tasks.length) {
      showNotification("Нет задач для запуска.", "info");
      return;
    }
    showNotification("Запускаем все задачи...", "info");
    try {
      const result = await window.api.startAllTasks();
      if (result && result.errors && result.errors.length) {
        const errorText = result.errors.map((item) => `${item.id}: ${item.error}`).join(" | ");
        showNotification(`Ошибки запуска: ${errorText}`, "error");
      }
      const tasksData = await loadTasks();
      await loadTaskStatuses(tasksData);
    } catch (error) {
      showNotification(error.message || String(error), "error");
    }
  };

  const stopTask = async (source = "sidebar") => {
    if (!window.api || selectedTaskId == null) {
      showNotification("Сначала выберите задачу.", "error");
      return;
    }
    if (taskActionLoading) return;
    if (!window.confirm(`Остановить задачу: ${selectedTaskName}?`)) {
      return;
    }
    setTaskActionLoading(true);
    showNotification("Остановка...", "info");
    const withTimeout = (promise, ms) => (
      Promise.race([
        promise,
        new Promise((_, reject) => setTimeout(() => reject(new Error("TIMEOUT")), ms))
      ])
    );
    try {
      await withTimeout(window.api.stopTaskById(selectedTaskId), 15000);
      setTaskNotice({ text: "Остановлено.", tone: "success", source });
    } catch (error) {
      const message = error.message === "TIMEOUT"
        ? "Остановка не ответила за 15 секунд. Проверьте логи/события и попробуйте снова."
        : (error.message || String(error));
      setTaskNotice({ text: message, tone: "error", source });
      showNotification(message, "error");
    } finally {
      setTaskActionLoading(false);
    }
  };

  const stopAllTasks = async () => {
    if (!window.api) {
      showNotification("Electron API недоступен. Откройте приложение в Electron.", "error");
      return;
    }
    if (!tasks.length) {
      showNotification("Нет задач для остановки.", "info");
      return;
    }
    if (!window.confirm("Остановить все задачи?")) {
      return;
    }
    showNotification("Останавливаем все задачи...", "info");
    try {
      await window.api.stopAllTasks();
      const tasksData = await loadTasks();
      await loadTaskStatuses(tasksData);
    } catch (error) {
      showNotification(error.message || String(error), "error");
    }
  };

  const parseHistory = async (source = "editor") => {
    if (!window.api || selectedTaskId == null) {
      showNotification("Сначала выберите задачу.", "error");
      return;
    }
    showNotification("Собираем историю...", "info");
    try {
      const result = await window.api.parseHistoryByTask(selectedTaskId);
      if (result && result.ok) {
        setTaskNotice({ text: "История добавлена в очередь.", tone: "success", source });
        if (result.errors && result.errors.length) {
          showNotification(`Ошибки истории: ${result.errors.join(" | ")}`, "error");
        }
        setLogs(await window.api.listLogs({ limit: 100, taskId: selectedTaskId }));
        setInvites(await window.api.listInvites({ limit: 200, taskId: selectedTaskId }));
        return;
      }
      const message = result.error || "Ошибка при сборе истории";
      setTaskNotice({ text: message, tone: "error", source });
      showNotification(message, "error");
    } catch (error) {
      const message = error.message || String(error);
      setTaskNotice({ text: message, tone: "error", source });
      showNotification(message, "error");
    }
  };

  const refreshMembership = async (source = "editor") => {
    if (!window.api || selectedTaskId == null) {
      showNotification("Сначала выберите задачу.", "error");
      return;
    }
    showNotification("Проверяем участие аккаунтов...", "info");
    try {
      const status = await window.api.membershipStatusByTask(selectedTaskId);
      const visibility = await window.api.groupVisibilityByTask(selectedTaskId);
      const map = {};
      status.forEach((item) => {
        map[item.accountId] = item;
      });
      setMembershipStatus(map);
      setGroupVisibility(visibility && visibility.result ? visibility.result : []);
      setTaskNotice({ text: "Статус участия обновлен.", tone: "success", source });
    } catch (error) {
      const message = error.message || String(error);
      setTaskNotice({ text: message, tone: "error", source });
    showNotification(message, "error");
  }
  };

  const checkAccess = async (source = "editor", silent = false) => {
    if (!window.api || selectedTaskId == null) {
      if (!silent) showNotification("Сначала выберите задачу.", "error");
      return;
    }
    if (!silent) showNotification("Проверяем доступ к группам...", "info");
    try {
      const result = await window.api.checkAccessByTask(selectedTaskId);
      if (!result.ok) {
        if (!silent) showNotification(result.error || "Не удалось проверить доступ", "error");
        return;
      }
      setAccessStatus(result.result || []);
    if (!silent) setTaskNotice({ text: "Проверка доступа завершена.", tone: "success", source });
  } catch (error) {
    if (!silent) showNotification(error.message || String(error), "error");
  }
  };

  const checkInviteAccess = async (source = "editor", silent = false) => {
    if (!window.api || selectedTaskId == null) {
      if (!silent) showNotification("Сначала выберите задачу.", "error");
      return;
    }
    setInviteAccessStatus([]);
    if (!silent) showNotification("Проверяем права инвайта...", "info");
    try {
      const result = await window.api.checkInviteAccessByTask(selectedTaskId);
      if (!result.ok) {
        if (!silent) showNotification(result.error || "Не удалось проверить права", "error");
        return;
      }
      setInviteAccessStatus(result.result || []);
      if (!silent) setTaskNotice({ text: "Проверка прав инвайта завершена.", tone: "success", source });
    } catch (error) {
      if (!silent) showNotification(error.message || String(error), "error");
    }
  };

  const clearLogs = async (source = "editor") => {
    if (!window.api || selectedTaskId == null) {
      showNotification("Сначала выберите задачу.", "error");
      return;
    }
    try {
      await window.api.clearLogs(selectedTaskId);
      setLogs([]);
      setTaskNotice({ text: "Логи очищены.", tone: "success", source });
    } catch (error) {
      showNotification(error.message || String(error), "error");
    }
  };

  const clearInvites = async (source = "editor") => {
    if (!window.api || selectedTaskId == null) {
      showNotification("Сначала выберите задачу.", "error");
      return;
    }
    try {
      await window.api.clearInvites(selectedTaskId);
      setInvites([]);
      setTaskNotice({ text: "История инвайтов очищена.", tone: "success", source });
    } catch (error) {
      showNotification(error.message || String(error), "error");
    }
  };

  const clearAccountEvents = async () => {
    if (!window.api) {
      showNotification("Electron API недоступен. Откройте приложение в Electron.", "error");
      return;
    }
    try {
      await window.api.clearAccountEvents();
      setAccountEvents([]);
      showNotification("События очищены.", "success");
    } catch (error) {
      showNotification(error.message || String(error), "error");
    }
  };

  const exportLogs = async (source = "editor") => {
    if (!window.api || selectedTaskId == null) {
      showNotification("Сначала выберите задачу.", "error");
      return;
    }
    try {
      const result = await window.api.exportLogs(selectedTaskId);
      if (result && result.canceled) return;
      setTaskNotice({ text: `Логи выгружены: ${result.filePath}`, tone: "success", source });
    } catch (error) {
      showNotification(error.message || String(error), "error");
    }
  };

  const exportInvites = async (source = "editor") => {
    if (!window.api || selectedTaskId == null) {
      showNotification("Сначала выберите задачу.", "error");
      return;
    }
    try {
      const result = await window.api.exportInvites(selectedTaskId);
      if (result && result.canceled) return;
      setTaskNotice({ text: `История инвайтов выгружена: ${result.filePath}`, tone: "success", source });
    } catch (error) {
      showNotification(error.message || String(error), "error");
    }
  };
  const exportProblemInvites = async (source = "editor") => {
    if (!window.api || selectedTaskId == null) {
      showNotification("Сначала выберите задачу.", "error");
      return;
    }
    try {
      const result = await window.api.exportProblemInvites(selectedTaskId);
      if (result.canceled) return;
      setTaskNotice({ text: `Проблемные инвайты выгружены: ${result.filePath}`, tone: "success", source });
    } catch (error) {
      showNotification(error.message || String(error), "error");
    }
  };
  const exportFallback = async (source = "editor") => {
    if (!window.api || selectedTaskId == null) {
      showNotification("Сначала выберите задачу.", "error");
      return;
    }
    try {
      const result = await window.api.exportFallback(selectedTaskId);
      if (result.canceled) return;
      setTaskNotice({ text: `Fallback выгружен: ${result.filePath}`, tone: "success", source });
    } catch (error) {
      showNotification(error.message || String(error), "error");
    }
  };
  const updateFallbackStatus = async (id, status) => {
    if (!window.api) return;
    try {
      await window.api.updateFallback({ id, status });
      if (selectedTaskId != null) {
        setFallbackList(await window.api.listFallback({ limit: 500, taskId: selectedTaskId }));
      }
    } catch (error) {
      showNotification(error.message || String(error), "error");
    }
  };
  const clearFallback = async (source = "editor") => {
    if (!window.api || selectedTaskId == null) {
      showNotification("Сначала выберите задачу.", "error");
      return;
    }
    try {
      await window.api.clearFallback(selectedTaskId);
      setFallbackList(await window.api.listFallback({ limit: 500, taskId: selectedTaskId }));
      setTaskNotice({ text: "Fallback очищен.", tone: "success", source });
    } catch (error) {
      showNotification(error.message || String(error), "error");
    }
  };

  const clearQueue = async (source = "editor") => {
    if (!window.api || selectedTaskId == null) {
      showNotification("Сначала выберите задачу.", "error");
      return;
    }
    try {
      await window.api.clearQueue(selectedTaskId);
      const data = await window.api.taskStatus(selectedTaskId);
      setTaskStatus(data);
      setTaskNotice({ text: "Очередь очищена.", tone: "success", source });
    } catch (error) {
      showNotification(error.message || String(error), "error");
    }
  };

  const clearDatabase = async () => {
    if (!window.api) {
      showNotification("Electron API недоступен. Откройте приложение в Electron.", "error");
      return;
    }
    if (!window.confirm("Удалить все данные из базы? Это действие нельзя отменить.")) {
      return;
    }
    try {
      await window.api.clearDatabase();
      showNotification("База очищена.", "info");
      setSelectedTaskId(null);
      setTaskForm(emptyTaskForm);
      setCompetitorText("");
      setSelectedAccountIds([]);
      setTaskAccountRoles({});
      setLogs([]);
      setInvites([]);
      setTaskStatus({
        running: false,
        queueCount: 0,
        dailyRemaining: 0,
        dailyUsed: 0,
        dailyLimit: 0,
        monitorInfo: { monitoring: false, accountId: 0, accountIds: [], groups: [], lastMessageAt: "", lastSource: "" }
      });
      await loadBase();
    } catch (error) {
      showNotification(error.message || String(error), "error");
    }
  };

  const resetSessions = async () => {
    if (!window.api) {
      showNotification("Electron API недоступен. Откройте приложение в Electron.", "error");
      return;
    }
    try {
      await window.api.resetSessions();
      showNotification("Сессии сброшены.", "info");
      setSelectedAccountIds([]);
      setTaskAccountRoles({});
      await loadBase();
    } catch (error) {
      showNotification(error.message || String(error), "error");
    }
  };

  const persistAccountRoles = async (next) => {
    if (!window.api || selectedTaskId == null) return;
    const rolePayload = Object.entries(next).map(([id, roles]) => ({
      accountId: Number(id),
      roleMonitor: Boolean(roles.monitor),
      roleInvite: Boolean(roles.invite),
      roleConfirm: Boolean(roles.confirm != null ? roles.confirm : roles.invite)
    }));
    await window.api.appendTaskAccounts({
      taskId: selectedTaskId,
      accountRoles: rolePayload
    });
    await window.api.addAccountEvent({
      accountId: 0,
      phone: "",
      action: "roles_changed",
      details: `задача ${selectedTaskId}: обновлены роли`
    });
    await loadAccountAssignments();
  };

  const updateAccountRole = (accountId, role, value) => {
    const next = { ...taskAccountRoles };
    const existing = next[accountId] || { monitor: false, invite: false, confirm: false };
    next[accountId] = { ...existing, [role]: value };
    if (!next[accountId].monitor && !next[accountId].invite && !next[accountId].confirm) {
      delete next[accountId];
    }
    const ids = Object.keys(next).map((id) => Number(id));
    setTaskAccountRoles(next);
    setSelectedAccountIds(ids);
    persistAccountRoles(next);
  };

  const setAccountRolesAll = (accountId, value) => {
    const next = { ...taskAccountRoles };
    if (value) {
      next[accountId] = { monitor: true, invite: true, confirm: true };
    } else {
      delete next[accountId];
    }
    const ids = Object.keys(next).map((id) => Number(id));
    setTaskAccountRoles(next);
    setSelectedAccountIds(ids);
    persistAccountRoles(next);
  };

  const applyRolePreset = (type) => {
    if (!hasSelectedTask) return;
    const availableIds = selectedAccountIds.length
      ? selectedAccountIds
      : accountBuckets.freeOrSelected.map((account) => account.id);
    if (!availableIds.length) {
      showNotification("Нет доступных аккаунтов для назначения.", "error");
      return;
    }
    const next = {};
    if (type === "all") {
      availableIds.forEach((id) => {
        next[id] = { monitor: true, invite: true, confirm: true };
      });
    } else if (type === "one") {
      const id = availableIds[0];
      next[id] = { monitor: true, invite: true, confirm: true };
    } else if (type === "split") {
      const monitorCount = Math.max(1, Number(taskForm.maxCompetitorBots || 1));
      const inviteCount = Math.max(1, Number(taskForm.maxOurBots || 1));
      const monitorIds = availableIds.slice(0, monitorCount);
      const inviteIds = availableIds.slice(monitorCount, monitorCount + inviteCount);
      const confirmCount = taskForm.separateConfirmRoles ? Math.max(1, Number(taskForm.maxConfirmBots || 1)) : 0;
      const confirmIds = taskForm.separateConfirmRoles
        ? availableIds.slice(monitorCount + inviteCount, monitorCount + inviteCount + confirmCount)
        : [];
      monitorIds.forEach((id) => {
        next[id] = { monitor: true, invite: false, confirm: false };
      });
      inviteIds.forEach((id) => {
        next[id] = { monitor: false, invite: true, confirm: true };
      });
      confirmIds.forEach((id) => {
        next[id] = { monitor: false, invite: false, confirm: true };
      });
      if (inviteIds.length < inviteCount) {
        showNotification("Не хватает аккаунтов для роли инвайта.", "error");
      }
      if (taskForm.separateConfirmRoles && confirmIds.length < confirmCount) {
        showNotification("Не хватает аккаунтов для роли подтверждения.", "error");
      }
    }
    const ids = Object.keys(next).map((id) => Number(id));
    setTaskAccountRoles(next);
    setSelectedAccountIds(ids);
    persistAccountRoles(next);
    const label = type === "one" ? "Один бот" : type === "split" ? "Разделить роли" : "Все роли";
    setTaskNotice({ text: `Пресет: ${label}`, tone: "success", source: "accounts" });
  };

  const applyTaskPreset = (type) => {
    if (!hasSelectedTask) {
      showNotification("Сначала выберите задачу.", "error");
      return;
    }
    if (!accounts.length) {
      showNotification("Нет доступных аккаунтов.", "error");
      return;
    }
    const masterId = accounts[0].id;
    const requiredCount = 3;
    const baseIds = selectedAccountIds.length >= requiredCount
      ? selectedAccountIds.slice()
      : accounts.map((account) => account.id);
    if (baseIds.length < 3) {
      showNotification("Для раздельных ролей желательно минимум 3 аккаунта (мониторинг/инвайт/подтверждение).", "info");
    }
    if (!baseIds.includes(masterId)) {
      baseIds.unshift(masterId);
    }
    const pool = baseIds.filter((id) => id !== masterId);
    const roleMap = {};
    const addRole = (id, role) => {
      if (!id) return;
      if (!roleMap[id]) roleMap[id] = { monitor: false, invite: false, confirm: false };
      roleMap[id][role] = true;
    };
    const takeFromPool = (count, used) => {
      const result = [];
      for (const id of pool) {
        if (result.length >= count) break;
        if (used.has(id)) continue;
        used.add(id);
        result.push(id);
      }
      return result;
    };
    const used = new Set();
    const monitorCount = 1;
    const inviteCount = 1;
    const confirmCount = 1;
    const monitorIds = takeFromPool(monitorCount, used);
    const confirmIds = takeFromPool(confirmCount, used);
    const inviteIds = masterId ? [masterId] : takeFromPool(inviteCount, used);
    if (monitorIds.length < monitorCount) addRole(masterId, "monitor");
    if (confirmIds.length < confirmCount) addRole(masterId, "confirm");
    monitorIds.forEach((id) => addRole(id, "monitor"));
    confirmIds.forEach((id) => addRole(id, "confirm"));
    inviteIds.forEach((id) => addRole(id, "invite"));

    const nextForm = sanitizeTaskForm({
      ...taskForm,
      warmupEnabled: true,
      historyLimit: 35,
      separateBotRoles: true,
      requireSameBotInBoth: false,
      maxCompetitorBots: 1,
      maxOurBots: 1,
      separateConfirmRoles: true,
      maxConfirmBots: 1,
      inviteViaAdmins: type === "admin",
      inviteAdminAnonymous: true,
      inviteAdminMasterId: masterId
    });
    const signature = buildPresetSignature(nextForm, roleMap);
    presetSignatureRef.current = signature;
    const label = type === "admin" ? "Автораспределение + Инвайт через админа" : "Автораспределение + Без админки";
    setTaskForm(nextForm);
    setTaskAccountRoles(roleMap);
    setSelectedAccountIds(Object.keys(roleMap).map((id) => Number(id)));
    persistAccountRoles(roleMap);
    if (window.api) {
      window.api.addAccountEvent({
        accountId: 0,
        phone: "",
        action: "preset_applied",
        details: `задача ${selectedTaskId}: ${label}`
      });
    }
    setTaskNotice({ text: `Пресет: ${label}`, tone: "success", source: "task" });
    setActivePreset(type);
  };

  const assignAccountsToTask = async (accountIds) => {
    if (!window.api || selectedTaskId == null) return;
    if (!accountIds.length) return;
    const nextRoles = { ...taskAccountRoles };
    accountIds.forEach((accountId) => {
      if (!nextRoles[accountId]) {
        nextRoles[accountId] = { monitor: true, invite: true, confirm: true };
      }
    });
    const rolePayload = Object.entries(nextRoles).map(([accountId, roles]) => ({
      accountId: Number(accountId),
      roleMonitor: Boolean(roles.monitor),
      roleInvite: Boolean(roles.invite),
      roleConfirm: Boolean(roles.confirm != null ? roles.confirm : roles.invite)
    }));
    const result = await window.api.appendTaskAccounts({
      taskId: selectedTaskId,
      accountRoles: rolePayload
    });
    if (result && result.ok) {
      setTaskAccountRoles(nextRoles);
      setSelectedAccountIds(Object.keys(nextRoles).map((id) => Number(id)));
      await loadAccountAssignments();
    }
  };

  const moveAccountToTask = async (accountId) => {
    if (!window.api || selectedTaskId == null) return;
    await assignAccountsToTask([accountId]);
    setTaskNotice({ text: "Аккаунт добавлен в задачу.", tone: "success", source: "accounts" });
  };

  const removeAccountFromTask = async (accountId) => {
    if (!window.api || selectedTaskId == null) return;
    const result = await window.api.removeTaskAccount({
      taskId: selectedTaskId,
      accountId
    });
    if (result && result.ok) {
      setTaskAccountRoles((prev) => {
        const next = { ...prev };
        delete next[accountId];
        setSelectedAccountIds(Object.keys(next).map((id) => Number(id)));
        return next;
      });
      await loadAccountAssignments();
      setTaskNotice({ text: "Аккаунт удален из задачи.", tone: "success", source: "accounts" });
    }
  };

  const startLogin = async () => {
    if (!window.api) {
      setLoginStatus("Electron API недоступен. Откройте приложение в Electron.");
      showNotification("Electron API недоступен. Откройте приложение в Electron.", "error");
      return;
    }
    if (selectedTaskId == null) {
      setLoginStatus("Сначала выберите задачу.");
      showNotification("Сначала выберите задачу.", "error");
      return;
    }
    setLoginStatus("Отправляем код...");
    showNotification("Отправляем код...", "info");
    try {
      const result = await window.api.startLogin({
        apiId: loginForm.apiId,
        apiHash: loginForm.apiHash,
        phone: loginForm.phone
      });
      setLoginId(result.loginId);
      setLoginStatus("Код отправлен. Введите код для входа.");
      showNotification("Код отправлен. Введите код для входа.", "success");
    } catch (error) {
      const message = error.message || String(error);
      setLoginStatus(message);
      showNotification(message, "error");
    }
  };

  const completeLogin = async () => {
    if (!window.api) {
      setLoginStatus("Electron API недоступен. Откройте приложение в Electron.");
      showNotification("Electron API недоступен. Откройте приложение в Electron.", "error");
      return;
    }
    if (selectedTaskId == null) {
      setLoginStatus("Сначала выберите задачу.");
      showNotification("Сначала выберите задачу.", "error");
      return;
    }
    setLoginStatus("Завершаем вход...");
    showNotification("Завершаем вход...", "info");
    const result = await window.api.completeLogin({
      loginId,
      code: loginForm.code,
      password: loginForm.password
    });

    if (result.ok) {
      setLoginStatus("Аккаунт добавлен.");
      setLoginId("");
      setLoginForm({ apiId: "", apiHash: "", phone: "", code: "", password: "" });
      await assignAccountsToTask([result.accountId].filter(Boolean));
      setAccounts(await window.api.listAccounts());
      return;
    }

    if (result.error === "DUPLICATE_ACCOUNT") {
      setLoginStatus("Аккаунт уже добавлен. Привязан к задаче.");
      setLoginId("");
      setLoginForm({ apiId: "", apiHash: "", phone: "", code: "", password: "" });
      await assignAccountsToTask([result.accountId].filter(Boolean));
      setAccounts(await window.api.listAccounts());
      return;
    }

    if (result.error === "PASSWORD_REQUIRED") {
      setLoginStatus("Нужен пароль 2FA. Введите пароль.");
      showNotification("Нужен пароль 2FA. Введите пароль.", "info");
      return;
    }

    setLoginStatus(result.error || "Ошибка входа");
    showNotification(result.error || "Ошибка входа", "error");
  };

  const importTdata = async () => {
    if (!window.api) {
      showNotification("Electron API недоступен. Откройте приложение в Electron.", "error");
      return;
    }
    showNotification("Импортируем tdata...", "info");
    setTdataLoading(true);
    try {
      const result = await window.api.importTdata({
        apiId: tdataForm.apiId,
        apiHash: tdataForm.apiHash,
        taskId: selectedTaskId || undefined
      });
      if (result && result.canceled) return;
      if (!result.ok) {
        const hint = explainTdataError(result.error || "");
        showNotification(hint ? `${result.error}. ${hint}` : (result.error || "Ошибка импорта tdata"), "error");
        return;
      }
      setTdataResult(result);
      const importedCount = (result.imported || []).length;
      const skippedCount = (result.skipped || []).length;
      const failedCount = (result.failed || []).length;
      const importedIds = (result.imported || []).map((item) => item.accountId).filter(Boolean);
      const skippedIds = (result.skipped || []).map((item) => item.accountId).filter(Boolean);
      if ((importedIds.length || skippedIds.length) && hasSelectedTask) {
        await assignAccountsToTask([...importedIds, ...skippedIds]);
      }
      if (result.authKeyDuplicatedCount) {
        setTdataNotice({
          text: `AUTH_KEY_DUPLICATED: ${result.authKeyDuplicatedCount}. Сессии сброшены после импорта.`,
          tone: "warn"
        });
      } else if (importedCount > 0) {
        setTdataNotice({ text: `Импортировано аккаунтов: ${importedCount}`, tone: "success" });
      } else if (skippedCount > 0 && failedCount === 0) {
        setTdataNotice({ text: `Пропущено дубликатов: ${skippedCount}`, tone: "success" });
      }
      if (failedCount > 0) {
        showNotification(`Не удалось импортировать: ${failedCount}`, "error");
      }
      setAccounts(await window.api.listAccounts());
    } catch (error) {
      showNotification(error.message || String(error), "error");
    } finally {
      setTdataLoading(false);
    }
  };

  const importInviteFile = async () => {
    if (!window.api) {
      showNotification("Electron API недоступен. Откройте приложение в Electron.", "error");
      return;
    }
    if (!hasSelectedTask) {
      showNotification("Сначала выберите задачу.", "error");
      return;
    }
    if (fileImportForm.onlyIds && !fileImportForm.sourceChat.trim()) {
      showNotification("Для файла только с ID нужен источник.", "error");
      return;
    }
    try {
      const result = await window.api.importInviteFile({
        taskId: selectedTaskId,
        onlyIds: fileImportForm.onlyIds,
        sourceChat: fileImportForm.sourceChat
      });
      if (result && result.canceled) return;
      if (!result.ok) {
        showNotification(result.error || "Ошибка импорта файла", "error");
        return;
      }
      setFileImportResult(result);
      if (result.importedCount) {
        setTaskNotice({ text: `Импортировано: ${result.importedCount}`, tone: "success", source: "sidebar" });
      }
    } catch (error) {
      showNotification(error.message || String(error), "error");
    }
  };

  return (
    <div className="app">
      <div className="top-row">
        <details className="card collapsible overview" open>
          <summary>
            <div className="row-header">
              <div className="row-header-main">
                <h2>Общий обзор</h2>
              </div>
              <div className="row-inline overview-actions">
                <div className="notification-bell" ref={bellRef}>
                  <button
                    type="button"
                    className={`icon-btn secondary ${notificationsOpen ? "active" : ""}`}
                    onClick={() => setNotificationsOpen((prev) => !prev)}
                  >
                    🔔
                  </button>
                  {notifications.length > 0 && (
                    <span className="bell-badge">{notifications.length}</span>
                  )}
                </div>
                <button
                  type="button"
                  className={`icon-btn secondary ${infoOpen ? "active" : ""}`}
                  onClick={() => setInfoOpen(true)}
                >
                  ℹ️
                </button>
              </div>
            </div>
          </summary>
          <div className="summary-grid">
            <div className="summary-card">
              <div className="live-label">Всего задач</div>
              <div className="summary-value">{taskSummary.total}</div>
            </div>
            <div className="summary-card">
              <div className="live-label">Запущено</div>
              <div className="summary-value">{taskSummary.running}</div>
            </div>
            <div className="summary-card">
              <div className="live-label">Сессии</div>
              <div className="summary-value">
                {globalStatus.connectedSessions}/{globalStatus.totalAccounts}
              </div>
            </div>
            <div className="summary-card">
              <div className="live-label">Очередь</div>
              <div className="summary-value">{taskSummary.queue}</div>
            </div>
            <div className="summary-card">
              <div className="live-label">Лимит в день</div>
              <div className="summary-value">{taskSummary.dailyUsed}/{taskSummary.dailyLimit}</div>
            </div>
          </div>
          <div className="row-header">
            <div className="row-header-main">
              <h3>Текущая задача</h3>
              <div className="status-caption">{selectedTaskName}</div>
            </div>
          </div>
          <div className="summary-grid compact">
            <div className="summary-card">
              <div className="live-label">Группы конкурентов</div>
              <div className="summary-value">{competitorGroups.length}</div>
            </div>
            <div className="summary-card">
              <div className="live-label">Аккаунты</div>
              <div className="summary-value">{assignedAccountCount}</div>
            </div>
            <div className="summary-card">
              <div className="live-label">Очередь</div>
              <div className="summary-value">{taskStatus.queueCount}</div>
            </div>
            <div className="summary-card">
              <div className="live-label">Лимит сегодня</div>
              <div className="summary-value">{taskStatus.dailyUsed}/{taskStatus.dailyLimit}</div>
            </div>
          </div>
        </details>

        <details className="card collapsible task-accounts" open>
          <summary>
            <div className="row-header">
              <div className="row-header-main">
                <h2>Аккаунты</h2>
                <div className="status-caption">Импорт общий</div>
              </div>
            </div>
          </summary>
          <div className="login-box">
            <div className="row-header">
              <h3>Добавить аккаунт по коду</h3>
              <button className="ghost" type="button" onClick={() => setManualLoginOpen(!manualLoginOpen)}>
                {manualLoginOpen ? "Свернуть" : "Развернуть"}
              </button>
            </div>
            {manualLoginOpen && (
              <div className="collapsible">
                {!hasSelectedTask && (
                  <div className="status-text compact">Выберите задачу, чтобы добавить аккаунт.</div>
                )}
                <div className="row">
                  <label>
                    <span className="label-line">API ID <span className="required">*</span></span>
                    <input
                      type="text"
                      value={loginForm.apiId}
                      onChange={(event) => setLoginForm({ ...loginForm, apiId: event.target.value })}
                    />
                  </label>
                  <label>
                    <span className="label-line">API Hash <span className="required">*</span></span>
                    <input
                      type="text"
                      value={loginForm.apiHash}
                      onChange={(event) => setLoginForm({ ...loginForm, apiHash: event.target.value })}
                    />
                  </label>
                </div>
                <label>
                  <span className="label-line">Телефон <span className="required">*</span></span>
                  <input
                    type="text"
                    value={loginForm.phone}
                    onChange={(event) => setLoginForm({ ...loginForm, phone: event.target.value })}
                  />
                </label>
                <div className="row">
                  <label>
                    <span className="label-line">Код <span className="required">*</span></span>
                    <input
                      type="text"
                      value={loginForm.code}
                      onChange={(event) => setLoginForm({ ...loginForm, code: event.target.value })}
                    />
                  </label>
                  <label>
                    <span className="label-line">2FA пароль <span className="optional">необязательно</span></span>
                    <input
                      type="password"
                      value={loginForm.password}
                      onChange={(event) => setLoginForm({ ...loginForm, password: event.target.value })}
                    />
                  </label>
                </div>
                <div className="row actions">
                  <button className="secondary" onClick={startLogin} disabled={!hasSelectedTask}>Отправить код</button>
                  <button className="primary" onClick={completeLogin} disabled={!hasSelectedTask}>Подтвердить</button>
                </div>
                {loginStatus && <div className="status-text">{loginStatus}</div>}
              </div>
            )}
          </div>

          <div className="login-box">
            <h3>Импорт из tdata</h3>
            <div className="status-text compact">
              Можно выбрать сразу несколько папок. Значения по умолчанию — API Telegram Desktop.
            </div>
            <div className="row">
              <label>
                <span className="label-line">API ID</span>
                <input
                  type="text"
                  value={tdataForm.apiId}
                  onChange={(event) => setTdataForm({ ...tdataForm, apiId: event.target.value })}
                />
              </label>
              <label>
                <span className="label-line">API Hash</span>
                <input
                  type="text"
                  value={tdataForm.apiHash}
                  onChange={(event) => setTdataForm({ ...tdataForm, apiHash: event.target.value })}
                />
              </label>
            </div>
            <button className="primary" onClick={importTdata} disabled={tdataLoading}>
              {tdataLoading ? "Импортируем..." : "Импортировать tdata"}
            </button>
            {tdataLoading && <div className="status-text">Идет импорт, это может занять несколько секунд.</div>}
            {tdataResult && (
              <div className="tdata-report">
                <div>Импортировано: {(tdataResult.imported || []).length}</div>
                <div>Пропущено: {(tdataResult.skipped || []).length}</div>
                <div>Ошибок: {(tdataResult.failed || []).length}</div>
                {(tdataResult.failed || []).length > 0 && (
                  <div className="tdata-errors">
                    {tdataResult.failed.map((item, index) => (
                      <div key={`${item.path}-${index}`} className="tdata-error-row">
                        <div className="tdata-error-path">{item.path}</div>
                        <div className="tdata-error-text">{item.error}</div>
                        {explainTdataError(item.error) && (
                          <div className="tdata-error-hint">{explainTdataError(item.error)}</div>
                        )}
                      </div>
                    ))}
                  </div>
                )}
              </div>
            )}
          </div>
        </details>
      </div>
      {notificationsOpen && (
        <div className="modal-overlay" onClick={() => setNotificationsOpen(false)}>
          <div className="modal" ref={notificationsModalRef} onClick={(event) => event.stopPropagation()}>
            <div className="row-header">
              <h2>Уведомления</h2>
              <button type="button" className="ghost" onClick={() => setNotifications([])}>
                Очистить
              </button>
            </div>
            <div className="bell-filters">
              <button
                type="button"
                className={`chip ${notificationFilter === "all" ? "active" : ""}`}
                onClick={() => setNotificationFilter("all")}
              >
                Все
              </button>
              <button
                type="button"
                className={`chip ${notificationFilter === "error" ? "active" : ""}`}
                onClick={() => setNotificationFilter("error")}
              >
                Ошибки
              </button>
              <button
                type="button"
                className={`chip ${notificationFilter === "info" ? "active" : ""}`}
                onClick={() => setNotificationFilter("info")}
              >
                Инфо
              </button>
            </div>
            {filteredNotifications.length === 0 && <div className="empty">Пока пусто.</div>}
            {filteredNotifications.map((item) => (
              <div key={item.id} className={`notice ${item.tone}`}>
                {item.text}{item.count > 1 ? ` (x${item.count})` : ""}
              </div>
            ))}
          </div>
        </div>
      )}
      {infoOpen && (
        <div className="modal-overlay" onClick={() => setInfoOpen(false)}>
          <div className="modal" onClick={(event) => event.stopPropagation()}>
            <div className="row-header">
              <h2>Как пользоваться</h2>
              <button className="ghost" type="button" onClick={() => setInfoOpen(false)}>Закрыть</button>
            </div>
            <div className="info-tabs">
              <button
                type="button"
                className={`tab ${infoTab === "usage" ? "active" : ""}`}
                onClick={() => setInfoTab("usage")}
              >
                Быстрый старт
              </button>
              <button
                type="button"
                className={`tab ${infoTab === "features" ? "active" : ""}`}
                onClick={() => setInfoTab("features")}
              >
                Функции
              </button>
              <button
                type="button"
                className={`tab ${infoTab === "strategies" ? "active" : ""}`}
                onClick={() => setInfoTab("strategies")}
              >
                Стратегии
              </button>
              <button
                type="button"
                className={`tab ${infoTab === "limits" ? "active" : ""}`}
                onClick={() => setInfoTab("limits")}
              >
                Ограничения Telegram
              </button>
            </div>
            {infoTab === "usage" && (
              <>
                <ol className="help-list">
                  <li>Создайте задачу: название, наша группа и группы конкурентов.</li>
                  <li>Импортируйте аккаунты (tdata) и назначьте роли для задачи.</li>
                  <li>Нажмите “Собрать историю”, чтобы добавить авторов из последних сообщений.</li>
                  <li>Нажмите “Запустить”, чтобы отслеживать новые сообщения и приглашать по расписанию.</li>
                  <li>Следите за статусом, логами, событиями и очередью.</li>
                </ol>
                <p className="help-note">
                  “Собрать историю” добавляет в очередь авторов старых сообщений. Без этого учитываются только новые сообщения.
                </p>
              </>
            )}
            {infoTab === "features" && (
              <div className="help-note">
                <strong>Функции и режимы:</strong>
                <div>1) Мониторинг: отслеживает новые сообщения в чатах конкурентов и добавляет авторов в очередь.</div>
                <div>2) Инвайт по расписанию: приглашает с интервалом и дневным лимитом.</div>
                <div>3) Инвайт через админов: временно выдает право “Приглашать”, затем снимает.</div>
                <div>4) Инвайт в чаты с флудом: распределяет инвайт через цепочку выдачи прав.</div>
                <div>5) Циклический обход конкурентов: переключает мониторинг по списку групп.</div>
                <div>6) Парсинг участников: пытается получить список участников для закрытых чатов.</div>
                <div>7) Прогрев лимита: плавно увеличивает дневной лимит по дням.</div>
                <div>8) Fallback‑лист: собирает проблемные инвайты и предлагает маршруты.</div>
              </div>
            )}
            {infoTab === "strategies" && (
              <div className="help-note">
                <strong>Стратегии инвайта:</strong>
                <div>1) access_hash из сообщения.</div>
                <div>2) Резолв через участников/источник.</div>
                <div>3) Инвайт по username (если доступен).</div>
                <div>4) Инвайт через админов (если включен).</div>
                <div>5) Отправка инвайт‑ссылки (если включено).</div>
                <div>После успешного инвайта выполняется проверка фактического вступления.</div>
              </div>
            )}
            {infoTab === "limits" && (
              <div className="help-note">
                <strong>Особенности Telegram и ошибки:</strong>
                <div>1) AUTH_KEY_DUPLICATED: tdata уже используется — выйдите из аккаунта на других устройствах и пересоберите tdata.</div>
                <div>2) CHAT_ADMIN_REQUIRED: аккаунт должен быть админом с правом “добавлять участников”.</div>
                <div>3) USER_ID_INVALID: скрытые/анонимные авторы, удаленные аккаунты — инвайт возможен только по username.</div>
                <div>4) USER_NOT_MUTUAL_CONTACT: ограничения Telegram/приватность пользователя — помогает инвайт‑ссылка или другой аккаунт.</div>
                <div>5) USER_PRIVACY_RESTRICTED: пользователь запретил инвайты в чаты.</div>
                <div>6) FLOOD/PEER_FLOOD: снизить лимиты, увеличить интервалы, распределить нагрузку.</div>
                <div>7) CHANNEL_PRIVATE/INVITE_HASH_INVALID: ссылка недействительна или чат приватный.</div>
              </div>
            )}
          </div>
        </div>
      )}

          {toasts.length > 0 && (
        <div className="toast-stack">
          {toasts.map((toast) => (
            <div key={toast.id} className={`toast ${toast.tone || "info"}`}>
              <span>{toast.text}{toast.count > 1 ? ` (x${toast.count})` : ""}</span>
              <button
                type="button"
                className="ghost"
                onClick={() => {
                  setToasts((prev) => prev.filter((item) => item.id !== toast.id));
                  if (toast.key && toastTimers.current.has(toast.key)) {
                    clearTimeout(toastTimers.current.get(toast.key));
                    toastTimers.current.delete(toast.key);
                  }
                }}
              >
                ✕
              </button>
            </div>
          ))}
        </div>
      )}

      <div className="layout">
        <aside className="sidebar left">
          <section className="card sticky">
            <div className="row-header">
              <h2>Задачи</h2>
              <button className="ghost" type="button" onClick={createTask}>Новая задача</button>
            </div>
            <div className="task-controls">
              <div className="task-search">
                <input
                  type="text"
                  value={taskSearch}
                  onChange={(event) => setTaskSearch(event.target.value)}
                  placeholder="Поиск по названию или ссылке"
                />
              </div>
              <div className="task-filters">
                <button
                  type="button"
                  className={`chip ${taskFilter === "all" ? "active" : ""}`}
                  onClick={() => setTaskFilter("all")}
                >
                  Все
                </button>
                <button
                  type="button"
                  className={`chip ${taskFilter === "running" ? "active" : ""}`}
                  onClick={() => setTaskFilter("running")}
                >
                  Запущены
                </button>
                <button
                  type="button"
                  className={`chip ${taskFilter === "stopped" ? "active" : ""}`}
                  onClick={() => setTaskFilter("stopped")}
                >
                  Остановлены
                </button>
              </div>
              <div className="row-inline">
                <label className="select-inline">
                  <span>Сортировка</span>
                  <select value={taskSort} onChange={(event) => setTaskSort(event.target.value)}>
                    <option value="activity">Активные сверху</option>
                    <option value="queue">По очереди</option>
                    <option value="limit">По лимиту</option>
                    <option value="lastMessage">По последнему сообщению</option>
                    <option value="id">По ID</option>
                  </select>
                </label>
              </div>
            </div>
            <div className="task-list">
              {filteredTasks.length === 0 && <div className="empty">Совпадений нет.</div>}
              {filteredTasks.map((task) => {
                const status = taskStatusMap[task.id];
                const statusLabel = status ? (status.running ? "Запущено" : "Остановлено") : "—";
                const statusClass = status ? (status.running ? "ok" : "off") : "off";
                const unconfirmedCount = status ? Number(status.unconfirmedCount || 0) : 0;
                const queueLabel = status ? `Очередь: ${status.queueCount}` : "Очередь: —";
                const dailyLabel = status ? `Лимит: ${status.dailyUsed}/${status.dailyLimit}` : "Лимит: —";
                const cycleLabel = status && status.running ? `Цикл: ${formatCountdown(status.nextRunAt)}` : "Цикл: —";
                const lastMessageRaw = status && status.monitorInfo && status.monitorInfo.lastMessageAt
                  ? status.monitorInfo.lastMessageAt
                  : "";
                const lastMessage = formatTimestamp(lastMessageRaw);
                const lastSource = status && status.monitorInfo && status.monitorInfo.lastSource
                  ? status.monitorInfo.lastSource
                  : "—";
                const monitoring = Boolean(status && status.monitorInfo && status.monitorInfo.monitoring);
                const monitorAccountIds = status && status.monitorInfo && status.monitorInfo.accountIds
                  ? status.monitorInfo.accountIds
                  : (status && status.monitorInfo && status.monitorInfo.accountId ? [status.monitorInfo.accountId] : []);
                const monitorLabels = monitorAccountIds
                  .map((id) => {
                    const account = accountById.get(id);
                    return account ? formatAccountLabel(account) : String(id);
                  })
                  .filter(Boolean);
                const monitorLabel = monitorLabels.length
                  ? (monitorLabels.length > 2 ? `${monitorLabels.length} аккаунта` : monitorLabels.join(", "))
                  : "—";
                const tooltip = [
                  `Статус: ${statusLabel}`,
                  `Очередь: ${status ? status.queueCount : "—"}`,
                  `Лимит: ${status ? `${status.dailyUsed}/${status.dailyLimit}` : "—"}`,
                  `Мониторинг: ${monitoring ? "активен" : "нет"}`,
                  `Мониторит: ${monitorLabel}`,
                  `Последнее: ${lastMessage}`,
                  `Источник: ${lastSource}`
                ].join(" | ");
                return (
                  <button
                    key={task.id}
                    type="button"
                    className={`task-item ${selectedTaskId === task.id ? "active" : ""}`}
                    onClick={() => selectTask(task.id)}
                    title={tooltip}
                  >
                    <div className="task-info">
                      <div className="task-title-row">
                        <div className="task-title">{task.name || `Задача #${task.id}`}</div>
                        <div className="task-badge-row">
                          <div className={`task-badge ${statusClass}`}>{statusLabel}</div>
                          {unconfirmedCount > 0 && (
                            <div className="task-badge warn" title={`Не подтверждено: ${unconfirmedCount}`}>
                              Не подтверждено: {unconfirmedCount}
                            </div>
                          )}
                        </div>
                      </div>
                      <div className="task-meta-row">
                        <span className="task-meta">{queueLabel}</span>
                        <span className="task-meta">{dailyLabel}</span>
                        <span className="task-meta">{cycleLabel}</span>
                      </div>
                    </div>
                  </button>
                );
              })}
            </div>
            <div className="sidebar-actions">
              <button className="danger" type="button" onClick={deleteTask} disabled={!hasSelectedTask}>Удалить задачу</button>
            </div>
          </section>
        </aside>

        <div className="main">
          <section className="card action-bar">
            <div className="row-header">
              <div className="row-header-main">
                <h2>Быстрые действия</h2>
                <div className="status-caption">
                  Задача: {selectedTaskName}
                  {autosaveNote && <span className="autosave-note">{autosaveNote}</span>}
                </div>
              </div>
              <div className={`status-pill ${taskStatus.running ? "ok" : "off"}`}>
                {taskStatus.running ? "Запущено" : "Остановлено"}
              </div>
            </div>
            <div className="row-inline action-buttons">
              <button className="secondary" onClick={() => saveTask("bar")} disabled={!canSaveTask}>Сохранить</button>
              <button className="secondary" onClick={() => parseHistory("bar")} disabled={!hasSelectedTask}>Собрать историю</button>
              <button className="secondary" onClick={() => checkAccess("bar")} disabled={!hasSelectedTask}>Проверить доступ</button>
              <button className="secondary" onClick={() => checkInviteAccess("bar")} disabled={!hasSelectedTask}>Проверить права</button>
              <button className="secondary" onClick={() => refreshMembership("bar")} disabled={!hasSelectedTask}>Проверить участие</button>
              <button className="secondary" onClick={() => clearQueue("bar")} disabled={!hasSelectedTask}>Очистить очередь</button>
              {taskStatus.running ? (
                <button className="danger cta" onClick={() => stopTask("bar")} disabled={!hasSelectedTask || taskActionLoading}>
                  <span className="cta-icon">[■]</span>
                  {taskActionLoading ? "Остановка..." : "Остановить"}
                </button>
              ) : (
                <button className="primary cta" onClick={() => startTask("bar")} disabled={!hasSelectedTask || taskActionLoading}>
                  <span className="cta-icon">[>]</span>
                  {taskActionLoading ? "Запуск..." : "Запустить"}
                </button>
              )}
            </div>
            <div className="row-inline global-actions">
              <button
                type="button"
                className="secondary"
                onClick={startAllTasks}
                disabled={!tasks.length}
              >
                Запустить все
              </button>
              <button
                type="button"
                className="secondary"
                onClick={stopAllTasks}
                disabled={!tasks.length}
              >
                Остановить все
              </button>
              <button type="button" className="danger" onClick={clearDatabase}>
                Очистить БД
              </button>
              <button type="button" className="danger ghost" onClick={resetSessions}>
                Сбросить сессии
              </button>
            </div>
          </section>
          <div className="tabs sticky-tabs">
            <button
              type="button"
              className={`tab ${activeTab === "task" ? "active" : ""}`}
              onClick={() => setActiveTab("task")}
            >
              Задача
            </button>
            <button
              type="button"
              className={`tab ${activeTab === "status" ? "active" : ""}`}
              onClick={() => setActiveTab("status")}
            >
              Статус
            </button>
            <button
              type="button"
              className={`tab ${activeTab === "accounts" ? "active" : ""}`}
              onClick={() => setActiveTab("accounts")}
            >
              Аккаунты
            </button>
            <button
              type="button"
              className={`tab ${activeTab === "logs" ? "active" : ""}`}
              onClick={() => setActiveTab("logs")}
            >
              Логи
            </button>
            <button
              type="button"
              className={`tab ${activeTab === "events" ? "active" : ""}`}
              onClick={() => setActiveTab("events")}
            >
              События
            </button>
            <button
              type="button"
              className={`tab ${activeTab === "settings" ? "active" : ""}`}
              onClick={() => setActiveTab("settings")}
            >
              Настройки
            </button>
          </div>

          {activeTab === "task" && (
            <div className="task-columns">
              <details className="card collapsible task-editor" open>
                <summary>
                  <div className="row-header">
                    <h2>Настройки задачи</h2>
                    <div className="status-caption">Для: {selectedTaskName}</div>
                  </div>
                </summary>
                <div className="section-title">Основное</div>
                <div className="row-inline">
                  <div className="status-caption">Пресеты:</div>
                  <button
                    className={`secondary ${activePreset === "admin" ? "active" : ""}`}
                    type="button"
                    onClick={() => applyTaskPreset("admin")}
                  >
                    Автораспределение + Инвайт через админа
                  </button>
                  <button
                    className={`secondary ${activePreset === "no_admin" ? "active" : ""}`}
                    type="button"
                    onClick={() => applyTaskPreset("no_admin")}
                  >
                    Автораспределение + Без админки
                  </button>
                  <span className="status-caption">
                    Мастер-админ: {taskForm.inviteAdminMasterId ? formatAccountLabel(accountById.get(taskForm.inviteAdminMasterId)) : "не выбран"}
                  </span>
                </div>
                <div className="row">
                  <label>
                    <span className="label-line">Название задачи <span className="required">*</span></span>
                    <input
                      type="text"
                      value={taskForm.name}
                      onChange={(event) => setTaskForm({ ...taskForm, name: event.target.value })}
                      placeholder="Например, Таиланд"
                    />
                  </label>
                  <label>
                    <span className="label-line">Наша группа <span className="required">*</span></span>
                    <input
                      type="text"
                      value={taskForm.ourGroup}
                      onChange={(event) => setTaskForm({ ...taskForm, ourGroup: event.target.value })}
                      placeholder="https://t.me/..."
                    />
                  </label>
                </div>
                <label>
                  <span className="label-line">Группы конкурентов <span className="required">*</span></span>
                  <textarea
                    rows="6"
                    value={competitorText}
                    onChange={(event) => setCompetitorText(event.target.value)}
                    placeholder="Каждая группа с новой строки"
                  />
                </label>
                <div className="task-editor-grid">
                  <details className="section" open>
                    <summary className="section-title">Базовые настройки</summary>
                    <details className="section" open>
                      <summary className="section-title">Роли ботов и вступление</summary>
                      <div className="toggle-row">
                        <label className="checkbox">
                          <input
                            type="checkbox"
                            checked={Boolean(taskForm.autoJoinCompetitors)}
                            onChange={(event) => setTaskForm({ ...taskForm, autoJoinCompetitors: event.target.checked })}
                          />
                          Автодобавление аккаунтов в группы конкурентов
                        </label>
                        <label className="checkbox">
                          <input
                            type="checkbox"
                            checked={Boolean(taskForm.autoJoinOurGroup)}
                            onChange={(event) => setTaskForm({ ...taskForm, autoJoinOurGroup: event.target.checked })}
                          />
                          Автодобавление аккаунтов в нашу группу
                        </label>
                      </div>
                      <div className="toggle-row">
                        <label className="checkbox">
                          <input
                            type="radio"
                            name="roleMode"
                            checked={roleMode === "shared"}
                            onChange={() => applyRoleMode("shared")}
                          />
                          Без разделения (любые аккаунты могут приглашать)
                        </label>
                        <label className="checkbox">
                          <input
                            type="radio"
                            name="roleMode"
                            checked={roleMode === "split"}
                            onChange={() => applyRoleMode("split")}
                          />
                          Разделить роли (конкуренты и наша группа разными аккаунтами)
                        </label>
                        <label className="checkbox">
                          <input
                            type="radio"
                            name="roleMode"
                            checked={roleMode === "same"}
                            onChange={() => applyRoleMode("same")}
                          />
                          Один и тот же бот в конкурентах и нашей группе
                        </label>
                      </div>
                      <div className="status-text compact">
                        Режим “один и тот же бот” нужен, когда аккаунт должен быть в конкурентах и в нашей группе для инвайта.
                      </div>
                    </details>
                    <details className="section" open>
                      <summary className="section-title">Интервалы и лимиты</summary>
                      <div className="row">
                      <label>
                        <span className="label-line">Мин. интервал (мин) <span className="required">*</span></span>
                        <input
                          type="number"
                          min="1"
                          value={taskForm.minIntervalMinutes || ""}
                          onChange={(event) => {
                            const value = event.target.value;
                            setTaskForm({ ...taskForm, minIntervalMinutes: value === "" ? "" : Number(value) });
                          }}
                          onBlur={() => setTaskForm(normalizeIntervals(taskForm))}
                        />
                      </label>
                      <label>
                        <span className="label-line">Макс. интервал (мин) <span className="required">*</span></span>
                        <input
                          type="number"
                          min="1"
                          value={taskForm.maxIntervalMinutes || ""}
                          onChange={(event) => {
                            const value = event.target.value;
                            setTaskForm({ ...taskForm, maxIntervalMinutes: value === "" ? "" : Number(value) });
                          }}
                          onBlur={() => setTaskForm(normalizeIntervals(taskForm))}
                        />
                      </label>
                      <label>
                        <span className="label-line">Лимит в день <span className="required">*</span></span>
                        <input
                          type="number"
                          min="1"
                          value={taskForm.dailyLimit === "" ? "" : taskForm.dailyLimit}
                          onChange={(event) => {
                            const value = event.target.value;
                            setTaskForm({ ...taskForm, dailyLimit: value === "" ? "" : Number(value) });
                          }}
                          onBlur={() => {
                            const value = Number(taskForm.dailyLimit);
                            setTaskForm({ ...taskForm, dailyLimit: Number.isFinite(value) && value > 0 ? value : 1 });
                          }}
                        />
                      </label>
                      <label>
                        <span className="label-line">История сообщений (шт) <span className="required">*</span></span>
                        <input
                          type="number"
                          min="1"
                          value={taskForm.historyLimit === 0 ? "" : taskForm.historyLimit}
                          onChange={(event) => {
                            const value = event.target.value;
                            setTaskForm({ ...taskForm, historyLimit: value === "" ? "" : Number(value) });
                          }}
                          onBlur={() => {
                            const value = Number(taskForm.historyLimit);
                            setTaskForm({ ...taskForm, historyLimit: Number.isFinite(value) && value > 0 ? value : 1 });
                          }}
                        />
                      </label>
                      <label>
                        <span className="label-line">Инвайтов за цикл <span className="required">*</span></span>
                        <input
                          type="number"
                          min="1"
                          value={taskForm.maxInvitesPerCycle === "" ? "" : taskForm.maxInvitesPerCycle}
                          onChange={(event) => {
                            const value = event.target.value;
                            setTaskForm({ ...taskForm, maxInvitesPerCycle: value === "" ? "" : Number(value) });
                          }}
                          onBlur={() => {
                            const value = Number(taskForm.maxInvitesPerCycle);
                            setTaskForm({ ...taskForm, maxInvitesPerCycle: Number.isFinite(value) && value > 0 ? value : 1 });
                          }}
                        />
                      </label>
                      <label className="checkbox">
                        <input
                          type="checkbox"
                          checked={Boolean(taskForm.warmupEnabled)}
                          onChange={(event) => setTaskForm({ ...taskForm, warmupEnabled: event.target.checked })}
                        />
                        Разогрев лимита
                        <span
                          className="hint"
                          title="График: дни 1–3 — 1/д; 4–7 — 2/д; 8–12 — 3/д; 13–18 — 4/д; 19–25 — 5/д; 26–33 — 6/д; с 34-го — 7/д. Итоговый лимит не превышает дневной лимит задачи."
                        >
                          Плавно увеличивает дневной лимит по дням.
                        </span>
                        <span className="hint">Нужен для “прогрева” новых аккаунтов и снижения риска флуда.</span>
                        <span className="hint">График: 1/д ×3, 2/д ×4, 3/д ×5, 4/д ×6, 5/д ×7, 6/д ×8, далее 7/д.</span>
                      </label>
                      {taskForm.warmupEnabled && (
                        <>
                          <label>
                            <span className="label-line">Стартовый лимит</span>
                            <input
                              type="number"
                              min="1"
                              value={taskForm.warmupStartLimit === "" ? "" : taskForm.warmupStartLimit}
                              onChange={(event) => {
                                const value = event.target.value;
                                setTaskForm({ ...taskForm, warmupStartLimit: value === "" ? "" : Number(value) });
                              }}
                              onBlur={() => {
                                const value = Number(taskForm.warmupStartLimit);
                                setTaskForm({ ...taskForm, warmupStartLimit: Number.isFinite(value) && value > 0 ? value : 1 });
                              }}
                            />
                          </label>
                          <label>
                            <span className="label-line">Прирост в день</span>
                            <input
                              type="number"
                              min="0"
                              value={taskForm.warmupDailyIncrease === "" ? "" : taskForm.warmupDailyIncrease}
                              onChange={(event) => {
                                const value = event.target.value;
                                setTaskForm({ ...taskForm, warmupDailyIncrease: value === "" ? "" : Number(value) });
                              }}
                              onBlur={() => {
                                const value = Number(taskForm.warmupDailyIncrease);
                                setTaskForm({ ...taskForm, warmupDailyIncrease: Number.isFinite(value) && value >= 0 ? value : 0 });
                              }}
                            />
                          </label>
                        </>
                      )}
                    </div>
                    </details>
                  </details>
                  <details className="section">
                    <summary className="section-title">Расширенные настройки</summary>
                    <details className="section">
                      <summary className="section-title">Инвайт через админов</summary>
                      <div className="admin-invite-grid">
                        <label className="checkbox admin-invite-toggle">
                          <input
                            type="checkbox"
                            checked={Boolean(taskForm.inviteViaAdmins)}
                            onChange={(event) => setTaskForm({ ...taskForm, inviteViaAdmins: event.target.checked })}
                          />
                          Инвайтить через админов
                          <span className="hint">
                            Временно назначаем пользователя админом с правом “Приглашать”, затем снимаем права.
                          </span>
                        </label>
                        <label className="checkbox admin-invite-toggle">
                          <input
                            type="checkbox"
                            checked={Boolean(taskForm.inviteLinkOnFail)}
                            onChange={(event) => setTaskForm({ ...taskForm, inviteLinkOnFail: event.target.checked })}
                          />
                          Отправлять ссылку при USER_NOT_MUTUAL_CONTACT
                          <span className="hint">
                            Отправляет пользователю ссылку из поля “Наша группа”.
                          </span>
                        </label>
                        <label className="admin-invite-master">
                          <span className="label-line">Главный аккаунт</span>
                          <div className="input-row">
                            <select
                              value={taskForm.inviteAdminMasterId || ""}
                              onChange={(event) => setTaskForm({ ...taskForm, inviteAdminMasterId: Number(event.target.value) || 0 })}
                              disabled={!taskForm.inviteViaAdmins}
                            >
                              <option value="">Не выбран</option>
                              {accounts.map((account) => (
                                <option key={`master-${account.id}`} value={account.id}>
                                  {formatAccountLabel(account)}
                                </option>
                              ))}
                            </select>
                            <button
                              type="button"
                              className="secondary"
                              disabled={!taskForm.inviteViaAdmins || !taskForm.inviteAdminMasterId}
                              onClick={async () => {
                                const account = accountById.get(taskForm.inviteAdminMasterId);
                                const username = account && account.username ? `@${account.username}` : "";
                                if (!username) {
                                  showNotification("У выбранного аккаунта нет username.", "error");
                                  return;
                                }
                                const ok = await copyToClipboard(username);
                                showNotification(ok ? `Скопировано: ${username}` : "Не удалось скопировать.", ok ? "success" : "error");
                              }}
                            >
                              Копировать username
                            </button>
                          </div>
                          <span className="hint">
                            Этот аккаунт должен быть админом в целевой группе и уметь выдавать права другим.
                          </span>
                        </label>
                        <label className="checkbox admin-invite-toggle">
                          <input
                            type="checkbox"
                            checked={Boolean(taskForm.inviteAdminAnonymous)}
                            onChange={(event) => setTaskForm({ ...taskForm, inviteAdminAnonymous: event.target.checked })}
                            disabled={!taskForm.inviteViaAdmins}
                          />
                          Делать админов анонимными
                          <span className="hint">
                            Мастер-админ назначает остальных админами с анонимностью и минимальными правами.
                          </span>
                        </label>
                        <label className="checkbox admin-invite-flood">
                          <input
                            type="checkbox"
                            checked={Boolean(taskForm.inviteAdminAllowFlood)}
                            onChange={(event) => setTaskForm({ ...taskForm, inviteAdminAllowFlood: event.target.checked })}
                            disabled={!taskForm.inviteViaAdmins}
                          />
                          Инвайтить в чаты с флудом
                          <span className="hint">
                            Использует выдачу прав между аккаунтами, если Telegram ограничивает инвайтинг.
                          </span>
                        </label>
                      </div>
                    </details>
                    <details className="section">
                      <summary className="section-title">Распределение ботов</summary>
                      <div className="row">
                        {roleMode === "same" ? (
                          <label>
                            <span className="label-line">Ботов в обеих группах</span>
                            <input
                              type="number"
                              min="1"
                              value={taskForm.maxCompetitorBots === "" ? "" : taskForm.maxCompetitorBots}
                              onChange={(event) => {
                                const value = event.target.value;
                                if (value === "") {
                                  setTaskForm({ ...taskForm, maxCompetitorBots: "", maxOurBots: "" });
                                  return;
                                }
                                const nextValue = Number(value);
                                const nextForm = { ...taskForm, maxCompetitorBots: nextValue, maxOurBots: nextValue };
                                setTaskForm(sanitizeTaskForm(nextForm));
                              }}
                              onBlur={() => {
                                const value = Number(taskForm.maxCompetitorBots);
                                const normalized = Number.isFinite(value) && value > 0 ? value : 1;
                                setTaskForm(sanitizeTaskForm({ ...taskForm, maxCompetitorBots: normalized, maxOurBots: normalized }));
                              }}
                            />
                            <span className="hint">Одинаковое количество для конкурентов и нашей группы.</span>
                          </label>
                        ) : (
                          <>
                            <label>
                              <span className="label-line">Ботов в конкурентах</span>
                              <input
                                type="number"
                                min="1"
                                value={taskForm.maxCompetitorBots === "" ? "" : taskForm.maxCompetitorBots}
                                onChange={(event) => {
                                  const value = event.target.value;
                                  setTaskForm({
                                    ...taskForm,
                                    maxCompetitorBots: value === "" ? "" : Number(value)
                                  });
                                }}
                                onBlur={() => {
                                  const value = Number(taskForm.maxCompetitorBots);
                                  setTaskForm({ ...taskForm, maxCompetitorBots: Number.isFinite(value) && value > 0 ? value : 1 });
                                }}
                              />
                              <span className="hint">Используется для авто-вступления в группы конкурентов.</span>
                            </label>
                            <label>
                              <span className="label-line">Ботов в нашей группе</span>
                              <input
                                type="number"
                                min="1"
                                value={taskForm.maxOurBots === "" ? "" : taskForm.maxOurBots}
                                onChange={(event) => {
                                  const value = event.target.value;
                                  setTaskForm({ ...taskForm, maxOurBots: value === "" ? "" : Number(value) });
                                }}
                                onBlur={() => {
                                  const value = Number(taskForm.maxOurBots);
                                  setTaskForm({ ...taskForm, maxOurBots: Number.isFinite(value) && value > 0 ? value : 1 });
                                }}
                              />
                              <span className="hint">
                                {roleMode === "split" ? "Ограничивает аккаунты, которые будут приглашать." : "Ограничивает количество инвайтящих аккаунтов."}
                              </span>
                            </label>
                          </>
                        )}
                      </div>
                      <div className="row">
                        <label className="checkbox">
                          <input
                            type="checkbox"
                            checked={Boolean(taskForm.separateConfirmRoles)}
                            onChange={(event) => setTaskForm({ ...taskForm, separateConfirmRoles: event.target.checked })}
                            disabled={!taskForm.separateBotRoles}
                          />
                          Подтверждение отдельными аккаунтами
                          <span className="hint">
                            Если включено, проверку участия выполняют отдельные аккаунты, не совпадающие с инвайтерами.
                            Ручные чекбоксы ролей в разделе “Аккаунты” имеют приоритет над авто‑распределением.
                          </span>
                        </label>
                        <label>
                          <span className="label-line">Ботов для подтверждения</span>
                          <input
                            type="number"
                            min="1"
                            value={taskForm.maxConfirmBots === "" ? "" : taskForm.maxConfirmBots}
                            onChange={(event) => {
                              const value = event.target.value;
                              setTaskForm({ ...taskForm, maxConfirmBots: value === "" ? "" : Number(value) });
                            }}
                            onBlur={() => {
                              const value = Number(taskForm.maxConfirmBots);
                              const normalized = Number.isFinite(value) && value > 0 ? value : 1;
                              setTaskForm({ ...taskForm, maxConfirmBots: normalized });
                            }}
                            disabled={!taskForm.separateBotRoles || !taskForm.separateConfirmRoles}
                          />
                          <span className="hint">Используется при авто-разделении ролей.</span>
                        </label>
                      </div>
                    </details>
                    <details className="section">
                      <summary className="section-title">Импорт аудитории</summary>
                      <div className="row">
                        <label className="checkbox">
                          <input
                            type="checkbox"
                            checked={Boolean(taskForm.parseParticipants)}
                            onChange={(event) => setTaskForm({ ...taskForm, parseParticipants: event.target.checked })}
                          />
                          Собирать участников чатов конкурентов
                          <span className="hint">
                            Используется для закрытых участников и полного списка аудитории.
                          </span>
                        </label>
                        <label className="checkbox">
                          <input
                            type="checkbox"
                            checked={Boolean(taskForm.cycleCompetitors)}
                            onChange={(event) => setTaskForm({ ...taskForm, cycleCompetitors: event.target.checked })}
                          />
                          Циклически обходить конкурентов
                          <span className="hint">
                            Мониторинг и сбор будут переключаться по группам по очереди.
                          </span>
                        </label>
                        <label className="checkbox">
                          <input
                            type="checkbox"
                            checked={Boolean(fileImportForm.onlyIds)}
                            onChange={(event) => setFileImportForm({ ...fileImportForm, onlyIds: event.target.checked })}
                          />
                          В файле только ID
                          <span className="hint">Если включено — нужен источник (чат), из которого брались ID.</span>
                        </label>
                        <label>
                          <span className="label-line">Источник для ID</span>
                          <input
                            type="text"
                            placeholder="https://t.me/чат"
                            value={fileImportForm.sourceChat}
                            onChange={(event) => setFileImportForm({ ...fileImportForm, sourceChat: event.target.value })}
                            disabled={!fileImportForm.onlyIds}
                          />
                          <span className="hint">Используется для резолва ID при инвайте.</span>
                        </label>
                      </div>
                      <div className="row-inline">
                        <button className="secondary" type="button" onClick={importInviteFile}>
                          Импортировать файл
                        </button>
                        {fileImportResult && (
                          <div className="status-text compact">
                            Импортировано: {fileImportResult.importedCount} · Пропущено: {fileImportResult.skippedCount} · Ошибок: {fileImportResult.failed.length}
                          </div>
                        )}
                      </div>
                      {fileImportResult && fileImportResult.failed.length > 0 && (
                        <div className="access-list">
                          {fileImportResult.failed.map((item, index) => (
                            <div key={`${item.path}-${index}`} className="access-row fail">
                              <div className="access-title">{item.path}</div>
                              <div className="access-error">{item.error}</div>
                            </div>
                          ))}
                        </div>
                      )}
                    </details>
                  </details>
                  <details className="section">
                    <summary className="section-title">Экспертные настройки</summary>
                    <details className="section">
                      <summary className="section-title">Безопасность</summary>
                      <div className="toggle-row">
                        <label className="checkbox">
                          <input
                            type="checkbox"
                            checked={Boolean(taskForm.randomAccounts)}
                            onChange={(event) => setTaskForm({ ...taskForm, randomAccounts: event.target.checked })}
                          />
                          Случайный выбор аккаунтов
                          <span className="hint">Инвайты распределяются случайно между доступными аккаунтами.</span>
                        </label>
                        <label className="checkbox">
                          <input
                            type="checkbox"
                            checked={Boolean(taskForm.multiAccountsPerRun)}
                            onChange={(event) => setTaskForm({ ...taskForm, multiAccountsPerRun: event.target.checked })}
                          />
                          Несколько аккаунтов за цикл
                          <span className="hint">Если выключено — в каждом цикле используется один аккаунт.</span>
                        </label>
                        <label className="checkbox">
                          <input
                            type="checkbox"
                            checked={Boolean(taskForm.retryOnFail)}
                            onChange={(event) => setTaskForm({ ...taskForm, retryOnFail: event.target.checked })}
                          />
                          Повторять при ошибке
                          <span className="hint">Повторяем до 2 раз при неудачном инвайте.</span>
                        </label>
                        <label className="checkbox">
                          <input
                            type="checkbox"
                            checked={Boolean(taskForm.stopOnBlocked)}
                            onChange={(event) => setTaskForm({ ...taskForm, stopOnBlocked: event.target.checked })}
                          />
                          Останавливать при блокировках
                          <span className="hint">Останавливает задачу, если % ограниченных аккаунтов выше порога.</span>
                        </label>
                        <label className="checkbox">
                          <input
                            type="checkbox"
                            checked={Boolean(taskForm.allowStartWithoutInviteRights)}
                            onChange={(event) => setTaskForm({ ...taskForm, allowStartWithoutInviteRights: event.target.checked })}
                          />
                          Разрешать запуск без прав инвайта
                          <span className="hint">Полезно, если вы выдаёте админов после автодобавления.</span>
                        </label>
                        <label className="checkbox">
                          <input
                            type="checkbox"
                            checked={Boolean(taskForm.useWatcherInviteNoUsername)}
                            onChange={(event) => setTaskForm({ ...taskForm, useWatcherInviteNoUsername: event.target.checked })}
                          />
                          Инвайт через наблюдателя, если нет username
                          <span className="hint">
                            Правило 1: сначала резолвим @username в сессии инвайтера — участие в группе конкурентов не требуется.
                          </span>
                          <span className="hint">
                            Правило 2: если username нет — инвайтим наблюдателем, потому что access_hash валиден только в его сессии.
                          </span>
                        </label>
                      </div>
                      <div className="row">
                        <label>
                          <span className="label-line">Остановить при блоке, %</span>
                          <input
                            type="number"
                            min="1"
                            value={taskForm.stopBlockedPercent === "" ? "" : taskForm.stopBlockedPercent}
                            onChange={(event) => {
                              const value = event.target.value;
                              setTaskForm({ ...taskForm, stopBlockedPercent: value === "" ? "" : Number(value) });
                            }}
                            onBlur={() => {
                              const value = Number(taskForm.stopBlockedPercent);
                              setTaskForm({ ...taskForm, stopBlockedPercent: Number.isFinite(value) && value > 0 ? value : 1 });
                            }}
                            disabled={!taskForm.stopOnBlocked}
                          />
                        </label>
                      </div>
                    </details>
                    <details className="section">
                      <summary className="section-title">Ошибки аккаунтов</summary>
                      {criticalErrorAccounts.length === 0 && (
                        <div className="status-text compact">Ошибок нет.</div>
                      )}
                      {criticalErrorAccounts.length > 0 && (
                        <div className="access-list">
                          {criticalErrorAccounts.map((account) => (
                            <div key={account.id} className="access-row fail">
                              <div className="access-title">{formatAccountLabel(account)}</div>
                              <div className="access-error">{account.last_error || "Ошибка сессии"}</div>
                            </div>
                          ))}
                        </div>
                      )}
                    </details>
                    <label>
                      <span className="label-line">Заметки</span>
                      <textarea
                        rows="2"
                        value={taskForm.notes}
                        onChange={(event) => setTaskForm({ ...taskForm, notes: event.target.value })}
                      />
                    </label>
                  </details>
                </div>
              </details>

            </div>
          )}
          {activeTab === "status" && (
            <section className="card live">
              <div className="row-header">
                <h2>Статус задачи</h2>
                <div className="status-caption">Для: {selectedTaskName}</div>
              </div>
              <div className="status-text compact">Отображаются данные только для выбранной задачи.</div>
              <div className="status-grid primary">
                <div>
                  <div className="live-label">Состояние</div>
                  <div className="live-value">{taskStatus.running ? "Запущено" : "Остановлено"}</div>
                </div>
                <div>
                  <div className="live-label">Мониторинг</div>
                  <div className="live-value">{taskStatus.monitorInfo && taskStatus.monitorInfo.monitoring ? "Активно" : "Нет"}</div>
                </div>
                <div>
                  <div className="live-label">Очередь</div>
                  <div className="live-value">{taskStatus.queueCount}</div>
                </div>
                <div>
                  <div className="live-label">Лимит в день</div>
                  <div className="live-value">{taskStatus.dailyUsed}/{taskStatus.dailyLimit}</div>
                </div>
                <div>
                  <div className="live-label">Следующий цикл</div>
                  <div className="live-value">{formatCountdown(taskStatus.nextRunAt)}</div>
                </div>
                <div>
                  <div className="live-label">Последнее сообщение</div>
                  <div className="live-value">{formatTimestamp(taskStatus.monitorInfo ? taskStatus.monitorInfo.lastMessageAt : "")}</div>
                </div>
              </div>
              <details className="status-details">
                <summary>Подробности мониторинга</summary>
                <div className="status-grid secondary">
                  <div>
                    <div className="live-label">Группы в мониторинге</div>
                    <div className="live-value">{taskStatus.monitorInfo && taskStatus.monitorInfo.groups ? taskStatus.monitorInfo.groups.length : 0}</div>
                  </div>
                  <div>
                    <div className="live-label">Наблюдает</div>
                    <div className="live-value">
                      {(() => {
                        const monitorIds = taskStatus.monitorInfo && taskStatus.monitorInfo.accountIds
                          ? taskStatus.monitorInfo.accountIds
                          : (taskStatus.monitorInfo && taskStatus.monitorInfo.accountId ? [taskStatus.monitorInfo.accountId] : []);
                        return monitorIds.length;
                      })()}
                    </div>
                  </div>
                  <div>
                    <div className="live-label">Источник</div>
                    <div className="live-value wrap">{taskStatus.monitorInfo && taskStatus.monitorInfo.lastSource ? taskStatus.monitorInfo.lastSource : "—"}</div>
                  </div>
                  <div>
                    <div className="live-label">Осталось сегодня</div>
                    <div className="live-value">{taskStatus.dailyRemaining}</div>
                  </div>
                  {taskStatus.cycleCompetitors && (
                    <div>
                      <div className="live-label">Текущий конкурент</div>
                      <div className="live-value wrap">
                        {competitorGroups.length
                          ? competitorGroups[taskStatus.competitorCursor % competitorGroups.length]
                          : "—"}
                      </div>
                    </div>
                  )}
                </div>
              </details>
              {hasSelectedTask && (
                <div className="status-text">
                  {taskStatus.readiness && (
                    <div className={`notice inline ${taskStatus.readiness.ok ? "success" : "warn"}`}>
                      Готовность: {taskStatus.readiness.ok ? "Да" : "Нет"}
                      {!taskStatus.readiness.ok && taskStatus.readiness.reasons && (
                        <div className="pre-line">{taskStatus.readiness.reasons.join("\n")}</div>
                      )}
                    </div>
                  )}
                  {taskStatus.lastStopReason && (
                    <div className="notice inline warn">
                      Последняя остановка: {taskStatus.lastStopReason}
                      {taskStatus.lastStopAt ? ` (${formatTimestamp(taskStatus.lastStopAt)})` : ""}
                    </div>
                  )}
                  {roleSummary.invite.length > 0 && (
                    <div className="status-text compact">
                      Лимиты аккаунтов: {roleSummary.invite.map((id) => {
                        const account = accountById.get(id);
                        const stats = accountStatsMap.get(id);
                        if (!account || !stats) return null;
                        const label = formatAccountLabel(account) || id;
                        const remaining = stats.remainingToday != null ? stats.remainingToday : "—";
                        return `${label} (${remaining})`;
                      }).filter(Boolean).join(", ")}
                    </div>
                  )}
                  {taskStatus.warnings && taskStatus.warnings.length > 0 && (
                    <div className="notice inline warn">
                      {taskStatus.warnings.map((warning, index) => (
                        <div key={`warn-${index}`}>{warning}</div>
                      ))}
                    </div>
                  )}
                  {roleSummary.monitor.length === 0 && (
                    <div className="notice inline warn">Нет аккаунтов с ролью мониторинга.</div>
                  )}
                  {roleSummary.invite.length === 0 && (
                    <div className="notice inline warn">Нет аккаунтов с ролью инвайта.</div>
                  )}
                </div>
              )}
              {groupVisibility.length > 0 && (
                <div className="status-text">
                  {groupVisibility.some((item) => item.hidden) && (
                    <div className="notice inline warn">
                      В некоторых группах скрыты участники — инвайт возможен только по username.
                      <div className="visibility-list">
                        {groupVisibility
                          .filter((item) => item.hidden)
                          .map((item) => (
                            <div key={item.source} className="visibility-item">
                              {item.title ? `${item.title} (${item.source})` : item.source}
                            </div>
                          ))}
                      </div>
                    </div>
                  )}
                </div>
              )}
            </section>
          )}

          {activeTab === "accounts" && (
            <Suspense fallback={<div className="card">Загрузка...</div>}>
              <AccountsTab
                accounts={accounts}
                accountStatsMap={accountStatsMap}
                settings={settings}
                membershipStatus={membershipStatus}
                assignedAccountMap={assignedAccountMap}
                accountBuckets={accountBuckets}
                filterFreeAccounts={filterFreeAccounts}
                selectedAccountIds={selectedAccountIds}
                taskAccountRoles={taskAccountRoles}
                hasSelectedTask={hasSelectedTask}
                inviteAdminMasterId={taskForm.inviteAdminMasterId}
                refreshMembership={refreshMembership}
                refreshIdentity={refreshIdentity}
                formatAccountStatus={formatAccountStatus}
                formatAccountLabel={formatAccountLabel}
                resetCooldown={resetCooldown}
                deleteAccount={deleteAccount}
                updateAccountRole={updateAccountRole}
                setAccountRolesAll={setAccountRolesAll}
                applyRolePreset={applyRolePreset}
                removeAccountFromTask={removeAccountFromTask}
                moveAccountToTask={moveAccountToTask}
              />
            </Suspense>
          )}

          {activeTab === "logs" && (
            <Suspense fallback={<div className="card">Загрузка...</div>}>
              <LogsTab
                logsTab={logsTab}
                setLogsTab={setLogsTab}
                hasSelectedTask={hasSelectedTask}
                exportLogs={exportLogs}
                clearLogs={clearLogs}
                exportInvites={exportInvites}
                exportProblemInvites={exportProblemInvites}
                exportFallback={exportFallback}
                updateFallbackStatus={updateFallbackStatus}
                clearFallback={clearFallback}
                clearInvites={clearInvites}
                logSearch={logSearch}
                setLogSearch={setLogSearch}
                logPage={logPage}
                setLogPage={setLogPage}
                logPageCount={logPageCount}
                pagedLogs={pagedLogs}
                inviteSearch={inviteSearch}
                setInviteSearch={setInviteSearch}
                invitePage={invitePage}
                setInvitePage={setInvitePage}
                invitePageCount={invitePageCount}
                inviteFilter={inviteFilter}
                setInviteFilter={setInviteFilter}
                pagedInvites={pagedInvites}
                fallbackSearch={fallbackSearch}
                setFallbackSearch={setFallbackSearch}
                fallbackPage={fallbackPage}
                setFallbackPage={setFallbackPage}
                fallbackPageCount={fallbackPageCount}
                pagedFallback={pagedFallback}
                auditSearch={auditSearch}
                setAuditSearch={setAuditSearch}
                auditPage={auditPage}
                setAuditPage={setAuditPage}
                auditPageCount={auditPageCount}
                pagedAudit={pagedAudit}
                formatTimestamp={formatTimestamp}
                explainInviteError={explainInviteError}
                accountById={accountById}
                formatAccountLabel={formatAccountLabel}
                expandedInviteId={expandedInviteId}
                setExpandedInviteId={setExpandedInviteId}
                inviteStats={inviteStats}
                selectedTask={selectedTask}
                taskAccountRoles={taskAccountRoles}
                accessStatus={accessStatus}
                inviteAccessStatus={inviteAccessStatus}
                selectedTaskName={selectedTaskName}
                roleSummary={roleSummary}
                mutualContactDiagnostics={mutualContactDiagnostics}
              />
            </Suspense>
          )}

          {activeTab === "events" && (
            <Suspense fallback={<div className="card">Загрузка...</div>}>
              <EventsTab
                accountEvents={accountEvents}
                formatTimestamp={formatTimestamp}
                onClearEvents={clearAccountEvents}
                accountById={accountById}
                formatAccountLabel={formatAccountLabel}
              />
            </Suspense>
          )}

          {activeTab === "settings" && (
            <Suspense fallback={<div className="card">Загрузка...</div>}>
              <SettingsTab
                settings={settings}
                onSettingsChange={onSettingsChange}
                saveSettings={saveSettings}
              />
            </Suspense>
          )}
        </div>
      </div>
    </div>
  );
}
