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: 5,
  maxIntervalMinutes: 10,
  dailyLimit: 100,
  historyLimit: 100,
  maxCompetitorBots: 1,
  maxOurBots: 1,
  randomAccounts: false,
  multiAccountsPerRun: false,
  retryOnFail: true,
  autoJoinCompetitors: true,
  autoJoinOurGroup: true,
  separateBotRoles: false,
  requireSameBotInBoth: true,
  stopOnBlocked: true,
  stopBlockedPercent: 25,
  notes: "",
  enabled: true,
  autoAssignAccounts: true
};

const normalizeTask = (row) => ({
  id: row.id,
  name: row.name || "",
  ourGroup: row.our_group || "",
  minIntervalMinutes: Number(row.min_interval_minutes || 5),
  maxIntervalMinutes: Number(row.max_interval_minutes || 10),
  dailyLimit: Number(row.daily_limit || 100),
  historyLimit: Number(row.history_limit || 200),
  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),
  stopOnBlocked: Boolean(row.stop_on_blocked),
  stopBlockedPercent: Number(row.stop_blocked_percent || 25),
  notes: row.notes || "",
  enabled: Boolean(row.enabled),
  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;
  }
  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 [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 [taskStatus, setTaskStatus] = useState({
    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 },
    warnings: []
  });
  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 [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 [taskActionLoading, setTaskActionLoading] = useState(false);
  const [loginId, setLoginId] = useState("");
  const [loginStatus, setLoginStatus] = useState("");
  const [taskNotice, setTaskNotice] = useState(null);
  const [settingsNotice, setSettingsNotice] = useState(null);
  const [tdataNotice, setTdataNotice] = useState(null);
  const [notification, setNotification] = useState(null);
  const [notifications, setNotifications] = useState([]);
  const [notificationsOpen, setNotificationsOpen] = useState(false);
  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 [activeTab, setActiveTab] = useState("task");
  const [logsTab, setLogsTab] = useState("logs");
  const [logSearch, setLogSearch] = useState("");
  const [inviteSearch, setInviteSearch] = useState("");
  const [logPage, setLogPage] = useState(1);
  const [invitePage, setInvitePage] = useState(1);
  const [inviteFilter, setInviteFilter] = useState("all");
  const [taskSort, setTaskSort] = useState("activity");
  const [sidebarExpanded, setSidebarExpanded] = useState(true);
  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 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 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 accountStatsMap = useMemo(() => {
    const map = new Map();
    (accountStats || []).forEach((item) => {
      map.set(item.id, item);
    });
    return map;
  }, [accountStats]);
  const roleSummary = useMemo(() => {
    const monitor = [];
    const invite = [];
    Object.entries(taskAccountRoles).forEach(([id, roles]) => {
      const accountId = Number(id);
      if (roles.monitor) monitor.push(accountId);
      if (roles.invite) invite.push(accountId);
    });
    return { monitor, invite };
  }, [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]);
    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)
      });
      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) => {
    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 }
      });
      return;
    }
    const details = await window.api.getTask(taskId);
    if (!details) return;
    setTaskForm(sanitizeTaskForm({ ...emptyTaskForm, ...normalizeTask(details.task) }));
    setCompetitorText((details.competitors || []).join("\n"));
    const roleMap = {};
    if (details.accountRoles && details.accountRoles.length) {
      details.accountRoles.forEach((item) => {
        roleMap[item.accountId] = {
          monitor: Boolean(item.roleMonitor),
          invite: Boolean(item.roleInvite)
        };
      });
    } else {
      (details.accountIds || []).forEach((accountId) => {
        roleMap[accountId] = { monitor: true, invite: 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 }));
    setGroupVisibility([]);
    setTaskStatus(await window.api.taskStatus(taskId));
  };

  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);
    if (selectedTaskId != null) {
      checkAccess("auto", true);
      checkInviteAccess("auto", true);
    }
  }, [selectedTaskId]);

  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 }));
      } 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);
      if (taskForm.maxCompetitorBots !== nextCompetitors || taskForm.maxOurBots !== nextOur) {
        setTaskForm((prev) => ({
          ...prev,
          maxCompetitorBots: nextCompetitors,
          maxOurBots: nextOur
        }));
      }
    }
  }, [
    hasSelectedTask,
    roleIntersectionCount,
    roleSummary.monitor.length,
    roleSummary.invite.length,
    taskForm.requireSameBotInBoth,
    taskForm.separateBotRoles
  ]);

  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 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 === "USER_ALREADY_PARTICIPANT") {
      return "Пользователь уже состоит в целевой группе.";
    }
    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) => {
    if (tone === "success") return;
    const entry = { text, tone, id: Date.now() };
    setNotification(entry);
    setNotifications((prev) => [entry, ...prev].slice(0, 6));
  };

  useEffect(() => {
    if (!notification) return undefined;
    const timer = setTimeout(() => {
      setNotification(null);
    }, 6000);
    return () => clearTimeout(timer);
  }, [notification]);

  useEffect(() => {
    const handleClickOutside = (event) => {
      if (!notificationsOpen) return;
      if (!bellRef.current) 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 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 === "success") return false;
      if (inviteFilter === "skipped" && !invite.skippedReason) return false;
      const text = [
        invite.invitedAt,
        invite.userId,
        invite.username,
        invite.sourceChat,
        invite.accountPhone,
        invite.watcherPhone,
        invite.strategy,
        invite.strategyMeta,
        invite.error,
        invite.skippedReason
      ]
        .join(" ")
        .toLowerCase();
      if (!query) return true;
      return text.includes(query);
    });
  }, [invites, deferredInviteSearch, inviteFilter]);

  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 logPageSize = 20;
  const invitePageSize = 20;
  const logPageCount = Math.max(1, Math.ceil(filteredLogs.length / logPageSize));
  const invitePageCount = Math.max(1, Math.ceil(filteredInvites.length / invitePageSize));
  const pagedLogs = filteredLogs.slice((logPage - 1) * logPageSize, logPage * logPageSize);
  const pagedInvites = filteredInvites.slice((invitePage - 1) * invitePageSize, invitePage * invitePageSize);

  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]);

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

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

  const saveTask = async (source = "editor") => {
    if (!window.api) {
      showNotification("Electron API недоступен. Откройте приложение в Electron.", "error");
      return;
    }
    try {
      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 };
        });
        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 };
        });
        setTaskAccountRoles(accountRolesMap);
        setSelectedAccountIds(accountIds);
        if (accountIds.length) {
          setTaskNotice({ text: `Автоназначены аккаунты: ${accountIds.length}`, tone: "success", source });
        }
      }
      if (!accountIds.length) {
        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);
        if (!hasMonitor) {
          showNotification("Нужен хотя бы один аккаунт с ролью мониторинга.", "error");
          return;
        }
        if (!hasInvite) {
          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))
            : 1;
        if (accountIds.length < requiredAccounts) {
          showNotification(`Нужно минимум ${requiredAccounts} аккаунтов для выбранного режима.`, "error");
          return;
        }
      }
      const accountRoles = Object.entries(accountRolesMap).map(([id, roles]) => ({
        accountId: Number(id),
        roleMonitor: Boolean(roles.monitor),
        roleInvite: Boolean(roles.invite)
      }));
      const result = await window.api.saveTask({
        task: nextForm,
        competitors: competitorGroups,
        accountIds,
        accountRoles
      });
      if (result.ok) {
        setTaskNotice({ text: "Задача сохранена.", tone: "success", source });
        await loadTasks();
        await loadAccountAssignments();
        setSelectedTaskId(result.taskId);
      } else {
        showNotification(result.error || "Не удалось сохранить задачу", "error");
      }
    } catch (error) {
      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");
    try {
      const result = await window.api.startTaskById(selectedTaskId);
      if (result && result.ok) {
        setTaskNotice({ text: "Запущено.", tone: "success", source });
        if (result.warnings && result.warnings.length) {
          showNotification(`Предупреждения: ${result.warnings.join(" | ")}`, "info");
        }
      } else {
        showNotification(result.error || "Не удалось запустить", "error");
      }
    } catch (error) {
      const message = 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");
    try {
      await window.api.stopTaskById(selectedTaskId);
      setTaskNotice({ text: "Остановлено.", tone: "success", source });
    } catch (error) {
      const message = 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 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 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)
    }));
    await window.api.appendTaskAccounts({
      taskId: selectedTaskId,
      accountRoles: rolePayload
    });
    await loadAccountAssignments();
  };

  const updateAccountRole = (accountId, role, value) => {
    const next = { ...taskAccountRoles };
    const existing = next[accountId] || { monitor: false, invite: false };
    next[accountId] = { ...existing, [role]: value };
    if (!next[accountId].monitor && !next[accountId].invite) {
      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 };
    } 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 };
      });
    } else if (type === "one") {
      const id = availableIds[0];
      next[id] = { monitor: true, invite: 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);
      monitorIds.forEach((id) => {
        next[id] = { monitor: true, invite: false };
      });
      inviteIds.forEach((id) => {
        next[id] = { monitor: false, invite: true };
      });
      if (inviteIds.length < inviteCount) {
        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 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 };
      }
    });
    const rolePayload = Object.entries(nextRoles).map(([accountId, roles]) => ({
      accountId: Number(accountId),
      roleMonitor: Boolean(roles.monitor),
      roleInvite: Boolean(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 (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);
    }
  };

  return (
    <div className="app">
      <header className="header">
        <div>
          <h1>Автоматизация инвайтов</h1>
          <p>Парсинг сообщений и приглашения в целевые группы.</p>
        </div>
        <div className="header-actions">
          <div className="task-switcher">
            <div className="task-switcher-label">Текущая задача</div>
            <select
              value={selectedTaskId || ""}
              onChange={(event) => selectTask(event.target.value ? Number(event.target.value) : null)}
            >
              <option value="">Задача не выбрана</option>
              {tasks.map((task) => (
                <option key={task.id} value={task.id}>
                  {task.name || `Задача #${task.id}`}
                </option>
              ))}
            </select>
          </div>
          <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>
            )}
            {notificationsOpen && (
              <div className="bell-panel">
                <div className="bell-header">
                  <span>Уведомления</span>
                  <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}
                  </div>
                ))}
              </div>
            )}
          </div>
          <button
            type="button"
            className={`icon-btn secondary ${infoOpen ? "active" : ""}`}
            onClick={() => setInfoOpen(true)}
          >
            ℹ️
          </button>
        </div>
      </header>
      {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>
            <ol className="help-list">
              <li>Создайте задачу: название, наша группа и группы конкурентов.</li>
              <li>Выберите аккаунты для задачи и сохраните.</li>
              <li>Нажмите “Собрать историю”, чтобы добавить авторов из последних сообщений.</li>
              <li>Нажмите “Запустить”, чтобы отслеживать новые сообщения и приглашать по расписанию.</li>
              <li>Создавайте несколько задач для разных групп и контролируйте их по списку.</li>
            </ol>
            <p className="help-note">
              “Собрать историю” добавляет в очередь пользователей, которые писали ранее. Без этого будут учитываться только новые сообщения.
            </p>
          </div>
        </div>
      )}

      <div className="top-row">
        <section className="card task-accounts">
          <div className="row-header">
            <h2>Аккаунты задачи</h2>
          </div>
          <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>}
            {tdataNotice && (
              <div className={`notice inline ${tdataNotice.tone}`}>{tdataNotice.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>
        </section>

        <section className="card overview">
          <div className="row-header">
            <div className="row-header-main">
              <h2>Общий обзор</h2>
              <div className="status-caption">Все задачи</div>
            </div>
            <div className="row-inline">
              <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>
            </div>
          </div>
          <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>
        </section>
      </div>

      {notification && (
        <div className={`notice ${notification.tone}`}>
          {notification.text}
        </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>
            {hasSelectedTask && (
              <div className="task-summary">
                <div className="task-summary-title">Выбрано: {selectedTaskName}</div>
                <div className="task-summary-row">
                  <span>Очередь: {taskStatus.queueCount}</span>
                  <span>Лимит: {taskStatus.dailyUsed}/{taskStatus.dailyLimit}</span>
                  <span>Статус: {taskStatus.running ? "Запущено" : "Остановлено"}</span>
                </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 queueLabel = status ? `Очередь: ${status.queueCount}` : "Очередь: —";
                const dailyLabel = status ? `Лимит: ${status.dailyUsed}/${status.dailyLimit}` : "Лимит: —";
                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 ? (account.phone || account.user_id || String(id)) : 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 ${statusClass}`}>{statusLabel}</div>
                      </div>
                      <div className="task-meta-row">
                        <span className="task-meta">{queueLabel}</span>
                        <span className="task-meta">{dailyLabel}</span>
                      </div>
                    </div>
                  </button>
                );
              })}
            </div>
            <div className="sidebar-actions">
              <button className="danger" type="button" onClick={deleteTask} disabled={!hasSelectedTask}>Удалить задачу</button>
            </div>
            {taskNotice && taskNotice.source === "tasks" && (
              <div className={`notice inline ${taskNotice.tone}`}>{taskNotice.text}</div>
            )}
          </section>
        </aside>

        <div className="main">
          <div className="tabs">
            <button
              type="button"
              className={`tab ${activeTab === "task" ? "active" : ""}`}
              onClick={() => setActiveTab("task")}
            >
              Задача
            </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" && (
            <>
              <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="live-grid">
                  <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.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">{formatTimestamp(taskStatus.monitorInfo ? taskStatus.monitorInfo.lastMessageAt : "")}</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.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">{taskStatus.dailyRemaining}</div>
                  </div>
                  <div>
                    <div className="live-label">Следующий цикл</div>
                    <div className="live-value">{formatCountdown(taskStatus.nextRunAt)}</div>
                  </div>
                </div>
                <div className="status-actions">
                  {taskStatus.running ? (
                    <button className="danger" onClick={() => stopTask("status")} disabled={!hasSelectedTask || taskActionLoading}>
                      {taskActionLoading ? "Остановка..." : "Остановить"}
                    </button>
                  ) : (
                    <button className="primary" onClick={() => startTask("status")} disabled={!hasSelectedTask || taskActionLoading}>
                      {taskActionLoading ? "Запуск..." : "Запустить"}
                    </button>
                  )}
                </div>
                {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>
                    )}
                    {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 = account.phone || account.user_id || 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>

              <section className="card task-editor">
                <div className="row-header">
                  <h2>Настройки задачи</h2>
                  <div className="status-caption">Для: {selectedTaskName}</div>
                </div>
                <div className="row-inline">
                  <button className="secondary task-toolbar" onClick={() => saveTask("editor")} disabled={!canSaveTask}>💾 Сохранить</button>
                  <button className="secondary task-toolbar" onClick={() => parseHistory("editor")} disabled={!hasSelectedTask}>📥 История</button>
                  <button className="secondary task-toolbar" onClick={() => checkAccess("editor")} disabled={!hasSelectedTask}>🔎 Доступ</button>
                  <button className="secondary task-toolbar" onClick={() => checkInviteAccess("editor")} disabled={!hasSelectedTask}>✅ Права инвайта</button>
                  <button className="secondary task-toolbar" onClick={() => refreshMembership("editor")} disabled={!hasSelectedTask}>👥 Участие</button>
                </div>
                {taskNotice && taskNotice.source === "editor" && (
                  <div className={`notice inline ${taskNotice.tone}`}>{taskNotice.text}</div>
                )}
                <div className="section-title">Основное</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>
                <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}
                        onChange={(event) => setTaskForm({ ...taskForm, dailyLimit: Number(event.target.value) })}
                      />
                    </label>
                    <label>
                      <span className="label-line">История сообщений (шт) <span className="required">*</span></span>
                      <input
                        type="number"
                        min="1"
                        value={taskForm.historyLimit}
                        onChange={(event) => setTaskForm({ ...taskForm, historyLimit: Number(event.target.value) })}
                      />
                    </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}
                          onChange={(event) => {
                            const nextValue = Number(event.target.value);
                            const nextForm = { ...taskForm, maxCompetitorBots: nextValue, maxOurBots: nextValue };
                            setTaskForm(sanitizeTaskForm(nextForm));
                          }}
                        />
                        <span className="hint">Одинаковое количество для конкурентов и нашей группы.</span>
                      </label>
                    ) : (
                      <>
                        <label>
                          <span className="label-line">Ботов в конкурентах</span>
                          <input
                            type="number"
                            min="1"
                            value={taskForm.maxCompetitorBots}
                            onChange={(event) => {
                              const nextValue = Number(event.target.value);
                              const nextForm = { ...taskForm, maxCompetitorBots: nextValue };
                              setTaskForm(nextForm);
                            }}
                          />
                          <span className="hint">Используется для авто-вступления в группы конкурентов.</span>
                        </label>
                        <label>
                          <span className="label-line">Ботов в нашей группе</span>
                          <input
                            type="number"
                            min="1"
                            value={taskForm.maxOurBots}
                            onChange={(event) => setTaskForm({ ...taskForm, maxOurBots: Number(event.target.value) })}
                          />
                          <span className="hint">
                            {roleMode === "split" ? "Ограничивает аккаунты, которые будут приглашать." : "Ограничивает количество инвайтящих аккаунтов."}
                          </span>
                        </label>
                      </>
                    )}
                  </div>
                </details>
                <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>
                  </div>
                  <div className="row">
                    <label>
                      <span className="label-line">Остановить при блоке, %</span>
                      <input
                        type="number"
                        min="1"
                        value={taskForm.stopBlockedPercent}
                        onChange={(event) => setTaskForm({ ...taskForm, stopBlockedPercent: Number(event.target.value) })}
                        disabled={!taskForm.stopOnBlocked}
                      />
                    </label>
                  </div>
                  <label>
                  <span className="label-line">Заметки</span>
                  <textarea
                    rows="2"
                    value={taskForm.notes}
                    onChange={(event) => setTaskForm({ ...taskForm, notes: event.target.value })}
                  />
                </label>
                </details>
              </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}
                  taskNotice={taskNotice}
                  refreshMembership={refreshMembership}
                  refreshIdentity={refreshIdentity}
                  formatAccountStatus={formatAccountStatus}
                  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}
                taskNotice={taskNotice}
                hasSelectedTask={hasSelectedTask}
                exportLogs={exportLogs}
                clearLogs={clearLogs}
                exportInvites={exportInvites}
                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}
                formatTimestamp={formatTimestamp}
                explainInviteError={explainInviteError}
                expandedInviteId={expandedInviteId}
                setExpandedInviteId={setExpandedInviteId}
              />
            </Suspense>
          )}

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

          {activeTab === "settings" && (
            <Suspense fallback={<div className="card">Загрузка...</div>}>
              <SettingsTab
                settings={settings}
                onSettingsChange={onSettingsChange}
                settingsNotice={settingsNotice}
                saveSettings={saveSettings}
              />
            </Suspense>
          )}
        </div><aside className="sidebar right">
          <section className="card sticky">
            <div className="row-header">
              <h2>Управление задачей</h2>
              <div className="status-caption">{selectedTaskName}</div>
            </div>
            <div className="side-stats">
              <div className="side-stat">
                <span>Группы конкурентов</span>
                <strong>{competitorGroups.length}</strong>
              </div>
              <div className="side-stat">
                <span>Аккаунты</span>
                <strong>{assignedAccountCount}</strong>
              </div>
              <div className="side-stat">
                <span>Очередь</span>
                <strong>{taskStatus.queueCount}</strong>
              </div>
              <div className="side-stat">
                <span>Лимит сегодня</span>
                <strong>{taskStatus.dailyUsed}/{taskStatus.dailyLimit}</strong>
              </div>
            </div>
            <div className="sidebar-actions">
              <button
                className="ghost"
                type="button"
                onClick={() => setSidebarExpanded((prev) => !prev)}
              >
                {sidebarExpanded ? "Свернуть действия" : "Показать действия"}
              </button>
            </div>
            {sidebarExpanded && (
              <div className="sidebar-actions expanded">
                  <button className="secondary" onClick={() => saveTask("sidebar")} disabled={!canSaveTask}>Сохранить задачу</button>
                  <button className="secondary" onClick={() => parseHistory("sidebar")} disabled={!hasSelectedTask}>Собрать историю</button>
                  <button className="secondary" onClick={() => checkAccess("sidebar")} disabled={!hasSelectedTask}>Проверить доступ</button>
                  <button className="secondary" onClick={() => checkInviteAccess("sidebar")} disabled={!hasSelectedTask}>Проверить права инвайта</button>
                  <button className="secondary" onClick={() => refreshMembership("sidebar")} disabled={!hasSelectedTask}>Проверить участие</button>
                  <button className="secondary" onClick={() => clearQueue("sidebar")} disabled={!hasSelectedTask}>Очистить очередь</button>
                  {taskStatus.running ? (
                    <button className="danger" onClick={() => stopTask("sidebar")} disabled={!hasSelectedTask || taskActionLoading}>
                      {taskActionLoading ? "Остановка..." : "Остановить"}
                    </button>
                  ) : (
                    <button className="primary" onClick={() => startTask("sidebar")} disabled={!hasSelectedTask || taskActionLoading}>
                      {taskActionLoading ? "Запуск..." : "Запустить"}
                    </button>
                  )}
                  {accessStatus.length > 0 && (
                    <div className="access-block">
                      <div className="access-title">Доступ к группам</div>
                    <div className="access-list">
                      {accessStatus.map((item, index) => (
                        <div key={`${item.value}-${index}`} className={`access-row ${item.ok ? "ok" : "fail"}`}>
                          <div className="access-title">
                            {item.type === "our" ? "Наша" : "Конкурент"}: {item.title || item.value}
                          </div>
                          <div className="access-status">
                            {item.ok ? "Доступ есть" : "Нет доступа"}
                          </div>
                          {!item.ok && <div className="access-error">{item.details}</div>}
                        </div>
                      ))}
                    </div>
                  </div>
                )}
                {inviteAccessStatus.length > 0 && (
                  <div className="access-block">
                    <div className="access-title">Права инвайта</div>
                    <div className="access-subtitle">
                      Проверяются аккаунты с ролью инвайта: {roleSummary.invite.length}
                    </div>
                    <div className="access-list">
                      {inviteAccessStatus.map((item, index) => (
                        <div key={`${item.accountId}-${index}`} className={`access-row ${item.canInvite ? "ok" : "fail"}`}>
                          <div className="access-title">
                            {item.accountPhone || item.accountId}: {item.title || item.targetChat}
                            {item.targetType ? ` (${item.targetType === "channel" ? "канал" : item.targetType === "megagroup" ? "супергруппа" : item.targetType === "group" ? "группа" : item.targetType})` : ""}
                          </div>
                          <div className="access-status">
                            {item.canInvite ? "Можно инвайтить" : "Нет прав"}
                          </div>
                          {!item.canInvite && <div className="access-error">{item.reason || "—"}</div>}
                        </div>
                      ))}
                    </div>
                  </div>
                )}
              </div>
            )}
            {taskNotice && taskNotice.source === "sidebar" && (
              <div className={`notice inline ${taskNotice.tone}`}>{taskNotice.text}</div>
            )}
          </section>
        </aside>
      </div>
    </div>
  );
}
