/*!
 * Copyright (c) 2021, 2022, 2023 Eclipse Foundation, Inc.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * Contributors:
 *   Zhou Fang <zhou.fang@eclipse-foundation.org>
 *   Eric Poirier <eric.poirier@eclipse-foundation.org>
 *   Olivier Goulet <olivier.goulet@eclipse-foundation.org>
 * 
 * SPDX-License-Identifier: EPL-2.0
 */

import 'isomorphic-fetch';

export const getNextPage = (linkHeader: string): URL | undefined => {
  if (!linkHeader) return;

  const links = linkHeader.split(', ');
  const nextLink = links.find(link => link.includes('rel="next"'));
  if (!nextLink) return;

  const matches = nextLink.match(/<(.+)>/);
  if (!matches || matches.length < 2) return;

  const nextUrl = matches[1];

  return new URL(nextUrl);
}

const absUrlPattern = /^[a-zA-Z]+:\/\//;
export const validateURL = (url: string) => {
  if (!url) return false;
  
  const isAbsolute = url.match(absUrlPattern);
  const base = isAbsolute ? undefined : window.location.href;
  
  let isValidate: boolean;
  try {
    // This will fail if the url is not valid
    new URL(url, base);
    isValidate = true;
  } catch (error) {
    isValidate = false;
  }
  return isValidate;
};

/**
  * Determines whether a given url string has a protocol included.
  * 
  * @param url - the url to test
  * @example
  * hasProtocol('https://www.eclipse.org/'); // true
  * hasProtocol('www.eclipse.org'); // false
  */ 
export const hasProtocol = (url: string): boolean => {
  // Matches the protocol. E.g. "https://"
  const protocolRegex = /^[a-zA-Z]+:\/\//;
  // Return true if the url does not have a protocol.
  return !protocolRegex.test(url);
}

export const resolveURL = (url: string) => {
  // We are assuming URLs without a protocol are relative.
  if (hasProtocol(url)) {
    return new URL(url, window.location.origin);
  }

  // Otherwise, if it is absolute, construct it normally.
  return new URL(url);
}

/**
  * Returns a boolean indicating whether the given date is valid.
  *
  * @param date - The date to validate.
  */
export const validateDate = (date: Date): boolean => {
  // Compare the date to the invalid date symbol.
  return Symbol.for(date.toString()) !== Symbol.for('Invalid Date');
}

export const convertToQueryString = (params: Record<string, string>) => {
  // Filters out undefined params
  const filteredParams = Object.fromEntries(
    Object
      .entries(params)
      .filter(([_, v]) => v !== undefined)
  );

  return new URLSearchParams(filteredParams).toString();
}

/** 
  * Converts a snake case string into a camel case string.
  *
  * @param str - The snake case string to convert.
  * @returns The camel case string.
  */
export const convertSnakeToCamel = (str: string): string => {
  // Capitalize the first letter of each word except the first.
  return str.split('_')
    .reduce((acc, curr, i) => {
      if (i === 0) {
        return curr;
      }
      return acc + curr[0].toUpperCase() + curr.slice(1);
    }, '');
}

/**
  * Transforms a snake case object into a camel case object.
  *
  * @param input - The object with snake case keys to transform.
  * @returns The transformed object with camel case keys.
  * @example
  * const input = { foo_bar: 'baz' };
  * // Returns { fooBar: 'baz' }
  * transformSnakeToCamel(input);
  */
export const transformSnakeToCamel = <T extends object,>(input: unknown): T => {
  // Ensure the input exists and is not null.
  if (input === null) {
    return input as any;
  }

  // If the input is an array, transform each item in the array.
  if (Array.isArray(input)) {
    return input.map((item) => transformSnakeToCamel(item)) as any;
  }

  // If the input is not an object, return it as-is.
  if (typeof input !== 'object') { 
    return input as T;
  }
  
  let transformedObject: Record<string, any> = {};

  Object.keys(input!).forEach((snakeCaseKey) => {
    // If the key is a property of the object, transform it.
    if (input!.hasOwnProperty(snakeCaseKey)) {
      const camelCaseKey = convertSnakeToCamel(snakeCaseKey);
      const value = input![snakeCaseKey as keyof typeof input];

      if (Array.isArray(value)) {
        // If the value is an array, transform each item in the array.
        transformedObject[camelCaseKey] = (value as any[]).map((input) => transformSnakeToCamel(input));
      } else if (typeof value === 'object' && value !== null) {
        // If the value is an object, transform it directly.
        transformedObject[camelCaseKey] = transformSnakeToCamel(value);
      } else {
        // Otherwise, add the value to the transformed object.
        transformedObject[camelCaseKey] = value;
      }
    }
  });

  return transformedObject as T;
};

/**
  * A util to fetch an XML document from a URL.
  *
  * @param url - The URL to fetch from.
  * @throws Will throw an error if there was a problem fetching or if the
  * response is malformed.
  * @returns - An XML document
  */
export const fetchXML = async (url: URL): Promise<XMLDocument> => {
  const response = await fetch(url.href);
  if (!response.ok) throw new Error(`Could not fetch XML from ${url.href}.`);

  const text = await response.text();
  const xml = new DOMParser().parseFromString(text, 'text/xml');
  
  const errorNode = xml.querySelector('parsererror');
  if (errorNode) { 
    throw new TypeError('A parsing error has occurred from a malformed XML response.');
  }

  return xml;
};


