import { MutationObserver, noop, notifyManager } from '@tanstack/query-core'
import { useQueryClient } from './useQueryClient.js'
import { watchChanges } from './utils.svelte.js'
import type {
  Accessor,
  CreateMutateFunction,
  CreateMutationOptions,
  CreateMutationResult,
} from './types.js'

import type { DefaultError, QueryClient } from '@tanstack/query-core'

/**
 * @param options - A function that returns mutation options
 * @param queryClient - Custom query client which overrides provider
 */
export function createMutation<
  TData = unknown,
  TError = DefaultError,
  TVariables = void,
  TContext = unknown,
>(
  options: Accessor<CreateMutationOptions<TData, TError, TVariables, TContext>>,
  queryClient?: Accessor<QueryClient>,
): CreateMutationResult<TData, TError, TVariables, TContext> {
  const client = $derived(useQueryClient(queryClient?.()))

  // svelte-ignore state_referenced_locally - intentional, initial value
  let observer = $state(
    // svelte-ignore state_referenced_locally - intentional, initial value
    new MutationObserver<TData, TError, TVariables, TContext>(
      client,
      options(),
    ),
  )

  watchChanges(
    () => client,
    'pre',
    () => {
      observer = new MutationObserver(client, options())
    },
  )

  $effect.pre(() => {
    observer.setOptions(options())
  })

  const mutate = <CreateMutateFunction<TData, TError, TVariables, TContext>>((
    variables,
    mutateOptions,
  ) => {
    observer.mutate(variables, mutateOptions).catch(noop)
  })

  let result = $state(observer.getCurrentResult())
  watchChanges(
    () => observer,
    'pre',
    () => {
      result = observer.getCurrentResult()
    },
  )

  $effect.pre(() => {
    const unsubscribe = observer.subscribe((val) => {
      notifyManager.batchCalls(() => {
        Object.assign(result, val)
      })()
    })
    return unsubscribe
  })

  const resultProxy = $derived(
    new Proxy(result, {
      get: (_, prop) => {
        const r = {
          ...result,
          mutate,
          mutateAsync: result.mutate,
        }
        if (prop == 'value') return r
        // @ts-expect-error
        return r[prop]
      },
    }),
  )

  // @ts-expect-error
  return resultProxy
}
