import deepmerge from 'deepmerge'

import { createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'

export const values = (obj: Record<string, any>) => {
  return Object.values(obj || {})
}

export const entries = (obj: Record<string, any>) => {
  return Object.entries(obj || {})
}
export const keys = (obj: Record<string, any>) => {
  return Object.keys(obj || {})
}

export const extractNumbers = (str?: string): string => (str ? str.replace(/\D/g, '') : '')
export const extractLetters = (str?: string): string =>
  str
    ? str
        .replace(/[^A-Za-z ]/g, '')
        .toUpperCase()
        .trim()
    : ''

export const getTypesActions = <Actions>(actions: Actions) => {
  type Types = {
    [key in keyof Actions]: string
  }
  return Object.keys(actions).reduce((acc, curr) => {
    return { ...acc, [curr]: `${actions[curr as keyof Actions]}` }
  }, {} as Types)
}

export const getError = (error: Error | { error: string } | string) => {
  if (error instanceof Error) {
    if (process.env.NODE_ENV === 'development') return `${error.message} - ${error.stack}`
    return error.message || 'Ocorreu um erro na requisição ao servidor.'
  } else if (typeof error === 'object') return error.error
  else return error
}

export const getTablePaperHeight = (heightReso: number) => {
  if (650 <= heightReso && heightReso <= 800) return heightReso / 1.65
  if (980 <= heightReso && heightReso <= 1100) return heightReso / 1.35
  return heightReso / 1.2
  // switch (heightReso) {
  //   // case 720:
  //   //   return 445
  //   // case 1080:
  //   //   return 800
  //   default:
  //     return heightReso / 1.65
  // }
}

export const mergeState = <State extends { byId: Record<string, DataType>; allIds: string[] }, DataType extends { id: string }>(
  state: State,
  payload: DataType[]
) => {
  const { allIds, byId } = state
  payload.forEach((p) => {
    if (!byId[p.id]) allIds.push(p.id)
    byId[p.id] = p
  })
}
type ActionType<Type, Payload> = {
  type: Type
  payload: Payload
}

export const reducerActions = <StateType extends { fetching: { [x: string]: any } | boolean }, Action extends { type: string; payload: any }>(
  name: string
): {
  [k in Action['type']]: (state: StateType, action: PayloadAction<Action['payload'] | undefined>) => void
} => {
  const request = `${name}Request`
  const success = `${name}Success`
  const error = `${name}Error`

  return {
    [request]: (state: StateType, action: PayloadAction<Action['payload']>) => {
      // eslint-disable-line
      if (typeof state.fetching === 'object') state.fetching[name] = true
      else state.fetching = true
    },
    [success]: (state: StateType) => {
      if (typeof state.fetching === 'object') state.fetching[name] = false
      else state.fetching = false
    },
    [error]: (state: StateType) => {
      if (typeof state.fetching === 'object') state.fetching[name] = false
      else state.fetching = false
    },
  } as {
    [k in Action['type']]: (state: StateType, action: PayloadAction<Action['payload'] | undefined>) => void
  }
}

export interface Entity {
  id: string
}

export interface NormalizedEntity<T> {
  [id: string]: T
}

export interface EntityStore<T> {
  byId: NormalizedEntity<T>
  allIds: string[]
}

export type WithNormalizedEntity<T extends Record<string, any>> = {
  [k in keyof T]: T[k] extends Array<any> ? NormalizedEntity<T[k][0]> : unknown
}

export type WithEntityStore<T> = {
  [K in keyof T]: T[K] extends Array<any> ? EntityStore<T[K][0]> : unknown
}

export function syncById<T extends Entity, State extends EntityStore<T>>(state: State, entities: NormalizedEntity<T> = {}): NormalizedEntity<T> {
  return deepmerge<NormalizedEntity<T>>(state.byId, entities, {
    arrayMerge: <Type>(dest: Type[], src: Type[]) => src,
  })
}

export function deepmergeArray<T = any>(array1: T[], array2: T[]) {
  return array2.reduce<T[]>((prev, curr) => (prev.includes(curr) ? prev : [...prev, curr]), array1)
}

export function syncAllIds<T extends Entity, State extends EntityStore<T>>(state: State, entities: NormalizedEntity<T> = {}): string[] {
  return deepmergeArray(state.allIds, Object.keys(entities))
}

export type ValueOf<T> = T[keyof T]

type FirstParameter<T extends (...args: any) => any> = Parameters<T>[0]
type RequiredBy<T, K extends keyof T> = Pick<T, K> & Partial<Omit<T, K>>

export type { ActionType, FirstParameter, RequiredBy }

interface AsyncReducers {
  pending: string
  fulfilled: string
  rejected: string
}

export const createAsyncReducers = <Types extends AsyncReducers, State extends { [K: string]: boolean }>(
  types: Types,
  property: keyof State = 'fetching'
) => {
  return {
    [types.pending]: (state: State) => {
      state[property] = true as any
    },
    [types.fulfilled]: (state: State) => {
      state[property] = false as any
    },
    [types.rejected]: (state: State) => {
      state[property] = false as any
    },
  }
}

export const getTypesThunkActions = <Actions>(actions: Actions) => {
  type Types = {
    [key in keyof Actions]: {
      pending: string
      fulfilled: string
      rejected: string
    }
  }

  return keys(actions).reduce((acc, curr) => {
    const thunkAction = actions[curr as keyof Actions] as unknown as ReturnType<typeof createAsyncThunk>
    return {
      ...acc,
      [curr]: {
        pending: thunkAction.pending.type,
        fulfilled: thunkAction.fulfilled.type,
        rejected: thunkAction.rejected.type,
      },
    }
  }, {} as Types)
}

export const getActionTypes = <Actions>(actions: Actions) => {
  type Types = {
    [key in keyof Actions]: string
  }
  return Object.keys(actions).reduce((acc, curr) => {
    return { ...acc, [curr]: `${actions[curr as keyof Actions]}` }
  }, {} as Types)
}

export type WithSuccess<T, A = void> = T & { onSuccess?: (args?: A) => void }

export const b64toBlob = (b64Data: string, contentType = '', sliceSize = 512) => {
  const byteCharacters = atob(b64Data.replace(/(\s)/g, ''))
  const byteArrays: Uint8Array[] = []

  for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    const slice = byteCharacters.slice(offset, offset + sliceSize)

    const byteNumbers = new Array(slice.length)
    for (let i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i)
    }
    const byteArray = new Uint8Array(byteNumbers)
    byteArrays.push(byteArray)
  }

  const blob = new Blob(byteArrays, { type: contentType })
  return blob
}

export const getBase64FromFile = async (file: File): Promise<string> => {
  return await new Promise((resolve, reject) => {
    const reader = new FileReader()
    reader.readAsDataURL(file)
    reader.onload = () => resolve((reader.result as string)?.split(',')[1] || '')
    reader.onerror = (error) => reject(error)
  })
}
