(function attachAstrBotPluginPageBridge() {
  const CHANNEL = "astrbot-plugin-page";
  const SELF_ORIGIN = window.location.origin;
  const pendingRequests = new Map();
  const sseHandlers = new Map();
  const contextHandlers = new Set();
  let requestCounter = 0;
  let subscriptionCounter = 0;
  let context = null;
  let parentOrigin = null;
  let resolveReady;
  const readyPromise = new Promise((resolve) => {
    resolveReady = resolve;
  });

  function getTargetOrigin() {
    if (
      typeof parentOrigin === "string" &&
      parentOrigin &&
      parentOrigin !== "null"
    ) {
      return parentOrigin;
    }
    if (SELF_ORIGIN !== "null") {
      return SELF_ORIGIN;
    }
    return "*";
  }

  function isAllowedParentOrigin(origin) {
    if (typeof origin !== "string" || !origin) {
      return false;
    }
    if (parentOrigin) {
      return origin === parentOrigin;
    }
    if (SELF_ORIGIN === "null") {
      return true;
    }
    return origin === SELF_ORIGIN;
  }

  function send(kind, payload, transfer) {
    window.parent.postMessage(
      {
        channel: CHANNEL,
        kind,
        ...(payload || {}),
      },
      getTargetOrigin(),
      transfer || [],
    );
  }

  function makeRequest(action, payload, transfer) {
    return new Promise((resolve, reject) => {
      requestCounter += 1;
      const requestId = `plugin_req_${requestCounter}`;
      pendingRequests.set(requestId, { resolve, reject });
      send(
        "request",
        {
          requestId,
          action,
          ...(payload || {}),
        },
        transfer,
      );
    });
  }

  function parseMaybeJson(value) {
    if (typeof value !== "string") {
      return value;
    }
    try {
      return JSON.parse(value);
    } catch {
      return value;
    }
  }

  function getByPath(source, key) {
    if (!source || typeof source !== "object" || !key) {
      return undefined;
    }

    return String(key)
      .split(".")
      .reduce((current, part) => {
        if (!current || typeof current !== "object" || !(part in current)) {
          return undefined;
        }
        return current[part];
      }, source);
  }

  function translate(key, fallback) {
    const locale = context?.locale;
    const messages = context?.i18n;
    const locales = [locale, "zh-CN", "en-US"].filter(Boolean);
    let value;
    for (const candidateLocale of locales) {
      value = getByPath(messages?.[candidateLocale], key);
      if (value !== undefined && value !== null) {
        break;
      }
    }
    if (value === undefined || value === null) {
      return fallback || "";
    }
    return typeof value === "string" ? value : String(value);
  }

  function notifyContextHandlers() {
    contextHandlers.forEach((handler) => {
      try {
        handler(context);
      } catch (error) {
        console.error("AstrBotPluginPage context handler failed:", error);
      }
    });
  }

  function applyContext(nextContext) {
    if (!nextContext || typeof nextContext !== "object") {
      return;
    }
    context = {
      ...(context || {}),
      ...nextContext,
    };
    if (typeof nextContext.isDark === "boolean") {
      document.documentElement.setAttribute(
        "data-theme",
        nextContext.isDark ? "dark" : "light",
      );
    }
    if (resolveReady) {
      resolveReady(context);
      resolveReady = null;
    }
    notifyContextHandlers();
  }

  window.addEventListener("message", (event) => {
    if (event.source !== window.parent) {
      return;
    }
    if (!isAllowedParentOrigin(event.origin)) {
      return;
    }
    if (!parentOrigin) {
      parentOrigin = event.origin;
    }

    const message = event.data;
    if (!message || message.channel !== CHANNEL) {
      return;
    }

    if (message.kind === "context") {
      applyContext(message.context);
      return;
    }

    if (message.kind === "response") {
      const pending = pendingRequests.get(message.requestId);
      if (!pending) {
        return;
      }
      pendingRequests.delete(message.requestId);
      if (message.ok) {
        pending.resolve(message.data);
      } else {
        pending.reject(
          new Error(message.error || "Plugin bridge request failed."),
        );
      }
      return;
    }

    if (message.kind === "sse_message") {
      const handlers = sseHandlers.get(message.subscriptionId);
      if (handlers?.onMessage) {
        handlers.onMessage({
          raw: message.data,
          parsed: parseMaybeJson(message.data),
          eventType: message.eventType || "message",
          lastEventId: message.lastEventId,
        });
      }
      return;
    }

    if (message.kind === "sse_state") {
      const handlers = sseHandlers.get(message.subscriptionId);
      if (message.state === "open" && handlers?.onOpen) {
        handlers.onOpen();
      }
      if (message.state === "error" && handlers?.onError) {
        handlers.onError();
      }
    }
  });

  window.AstrBotPluginPage = {
    ready() {
      return readyPromise;
    },
    getContext() {
      return context;
    },
    getLocale() {
      return context?.locale || "zh-CN";
    },
    getI18n() {
      return context?.i18n || {};
    },
    t(key, fallback) {
      return translate(key, fallback);
    },
    onContext(handler) {
      if (typeof handler !== "function") {
        return () => {};
      }
      contextHandlers.add(handler);
      if (context) {
        handler(context);
      }
      return () => {
        contextHandlers.delete(handler);
      };
    },
    __setInitialContext(nextContext) {
      applyContext(nextContext);
    },
    apiGet(endpoint, params) {
      return makeRequest("api:get", { endpoint, params });
    },
    apiPost(endpoint, body) {
      return makeRequest("api:post", { endpoint, body });
    },
    async upload(endpoint, file) {
      if (!file || typeof file.arrayBuffer !== "function") {
        throw new Error("Missing uploaded file payload.");
      }
      const fileBuffer = await file.arrayBuffer();
      return makeRequest(
        "files:upload",
        {
          endpoint,
          fileName: file?.name || "upload.bin",
          fileType: file?.type || "application/octet-stream",
          fileLastModified:
            typeof file?.lastModified === "number" ? file.lastModified : null,
          fileBuffer,
        },
        [fileBuffer],
      );
    },
    download(endpoint, params, filename) {
      return makeRequest("files:download", { endpoint, params, filename });
    },
    async subscribeSSE(endpoint, handlers, params) {
      subscriptionCounter += 1;
      const subscriptionId = `plugin_sse_${subscriptionCounter}`;
      sseHandlers.set(subscriptionId, handlers || {});
      try {
        await makeRequest("sse:subscribe", {
          endpoint,
          params,
          subscriptionId,
        });
        return subscriptionId;
      } catch (error) {
        sseHandlers.delete(subscriptionId);
        throw error;
      }
    },
    async unsubscribeSSE(subscriptionId) {
      sseHandlers.delete(subscriptionId);
      return makeRequest("sse:unsubscribe", { subscriptionId });
    },
  };

  send("ready");
})();
