import { scrypt } from "./scrypt.js";

const STORAGE_KEY = "blackeagleConfigs";
const STATUS_DURATION = 4000;
const TASK_STORAGE_KEY = "blackeagleTasks";
const MORE_STORAGE_KEY = "blackeagleMoreConfig";
const DEVICE_CONFIG_KEY = "beDeviceConfig";
const DEFAULT_SERVER_BASE = typeof getServerBaseForEnv === "function" ? getServerBaseForEnv(SERVER_CONFIG?.defaultEnv || "prod") : SERVER_CONFIG?.envs?.prod || SERVER_CONFIG?.envs?.local || "";

const storage = (() => {
  const chromeStorage = typeof chrome !== "undefined" && chrome?.storage?.local;
  if (chromeStorage) {
    return {
      async get() {
        return new Promise((resolve, reject) => {
          chrome.storage.local.get([STORAGE_KEY], (result) => {
            if (chrome.runtime?.lastError) {
              reject(chrome.runtime.lastError);
              return;
            }
            resolve(result[STORAGE_KEY] ?? []);
          });
        });
      },
      async set(configs) {
        return new Promise((resolve, reject) => {
          chrome.storage.local.set({ [STORAGE_KEY]: configs }, () => {
            if (chrome.runtime?.lastError) {
              reject(chrome.runtime.lastError);
              return;
            }
            resolve();
          });
        });
      },
    };
  }

  // Fallback to localStorage when the extension APIs are unavailable (e.g. local dev preview).
  return {
    async get() {
      try {
        const raw = localStorage.getItem(STORAGE_KEY);
        return raw ? JSON.parse(raw) : [];
      } catch (error) {
        console.error("Unable to read configs from localStorage", error);
        return [];
      }
    },
    async set(configs) {
      localStorage.setItem(STORAGE_KEY, JSON.stringify(configs));
    },
  };
})();

// Additional config storage (e.g., Notion token)
const moreStorage = (() => {
  const chromeStorage = typeof chrome !== "undefined" && chrome?.storage?.local;
  if (chromeStorage) {
    return {
      async get() {
        return new Promise((resolve, reject) => {
          chrome.storage.local.get([MORE_STORAGE_KEY], (result) => {
            if (chrome.runtime?.lastError) {
              reject(chrome.runtime.lastError);
              return;
            }
            resolve(result[MORE_STORAGE_KEY] ?? {});
          });
        });
      },
      async set(config) {
        return new Promise((resolve, reject) => {
          chrome.storage.local.set({ [MORE_STORAGE_KEY]: config }, () => {
            if (chrome.runtime?.lastError) {
              reject(chrome.runtime.lastError);
              return;
            }
            resolve();
          });
        });
      },
    };
  }

  return {
    async get() {
      try {
        const raw = localStorage.getItem(MORE_STORAGE_KEY);
        return raw ? JSON.parse(raw) : {};
      } catch (error) {
        console.error("Unable to read more config from localStorage", error);
        return {};
      }
    },
    async set(config) {
      localStorage.setItem(MORE_STORAGE_KEY, JSON.stringify(config || {}));
    },
  };
})();

const deviceStorage = (() => {
  const chromeStorage = typeof chrome !== "undefined" && chrome?.storage?.local;
  if (chromeStorage) {
    return {
      async get() {
        return new Promise((resolve, reject) => {
          chrome.storage.local.get([DEVICE_CONFIG_KEY], (result) => {
            if (chrome.runtime?.lastError) {
              reject(chrome.runtime.lastError);
              return;
            }
            resolve(result[DEVICE_CONFIG_KEY] ?? {});
          });
        });
      },
      async set(config) {
        return new Promise((resolve, reject) => {
          chrome.storage.local.set({ [DEVICE_CONFIG_KEY]: config }, () => {
            if (chrome.runtime?.lastError) {
              reject(chrome.runtime.lastError);
              return;
            }
            resolve();
          });
        });
      },
    };
  }

  return {
    async get() {
      try {
        const raw = localStorage.getItem(DEVICE_CONFIG_KEY);
        return raw ? JSON.parse(raw) : {};
      } catch (error) {
        console.error("Unable to read device config from localStorage", error);
        return {};
      }
    },
    async set(config) {
      localStorage.setItem(DEVICE_CONFIG_KEY, JSON.stringify(config || {}));
    },
  };
})();

// Task storage goes through background to ensure alarms are rescheduled.
const taskApi = {
  async list() {
    if (chrome?.runtime?.id) {
      return new Promise((resolve) => {
        chrome.runtime.sendMessage({ type: "TASK_LIST" }, (res) => {
          const err = chrome.runtime.lastError;
          if (err || !res?.success) {
            console.warn("TASK_LIST failed", err || res?.error);
            resolve([]);
            return;
          }
          resolve(res.tasks || []);
        });
      });
    }
    // fallback (preview without extension runtime)
    try {
      const raw = localStorage.getItem(TASK_STORAGE_KEY);
      return raw ? JSON.parse(raw) : [];
    } catch (e) {
      return [];
    }
  },
  async upsert(task) {
    if (chrome?.runtime?.id) {
      return new Promise((resolve, reject) => {
        chrome.runtime.sendMessage({ type: "TASK_UPSERT", payload: task }, (res) => {
          const err = chrome.runtime.lastError;
          if (err || !res?.success) {
            reject(err || res?.error || "TASK_UPSERT failed");
            return;
          }
          resolve(res.task);
        });
      });
    }
    // fallback
    const list = await taskApi.list();
    const id = task.id || `task_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`;
    const now = new Date().toISOString();
    const existing = list.find((t) => t.id === id);
    const nextTask = existing ? { ...existing, ...task, id, updatedAt: now, createdAt: existing.createdAt || now } : { ...task, id, createdAt: task.createdAt || now, updatedAt: now };
    const next = existing ? list.map((t) => (t.id === id ? nextTask : t)) : [...list, nextTask];
    localStorage.setItem(TASK_STORAGE_KEY, JSON.stringify(next));
    return next.find((t) => t.id === id);
  },
  async remove(id) {
    if (chrome?.runtime?.id) {
      return new Promise((resolve, reject) => {
        chrome.runtime.sendMessage({ type: "TASK_DELETE", payload: { id } }, (res) => {
          const err = chrome.runtime.lastError;
          if (err || !res?.success) {
            reject(err || res?.error || "TASK_DELETE failed");
            return;
          }
          resolve();
        });
      });
    }
    const list = await taskApi.list();
    const next = list.filter((t) => t.id !== id);
    localStorage.setItem(TASK_STORAGE_KEY, JSON.stringify(next));
  },
  async runNow(id) {
    if (chrome?.runtime?.id) {
      return new Promise((resolve, reject) => {
        chrome.runtime.sendMessage({ type: "TASK_RUN_NOW", payload: { id } }, (res) => {
          const err = chrome.runtime.lastError;
          if (err || !res?.success) {
            reject(err || res?.error || "TASK_RUN_NOW failed");
            return;
          }
          resolve();
        });
      });
    }
    // fallback (preview): mark lastRunAt locally
    const list = await taskApi.list();
    const now = new Date().toISOString();
    const next = list.map((t) => (t.id === id ? { ...t, lastRunAt: now, updatedAt: now } : t));
    localStorage.setItem(TASK_STORAGE_KEY, JSON.stringify(next));
  },
};

const state = {
  configs: [],
  editingId: null,
  statusTimer: null,
  section: "llm",
  tasks: [],
  editingTaskId: null,
  taskStatusTimer: null,
  moreConfig: { notionToken: "" },
  moreStatusTimer: null,
  deviceConfig: { deviceId: "", deviceToken: "", deviceType: "extension" },
  deviceStatusTimer: null,
  configPassword: "",
  configStatusTimer: null,
};

const notifyRefresh = () => {
  const payload = { type: "REFRESH" };
  if (typeof chrome !== "undefined" && chrome?.runtime?.id) {
    try {
      chrome.runtime.sendMessage(payload, () => {
        const err = chrome.runtime?.lastError;
        if (err) {
          console.debug("REFRESH message warning", err);
        }
      });
      return;
    } catch (error) {
      console.debug("REFRESH message failed", error);
    }
  }

  if (typeof window !== "undefined" && typeof window.dispatchEvent === "function") {
    window.dispatchEvent(new CustomEvent("REFRESH", { detail: payload }));
  }
};

const elements = {
  form: document.getElementById("configForm"),
  formTitle: document.getElementById("formTitle"),
  submitBtn: document.getElementById("submitBtn"),
  resetBtn: document.getElementById("resetBtn"),
  status: document.getElementById("status"),
  configId: document.getElementById("configId"),
  name: document.getElementById("name"),
  apiKey: document.getElementById("apiKey"),
  baseURL: document.getElementById("baseURL"),
  apiVersion: document.getElementById("apiVersion"),
  model: document.getElementById("model"),
  llmType: document.getElementById("llmType"),
  configList: document.getElementById("configList"),
  configCounter: document.getElementById("configCounter"),
  emptyState: document.getElementById("emptyState"),
  tabs: Array.from(document.querySelectorAll(".tab-btn")),
  sections: Array.from(document.querySelectorAll("main.grid")),
  taskForm: document.getElementById("taskForm"),
  taskFormTitle: document.getElementById("taskFormTitle"),
  taskSubmitBtn: document.getElementById("taskSubmitBtn"),
  taskResetBtn: document.getElementById("taskResetBtn"),
  taskStatus: document.getElementById("taskStatus"),
  taskId: document.getElementById("taskId"),
  taskName: document.getElementById("taskName"),
  taskInstruction: document.getElementById("taskInstruction"),
  intervalMinutes: document.getElementById("intervalMinutes"),
  onceAt: document.getElementById("onceAt"),
  taskEnabled: document.getElementById("taskEnabled"),
  taskList: document.getElementById("taskList"),
  taskCounter: document.getElementById("taskCounter"),
  taskEmpty: document.getElementById("taskEmpty"),
  configExportBtn: document.getElementById("configExportBtn"),
  configImportBtn: document.getElementById("configImportBtn"),
  configImportInput: document.getElementById("configImportInput"),
  configPassword: document.getElementById("configPassword"),
  configStatus: document.getElementById("configStatus"),
  taskExportBtn: document.getElementById("taskExportBtn"),
  taskImportBtn: document.getElementById("taskImportBtn"),
  taskImportInput: document.getElementById("taskImportInput"),
  moreForm: document.getElementById("moreForm"),
  moreStatus: document.getElementById("moreStatus"),
  notionToken: document.getElementById("notionToken"),
  moreSubmitBtn: document.getElementById("moreSubmitBtn"),
  moreResetBtn: document.getElementById("moreResetBtn"),
  deviceForm: document.getElementById("deviceForm"),
  deviceStatus: document.getElementById("deviceStatus"),
  deviceBindCode: document.getElementById("deviceBindCode"),
  deviceId: document.getElementById("deviceId"),
  deviceToken: document.getElementById("deviceToken"),
  deviceBindBtn: document.getElementById("deviceBindBtn"),
  deviceUnbindBtn: document.getElementById("deviceUnbindBtn"),
};

const createId = () => crypto?.randomUUID?.() ?? `cfg_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`;
const createTaskId = () => `task_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`;

const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
const SCRYPT_N = 16384;
const SCRYPT_R = 8;
const SCRYPT_P = 1;
const KEY_BYTES = 32;
const TAG_BYTES = 16;

const getConfigPassword = () => (elements.configPassword?.value || "").trim();

const clearConfigPassword = () => {
  state.configPassword = "";
  if (elements.configPassword) {
    elements.configPassword.value = "";
  }
};

const bytesToBase64 = (bytes) => {
  let binary = "";
  const chunkSize = 0x8000;
  for (let i = 0; i < bytes.length; i += chunkSize) {
    binary += String.fromCharCode(...bytes.slice(i, i + chunkSize));
  }
  return btoa(binary);
};

const base64ToBytes = (value) => {
  const binary = atob(value);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i += 1) {
    bytes[i] = binary.charCodeAt(i);
  }
  return bytes;
};

const randomBytes = (size) => {
  const buf = new Uint8Array(size);
  crypto.getRandomValues(buf);
  return buf;
};

const deriveKey = async (password, salt) => {
  const passBytes = textEncoder.encode(password);
  const keyBytes = await scrypt(passBytes, salt, SCRYPT_N, SCRYPT_R, SCRYPT_P, KEY_BYTES);
  return crypto.subtle.importKey("raw", keyBytes, "AES-GCM", false, ["encrypt", "decrypt"]);
};

const encryptConfigPayload = async (payload, password) => {
  const salt = randomBytes(16);
  const iv = randomBytes(12);
  const key = await deriveKey(password, salt);
  const plaintext = textEncoder.encode(JSON.stringify(payload));
  const cipherBuf = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, plaintext);
  const cipherBytes = new Uint8Array(cipherBuf);
  const tag = cipherBytes.slice(cipherBytes.length - TAG_BYTES);
  const data = cipherBytes.slice(0, cipherBytes.length - TAG_BYTES);
  return {
    encrypted: true,
    version: 1,
    kdf: "scrypt",
    cipher: "aes-256-gcm",
    salt: bytesToBase64(salt),
    iv: bytesToBase64(iv),
    tag: bytesToBase64(tag),
    data: bytesToBase64(data),
    exportedAt: payload.exportedAt,
  };
};

const decryptConfigPayload = async (wrapper, password) => {
  const salt = base64ToBytes(wrapper.salt || "");
  const iv = base64ToBytes(wrapper.iv || "");
  const tag = base64ToBytes(wrapper.tag || "");
  const data = base64ToBytes(wrapper.data || "");
  if (!salt.length || !iv.length || !tag.length || !data.length) {
    throw new Error("Invalid encrypted payload");
  }
  const key = await deriveKey(password, salt);
  const combined = new Uint8Array(data.length + tag.length);
  combined.set(data, 0);
  combined.set(tag, data.length);
  const plainBuf = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, combined);
  return JSON.parse(textDecoder.decode(plainBuf));
};

const maskApiKey = (value) => {
  if (!value) return "—";
  return value.length <= 8 ? `${value.slice(0, 2)}••••` : `${value.slice(0, 4)}••••${value.slice(-2)}`;
};

const formatHost = (url) => {
  try {
    return new URL(url).host;
  } catch (_) {
    return url;
  }
};

const setStatus = (messageOrKey, variant = "info", vars) => {
  if (!elements.status) return;
  let text = "";
  if (typeof i18n !== "undefined" && i18n.t) {
    if (typeof messageOrKey === "string") {
      text = i18n.t(messageOrKey, vars) || messageOrKey;
    } else if (messageOrKey && messageOrKey.key) {
      text = i18n.t(messageOrKey.key, messageOrKey.vars) || "";
    } else {
      text = String(messageOrKey);
    }
  } else {
    text = typeof messageOrKey === "string" ? messageOrKey : messageOrKey && messageOrKey.key ? messageOrKey.key : String(messageOrKey);
  }

  elements.status.textContent = text;
  elements.status.style.color = variant === "error" ? "var(--danger)" : "var(--muted)";

  if (state.statusTimer) {
    clearTimeout(state.statusTimer);
  }

  state.statusTimer = setTimeout(() => {
    elements.status.textContent = "";
  }, STATUS_DURATION);
};

const setConfigStatus = (message, variant = "info") => {
  if (!elements.configStatus) return;
  elements.configStatus.textContent = message;
  elements.configStatus.style.color = variant === "error" ? "var(--danger)" : "var(--muted)";
  if (state.configStatusTimer) {
    clearTimeout(state.configStatusTimer);
  }
  state.configStatusTimer = setTimeout(() => {
    elements.configStatus.textContent = "";
  }, STATUS_DURATION);
};

const setTaskStatus = (messageOrKey, variant = "info", vars) => {
  if (!elements.taskStatus) return;
  let text = "";
  if (typeof i18n !== "undefined" && i18n.t) {
    if (typeof messageOrKey === "string") {
      text = i18n.t(messageOrKey, vars) || messageOrKey;
    } else if (messageOrKey && messageOrKey.key) {
      text = i18n.t(messageOrKey.key, messageOrKey.vars) || "";
    } else {
      text = String(messageOrKey);
    }
  } else {
    text = typeof messageOrKey === "string" ? messageOrKey : messageOrKey && messageOrKey.key ? messageOrKey.key : String(messageOrKey);
  }

  elements.taskStatus.textContent = text;
  elements.taskStatus.style.color = variant === "error" ? "var(--danger)" : "var(--muted)";

  if (state.taskStatusTimer) {
    clearTimeout(state.taskStatusTimer);
  }

  state.taskStatusTimer = setTimeout(() => {
    elements.taskStatus.textContent = "";
  }, STATUS_DURATION);
};

const setMoreStatus = (messageOrKey, variant = "info", vars) => {
  if (!elements.moreStatus) return;
  let text = "";
  if (typeof i18n !== "undefined" && i18n.t) {
    if (typeof messageOrKey === "string") {
      text = i18n.t(messageOrKey, vars) || messageOrKey;
    } else if (messageOrKey && messageOrKey.key) {
      text = i18n.t(messageOrKey.key, messageOrKey.vars) || "";
    } else {
      text = String(messageOrKey);
    }
  } else {
    text = typeof messageOrKey === "string" ? messageOrKey : messageOrKey && messageOrKey.key ? messageOrKey.key : String(messageOrKey);
  }

  elements.moreStatus.textContent = text;
  elements.moreStatus.style.color = variant === "error" ? "var(--danger)" : "var(--muted)";

  if (state.moreStatusTimer) {
    clearTimeout(state.moreStatusTimer);
  }

  state.moreStatusTimer = setTimeout(() => {
    elements.moreStatus.textContent = "";
  }, STATUS_DURATION);
};

const setDeviceStatus = (messageOrKey, variant = "info", vars) => {
  if (!elements.deviceStatus) return;
  let text = "";
  if (typeof i18n !== "undefined" && i18n.t) {
    if (typeof messageOrKey === "string") {
      text = i18n.t(messageOrKey, vars) || messageOrKey;
    } else if (messageOrKey && messageOrKey.key) {
      text = i18n.t(messageOrKey.key, messageOrKey.vars) || "";
    } else {
      text = String(messageOrKey);
    }
  } else {
    text = typeof messageOrKey === "string" ? messageOrKey : messageOrKey && messageOrKey.key ? messageOrKey.key : String(messageOrKey);
  }

  elements.deviceStatus.textContent = text;
  elements.deviceStatus.style.color = variant === "error" ? "var(--danger)" : "var(--muted)";

  if (state.deviceStatusTimer) {
    clearTimeout(state.deviceStatusTimer);
  }

  state.deviceStatusTimer = setTimeout(() => {
    elements.deviceStatus.textContent = "";
  }, STATUS_DURATION);
};

const downloadJson = (filename, payload) => {
  try {
    const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
    const url = URL.createObjectURL(blob);
    const link = document.createElement("a");
    link.href = url;
    link.download = filename;
    link.click();
    URL.revokeObjectURL(url);
  } catch (error) {
    console.error("Download JSON failed", error);
  }
};

const normalizeConfigImport = (data) => {
  if (Array.isArray(data)) return data;
  if (Array.isArray(data?.configs)) return data.configs;
  return [];
};

const normalizeTaskImport = (data) => {
  if (Array.isArray(data)) return data;
  if (Array.isArray(data?.tasks)) return data.tasks;
  return [];
};

const toIsoStringSafe = (value) => {
  if (!value) return undefined;
  const d = new Date(value);
  return Number.isNaN(d.getTime()) ? undefined : d.toISOString();
};

const updateCounter = () => {
  const count = state.configs.length;
  if (typeof i18n !== "undefined" && i18n.t) {
    elements.configCounter.textContent = i18n.t("configCounter", { count });
  } else {
    elements.configCounter.textContent = `${count} ${count === 1 ? "config" : "configs"}`;
  }
};

const renderConfigs = () => {
  elements.configList.innerHTML = "";

  if (!state.configs.length) {
    elements.emptyState.style.display = "block";
    updateCounter();
    return;
  }

  elements.emptyState.style.display = "none";

  state.configs
    .sort((a, b) => new Date(b.updatedAt ?? b.createdAt) - new Date(a.updatedAt ?? a.createdAt))
    .forEach((cfg) => {
      const card = document.createElement("article");
      card.className = "config-card";
      card.dataset.id = cfg.id;

      const displayName = cfg.name?.trim() || formatHost(cfg.baseURL);

      card.innerHTML = `
        <h3>${displayName}</h3>
        <dl>
          <div>
            <dt>${typeof i18n !== "undefined" && i18n.t ? i18n.t("dtModelName") : "Model Name"}</dt>
            <dd>${cfg.name || "—"}</dd>
          </div>
          <div>
            <dt>${typeof i18n !== "undefined" && i18n.t ? i18n.t("dtApiKey") : "API Key"}</dt>
            <dd>${maskApiKey(cfg.apiKey)}</dd>
          </div>
          <div>
            <dt>${typeof i18n !== "undefined" && i18n.t ? i18n.t("dtBaseUrl") : "Base URL"}</dt>
            <dd>${cfg.baseURL}</dd>
          </div>
          <div>
            <dt>${typeof i18n !== "undefined" && i18n.t ? i18n.t("dtApiVersion") : "API Version"}</dt>
            <dd>${cfg.apiVersion || "—"}</dd>
          </div>
          <div>
            <dt>${typeof i18n !== "undefined" && i18n.t ? i18n.t("dtModel") : "Model"}</dt>
            <dd>${cfg.model}</dd>
          </div>
          <div>
            <dt>${typeof i18n !== "undefined" && i18n.t ? i18n.t("dtLlmType") : "LLM Type"}</dt>
            <dd>${cfg.llmType ?? "talk"}</dd>
          </div>
        </dl>
        <div class="card-actions">
          <button type="button" data-action="edit" data-id="${cfg.id}" class="secondary">${typeof i18n !== "undefined" && i18n.t ? i18n.t("btnEdit") : "Edit"}</button>
          <button type="button" data-action="delete" data-id="${cfg.id}" class="danger">${typeof i18n !== "undefined" && i18n.t ? i18n.t("btnDelete") : "Delete"}</button>
        </div>
      `;

      elements.configList.appendChild(card);
    });

  updateCounter();
};

const updateTaskCounter = () => {
  const count = state.tasks.length;
  if (typeof i18n !== "undefined" && i18n.t) {
    elements.taskCounter.textContent = i18n.t("taskCounter", { count });
  } else {
    elements.taskCounter.textContent = `${count} ${count === 1 ? "task" : "tasks"}`;
  }
};

const formatDateTimeLocal = (iso) => {
  if (!iso) return "—";
  try {
    const d = new Date(iso);
    const yyyy = d.getFullYear();
    const mm = String(d.getMonth() + 1).padStart(2, "0");
    const dd = String(d.getDate()).padStart(2, "0");
    const hh = String(d.getHours()).padStart(2, "0");
    const mi = String(d.getMinutes()).padStart(2, "0");
    return `${yyyy}-${mm}-${dd} ${hh}:${mi}`;
  } catch (_) {
    return iso;
  }
};

const renderTasks = () => {
  elements.taskList.innerHTML = "";

  if (!state.tasks.length) {
    elements.taskEmpty.style.display = "block";
    updateTaskCounter();
    return;
  }

  elements.taskEmpty.style.display = "none";

  state.tasks
    .sort((a, b) => new Date(b.updatedAt ?? b.createdAt ?? 0) - new Date(a.updatedAt ?? a.createdAt ?? 0))
    .forEach((task) => {
      const card = document.createElement("article");
      card.className = "config-card";
      card.dataset.id = task.id;

      const interval = task.intervalMinutes ? (typeof i18n !== "undefined" && i18n.t ? i18n.t("minutesShort", { n: task.intervalMinutes }) : `${task.intervalMinutes} min`) : "—";
      const onceAt = task.onceAt ? formatDateTimeLocal(task.onceAt) : "—";
      const enabled = task.enabled !== false;
      const createdAt = task.createdAt ? formatDateTimeLocal(task.createdAt) : "—";
      const lastRunAt = task.lastRunAt ? formatDateTimeLocal(task.lastRunAt) : "—";

      card.innerHTML = `
        <h3>${task.name || (typeof i18n !== "undefined" && i18n.t ? i18n.t("untitledTask") : "Untitled task")}</h3>
        <dl>
          <div>
            <dt>${typeof i18n !== "undefined" && i18n.t ? i18n.t("dtInstruction") : "Instruction"}</dt>
            <dd>${task.instruction || "—"}</dd>
          </div>
          <div>
            <dt>${typeof i18n !== "undefined" && i18n.t ? i18n.t("dtInterval") : "Interval"}</dt>
            <dd>${interval}</dd>
          </div>
          <div>
            <dt>${typeof i18n !== "undefined" && i18n.t ? i18n.t("dtSchedule") : "Schedule"}</dt>
            <dd>${onceAt}</dd>
          </div>
          <div>
            <dt>${typeof i18n !== "undefined" && i18n.t ? i18n.t("dtStatus") : "Status"}</dt>
            <dd>${enabled ? (typeof i18n !== "undefined" && i18n.t ? i18n.t("enabled") : "Enabled") : typeof i18n !== "undefined" && i18n.t ? i18n.t("disabled") : "Disabled"}</dd>
          </div>
          <div>
            <dt>${typeof i18n !== "undefined" && i18n.t ? i18n.t("dtCreated") : "Created"}</dt>
            <dd>${createdAt}</dd>
          </div>
          <div>
            <dt>${typeof i18n !== "undefined" && i18n.t ? i18n.t("dtLastRun") : "Last run"}</dt>
            <dd>${lastRunAt}</dd>
          </div>
        </dl>
        <div class="card-actions">
          <button type="button" data-action="task-run" data-id="${task.id}" class="secondary">${typeof i18n !== "undefined" && i18n.t ? i18n.t("btnRunNow") : "Run now"}</button>
          <button type="button" data-action="task-edit" data-id="${task.id}" class="secondary">${typeof i18n !== "undefined" && i18n.t ? i18n.t("btnEdit") : "Edit"}</button>
          <button type="button" data-action="task-toggle" data-id="${task.id}" class="secondary">${
            enabled ? (typeof i18n !== "undefined" && i18n.t ? i18n.t("btnPause") : "Pause") : typeof i18n !== "undefined" && i18n.t ? i18n.t("btnResume") : "Resume"
          }</button>
          <button type="button" data-action="task-delete" data-id="${task.id}" class="danger">${typeof i18n !== "undefined" && i18n.t ? i18n.t("btnDelete") : "Delete"}</button>
        </div>
      `;

      elements.taskList.appendChild(card);
    });

  updateTaskCounter();
};

const loadConfigs = async () => {
  try {
    state.configs = await storage.get();
    renderConfigs();
  } catch (error) {
    console.error("Failed to load configs", error);
    setStatus("unableToLoadConfigs", "error");
  }
};

const loadTasks = async () => {
  try {
    state.tasks = await taskApi.list();
    renderTasks();
  } catch (error) {
    console.error("Failed to load tasks", error);
    setTaskStatus("Unable to load tasks", "error");
  }
};

const loadMoreConfig = async () => {
  try {
    const cfg = (await moreStorage.get()) || {};
    state.moreConfig = { notionToken: cfg.notionToken || "" };
    if (elements.notionToken) {
      elements.notionToken.value = state.moreConfig.notionToken || "";
    }
  } catch (error) {
    console.error("Failed to load more config", error);
    setMoreStatus("unableToLoadMore", "error");
  }
};

const loadDeviceConfig = async () => {
  try {
    const cfg = (await deviceStorage.get()) || {};
    state.deviceConfig = {
      deviceId: cfg.deviceId || "",
      deviceToken: cfg.deviceToken || "",
      deviceType: cfg.deviceType || "extension",
    };
    if (elements.deviceId) elements.deviceId.value = state.deviceConfig.deviceId || "";
    if (elements.deviceToken) elements.deviceToken.value = state.deviceConfig.deviceToken || "";
  } catch (error) {
    console.error("Failed to load device config", error);
    setDeviceStatus("Unable to load device config", "error");
  }
};

const persistDeviceConfig = async () => {
  try {
    await deviceStorage.set({
      deviceId: state.deviceConfig.deviceId,
      deviceToken: state.deviceConfig.deviceToken,
      deviceType: state.deviceConfig.deviceType,
    });
  } catch (error) {
    console.error("Failed to save device config", error);
    setDeviceStatus("Save failed", "error");
    throw error;
  }
};

const handleDeviceBind = async () => {
  const serverBase = DEFAULT_SERVER_BASE;
  const code = elements.deviceBindCode?.value?.trim();
  if (!code) {
    setDeviceStatus("Bind code required", "error");
    return;
  }
  if (!serverBase) {
    setDeviceStatus("Server base unavailable", "error");
    return;
  }
  try {
    const response = await fetch(`${serverBase}/api/blackeagle/devices/bind`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        code,
        deviceType: "extension",
        deviceName: "BlackEagle Extension",
        deviceMeta: { browser: navigator.userAgent },
      }),
    });
    const payload = await response.json();
    if (!payload?.ok) {
      setDeviceStatus(payload?.error || "Bind failed", "error");
      return;
    }
    state.deviceConfig = {
      deviceId: payload.deviceId || "",
      deviceToken: payload.deviceToken || "",
      deviceType: "extension",
    };
    if (elements.deviceId) elements.deviceId.value = state.deviceConfig.deviceId;
    if (elements.deviceToken) elements.deviceToken.value = state.deviceConfig.deviceToken;
    await persistDeviceConfig();
    setDeviceStatus("Device bound");
  } catch (error) {
    console.error("Bind device failed", error);
    setDeviceStatus("Bind failed", "error");
  }
};

const handleDeviceUnbind = async () => {
  const serverBase = DEFAULT_SERVER_BASE;
  const deviceId = state.deviceConfig.deviceId || "";
  const deviceToken = state.deviceConfig.deviceToken || "";
  if (!deviceId || !deviceToken) {
    setDeviceStatus("No device bound", "error");
    return;
  }
  if (!serverBase) {
    setDeviceStatus("Server base unavailable", "error");
    return;
  }
  if (!window.confirm("Unbind the current device?")) {
    return;
  }
  try {
    const response = await fetch(`${serverBase}/api/blackeagle/devices/unbind`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-device-id": deviceId,
        "x-device-token": deviceToken,
      },
      body: "{}",
    });
    const payload = await response.json();
    if (!payload?.ok) {
      setDeviceStatus(payload?.error || "Unbind failed", "error");
      return;
    }
    state.deviceConfig = {
      ...state.deviceConfig,
      deviceId: "",
      deviceToken: "",
    };
    if (elements.deviceId) elements.deviceId.value = "";
    if (elements.deviceToken) elements.deviceToken.value = "";
    if (elements.deviceBindCode) elements.deviceBindCode.value = "";
    await persistDeviceConfig();
    setDeviceStatus("Device unbound");
  } catch (error) {
    console.error("Unbind device failed", error);
    setDeviceStatus("Unbind failed", "error");
  }
};

const persistConfigs = async () => {
  try {
    await storage.set(state.configs);
    renderConfigs();
  } catch (error) {
    console.error("Failed to save configs", error);
    setStatus("savingFailed", "error");
    throw error;
  }
};

const resetForm = () => {
  state.editingId = null;
  elements.form.reset();
  elements.llmType.value = "talk";
  elements.apiVersion.value = "";
  elements.configId.value = "";
  elements.formTitle.textContent = typeof i18n !== "undefined" && i18n.t ? i18n.t("formTitle") : "Create configuration";
  elements.submitBtn.textContent = typeof i18n !== "undefined" && i18n.t ? i18n.t("submitSave") : "Save configuration";
};

const resetMoreForm = async () => {
  if (elements.moreForm) elements.moreForm.reset();
  state.moreConfig = { notionToken: "" };
  if (elements.notionToken) elements.notionToken.value = "";
  try {
    await moreStorage.set(state.moreConfig);
  } catch (error) {
    console.error("Reset more config failed", error);
  }
  setMoreStatus("moreCleared");
};

const handleConfigExport = async () => {
  const password = getConfigPassword();
  if (!password) {
    setConfigStatus("Export failed: password required.", "error");
    return;
  }
  try {
    const payload = {
      version: 1,
      exportedAt: new Date().toISOString(),
      settings: {
        temperature: 0.7,
        maxTokens: 1024,
        notionConfig: {
          token: state.moreConfig?.notionToken || "",
          parentPageId: "",
        },
        serperConfig: { apiKey: "" },
        closeToTray: true,
        deviceConfig: {
          deviceId: state.deviceConfig?.deviceId || "",
          deviceToken: state.deviceConfig?.deviceToken || "",
          deviceType: state.deviceConfig?.deviceType || "extension",
        },
      },
      configs: state.configs || [],
    };
    const encrypted = await encryptConfigPayload(payload, password);
    downloadJson("blackeagle-all-configs.json", encrypted);
    clearConfigPassword();
    setConfigStatus("Export succeeded.");
  } catch (error) {
    console.error("Export configs failed", error);
    setConfigStatus("Export failed.", "error");
  }
};

const handleConfigImport = async (file) => {
  if (!file) return;
  try {
    const rawText = await file.text();
    const json = JSON.parse(rawText);
    let payload = json;
    if (json?.encrypted) {
      const password = getConfigPassword();
      if (!password) {
        setConfigStatus("Import failed: password required.", "error");
        return;
      }
      try {
        payload = await decryptConfigPayload(json, password);
      } catch (error) {
        console.error("Decrypt configs failed", error);
        setConfigStatus("Import failed: check password.", "error");
        return;
      }
    }

    const incoming = normalizeConfigImport(payload);
    if (!incoming.length) {
      setConfigStatus("Import failed: no configs found.", "error");
      return;
    }

    const now = new Date().toISOString();
    const sanitized = incoming
      .map((cfg) => {
        const apiKey = (cfg.apiKey || "").trim();
        const baseURL = (cfg.baseURL || "").trim();
        const model = (cfg.model || "").trim();
        const llmType = (cfg.llmType || "talk").trim();
        if (!apiKey || !baseURL || !model || !llmType) return null;
        return {
          ...cfg,
          id: cfg.id || createId(),
          name: (cfg.name || "").trim() || formatHost(baseURL),
          apiKey,
          baseURL,
          model,
          llmType,
          createdAt: cfg.createdAt || now,
          updatedAt: now,
        };
      })
      .filter(Boolean);

    if (!sanitized.length) {
      setConfigStatus("Import failed: no valid configs.", "error");
      return;
    }

    const merged = new Map(state.configs.map((cfg) => [cfg.id, cfg]));
    sanitized.forEach((cfg) => {
      const existing = merged.get(cfg.id);
      merged.set(cfg.id, { ...existing, ...cfg, createdAt: existing?.createdAt || cfg.createdAt });
    });
    state.configs = Array.from(merged.values());
    await persistConfigs();
    notifyRefresh();

    const settings = payload?.settings || {};
    if (settings?.notionConfig?.token !== undefined) {
      state.moreConfig = { notionToken: settings.notionConfig.token || "" };
      if (elements.notionToken) elements.notionToken.value = state.moreConfig.notionToken;
      await moreStorage.set(state.moreConfig);
    }
    if (settings?.deviceConfig) {
      state.deviceConfig = {
        deviceId: settings.deviceConfig.deviceId || "",
        deviceToken: settings.deviceConfig.deviceToken || "",
        deviceType: settings.deviceConfig.deviceType || "extension",
      };
      if (elements.deviceId) elements.deviceId.value = state.deviceConfig.deviceId;
      if (elements.deviceToken) elements.deviceToken.value = state.deviceConfig.deviceToken;
      await persistDeviceConfig();
    }

    clearConfigPassword();
    setConfigStatus(`Import succeeded: ${sanitized.length} configs.`);
  } catch (error) {
    console.error("Import configs failed", error);
    setConfigStatus("Import failed: invalid JSON.", "error");
  }
};

const resetTaskForm = () => {
  state.editingTaskId = null;
  elements.taskForm.reset();
  elements.taskEnabled.checked = true;
  elements.taskId.value = "";
  elements.onceAt.value = "";
  elements.taskFormTitle.textContent = typeof i18n !== "undefined" && i18n.t ? i18n.t("taskFormTitle") : "Create Task";
  elements.taskSubmitBtn.textContent = typeof i18n !== "undefined" && i18n.t ? i18n.t("submitSave") : "Save Task";
};

const populateForm = (config) => {
  elements.configId.value = config.id;
  elements.name.value = config.name ?? formatHost(config.baseURL);
  elements.apiKey.value = config.apiKey;
  elements.baseURL.value = config.baseURL;
  elements.apiVersion.value = config.apiVersion ?? "";
  elements.model.value = config.model;
  elements.llmType.value = config.llmType ?? "talk";
  state.editingId = config.id;
  elements.formTitle.textContent = typeof i18n !== "undefined" && i18n.t ? i18n.t("submitUpdate") : "Update configuration";
  elements.submitBtn.textContent = typeof i18n !== "undefined" && i18n.t ? i18n.t("submitUpdate") : "Update configuration";
};

const populateTaskForm = (task) => {
  state.editingTaskId = task.id;
  elements.taskId.value = task.id;
  elements.taskName.value = task.name || "";
  elements.taskInstruction.value = task.instruction || "";
  elements.intervalMinutes.value = task.intervalMinutes ?? "";
  elements.onceAt.value = task.onceAt ? task.onceAt.slice(0, 16) : ""; // ISO to datetime-local
  elements.taskEnabled.checked = task.enabled !== false;
  elements.taskFormTitle.textContent = typeof i18n !== "undefined" && i18n.t ? i18n.t("taskFormTitle") : "Update Task";
  elements.taskSubmitBtn.textContent = typeof i18n !== "undefined" && i18n.t ? i18n.t("submitUpdate") : "Update Task";
};

const handleTaskExport = () => {
  if (!state.tasks.length) {
    setTaskStatus("noTasksExport", "error");
    return;
  }
  downloadJson("blackeagle-tasks.json", { version: 1, exportedAt: new Date().toISOString(), tasks: state.tasks });
  setTaskStatus("tasksExported");
};

const handleTaskImport = async (file) => {
  if (!file) return;
  try {
    const rawText = await file.text();
    const json = JSON.parse(rawText);
    const incoming = normalizeTaskImport(json);
    if (!incoming.length) {
      setTaskStatus("importNoTasks", "error");
      return;
    }

    const now = new Date().toISOString();
    const sanitized = incoming
      .map((task) => {
        const instruction = (task.instruction || "").trim();
        if (!instruction) return null;
        const interval = task.intervalMinutes !== undefined ? Number(task.intervalMinutes) : undefined;
        const intervalMinutes = Number.isFinite(interval) ? Math.max(5, interval) : undefined;
        const onceAt = toIsoStringSafe(task.onceAt);

        return {
          ...task,
          id: task.id || createTaskId(),
          name: (task.name || "").trim() || "Untitled Task",
          instruction,
          intervalMinutes,
          onceAt,
          enabled: task.enabled !== false,
          createdAt: task.createdAt || now,
          updatedAt: now,
        };
      })
      .filter(Boolean);

    if (!sanitized.length) {
      setTaskStatus("noValidTasksImport", "error");
      return;
    }

    for (const task of sanitized) {
      await taskApi.upsert(task);
    }

    state.tasks = await taskApi.list();
    renderTasks();
    setTaskStatus({ key: "importedTasks", vars: { n: sanitized.length } });
  } catch (error) {
    console.error("Import tasks failed", error);
    setTaskStatus("importFailedInvalidJson", "error");
  }
};

const handleMoreSubmit = async (event) => {
  event.preventDefault();
  const notionToken = elements.notionToken?.value.trim();

  if (!notionToken) {
    setMoreStatus("notionTokenRequired", "error");
    return;
  }

  const payload = { notionToken };
  try {
    await moreStorage.set(payload);
    state.moreConfig = payload;
    setMoreStatus("notionSaved");
    notifyRefresh();
  } catch (error) {
    console.error("Save Notion token failed", error);
    setMoreStatus("saveFailed", "error");
  }
};

const handleSubmit = async (event) => {
  event.preventDefault();
  const name = elements.name.value.trim();
  const apiKey = elements.apiKey.value.trim();
  const baseURL = elements.baseURL.value.trim();
  const apiVersion = elements.apiVersion.value.trim();
  const model = elements.model.value.trim();
  const llmType = (elements.llmType.value || "talk").trim();

  if (!name || !apiKey || !baseURL || !model || !llmType) {
    setStatus("pleaseFillRequired", "error");
    return;
  }

  const timestamp = new Date().toISOString();

  if (state.editingId) {
    state.configs = state.configs.map((cfg) => (cfg.id === state.editingId ? { ...cfg, name, apiKey, baseURL, apiVersion: apiVersion || undefined, model, llmType, updatedAt: timestamp } : cfg));
    await persistConfigs();
    notifyRefresh();
    setStatus("configUpdated");
  } else {
    const newConfig = {
      id: createId(),
      name,
      apiKey,
      baseURL,
      apiVersion: apiVersion || undefined,
      model,
      llmType,
      createdAt: timestamp,
      updatedAt: timestamp,
    };
    state.configs = [...state.configs, newConfig];
    await persistConfigs();
    notifyRefresh();
    setStatus("configSaved");
  }

  resetForm();
};

const handleTaskSubmit = async (event) => {
  event.preventDefault();
  const name = elements.taskName.value.trim();
  const instruction = elements.taskInstruction.value.trim();
  const intervalMinutesRaw = elements.intervalMinutes.value;
  const onceAtRaw = elements.onceAt.value;
  const enabled = elements.taskEnabled.checked;

  if (!instruction) {
    setTaskStatus("Please provide a task instruction", "error");
    return;
  }

  // Enforce minimum 5-minute interval when provided
  if (intervalMinutesRaw) {
    const n = Number(intervalMinutesRaw);
    if (!Number.isFinite(n) || n < 5) {
      setTaskStatus("Interval cannot be less than 5 minutes", "error");
      return;
    }
  }

  const task = {
    id: state.editingTaskId || undefined,
    name: name || "Untitled Task",
    instruction,
    intervalMinutes: intervalMinutesRaw ? Number(intervalMinutesRaw) : undefined,
    onceAt: onceAtRaw ? new Date(onceAtRaw).toISOString() : undefined,
    enabled,
    // timestamps provided for fallback/local preview; background may override
    updatedAt: new Date().toISOString(),
    createdAt: state.editingTaskId ? undefined : new Date().toISOString(),
  };

  try {
    await taskApi.upsert(task);
    setTaskStatus("Task saved");
    state.tasks = await taskApi.list();
    renderTasks();
    resetTaskForm();
  } catch (error) {
    console.error("Save task failed", error);
    setTaskStatus("Save failed", "error");
  }
};

const handleListClick = async (event) => {
  const button = event.target.closest("button[data-action]");
  if (!button) return;

  const { action, id } = button.dataset;
  const config = state.configs.find((cfg) => cfg.id === id);
  if (!config) return;

  if (action === "edit") {
    populateForm(config);
    elements.apiKey.focus();
    return;
  }

  if (action === "delete") {
    const confirmed = confirm(typeof i18n !== "undefined" && i18n.t ? i18n.t("confirmDeleteConfig") : "Delete this configuration? This cannot be undone.");
    if (!confirmed) return;
    state.configs = state.configs.filter((cfg) => cfg.id !== id);
    await persistConfigs();
    notifyRefresh();
    setStatus("configRemoved");
    if (state.editingId === id) {
      resetForm();
    }
  }
};

const handleTaskListClick = async (event) => {
  const button = event.target.closest("button[data-action]");
  if (!button) return;

  const { action, id } = button.dataset;
  const task = state.tasks.find((t) => t.id === id);
  if (!task) return;

  if (action === "task-edit") {
    populateTaskForm(task);
    elements.taskInstruction.focus();
    return;
  }

  if (action === "task-delete") {
    const confirmed = confirm(typeof i18n !== "undefined" && i18n.t ? i18n.t("confirmDeleteTask") : "Delete this task?");
    if (!confirmed) return;
    try {
      await taskApi.remove(id);
      state.tasks = await taskApi.list();
      renderTasks();
      if (state.editingTaskId === id) resetTaskForm();
      setTaskStatus("taskDeleted");
    } catch (error) {
      console.error("Delete task failed", error);
      setTaskStatus("saveFailed", "error");
    }
    return;
  }

  if (action === "task-toggle") {
    try {
      const nextEnabled = task.enabled === false ? true : false;
      await taskApi.upsert({ ...task, enabled: nextEnabled });
      state.tasks = await taskApi.list();
      renderTasks();
      setTaskStatus(nextEnabled ? "taskResumed" : "taskPaused");
    } catch (error) {
      console.error("Toggle task failed", error);
      setTaskStatus("saveFailed", "error");
    }
    return;
  }

  if (action === "task-run") {
    try {
      await taskApi.runNow(id);
      setTaskStatus("taskSaved");
      // Refresh tasks to reflect latest execution time
      state.tasks = await taskApi.list();
      renderTasks();
    } catch (error) {
      console.error("Run task failed", error);
      setTaskStatus("saveFailed", "error");
    }
    return;
  }
};

const init = () => {
  elements.form.addEventListener("submit", handleSubmit);
  elements.resetBtn.addEventListener("click", () => {
    resetForm();
    setStatus("formCleared");
  });
  elements.configList.addEventListener("click", handleListClick);
  elements.taskForm.addEventListener("submit", handleTaskSubmit);
  elements.taskResetBtn.addEventListener("click", () => {
    resetTaskForm();
    setTaskStatus("formReset");
  });
  elements.taskList.addEventListener("click", handleTaskListClick);

  elements.configExportBtn?.addEventListener("click", handleConfigExport);
  elements.configImportBtn?.addEventListener("click", () => elements.configImportInput?.click());
  elements.configImportInput?.addEventListener("change", async (event) => {
    const file = event.target.files?.[0];
    await handleConfigImport(file);
    event.target.value = "";
  });

  elements.taskExportBtn?.addEventListener("click", handleTaskExport);
  elements.taskImportBtn?.addEventListener("click", () => elements.taskImportInput?.click());
  elements.taskImportInput?.addEventListener("change", async (event) => {
    const file = event.target.files?.[0];
    await handleTaskImport(file);
    event.target.value = "";
  });

  elements.moreForm?.addEventListener("submit", handleMoreSubmit);
  elements.moreResetBtn?.addEventListener("click", () => {
    resetMoreForm();
  });

  elements.deviceBindBtn?.addEventListener("click", () => {
    handleDeviceBind().catch(() => {});
  });
  elements.deviceUnbindBtn?.addEventListener("click", () => {
    handleDeviceUnbind().catch(() => {});
  });

  elements.tabs.forEach((btn) => {
    btn.addEventListener("click", () => {
      const target = btn.dataset.section;
      state.section = target;
      elements.tabs.forEach((b) => b.classList.toggle("active", b.dataset.section === target));
      elements.sections.forEach((sec) => {
        const isActive = sec.dataset.section === target;
        sec.style.display = isActive ? "grid" : "none";
        sec.classList.toggle("is-hidden", !isActive);
        if (sec.hasAttribute("aria-hidden")) {
          sec.setAttribute("aria-hidden", String(!isActive));
        }
      });
    });
  });

  // re-apply translations and re-render dynamic content when language changes
  if (typeof window !== "undefined") {
    window.addEventListener("i18n:change", function () {
      if (typeof i18n !== "undefined" && i18n.apply) i18n.apply();
      renderConfigs();
      renderTasks();
      updateCounter();
      updateTaskCounter();
    });
  }

  loadConfigs();
  loadTasks();
  loadMoreConfig();
  loadDeviceConfig();
};

document.addEventListener("DOMContentLoaded", init);
