import { KeysV2 as Keys } from '@inject/shared/localstorage/keys'
import type { ResultOf, VariablesOf } from '../graphql'
import { graphql } from '../graphql'
import { httpExecutor } from './subschemas/remoteSchema'

const mutation = graphql(`
  mutation mutateKV($exerciseId: ID!, $key: String!, $value: String!) {
    writeValue(exerciseId: $exerciseId, key: $key, value: $value) {
      newValue
    }
  }
`)

const query = graphql(`
  query readKV($exerciseId: ID!, $key: String!) {
    readValue(exerciseId: $exerciseId, key: $key)
  }
`)

type KeysV2 = typeof Keys

// Separate types for string keys and function keys
type StringKeyType = {
  [K in keyof KeysV2]: KeysV2[K][0] extends string ? K : never
}[keyof KeysV2]

type FunctionKeyType = {
  [K in keyof KeysV2]: KeysV2[K][0] extends (...args: infer _A) => string
    ? K
    : never
}[keyof KeysV2]

// Union type for all keys
type KeyType = StringKeyType | FunctionKeyType
type KeyArgs<K extends KeyType> = KeysV2[K][0] extends (
  ...args: infer A
) => string
  ? A
  : never

type KvStorageEventType<K extends KeyType> = {
  exerciseId: string
  value: string
  key: K
} & (
  | {
      key: K & StringKeyType
      params?: never
    }
  | {
      key: K & FunctionKeyType
      params: KeyArgs<K>
    }
)
type WindowEventMapExtension = {
  [K in keyof KeysV2]: CustomEvent<KvStorageEventType<K>>
}
declare global {
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
  interface WindowEventMap extends WindowEventMapExtension {}
}
// Helper function to infer the type
export function processKvStorageEvent<K extends KeyType>(
  type: K,
  event: KvStorageEventType<K>
): KvStorageEventType<K> | undefined {
  if (event.key === type) {
    return event as KvStorageEventType<K>
  } else {
    return undefined
  }
}

// TODO: create temporary cache for noting changed variables (to prevent double re-rendering)
export function executeWrite<K extends KeyType>({
  exerciseId,
  key,
  value,
  subkey,
}: {
  exerciseId: string
  value: string
} & (
  | {
      key: K & StringKeyType
      subkey?: never
    }
  | {
      key: K & FunctionKeyType
      subkey: KeyArgs<K>
    }
)) {
  if (typeof Keys[key][0] === 'string') {
    return httpExecutor<
      ResultOf<typeof mutation>,
      VariablesOf<typeof mutation>
    >({
      document: mutation,
      variables: {
        exerciseId,
        key: Keys[key][0],
        value,
      },
    })
  } else if (typeof Keys[key][0] === 'function' && subkey !== undefined) {
    const fn = Keys[key][0]
    //@ts-ignore
    const fullkey = fn(...subkey) // there is a type error expected, ignore it
    return httpExecutor<
      ResultOf<typeof mutation>,
      VariablesOf<typeof mutation>
    >({
      document: mutation,
      variables: {
        exerciseId,
        key: fullkey,
        value,
      },
    })
  }
  throw Error('Unknown')
}

// TODO: establish hot-cache for already existing fields with ensurance of TTL
export function executeRead<K extends KeyType>({
  exerciseId,
  key,
  subkey,
}: {
  exerciseId: string
} & (
  | {
      key: K & StringKeyType
      subkey?: never
    }
  | {
      key: K & FunctionKeyType
      subkey: KeyArgs<K>
    }
)) {
  if (typeof Keys[key][0] === 'string') {
    return httpExecutor<ResultOf<typeof query>, VariablesOf<typeof query>>({
      document: query,
      variables: {
        exerciseId,
        key: Keys[key][0],
      },
    })
  } else if (typeof Keys[key][0] === 'function' && subkey !== undefined) {
    const fn = Keys[key][0]
    //@ts-ignore
    const fullkey = fn(...subkey) // there is a type error expected, ignore it
    return httpExecutor<ResultOf<typeof query>, VariablesOf<typeof query>>({
      document: query,
      variables: {
        exerciseId,
        key: fullkey,
      },
    })
  }
  throw Error('Unknown')
}

export function processKVSubscription({
  exerciseId,
  key,
  value,
}: {
  exerciseId: string
  key: string
  value: string
}) {
  // identify the key
  const needle = Object.entries(Keys).find(([, objectValue]) => {
    if (key.startsWith(objectValue[1])) {
      return true
    }
  })

  if (!needle) {
    return
  }

  const [objectKey, objectValue] = needle
  const [fn, prefix] = objectValue
  const removedPrefix = key.slice(prefix?.length)

  if (removedPrefix === '') {
    // TODO: emit event for modding this value
    return
  }
  if (!removedPrefix.startsWith(':')) {
    throw Error("Invariant error, all prefixed keys must delimitize with ':'")
  }
  const args = removedPrefix.slice(1).split(',')
  //@ts-ignore
  const fnOutput = fn(...args) //expected to not work, we are doing magic here
  if (fnOutput !== key) {
    throw Error(
      'Invariant error, all variables must be separated by delimiters or formatting does not answer to arguments'
    )
  }

  /*
   * @ts-ignore
   *  window.dispatchEvent(new CustomEvent(objectKey, {
   *      detail: {
   *          key: objectKey,
   *          exerciseId,
   *          value,
   *          // eslint-disable-next-line @typescript-eslint/no-explicit-any
   *          params: args as unknown as any
   *      }
   *  }))
   */

  return {
    key: objectKey,
    exerciseId,
    value,
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    params: args as unknown as any,
  } as KvStorageEventType<KeyType>
}
