import { useCallback, useEffect, useRef, useState } from 'react'
import type { ContentEntry, ContentInput, ContentVisibility, EntityRef } from '@platform-modules/content'
import type { ContentClient } from './client.js'
import { ContentEntryNotSavedError } from './errors.js'

function entryToInput(ref: ContentEntry): ContentInput {
  return {
    id: ref.id,
    slug: ref.slug,
    type: ref.type,
    title: ref.title,
    body: ref.body,
    visibility: ref.visibility,
    termIds: ref.terms.map((term) => term.id),
  }
}

function emptyInput(): ContentInput {
  return { slug: '', type: '', title: '', body: '', visibility: 'public' }
}

function requireId(entry: ContentEntry | null, action: string): string {
  if (!entry?.id) throw new ContentEntryNotSavedError(action)
  return entry.id
}

export function useContentEntry(client: ContentClient, ref: ContentEntry | null) {
  const [entry, setEntry] = useState<ContentEntry | null>(ref)
  const [draft, setDraft] = useState<ContentInput>(() => (ref ? entryToInput(ref) : emptyInput()))
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState<Error | null>(null)

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

  useEffect(() => {
    setEntry(ref)
    setDraft(ref ? entryToInput(ref) : emptyInput())
    setError(null)
  }, [ref])

  const run = useCallback(async <T>(fn: () => Promise<T>): Promise<T | undefined> => {
    setLoading(true)
    setError(null)
    try {
      const result = await fn()
      setLoading(false)
      return result
    } catch (e: unknown) {
      const err = e instanceof Error ? e : new Error(String(e))
      setError(err)
      setLoading(false)
      throw err
    }
  }, [])

  const save = useCallback(
    async (input?: ContentInput) => {
      const payload = input ?? draft
      const saved = await run(() => clientRef.current.put(payload))
      if (saved) {
        setEntry(saved)
        setDraft(entryToInput(saved))
      }
      return saved
    },
    [draft, run],
  )

  const publish = useCallback(async () => {
    return run(async () => {
      const id = requireId(entry, 'publish')
      return clientRef.current.publish(id)
    })
  }, [entry, run])

  const schedule = useCallback(
    async (at: string) => {
      return run(async () => {
        const id = requireId(entry, 'schedule')
        return clientRef.current.schedule(id, at)
      })
    },
    [entry, run],
  )

  const unpublish = useCallback(async () => {
    return run(async () => {
      const id = requireId(entry, 'unpublish')
      return clientRef.current.unpublish(id)
    })
  }, [entry, run])

  const trash = useCallback(async () => {
    return run(async () => {
      const id = requireId(entry, 'trash')
      return clientRef.current.trash(id)
    })
  }, [entry, run])

  const restore = useCallback(async () => {
    return run(async () => {
      const id = requireId(entry, 'restore')
      return clientRef.current.restore(id)
    })
  }, [entry, run])

  const setVisibility = useCallback(
    async (visibility: ContentVisibility) => {
      const result = await run(async () => {
        const id = requireId(entry, 'setVisibility')
        return clientRef.current.setVisibility(id, visibility)
      })
      if (result && entry) {
        setEntry({ ...entry, visibility })
      }
      return result
    },
    [entry, run],
  )

  const remove = useCallback(async () => {
    return run(async () => {
      const id = requireId(entry, 'remove')
      return clientRef.current.remove(id)
    })
  }, [entry, run])

  return {
    entry,
    loading,
    error,
    save,
    publish,
    schedule,
    unpublish,
    trash,
    restore,
    setVisibility,
    remove,
  }
}

export type UseContentEntryResult = ReturnType<typeof useContentEntry>
