import { useRef as S, useEffect as C } from "react";
import W from "lodash/get";
import fe from "js-cookie";
import M from "lodash/omitBy";
import H from "lodash/isNil";
import de from "lodash/omit";
import pe from "lodash/cloneDeep";
import { useReactive as Q, useCreation as me, useMemoizedFn as T } from "ahooks";
import ke from "@arcblock/react-hooks/lib/useInterval";
import ge from "@arcblock/react-hooks/lib/useBrowser";
import { getVisitorId as he, stringifyQuery as x, isUrl as be } from "@arcblock/ux/lib/Util";
import { WsClient as we } from "@arcblock/ws";
import { sleep as G, getExtraHeaders as v, getConnectedInfo as Se, updateConnectedInfo as Ce, parseNextWorkflow as z, createAxios as V, decodeConnectUrl as Ie, parseTokenFromConnectUrl as $e } from "../../utils.js";
import X from "../assets/locale.js";
import xe from "./security.js";
import ve from "./page-show.js";
import { RELAY_SOCKET_PREFIX as J } from "../../constant.js";
function Ue(o) {
  const l = o || window.location.href;
  return new URL(l).host;
}
const P = {};
function Ae({ action: o, prefix: l, checkFn: m, extraParams: U, baseUrl: k }) {
  const d = `${l}/token?${x(U)}`;
  return P[d] || (P[d] = async function() {
    const f = +/* @__PURE__ */ new Date(), g = await m(d, { headers: v(k) }), h = +/* @__PURE__ */ new Date();
    if (h - f < 500 && await G(500 - (h - f)), g?.data?.token)
      return g.data;
    if (g?.data?.error)
      throw new Error(g.data.error);
    const A = be(l) ? l : `${k}${l}`;
    throw new Error(`Error generating ${o} QR Code from: ${A}`);
  }), P[d];
}
const ye = () => {
  try {
    const l = new URL(window.location.href).searchParams.get("__connect_url__");
    if (!l)
      return null;
    const m = Ie(l);
    return {
      token: $e(m),
      url: m
    };
  } catch (o) {
    return {
      error: o
    };
  }
}, Z = () => W(globalThis, "blocklet.appPid") || W(globalThis, "blocklet.appId") || W(globalThis, "env.appId") || "", Ee = () => (W(globalThis, "env.apiPrefix") || "/").replace(/\/$/, "").replace(J, ""), _ = (o) => `relay:${Z()}:${o}`, Te = () => window.location.protocol === "https:" ? "wss:" : "ws:", Y = (o) => !o || new URL(o).origin === window.location.origin, q = (o) => ["succeed", "error", "timeout", "busy"].includes(o);
function Ye({
  action: o,
  checkFn: l,
  checkInterval: m,
  checkTimeout: U,
  extraParams: k,
  locale: d,
  prefix: F,
  tokenKey: f,
  encKey: g,
  onError: h,
  onSuccess: A,
  baseUrl: K,
  autoConnect: ee = !0,
  forceConnected: re = !0,
  saveConnect: te = !0,
  // FIXME: @zhanghan 将来需要设置为默认 false，仅在需要的时候进行保存 login, switch/role/profile
  useSocket: ne = !0,
  provider: oe = "wallet"
}) {
  const e = Q({
    checking: !1,
    loading: !1,
    token: "",
    url: "",
    store: null,
    status: "created",
    error: "",
    mfaCode: 0,
    appInfo: null,
    memberAppInfo: null,
    connectedDid: "",
    saveConnect: !1,
    inExistingSession: !1,
    nextWorkflow: "",
    baseUrl: K,
    prefix: `${F}/${o}`,
    extraParams: M(k, H),
    checkFn: l,
    results: {},
    action: o,
    provider: oe,
    reset() {
      this.error = "", this.status = "created";
    }
  }), I = S(0), D = S(null), $ = X[d] || X.en, u = me(() => ye(), []), se = Math.ceil(U / m), c = Q({
    onSuccessCalled: !1,
    cancelWhenScannedCounter: 0,
    isSocketAvailable: !1
  }), { encryptKey: ae, decrypt: y } = xe(), O = ge(), L = he(), a = S(null), p = S(null), b = {
    ...e.extraParams,
    locale: d,
    [f]: e.token || u?.token,
    provider: e.provider
  }, j = async () => {
    if (!(u?.token && u?.token === e.token)) {
      try {
        b[f] && e.status === "created" && await e.checkFn(`${e.prefix}/timeout?${x(b)}`, {
          headers: v(e.baseUrl)
        });
      } catch {
      }
      try {
        e.token && p.current && (await a.current.unsubscribe(_(e.token)), p.current = null);
      } catch {
      }
    }
  }, ce = T((...t) => {
    const r = {
      [g]: ae,
      ...e.extraParams,
      locale: d,
      forceConnected: re,
      // - autoConnect 请求参数用于控制服务端是否给钱包应用发送自动连接通知
      // - 使用 connect 组件时明确传入了 autoConnect = false, 则 autoConnect 请求参数 为 false
      // - 如果 cancelWhenScannedCounter > 0, 说明用户进行过 cancel 操作, 则临时禁用自动连接, autoConnect 请求参数 为 false
      //   (避免 "无限自动连接问题")
      // - 如果上次连接设备 (connected_wallet_os) 不是 ios/android, 则禁用自动连接
      autoConnect: ee && // 如果是 wallet webview 环境, 不发送通知, 避免 wallet 连续弹出 auth 窗口 2 次 (#341)
      !(O.wallet || O.arcSphere) && !c.cancelWhenScannedCounter && ["ios", "android"].includes(fe.get("connected_wallet_os")),
      provider: e.provider
    };
    L && (r.visitorId = L);
    const n = de(D.current || {}, ["sourceAppPid", "verbose"]);
    return Object.keys(n).forEach((s) => {
      (!r[s] || s === g && n[s]) && (r[s] = n[s]);
    }), u?.token && !r.sourceToken && (r.sourceToken = u?.token), Ae({
      action: o,
      prefix: e.prefix,
      baseUrl: e.baseUrl,
      checkFn: e.checkFn,
      extraParams: r
    })(...t);
  }), E = T(async (t = !0) => {
    t && j();
    try {
      e.loading = !0, e.token = "", e.url = "", e.store = null, e.extraParams = M(k, H), await G();
      const r = await ce(), n = r.extra || {};
      e.loading = !1, e.token = r.token, e.url = r.url, e.status = "created", e.error = "", I.current = 0, e.appInfo = r.appInfo, e.memberAppInfo = r.memberAppInfo, e.connectedDid = n.connectedDid, e.saveConnect = n.saveConnect;
    } catch (r) {
      e.loading = !1, e.status = "error", e.error = `${$.generateError}: ${r.message}`;
    }
  }), ie = T(() => {
    c.cancelWhenScannedCounter++;
  });
  C(() => {
    c.cancelWhenScannedCounter > 0 && (k.nw ? h(new Error($.retryForbidden)) : E());
  }, [c.cancelWhenScannedCounter]), C(() => (!(/^((?!chrome|android).)*safari/i.test(globalThis.navigator.userAgent) || Object.hasOwn(globalThis?.blocklet || {}, "DID_CONNECT_DISABLE_SOCKET")) && ne && Y(e.baseUrl) && Z() ? (!a.current || a.current.isConnected() === !1 || a.current.baseUrl !== e.baseUrl) && (a.current = new we(
    `${Te()}//${Ue(e.baseUrl)}${Ee()}${J}/relay`,
    {
      longpollerTimeout: 5e3,
      // connection timeout
      heartbeatIntervalMs: 30 * 1e3
    }
  ), a.current.baseUrl = e.baseUrl, a.current.onOpen(() => {
    c.isSocketAvailable = !0;
  }), a.current.connect()) : c.isSocketAvailable && (c.isSocketAvailable = !1), () => {
    a.current && (a.current.disconnect(), a.current = null);
  }), [e.baseUrl]);
  const le = () => !e.token || e.loading || q(e.status) ? null : m, N = S(null);
  C(() => {
    N.current = { state: e, params: b };
  }), C(() => () => {
    const { state: t, params: r } = N.current;
    t.status === "created" && r[f] && t.checkFn(`${t.prefix}/timeout?${x(r)}`, {
      headers: v(t.baseUrl)
    });
  }, []);
  const w = T(async (t = !1) => {
    if ((e.checking || document.hidden) && !t)
      return null;
    if (e.status === "created" && (I.current++, e.status !== "timeout" && I.current > se))
      return e.status = "timeout", j(), null;
    if (c.isSocketAvailable && !t)
      return null;
    try {
      e.checking = !0;
      const r = await e.checkFn(`${e.prefix}/status?${x(b)}`, {
        headers: v(e.baseUrl)
      }), { status: n, error: i, mfaCode: s = 0 } = r.data;
      if (e.store = r.data, e.mfaCode = s, e.checking = !1, e.status === "scanned" && n === "created" || (e.status = n), n === "error" && i) {
        const B = new Error(i);
        throw B.response = r, B;
      }
      return r.data;
    } catch (r) {
      const { response: n } = r;
      if (n?.status) {
        b[f] && e.checkFn(`${e.prefix}/timeout?${x(b)}`, {
          headers: v(e.baseUrl)
        });
        const i = n?.data?.error ? n.data.error : r.message, s = new Error(i);
        s.code = n.status, e.status = "error", e.checking = !1, e.error = i, h(s, e?.store, y);
      } else
        e.status = "error", e.checking = !1, e.error = $.generateError;
    }
    return null;
  }), ue = async () => {
    try {
      if (u.error)
        throw u.error;
      e.loading = !0, e.token = u.token, e.url = u.url, e.inExistingSession = !0;
      let t = await w();
      if ((["scanned"].includes(t?.status) || t?.sourceToken === u?.token) && (await E(!1), t = await w()), D.current = t?.extraParams || null, t?.status && !["created"].includes(t?.status))
        throw new Error(`${$.invalidSessionStatus} [${t.status}]`);
    } catch (t) {
      e.status = "error", e.error = t.message, h(t, e?.store, y);
    } finally {
      e.loading = !1;
    }
  };
  C(() => {
    if (!e.token && !e.store && !e.loading && !e.error)
      if (u)
        ue();
      else {
        E(!1);
        return;
      }
    if (e.status === "succeed" && !c.onSuccessCalled) {
      const t = Se({
        ...e.store,
        appInfo: e.appInfo,
        memberAppInfo: e.memberAppInfo
      });
      if (Y(e.baseUrl) && te && e.saveConnect && e.store.did && Ce(t, !0), e.store) {
        const { nextWorkflow: r } = e.store;
        if (r)
          try {
            const n = z(r, f);
            e.nextWorkflow = n?.nextWorkflow || r, Object.assign(e, n), e.store = null, e.error = "", e.url = "", e.appInfo = null, e.memberAppInfo = null, I.current = 0, e.status = "scanned", e.results = {
              ...e.results,
              [e.token]: e.store
            }, e.checkFn = V({ baseURL: n.baseUrl, timeout: 8e3 }).get, w();
          } catch (n) {
            console.error(`Invalid nextWorkflow: ${r}`, n), e.status = "error", e.error = `Invalid nextWorkflow: ${r}: ${n.message}`;
          }
        else if (e.nextWorkflow && (e.nextWorkflow = "", e.url = ""), typeof A == "function") {
          const n = Object.values({ ...e.results, [e.token]: e.store });
          c.onSuccessCalled = !0;
          const i = n.length > 1 ? n : n[0], s = pe(i);
          A(s, y, {
            sourceAppPid: k?.sourceAppPid,
            ...t
          });
        }
      }
      return;
    }
    if (e.store?.nextWorkflow) {
      const { nextWorkflow: t } = e.store;
      try {
        const r = z(t, f);
        e.nextWorkflow = r?.nextWorkflow || t, Object.assign(e, r), e.store = null, e.error = "", e.url = "", e.appInfo = null, e.memberAppInfo = null, I.current = 0, e.status = "scanned", e.results = {
          ...e.results,
          [e.token]: e.store
        }, e.checkFn = V({ baseURL: r.baseUrl, timeout: 8e3 }).get, w();
      } catch (r) {
        console.error(`Invalid nextWorkflow: ${t}`, r), e.status = "error", e.error = `Invalid nextWorkflow: ${t}: ${r.message}`;
      }
    }
  });
  const R = S(0);
  return C(() => {
    if (e.token && c.isSocketAvailable && a.current) {
      let t = !1;
      p.current ? p.current.token !== e.token && (a.current.unsubscribe(_(p.current.token)), t = !0) : t = !0, t && (p.current = a.current.subscribe(_(e.token)), R.current = 0, p.current.token = e.token, p.current.on("updated", ({ response: r }) => {
        const n = +new Date(r.updatedAt);
        if (n <= R.current) {
          console.warn("Ignore outdated message", r);
          return;
        }
        R.current = n;
        let { status: i, error: s } = r;
        i === "forbidden" && (s = $.forbidden, i = "error"), e.status = i, e.mfaCode = r.mfaCode || 0, e.store = r, e.error = s, s && h(new Error(s), e?.store, y);
      }));
    }
  }, [e.token, c.isSocketAvailable, a.current]), ke(w, le()), ve(() => {
    e.token && q(e.status) === !1 && (e.checking = !1, setTimeout(() => {
      w(!0);
    }, 100));
  }), { state: e, generate: E, cancelWhenScanned: ie };
}
export {
  Ye as default
};
