// contexts/auth-context.js
import React, { useReducer, useContext, createContext } from 'react' 

export const AuthStateContext = createContext()

export const AuthDispatchContext = createContext()

export const AuthProvider = ({ initialState, reducer, children }) => {
  const [state, dispatch] = useReducer(reducer, initialState)
  
  return (
    <AuthStateContext.Provider value={state}>
      <AuthDispatchContext.Provider value={dispatch}>
        {children}
      </AuthDispatchContext.Provider>
    </AuthStateContext.Provider> 
  )
}

export const useAuthState = () => {
  const state = useContext(AuthStateContext)
  
  if (state === undefined) {
    throw new Error(`useAuthState must be within a AuthProvider`)
  }
  
  return state;
}

export const useAuthDispatch = () => {
  const dispatch = useContext(AuthDispatchContext)
  
  if (dispatch === undefined) {
    throw new Error(`useAuthDispatch must be used within an AuthProvider`)
  }
  
  return dispatch;
}

// reducers/auth-reducer.js
export const authInitialState = {
  user: null,
  loading: false,
  error: '',
}

export const authReducer = (state, action) => {
  switch(action.type) {
    case 'RESET_STATE': {
      return authInitialState;
    }
    case 'LOGIN': {
      return { ...state, loading: true }
    }
    case 'LOGIN.DONE': {
      return { ...state, loading: false, user: action.payload }
    }
    case 'LOGIN.FAIL': {
      return { ...state, loading: false, error: action.payload }
    }
    case 'LOGOUT': {
      return { ...state, loading: true }
    }
    case 'LOGOUT.DONE': {
      return { ...state, loading: false, user: null }
    }
    case 'LOGOUT.FAIL': {
      return { ...state, loading: false, error: action.payload }
    }
    default: {
      throw new Error(`The ${action.type} doesn't exists.`)
    }
  }
}

// actions/auth-actions.js
// Pollyfill for fetch suggested: unfetch
// How to use: import fetch from 'unfetch'
export const login = (dispatch) => async (user, password) => {
  try {
    dispatch({ type: 'LOGIN' })
    
    const response = fetch(`http://awesome.api/user/login/`, {
      body: JSON.stringify({ user, password }),
      headers: { 'Content-Type': 'application/json' },
    })
    const user = response.json()
    
    dispatch({ type: 'LOGIN.DONE', payload: user })
  } catch(e) {
    console.error(`<auth-actions:login>: ${e.message}`)
    
    dispatch({ type: 'LOGIN.FAIL', payload: e.message })
  }
}

export const logout = (dispatch) => async () => {
  try {
    dispatch({ type: 'LOGOUT' })
    
    const response = fetch(`http://awesome.api/user/logout`)
    
    dispatch({ type: 'LOGOUT.DONE' })
  } catch(e) {
    console.error(`<auth-actions:logout>: ${e.message}`)
    
    dispatch({ type: 'LOGOUT.FAIL', payload: e.message })
  }
}

// index.js
import React from 'react'
import { AuthProvider } from 'contexts/auth-context'
import { authInitialState, authReducer } from 'reducers/auth-reducer'
import { App } from 'App'

ReactDOM.render(
  <React.StrictMode>
    <AuthProvider initialState={authInitialState} reducer={authReducer}>
      <App />
    </AuthProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

// components/AuthenticatedApp
import React from 'react'

import { useAuthState, useAuthDispatch } from 'components/auth-context'
import { logout } from 'actions/auth-actions'

export const AuthenticatedApp = () => {
  const authState = useAuthState()
  const authDispatch = useAuthDispatch()
  
  return (
    <div>
      <h1>{authState.user.name}</h1>
      <button type="button" onClick={async () => await logout(authDispatch)()}>Logout</button>
    </div>
  )
}

// components/UnAuthenticatedApp
import React, { useState } from 'react'

import { useAuthDispatch } from 'components/auth-context'
import { login } from 'actions/auth-actions'

export const UnAuthenticatedApp = () => {
  const [user, setUser] = useState('')
  const [password, setPassword] = useState('')
  const authDispatch = useAuthDispatch()
  
  return (
    <div>
      <input type="text" value={user} onChange={e => setUser(e.target.value)} placeholder="Insert you username" />
      <input type="password" value={password} onChange={e => setPassword(e.target.value)} placeholder="Insert your password" />
      <button type="button" onClick={async () => await login(authDispatch)(user, password)}>Login</button>
    </div>
  )
}

// App.js
import React, {} from 'react'

import { AuthenticatedApp } from 'components/AuthenticatedApp'
import { UnAuthenticatedApp } from 'components/UnAuthenticatedApp'

import { useAuthState } from 'contexts/auth-context.js'

export const App = () => {
  const authState = useAuthState()
  return authState.user ? <AuthenticatedApp /> : <UnAuthenticatedApp />
}
