import { useCallback, useEffect, useRef, useState } from 'react'
import type { EntityRef, EntityType, FieldGroup, FieldValueMap, ResolvedFieldGroup } from './client.js'
import { useFieldsClient } from './provider.js'

function toError(e: unknown): Error {
  return e instanceof Error ? e : new Error(String(e))
}

export function useFieldValues(
  ref: EntityRef,
  opts: { subType?: string; initialData?: FieldValueMap } = {},
) {
  const client = useFieldsClient()
  const hasSeed = opts.initialData !== undefined
  const [values, setValues] = useState<FieldValueMap>(opts.initialData ?? {})
  const [loading, setLoading] = useState(!hasSeed)
  const [error, setError] = useState<Error | null>(null)
  const [reloadToken, setReloadToken] = useState(0)

  const clientRef = useRef(client)
  clientRef.current = client

  const refKey = `${ref.entityType}:${ref.entityId}:${opts.subType ?? ''}`
  const prevRefKey = useRef(refKey)

  const reload = useCallback(() => {
    setReloadToken((t) => t + 1)
  }, [])

  useEffect(() => {
    const refChanged = prevRefKey.current !== refKey
    prevRefKey.current = refKey

    if (hasSeed && reloadToken === 0 && !refChanged) return

    let cancelled = false
    setLoading(true)
    setError(null)

    void clientRef.current
      .getEntityValues(ref, { subType: opts.subType })
      .then((next) => {
        if (!cancelled) {
          setValues(next)
          setLoading(false)
        }
      })
      .catch((e: unknown) => {
        if (!cancelled) {
          setError(toError(e))
          setLoading(false)
        }
      })

    return () => {
      cancelled = true
    }
  }, [ref, refKey, opts.subType, hasSeed, reloadToken])

  return { values, loading, error, reload }
}

export function useEntityFields(
  ref: EntityRef,
  opts: { subType?: string; initialData?: FieldValueMap } = {},
) {
  const client = useFieldsClient()
  const { values, loading: valuesLoading, error: valuesError, reload: reloadValues } = useFieldValues(ref, opts)
  const [groups, setGroups] = useState<ResolvedFieldGroup[]>([])
  const [groupsLoading, setGroupsLoading] = useState(true)
  const [groupsError, setGroupsError] = useState<Error | null>(null)
  const [reloadToken, setReloadToken] = useState(0)

  const clientRef = useRef(client)
  clientRef.current = client

  const refKey = `${ref.entityType}:${ref.entityId}:${opts.subType ?? ''}`

  const reload = useCallback(() => {
    setReloadToken((t) => t + 1)
    reloadValues()
  }, [reloadValues])

  useEffect(() => {
    let cancelled = false
    setGroupsLoading(true)
    setGroupsError(null)

    void clientRef.current
      .resolveGroups({ entityType: ref.entityType, subType: opts.subType })
      .then((next) => {
        if (!cancelled) {
          setGroups(next)
          setGroupsLoading(false)
        }
      })
      .catch((e: unknown) => {
        if (!cancelled) {
          setGroupsError(toError(e))
          setGroupsLoading(false)
        }
      })

    return () => {
      cancelled = true
    }
  }, [ref.entityType, refKey, opts.subType, reloadToken])

  const save = useCallback(
    async (groupId: string, nextValues: FieldValueMap) => {
      await clientRef.current.setEntityValues({ ref, groupId, values: nextValues })
      reloadValues()
    },
    [ref, reloadValues],
  )

  const loading = valuesLoading || groupsLoading
  const error = valuesError ?? groupsError

  return { values, groups, loading, error, save, reload }
}

export function useFieldGroupBuilder(entityType: EntityType, opts: { subType?: string } = {}) {
  const client = useFieldsClient()
  const [groups, setGroups] = useState<ResolvedFieldGroup[]>([])
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<Error | null>(null)
  const [reloadToken, setReloadToken] = useState(0)

  const clientRef = useRef(client)
  clientRef.current = client

  const refresh = useCallback(async () => {
    const next = await clientRef.current.listGroups({ entityType, subType: opts.subType })
    setGroups(next)
    return next
  }, [entityType, opts.subType])

  const reload = useCallback(() => {
    setReloadToken((t) => t + 1)
  }, [])

  useEffect(() => {
    let cancelled = false
    setLoading(true)
    setError(null)

    void clientRef.current
      .listGroups({ entityType, subType: opts.subType })
      .then((next) => {
        if (!cancelled) {
          setGroups(next)
          setLoading(false)
        }
      })
      .catch((e: unknown) => {
        if (!cancelled) {
          setError(toError(e))
          setLoading(false)
        }
      })

    return () => {
      cancelled = true
    }
  }, [entityType, opts.subType, reloadToken])

  const create = useCallback(
    async (group: FieldGroup) => {
      await clientRef.current.createGroup(group)
      await refresh()
    },
    [refresh],
  )

  const update = useCallback(
    async (id: string, patch: Partial<FieldGroup>) => {
      await clientRef.current.updateGroup(id, patch)
      await refresh()
    },
    [refresh],
  )

  const remove = useCallback(
    async (id: string) => {
      await clientRef.current.deleteGroup(id)
      await refresh()
    },
    [refresh],
  )

  return { groups, loading, error, create, update, remove, reload }
}
