import { TimeoutError } from '../types/errors/TimeoutError.ts'
import { CurrentUserFile } from '../types/file.ts'
import { SigitFile } from './file.ts'

export const compareObjects = (
  obj1: object | null | undefined,
  obj2: object | null | undefined
): boolean => {
  if (Array.isArray(obj1) && Array.isArray(obj2)) {
    const obj1Copy = [...obj1].sort()
    const obj2Copy = [...obj2].sort()

    return JSON.stringify(obj1Copy) === JSON.stringify(obj2Copy)
  }

  return JSON.stringify(obj1) === JSON.stringify(obj2)
}

// Function to check if the system is online by making a network request
export const isOnline = async () => {
  // First, check the navigator's online status
  if (!navigator.onLine) return false // If navigator reports offline, return false

  /**
   * If navigator.onLine is true, it can be false positive.
   * In other words, navigator.onLine being true does not necessarily mean that there is a working internet connection.
   * Most implementations seem to only be checking if there's a connected network adapter.
   * There are a number of circumstances where that's the case, but the internet is still unreachable. For example:
   * The user is only connected to an internal network
   * The user is inside a virtual machine, and the virtual network adapter is connected, but the host system is offline
   * The user uses a VPN which has installed a virtual network adapter that is always connected
   *
   * To overcome the above problem we'll have to make a http request
   */

  try {
    // Define a URL to check the online status
    const url = document.location.pathname + '?v=' + new Date().getTime()

    // Make a HEAD request to the URL with 'no-cors' mode
    // This mode is used to handle opaque responses which do not expose their content
    const response = await fetch(url, { method: 'HEAD', mode: 'no-cors' })

    // Check if the response is OK or if the response type is 'opaque'
    // An 'opaque' response type means that the request succeeded but the response content is not available
    if (response.ok || response.type === 'opaque') {
      return true // If the request is successful, return true
    } else {
      return false // If the request fails, return false
    }
  } catch (error) {
    // Catch any errors that occur during the fetch request
    return false // If an error occurs, return false
  }
}

/**
 * Creates a promise that rejects with a timeout error after a specified duration.
 * @param ms The duration in milliseconds after which the promise should reject. Defaults to 60000 milliseconds (1 minute).
 * @returns A promise that rejects with an Error('Timeout') after the specified duration.
 */
export const timeout = (ms: number = 60000) => {
  return new Promise<never>((_, reject) => {
    // Set a timeout using setTimeout
    setTimeout(() => {
      // Reject the promise with an Error indicating a timeout
      reject(new TimeoutError())
    }, ms) // Timeout duration in milliseconds
  })
}
/**
 * Creates a flat array where each object contains all required information about a Pdf File,
 * including its name, hash, and content
 * @param files
 * @param fileHashes
 * @param creatorFileHashes
 */
export const getCurrentUserFiles = (
  files: { [filename: string]: SigitFile },
  fileHashes: { [key: string]: string | null },
  creatorFileHashes: { [key: string]: string }
): CurrentUserFile[] => {
  return Object.entries(files).map(([filename, file], index) => {
    return {
      file,
      filename,
      id: index + 1,
      ...(!!fileHashes[filename] && { hash: fileHashes[filename]! }),
      isHashValid: creatorFileHashes[filename] === fileHashes[filename]
    }
  })
}

/**
 * Utility function that generates a promise with a callback on each array item
 * and retuns only non-null fulfilled results
 * @param array
 * @param cb callback that generates a promise
 * @returns Array with the non-null results
 */
export const settleAllFullfilfedPromises = async <Item, FulfilledItem = Item>(
  array: Item[],
  cb: (arg: Item) => Promise<FulfilledItem>
) => {
  // Run the callback on the array to get promises
  const promises = array.map(cb)

  // Use Promise.allSettled to wait for all promises to settle
  const results = await Promise.allSettled(promises)

  // Extract non-null values from fulfilled promises in a single pass
  return results.reduce<NonNullable<FulfilledItem>[]>((acc, result) => {
    if (result.status === 'fulfilled') {
      const value = result.value
      if (value) {
        acc.push(value)
      }
    }
    return acc
  }, [])
}

export const isPromiseFulfilled = <T>(
  result: PromiseSettledResult<T>
): result is PromiseFulfilledResult<T> => {
  return result.status === 'fulfilled'
}

export const isPromiseRejected = <T>(
  result: PromiseSettledResult<T>
): result is PromiseRejectedResult => {
  return result.status === 'rejected'
}
