/*!
 * 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
 */

// UI utilities
export const displayErrMsg = (element: HTMLElement, err = '', errText = 'Sorry, something went wrong, please try again later.') =>
  (element.innerHTML = `<div class="alert alert-danger" role="alert"><p><strong>Error ${err}</strong></p> <p>${errText}</p></div>`);

export const scrollToAnchor = () => {
  const elementId = location.hash.replace('#', '');
  const element = document.getElementById(elementId);

  if (!element) return;

  element.scrollIntoView();
}

/** Resolves when the given element has finished rendering its children.
  * @param element
  */
export const waitForRender = (element: HTMLElement | Node): Promise<void> => {
  return new Promise(resolve => {
    // If the element already has children nodes, it's already rendered
    if (element.childNodes?.length > 0) {
      resolve();
      return;
    }
    
    // Otherwise, wait for the element to have children
    const observer = new MutationObserver((mutationList, observer) => {
      for (const mutation of mutationList) {
        if (mutation.type === 'childList') {
          observer.disconnect();
          resolve();
        }
      }
    });

    observer.observe(element, { childList: true });
  });
};

// API utilities
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;
};

/**
  * 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;
};
