// Task scheduling & storage module for background service worker
// Keeps alarms, storage, and message handling isolated from other background logic.

const TaskModule = (() => {
  const TASKS_KEY = "blackeagleTasks";
  const ALARM_PREFIX = "TASK_";

  const taskStorage = {
    async getAll() {
      return new Promise((resolve, reject) => {
        chrome.storage.local.get([TASKS_KEY], (res) => {
          if (chrome.runtime.lastError) return reject(chrome.runtime.lastError);
          resolve(res[TASKS_KEY] ?? []);
        });
      });
    },
    async setAll(tasks) {
      return new Promise((resolve, reject) => {
        chrome.storage.local.set({ [TASKS_KEY]: tasks }, () => {
          if (chrome.runtime.lastError) return reject(chrome.runtime.lastError);
          resolve();
        });
      });
    },
  };

  const taskAlarmName = (id) => `${ALARM_PREFIX}${id}`;

  async function clearTaskAlarms() {
    const alarms = await chrome.alarms.getAll();
    const clears = alarms.filter((a) => a.name.startsWith(ALARM_PREFIX)).map((a) => chrome.alarms.clear(a.name));
    await Promise.all(clears);
  }

  async function scheduleTask(task) {
    if (!task?.enabled) return;
    const name = taskAlarmName(task.id);
    const now = Date.now();
    const when = task.onceAt ? new Date(task.onceAt).getTime() : undefined;
    const intervalMinutes = task.intervalMinutes ? Math.max(5, Number(task.intervalMinutes)) : undefined;
    const intervalMs = intervalMinutes ? intervalMinutes * 60 * 1000 : undefined;

    // Skip expired one-time tasks with no interval fallback.
    if (when && when <= now && !intervalMs) return;

    const alarmConfig = {};
    if (when && when > now) {
      // Explicit one-time start time takes precedence for first run.
      alarmConfig.when = when;
    } else if (intervalMs) {
      // No explicit start time: first run occurs after the interval (no immediate execution on save).
      alarmConfig.when = now + intervalMs;
    } else {
      // Fallback: run once, ASAP, for tasks without interval or future time.
      alarmConfig.when = now + 1000;
    }

    if (intervalMinutes) {
      // Enforce minimum 5 minutes between runs
      alarmConfig.periodInMinutes = intervalMinutes;
    }

    chrome.alarms.create(name, alarmConfig);
  }

  async function rescheduleAllTasks() {
    const tasks = await taskStorage.getAll();
    await clearTaskAlarms();
    await Promise.all(tasks.map(scheduleTask));
  }

  async function runTask(task) {
    if (!task) return;
    try {
      // 1) 调用你的 Agent 执行函数（示例）: await runAgent(task.instruction, task)
      // 2) 同时把任务推给 AgentTalk Angular 组件，组件再调用你的处理函数
      await notifyAgentTalk(task);
      await showTaskNotification(task);
      console.info("[TaskRunner] execute", task.id, task.instruction);
    } catch (err) {
      console.error("[TaskRunner] failed", task.id, err);
    }
  }

  async function markLastRun(taskId) {
    const tasks = await taskStorage.getAll();
    const nowIso = new Date().toISOString();
    const next = tasks.map((t) => (t.id === taskId ? { ...t, lastRunAt: nowIso, updatedAt: nowIso } : t));
    await taskStorage.setAll(next);
  }

  async function notifyAgentTalk(task) {
    return new Promise((resolve) => {
      chrome.runtime.sendMessage({ type: "AGENT_TASK_EXECUTE", payload: { ...task, suppressInAppNotification: true } }, () => {
        // Touch lastError so Chrome does not emit warnings when no receiver is present.
        void chrome.runtime.lastError;
        // ignore response; best-effort fire-and-forget
        resolve();
      });
    });
  }

  async function showTaskNotification(task) {
    try {
      const title = task?.name || "Scheduled task started";
      const body = task?.instruction || "A scheduled task is running.";
      // Use service worker notification; requires notifications permission.
      await self.registration.showNotification(title, {
        body,
        tag: `task-${task?.id || "unknown"}`,
        data: { taskId: task?.id },
      });
    } catch (err) {
      console.warn("[TaskRunner] showNotification failed", err);
    }
  }

  function wireNotificationClicks() {
    self.addEventListener("notificationclick", (event) => {
      event.notification.close();
      event.waitUntil(
        (async () => {
          try {
            const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
            if (tab?.id) {
              await chrome.sidePanel.open({ tabId: tab.id });
            }
          } catch (err) {
            console.warn("[TaskRunner] notificationclick focus failed", err);
          }
        })()
      );
    });
  }

  function wireLifecycle() {
    chrome.runtime.onInstalled.addListener(() => {
      rescheduleAllTasks().catch((e) => console.error("Reschedule on install failed", e));
    });

    chrome.runtime.onStartup.addListener(() => {
      rescheduleAllTasks().catch((e) => console.error("Reschedule on startup failed", e));
    });

    wireNotificationClicks();

    chrome.alarms.onAlarm.addListener(async (alarm) => {
      if (!alarm?.name?.startsWith(ALARM_PREFIX)) return;
      const taskId = alarm.name.replace(ALARM_PREFIX, "");
      const tasks = await taskStorage.getAll();
      const task = tasks.find((t) => t.id === taskId && t.enabled);
      if (!task) return;
      await runTask(task);
      await markLastRun(taskId);
    });
  }

  function handleMessage(msg, sendResponse) {
    switch (msg?.type) {
      case "TASK_LIST": {
        taskStorage
          .getAll()
          .then((tasks) => sendResponse({ success: true, tasks }))
          .catch((err) => sendResponse({ success: false, error: err?.message || String(err) }));
        return true;
      }
      case "TASK_UPSERT": {
        (async () => {
          const incoming = msg.payload;
          const tasks = await taskStorage.getAll();
          const nowIso = new Date().toISOString();
          const id = incoming.id || (crypto?.randomUUID?.() ?? `task_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`);
          const normalized = {
            id,
            name: incoming.name?.trim() || "Unnamed task",
            instruction: incoming.instruction?.trim() || "",
            enabled: Boolean(incoming.enabled),
            // Enforce minimum interval at storage level as well
            intervalMinutes: incoming.intervalMinutes ? Math.max(5, Number(incoming.intervalMinutes)) : undefined,
            onceAt: incoming.onceAt || undefined,
            createdAt: incoming.createdAt || nowIso,
            updatedAt: nowIso,
          };

          if (!normalized.instruction) {
            sendResponse({ success: false, error: "Instruction is required" });
            return;
          }

          const next = tasks.some((t) => t.id === id) ? tasks.map((t) => (t.id === id ? { ...t, ...normalized } : t)) : [...tasks, normalized];
          await taskStorage.setAll(next);
          await rescheduleAllTasks();
          sendResponse({ success: true, task: normalized });
        })().catch((err) => sendResponse({ success: false, error: err?.message || String(err) }));
        return true;
      }
      case "TASK_DELETE": {
        (async () => {
          const { id } = msg.payload || {};
          const tasks = await taskStorage.getAll();
          const next = tasks.filter((t) => t.id !== id);
          await taskStorage.setAll(next);
          await rescheduleAllTasks();
          sendResponse({ success: true });
        })().catch((err) => sendResponse({ success: false, error: err?.message || String(err) }));
        return true;
      }
      case "TASK_RUN_NOW": {
        (async () => {
          const { id } = msg.payload || {};
          const tasks = await taskStorage.getAll();
          const task = tasks.find((t) => t.id === id && t.enabled !== false);
          if (!task) {
            sendResponse({ success: false, error: "Task not found" });
            return;
          }
          await runTask(task);
          await markLastRun(id);
          sendResponse({ success: true });
        })().catch((err) => sendResponse({ success: false, error: err?.message || String(err) }));
        return true;
      }
      default:
        return false;
    }
  }

  function init() {
    wireLifecycle();
  }

  return {
    init,
    handleMessage,
  };
})();
