import React, { createContext, Dispatch, SetStateAction, useContext, useEffect, useState } from 'react'

import { Dict, Environment, isObject, noop, useCallbackRef } from '../utils'
import { cdnUrls } from './consts'

export type LinksDict = Dict<string>

type LinksContextType = [LinksDict, Dispatch<SetStateAction<LinksDict>>]

const LinksContext = createContext<LinksContextType | undefined>(undefined)

/**
 * Adds loggedIn=1 parameter to the URL if 'isLoggedIn' flag is true, which means the user is logged in.
 * If 'isLoggedIn' is falsy, loggedin param will be removed.
 * This is supposed to aid login flow when navigating between Veracity applications.
 */
export function getLoggedInLink(link: string, isLoggedIn?: boolean) {
  try {
    const url = new URL(link)

    if (isLoggedIn) url.searchParams.append('loggedin', '1')
    else url.searchParams.delete('loggedin')

    return url.toString()
  } catch {
    return link
  }
}

export function LinksProvider(props: { children: React.ReactNode }) {
  const linksState = useState<LinksDict>({})

  return <LinksContext.Provider value={linksState} {...props} />
}

/** Returns links and setLinks from LinksContext. */
export function useLinks() {
  const context = useContext(LinksContext)

  if (context === undefined) {
    console.error('useLinks must be used within a LinksContext.')
    return [{}, noop] as LinksContextType
  }

  return context
}

/**
 * Retrieves the links from CDN for the given environment and appends them with loggedin=1 parameter
 * if user is logged in. Links will be reloaded when 'env' changes or updated when 'isLoggedIn' changes.
 */
export function useLoadLinks(env?: Environment, isLoggedIn?: boolean) {
  const [, setLinks] = useLinks()

  // Load links from the CDN for the given environment.
  const loadLinks = async (env: Environment) => {
    try {
      const response = await fetch(`${cdnUrls[env]}/common/data/links/base.json`)
      if (!response.ok) return

      const data = await response.json()

      if (isObject(data)) setLinks(updateLoggedInParam(data))
    } catch {
      console.error('Failed to retrieve links')
    }
  }

  /**
   * Adds/removes loggedin=1 URL param for each link based on isLoggedIn arg.
   * useCallbackRef is used here to prevent a stale closure in async loadLinks.
   */
  const updateLoggedInParam = useCallbackRef((links: LinksDict) => {
    return Object.keys(links).reduce((loggedInLinks: LinksDict, key) => {
      loggedInLinks[key] = getLoggedInLink(links[key], isLoggedIn)
      return loggedInLinks
    }, {})
  })

  useEffect(() => {
    if (!env) return

    loadLinks(env)
  }, [env])

  useEffect(() => {
    setLinks(updateLoggedInParam)
  }, [isLoggedIn])
}
