const WEBVIEW_TOKEN_STORAGE_KEY = "bmt_webview_token";
let cachedToken = "";
const MAX_NESTED_PARSE_DEPTH = 4;
const NESTED_URL_PARAM_KEYS = [
  "url",
  "targetUrl",
  "target_url",
  "redirect",
  "redirectUrl",
  "redirect_url",
  "redirectUri",
  "redirect_uri",
  "returnUrl",
  "return_url",
  "link",
  "href",
];
const NESTED_URL_PARAM_KEYS_LOWER = NESTED_URL_PARAM_KEYS.map((item) =>
  item.toLowerCase(),
);

function safeDecodeURIComponent(value) {
  if (!value) {
    return "";
  }

  try {
    return decodeURIComponent(value);
  } catch (error) {
    return String(value);
  }
}

function decodeMultiEncodedValue(value) {
  let currentValue = String(value || "");
  if (!currentValue) {
    return "";
  }

  for (let round = 0; round < 4; round += 1) {
    const decodedValue = safeDecodeURIComponent(currentValue);
    if (!decodedValue || decodedValue === currentValue) {
      break;
    }
    currentValue = decodedValue;
  }

  return currentValue;
}

function sanitizeTokenValue(token) {
  let normalizedToken = decodeMultiEncodedValue(token || "").trim();
  if (!normalizedToken) {
    return "";
  }

  if (/^bearer\s+/i.test(normalizedToken)) {
    normalizedToken = normalizedToken.replace(/^bearer\s+/i, "").trim();
  }

  const hashIndex = normalizedToken.indexOf("#");
  if (hashIndex > -1) {
    normalizedToken = normalizedToken.slice(0, hashIndex).trim();
  }

  const ampersandIndex = normalizedToken.indexOf("&");
  if (ampersandIndex > -1) {
    normalizedToken = normalizedToken.slice(0, ampersandIndex).trim();
  }

  return normalizedToken;
}

function parseQueryByRegex(queryText) {
  const query = String(queryText || "").trim();
  if (!query) {
    return [];
  }

  const normalizedQuery = query.replace(/^[?#&/]+/, "");
  if (!normalizedQuery) {
    return [];
  }

  const entries = [];
  const pairs = normalizedQuery.split("&");
  for (let index = 0; index < pairs.length; index += 1) {
    const pair = String(pairs[index] || "");
    if (!pair) {
      continue;
    }

    const splitIndex = pair.indexOf("=");
    if (splitIndex === -1) {
      continue;
    }

    const key = pair.slice(0, splitIndex);
    const value = pair.slice(splitIndex + 1);
    if (!key) {
      continue;
    }

    entries.push({
      key: key,
      value: value,
    });
  }

  return entries;
}

function findTokenInText(text, depth = 0) {
  if (depth > MAX_NESTED_PARSE_DEPTH) {
    return "";
  }
  if (!text) {
    return "";
  }

  const rawText = String(text || "").trim();
  if (!rawText) {
    return "";
  }

  const candidates = [];
  const queryIndex = rawText.indexOf("?");
  const hashIndex = rawText.indexOf("#");

  if (queryIndex > -1) {
    let queryCandidate = rawText.slice(queryIndex + 1);
    const hashInQuery = queryCandidate.indexOf("#");
    if (hashInQuery > -1) {
      queryCandidate = queryCandidate.slice(0, hashInQuery);
    }
    candidates.push(queryCandidate);
  }

  if (hashIndex > -1) {
    candidates.push(rawText.slice(hashIndex + 1));
  }

  candidates.push(rawText);

  for (let index = 0; index < candidates.length; index += 1) {
    let queryText = String(candidates[index] || "").trim();
    if (!queryText) {
      continue;
    }

    const nextQueryIndex = queryText.indexOf("?");
    if (nextQueryIndex > -1) {
      queryText = queryText.slice(nextQueryIndex + 1);
    }

    queryText = queryText.replace(/^[/#&]+/, "");
    if (!queryText || queryText.indexOf("=") === -1) {
      continue;
    }

    try {
      const search = new URLSearchParams(queryText);
      const token = search.get("token") || search.get("access_token") || "";
      if (token) {
        return sanitizeTokenValue(token);
      }

      for (let keyIndex = 0; keyIndex < NESTED_URL_PARAM_KEYS.length; keyIndex += 1) {
        const nestedKey = NESTED_URL_PARAM_KEYS[keyIndex];
        const nestedValue = search.get(nestedKey) || "";
        if (!nestedValue) {
          continue;
        }

        const nestedDecodedValue = decodeMultiEncodedValue(nestedValue);
        const nestedToken =
          findTokenInText(nestedValue, depth + 1) ||
          findTokenInText(nestedDecodedValue, depth + 1);
        if (nestedToken) {
          return nestedToken;
        }
      }
    } catch (error) {
      // Continue to fallback regex mode.
    }

    const matcher = queryText.match(
      /(?:^|[?&#/])(token|access_token)=([^&#]+)/i,
    );
    if (matcher && matcher[2]) {
      return sanitizeTokenValue(matcher[2]);
    }

    const fallbackEntries = parseQueryByRegex(queryText);
    for (let entryIndex = 0; entryIndex < fallbackEntries.length; entryIndex += 1) {
      const entry = fallbackEntries[entryIndex];
      const entryKey = String(entry.key || "");
      const entryValue = String(entry.value || "");
      if (!entryKey || !entryValue) {
        continue;
      }

      const normalizedEntryKey = entryKey.toLowerCase();
      const isTokenKey =
        normalizedEntryKey === "token" || normalizedEntryKey === "access_token";
      if (isTokenKey) {
        return sanitizeTokenValue(entryValue);
      }

      const isNestedUrlKey =
        NESTED_URL_PARAM_KEYS_LOWER.indexOf(normalizedEntryKey) > -1;
      if (!isNestedUrlKey) {
        continue;
      }

      const nestedDecodedValue = decodeMultiEncodedValue(entryValue);
      const nestedToken =
        findTokenInText(entryValue, depth + 1) ||
        findTokenInText(nestedDecodedValue, depth + 1);
      if (nestedToken) {
        return nestedToken;
      }
    }
  }

  return "";
}

export function extractTokenFromUrl(url) {
  if (!url) {
    return "";
  }

  const directToken = findTokenInText(url);
  if (directToken) {
    return directToken;
  }

  const hashIndex = url.indexOf("#");
  if (hashIndex === -1) {
    return "";
  }

  return findTokenInText(url.slice(hashIndex + 1));
}

export function getCurrentWebviewUrl() {
  // #ifdef H5
  return window.location.href || "";
  // #endif

  // #ifdef APP-PLUS
  if (typeof plus === "undefined" || !plus.webview) {
    return "";
  }

  const currentWebview =
    plus.webview.currentWebview() || plus.webview.getLaunchWebview();

  if (!currentWebview || typeof currentWebview.getURL !== "function") {
    return "";
  }

  return currentWebview.getURL() || "";
  // #endif

  return "";
}

function getH5ReferrerUrl() {
  // #ifdef H5
  if (typeof document !== "undefined") {
    return String(document.referrer || "").trim();
  }
  // #endif

  return "";
}

function normalizeToken(token) {
  return sanitizeTokenValue(token);
}

function saveTokenToStorage(token) {
  const normalizedToken = normalizeToken(token);
  if (!normalizedToken) {
    return "";
  }

  cachedToken = normalizedToken;

  try {
    if (typeof uni !== "undefined" && uni.setStorageSync) {
      uni.setStorageSync(WEBVIEW_TOKEN_STORAGE_KEY, normalizedToken);
    }
  } catch (error) {
    // Ignore storage failures and keep in-memory fallback.
  }

  return normalizedToken;
}

function readTokenFromStorage() {
  if (cachedToken) {
    return cachedToken;
  }

  try {
    if (typeof uni !== "undefined" && uni.getStorageSync) {
      const token = normalizeToken(uni.getStorageSync(WEBVIEW_TOKEN_STORAGE_KEY));
      if (token) {
        cachedToken = token;
        return token;
      }
    }
  } catch (error) {
    // Ignore storage failures and fallback to runtime token extraction.
  }

  return "";
}

export function setCurrentWebviewToken(token) {
  return saveTokenToStorage(token);
}

export function refreshCurrentWebviewToken(url) {
  const candidateUrls = [];
  const inputUrl = String(url || "").trim();
  const currentUrl = String(getCurrentWebviewUrl() || "").trim();
  const referrerUrl = getH5ReferrerUrl();

  if (inputUrl) {
    candidateUrls.push(inputUrl);
  }

  if (currentUrl && candidateUrls.indexOf(currentUrl) === -1) {
    candidateUrls.push(currentUrl);
  }

  if (referrerUrl && candidateUrls.indexOf(referrerUrl) === -1) {
    candidateUrls.push(referrerUrl);
  }

  for (let index = 0; index < candidateUrls.length; index += 1) {
    const nextToken = extractTokenFromUrl(candidateUrls[index]);
    if (nextToken) {
      return saveTokenToStorage(nextToken);
    }
  }

  return readTokenFromStorage();
}

export function getCurrentWebviewToken() {
  return refreshCurrentWebviewToken();
}
