/**
 * scheduler.js
 * Automation task registry, trigger matching, execution, and logs.
 */
(function () {
  'use strict';

  const TASKS_KEY = 'automationTasks';
  const LOGS_KEY = 'automationLogs';
  const CRON_KEYS_KEY = 'automationCronKeys';
  const TICK_ALARM = 'aisc_automation_tick';
  const MAX_LOGS = 80;

  function storageGet(keys) {
    return new Promise((resolve, reject) => {
      chrome.storage.local.get(keys, (value) => {
        const err = chrome.runtime.lastError;
        if (err) reject(new Error(err.message));
        else resolve(value || {});
      });
    });
  }

  function storageSet(value) {
    return new Promise((resolve, reject) => {
      chrome.storage.local.set(value, () => {
        const err = chrome.runtime.lastError;
        if (err) reject(new Error(err.message));
        else resolve();
      });
    });
  }

  function makeId(prefix = 'task') {
    return prefix + '_' + Date.now() + '_' + Math.random().toString(36).slice(2, 8);
  }

  function normalizeTask(task = {}) {
    const trigger = task.trigger || {};
    return {
      id: task.id || makeId(),
      name: String(task.name || '未命名自动化任务').trim(),
      enabled: task.enabled !== false,
      trigger: {
        type: trigger.type || 'manual',
        rule: trigger.rule || '',
        urlPattern: trigger.urlPattern || trigger.pattern || '',
        description: trigger.description || ''
      },
      instruction: String(task.instruction || task.description || '').trim(),
      sourceUrl: task.sourceUrl || '',
      schedule: task.schedule || null,
      steps: Array.isArray(task.steps) ? task.steps : [],
      createdAt: task.createdAt || new Date().toISOString(),
      updatedAt: new Date().toISOString(),
      lastRunAt: task.lastRunAt || '',
      lastStatus: task.lastStatus || '',
      runCount: Number(task.runCount || 0),
      lastError: task.lastError || ''
    };
  }

  async function listTasks() {
    const stored = await storageGet([TASKS_KEY]);
    return Array.isArray(stored[TASKS_KEY]) ? stored[TASKS_KEY].map(normalizeTask) : [];
  }

  async function saveTasks(tasks) {
    await storageSet({ [TASKS_KEY]: tasks.map(normalizeTask) });
  }

  async function saveTask(task) {
    const tasks = await listTasks();
    const normalized = normalizeTask(task);
    const idx = tasks.findIndex(t => t.id === normalized.id);
    if (idx >= 0) {
      normalized.createdAt = tasks[idx].createdAt || normalized.createdAt;
      tasks[idx] = { ...tasks[idx], ...normalized, updatedAt: new Date().toISOString() };
    } else {
      tasks.unshift(normalized);
    }
    await saveTasks(tasks);
    await syncTickAlarm();
    return normalized;
  }

  async function deleteTask(id) {
    const tasks = await listTasks();
    const next = tasks.filter(t => t.id !== id);
    await saveTasks(next);
    await syncTickAlarm();
    return { deleted: tasks.length - next.length };
  }

  async function toggleTask(id, enabled) {
    const tasks = await listTasks();
    const target = tasks.find(t => t.id === id);
    if (!target) throw new Error('任务不存在');
    target.enabled = enabled !== false;
    target.updatedAt = new Date().toISOString();
    await saveTasks(tasks);
    await syncTickAlarm();
    return target;
  }

  async function appendLog(entry) {
    const stored = await storageGet([LOGS_KEY]);
    const logs = Array.isArray(stored[LOGS_KEY]) ? stored[LOGS_KEY] : [];
    logs.unshift({
      id: entry.id || makeId('log'),
      taskId: entry.taskId || '',
      taskName: entry.taskName || '',
      status: entry.status || 'info',
      trigger: entry.trigger || '',
      message: entry.message || '',
      details: entry.details || null,
      createdAt: entry.createdAt || new Date().toISOString()
    });
    await storageSet({ [LOGS_KEY]: logs.slice(0, MAX_LOGS) });
  }

  async function listLogs() {
    const stored = await storageGet([LOGS_KEY]);
    return Array.isArray(stored[LOGS_KEY]) ? stored[LOGS_KEY] : [];
  }

  function normalizeDow(value) {
    const names = { SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6 };
    const upper = String(value).toUpperCase();
    if (upper in names) return names[upper];
    const num = Number(value);
    if (num === 7) return 0;
    return num;
  }

  function matchField(field, actual, min, max, isDow = false) {
    const raw = String(field || '*').trim();
    if (raw === '*') return true;
    return raw.split(',').some(part => {
      const value = part.trim();
      if (!value) return false;
      const stepMatch = value.match(/^\*\/(\d+)$/);
      if (stepMatch) {
        const step = Math.max(1, Number(stepMatch[1]));
        return (actual - min) % step === 0;
      }
      const rangeMatch = value.match(/^([A-Za-z0-9]+)-([A-Za-z0-9]+)$/);
      if (rangeMatch) {
        const start = isDow ? normalizeDow(rangeMatch[1]) : Number(rangeMatch[1]);
        const end = isDow ? normalizeDow(rangeMatch[2]) : Number(rangeMatch[2]);
        if (!Number.isFinite(start) || !Number.isFinite(end)) return false;
        if (start <= end) return actual >= start && actual <= end;
        return actual >= start || actual <= end;
      }
      const expected = isDow ? normalizeDow(value) : Number(value);
      return Number.isFinite(expected) && expected >= min && expected <= max && actual === expected;
    });
  }

  function matchesCron(rule = '', date = new Date()) {
    const parts = String(rule || '').trim().split(/\s+/);
    if (parts.length !== 5) return false;
    const [minute, hour, day, month, dow] = parts;
    return matchField(minute, date.getMinutes(), 0, 59) &&
      matchField(hour, date.getHours(), 0, 23) &&
      matchField(day, date.getDate(), 1, 31) &&
      matchField(month, date.getMonth() + 1, 1, 12) &&
      matchField(dow, date.getDay(), 0, 6, true);
  }

  function minuteKey(date = new Date()) {
    const pad = n => String(n).padStart(2, '0');
    return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
  }

  function urlMatches(pattern, url) {
    const value = String(pattern || '').trim();
    if (!value || !url) return false;
    try {
      return new RegExp(value).test(url);
    } catch (_) {
      return String(url).includes(value);
    }
  }

  function getPathValue(source, path) {
    return String(path || '').split('.').reduce((acc, key) => {
      if (acc == null) return '';
      return acc[key];
    }, source);
  }

  function resolveTemplates(value, state) {
    if (typeof value === 'string') {
      return value.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (_, path) => {
        const resolved = getPathValue(state, path);
        if (resolved == null) return '';
        return typeof resolved === 'object' ? JSON.stringify(resolved) : String(resolved);
      });
    }
    if (Array.isArray(value)) return value.map(item => resolveTemplates(item, state));
    if (value && typeof value === 'object') {
      const out = {};
      Object.entries(value).forEach(([key, val]) => { out[key] = resolveTemplates(val, state); });
      return out;
    }
    return value;
  }

  function stepTool(step = {}) {
    return step.tool || step.type || step.action || '';
  }

  async function notify(args = {}) {
    const title = String(args.title || '个人AI助理自动化');
    const message = String(args.message || args.body || '任务已完成');
    if (chrome.notifications?.create) {
      await new Promise(resolve => {
        chrome.notifications.create('', {
          type: 'basic',
          iconUrl: 'icons/icon48.png',
          title,
          message
        }, () => resolve());
      });
    }
    return { notified: true, title, message };
  }

  function readLLMSettings(stored = {}) {
    const settings = stored.llmSettings || {};
    return {
      apiKey: settings.api_key || settings.apiKey || settings.aliyun_api_key || '',
      apiUrl: settings.api_url || settings.apiUrl || settings.aliyun_api_url || 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
      model: settings.model || settings.aliyun_model || 'qwen-plus'
    };
  }

  async function runAssistant(args = {}, state = {}) {
    const stored = await storageGet(['llmSettings']);
    const { apiKey, apiUrl, model } = readLLMSettings(stored);
    if (!apiKey) throw new Error('LLM API未配置，请先在设置里保存 API Key');
    const contextText = JSON.stringify({
      task: state.task?.name || '',
      trigger: state.trigger || '',
      pageContext: state.context || null,
      last: state.last || null,
      results: state.results || []
    }, null, 2).slice(0, 12000);
    const instruction = args.instruction || args.prompt || state.task?.instruction || '请执行这个自动化任务。';
    const resp = await fetch(apiUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + apiKey
      },
      body: JSON.stringify({
        model,
        messages: [
          { role: 'system', content: '你是个人AI助理的自动化执行器。按用户自然语言任务执行，输出简洁、结构化、可直接使用的中文结果。无人值守自动化场景下，如果信息不足，不要停下来追问；基于已有信息完成，并把缺口标为“待补充”。' },
          { role: 'user', content: `【用户任务】\n${instruction}\n\n【自动化上下文】\n${contextText}` }
        ],
        temperature: args.temperature ?? 0.3,
        max_tokens: args.max_tokens || 4096
      })
    });
    if (!resp.ok) throw new Error('LLM API返回错误 ' + resp.status + ': ' + (await resp.text()).slice(0, 300));
    const data = await resp.json();
    return { text: data.choices?.[0]?.message?.content || '' };
  }

  async function runSkill(args = {}, state = {}) {
    const skillName = args.skill || args.name;
    if (!skillName) throw new Error('skill.run 缺少 skill 名称');
    const stored = await storageGet(['customSkills', 'llmSettings']);
    let content = stored.customSkills?.[skillName]?.content || '';
    if (!content) {
      const url = chrome.runtime.getURL(`skills/${skillName}.md`);
      const resp = await fetch(url);
      if (resp.ok) content = await resp.text();
    }
    if (!content) throw new Error('未找到 Skill: ' + skillName);
    const { apiKey, apiUrl, model } = readLLMSettings(stored);
    if (!apiKey) throw new Error('LLM API未配置，请先在设置里保存 API Key');
    const contextText = JSON.stringify({
      pageContext: state.context || null,
      last: state.last || null,
      results: state.results || []
    }, null, 2).slice(0, 12000);
    const resp = await fetch(apiUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + apiKey
      },
      body: JSON.stringify({
        model,
        messages: [
          { role: 'system', content: '你是个人AI助理的自动化执行器。请按Skill规范执行任务，只输出最终结果。无人值守自动化场景下，如果信息不足，不要停下来追问；基于已有信息完成，并把缺口标为“待补充”。' },
          { role: 'user', content: `【Skill】\n${content}\n\n【自动化上下文】\n${contextText}\n\n【用户补充】\n${args.instruction || args.prompt || '请执行。'}` }
        ],
        temperature: args.temperature ?? 0.3,
        max_tokens: args.max_tokens || 4096
      })
    });
    if (!resp.ok) throw new Error('LLM API返回错误 ' + resp.status + ': ' + (await resp.text()).slice(0, 300));
    const data = await resp.json();
    return { skill: skillName, text: data.choices?.[0]?.message?.content || '' };
  }

  async function executeStep(step, state) {
    const tool = stepTool(step);
    const args = resolveTemplates(step.args || step.params || {}, state);
    switch (tool) {
      case 'browser.open': return await globalThis.AiscCdp.open(args);
      case 'browser.navigate': return await globalThis.AiscCdp.navigate(args);
      case 'browser.click': return await globalThis.AiscCdp.click(args);
      case 'browser.type':
      case 'browser.fill': return await globalThis.AiscCdp.type(args);
      case 'browser.press': return await globalThis.AiscCdp.press(args);
      case 'browser.waitFor': return await globalThis.AiscCdp.waitFor(args);
      case 'browser.getText':
      case 'page.readText': return await globalThis.AiscCdp.getText(args);
      case 'browser.screenshot': return await globalThis.AiscCdp.screenshot(args);
      case 'browser.pdf': return await globalThis.AiscCdp.pdf(args);
      case 'browser.close': return await globalThis.AiscCdp.close(args);
      case 'page.read': {
        if (typeof globalThis.safeExtractActivePage !== 'function') throw new Error('页面读取器未就绪');
        return await globalThis.safeExtractActivePage();
      }
      case 'web.fetch': {
        if (typeof globalThis.fetchWebPageContext !== 'function') throw new Error('网页解析器未就绪');
        return await globalThis.fetchWebPageContext(args.url);
      }
      case 'assistant.run':
      case 'ai.run': return await runAssistant(args, state);
      case 'skill.run': return await runSkill(args, state);
      case 'notify':
      case 'notify.send': return await notify(args);
      case 'fs.write':
      case 'fs.read':
      case 'fs.list':
      case 'shell.exec':
      case 'clipboard.read':
      case 'clipboard.write':
      case 'screen.capture':
      case 'window.activate':
        if (!globalThis.AiscNative?.call) throw new Error('Native Agent 通道未就绪');
        {
          const nativeResult = await globalThis.AiscNative.call(tool, args);
          if (nativeResult?.ok === false) throw new Error(nativeResult.error || 'Native Agent 调用失败');
          return nativeResult;
        }
      default:
        throw new Error('未知自动化步骤工具: ' + tool);
    }
  }

  async function runTask(input, trigger = 'manual') {
    const tasks = await listTasks();
    const task = typeof input === 'string'
      ? tasks.find(t => t.id === input)
      : normalizeTask(input);
    if (!task) throw new Error('任务不存在');
    if (trigger !== 'manual' && task.enabled === false) return { skipped: true, reason: 'disabled' };

    const state = { task, trigger, results: [], last: null, context: null };
    await appendLog({ taskId: task.id, taskName: task.name, status: 'running', trigger, message: '开始执行' });
    try {
      for (let i = 0; i < task.steps.length; i++) {
        const step = task.steps[i];
        const result = await executeStep(step, state);
        state.last = result;
        state.results.push({ index: i + 1, tool: stepTool(step), result });
        if (stepTool(step) === 'page.read' || stepTool(step) === 'web.fetch') state.context = result;
      }
      const persisted = tasks.find(t => t.id === task.id);
      if (persisted) {
        persisted.lastRunAt = new Date().toISOString();
        persisted.lastStatus = 'success';
        persisted.lastError = '';
        persisted.runCount = Number(persisted.runCount || 0) + 1;
        persisted.updatedAt = new Date().toISOString();
        await saveTasks(tasks);
      }
      await appendLog({
        taskId: task.id,
        taskName: task.name,
        status: 'success',
        trigger,
        message: '执行完成',
        details: state.results
      });
      return { ok: true, taskId: task.id, results: state.results };
    } catch (e) {
      const persisted = tasks.find(t => t.id === task.id);
      if (persisted) {
        persisted.lastRunAt = new Date().toISOString();
        persisted.lastStatus = 'error';
        persisted.lastError = e.message || String(e);
        persisted.runCount = Number(persisted.runCount || 0) + 1;
        persisted.updatedAt = new Date().toISOString();
        await saveTasks(tasks);
      }
      await appendLog({
        taskId: task.id,
        taskName: task.name,
        status: 'error',
        trigger,
        message: e.message || String(e),
        details: state.results
      });
      throw e;
    }
  }

  async function runCronDue() {
    const now = new Date();
    const key = minuteKey(now);
    const stored = await storageGet([CRON_KEYS_KEY]);
    const cronKeys = stored[CRON_KEYS_KEY] || {};
    const tasks = await listTasks();
    for (const task of tasks) {
      if (!task.enabled || task.trigger?.type !== 'cron') continue;
      if (!matchesCron(task.trigger.rule, now)) continue;
      const taskMinuteKey = task.id + ':' + key;
      if (cronKeys[task.id] === taskMinuteKey) continue;
      cronKeys[task.id] = taskMinuteKey;
      await storageSet({ [CRON_KEYS_KEY]: cronKeys });
      runTask(task.id, 'cron').catch(e => console.warn('[automation] cron run failed:', e.message));
    }
  }

  async function runUrlMatches(tabId, tabUrl) {
    const tasks = await listTasks();
    for (const task of tasks) {
      if (!task.enabled || task.trigger?.type !== 'url') continue;
      if (!urlMatches(task.trigger.urlPattern || task.trigger.rule, tabUrl)) continue;
      runTask(task.id, 'url:' + tabId).catch(e => console.warn('[automation] url run failed:', e.message));
    }
  }

  async function syncTickAlarm() {
    if (!chrome.alarms?.create) return;
    const tasks = await listTasks().catch(() => []);
    const hasCron = tasks.some(t => t.enabled && t.trigger?.type === 'cron');
    if (hasCron) chrome.alarms.create(TICK_ALARM, { periodInMinutes: 1 });
    else chrome.alarms.clear(TICK_ALARM);
  }

  async function handleMessage(message) {
    switch (message.type) {
      case 'AUTOMATION_LIST':
        return { tasks: await listTasks(), logs: await listLogs() };
      case 'AUTOMATION_SAVE':
        return { task: await saveTask(message.task || {}) };
      case 'AUTOMATION_DELETE':
        return await deleteTask(message.id);
      case 'AUTOMATION_TOGGLE':
        return { task: await toggleTask(message.id, message.enabled) };
      case 'AUTOMATION_RUN':
        return await runTask(message.id, 'manual');
      case 'AUTOMATION_RUN_DRAFT':
        return await runTask(message.task || {}, 'manual');
      case 'AUTOMATION_LOGS':
        return { logs: await listLogs() };
      case 'AUTOMATION_NOTIFY':
        return await notify(message.args || message);
      default:
        throw new Error('未知自动化消息: ' + message.type);
    }
  }

  function init() {
    chrome.alarms?.onAlarm?.addListener((alarm) => {
      if (alarm.name === TICK_ALARM) runCronDue().catch(e => console.warn('[automation] tick failed:', e.message));
    });
    chrome.tabs?.onUpdated?.addListener((tabId, changeInfo, tab) => {
      if (changeInfo.status !== 'complete') return;
      const url = tab?.url || changeInfo.url || '';
      if (!url) return;
      runUrlMatches(tabId, url).catch(e => console.warn('[automation] url trigger failed:', e.message));
    });
    chrome.runtime?.onMessage?.addListener((message, sender, sendResponse) => {
      if (!message?.type || !String(message.type).startsWith('AUTOMATION_')) return false;
      (async () => {
        try {
          const data = await handleMessage(message);
          sendResponse({ ok: true, data });
        } catch (e) {
          sendResponse({ ok: false, error: e.message || String(e) });
        }
      })();
      return true;
    });
    syncTickAlarm().catch(() => {});
  }

  globalThis.AiscAutomation = {
    init,
    listTasks,
    saveTask,
    deleteTask,
    toggleTask,
    runTask,
    listLogs,
    handleMessage,
    matchesCron
  };

  init();
})();
