import { type FC, useEffect, useState, type JSX } from "hono/jsx"
import type { Action } from "./main"

type ActionData<T extends (...args: any) => Promise<any>> = Exclude<Awaited<ReturnType<T>>, Response>

export type ActionFns = {
  setStatus: (state: "idle" | "submitting") => void
  setData: (data: any) => void
  setError: (error: string) => void
}

declare global {
  interface Window {
    _setLoaderData?: (data: any) => void
    _actionFns?: ActionFns
  }
}

export const wrapComponentWithLoader = <D,>(Component: FC<D>) => {
  const WrappedComponent = () => {
    const [loaderData, setLoaderData] = useState(() => {
      const loaderDataString = document.getElementById("__LOADER_DATA__")?.textContent
      return JSON.parse(loaderDataString || "{}")
    })

    useEffect(() => {
      window._setLoaderData = setLoaderData
    }, [])

    return <Component {...loaderData} />
  }

  return WrappedComponent
}

// Hook to access action result on the client
export const useAction = <TAction extends Action>() => {
  const [data, setData] = useState<ActionData<TAction> | undefined>()
  const [status, setStatus] = useState("idle")
  const [error, setError] = useState<string>()

  useEffect(() => {
    window._actionFns = { setData, setStatus, setError }
  }, [])

  return { data, loading: status === "submitting", error }
}

// Helper to submit an action without a form
type SubmitActionOptions = {
  url?: string
  method?: "POST" | "PUT" | "PATCH" | "DELETE"
}
export const submitAction = async (data: any, options: SubmitActionOptions = {}) => {
  const actionFns = window._actionFns

  try {
    const { url = window.location.href, method = "POST" } = options

    actionFns?.setStatus("submitting")

    let body
    // if data is a FormData, we can use it directly
    if (data instanceof FormData) {
      body = data
    } else {
      // Otherwise, convert to FormData
      const formData = new FormData()
      Object.entries(data).forEach(([key, value]) => {
        formData.append(key, String(value))
      })
      body = formData
    }

    const res = await fetch(url, { method, body })

    if (res.ok) {
      const { actionData, loaderData } = (await res.json()) as any
      window._setLoaderData!(loaderData)

      actionFns?.setData(actionData)
    } else {
      const errorText = await res.text()
      throw new Error(`Error ${res.status}: ${errorText}`)
    }
  } catch (error) {
    actionFns?.setError(`${error}`)
    console.log(`🚨 Failed to submit`, error)
  } finally {
    actionFns?.setStatus("idle")
  }
}

// Form component with built-in enhancement
type FormProps = JSX.IntrinsicElements["form"] & { name: string }
export const Form = (props: FormProps) => {
  const handleSubmit = async (e: Event) => {
    const actionFns = window._actionFns
    if (!actionFns) {
      throw new Error(`No useAction hook found.`)
    }

    try {
      e.preventDefault()

      actionFns.setStatus("submitting")
      const form = e.currentTarget as HTMLFormElement
      const body = new FormData(form)
      body.append("_actionName", props.name)
      const url = form.action || window.location.href
      const res = await fetch(url, { method: props.method || "POST", body })

      if (res.status === 303) {
        window.location.href = res.headers.get("Location")!
      } else if (res.ok) {
        const { actionData, loaderData } = (await res.json()) as any
        window._setLoaderData!(loaderData)
        actionFns.setData(actionData)
      } else {
        const errorText = await res.text()
        throw new Error(`Error ${res.status}: ${errorText}`)
      }
    } catch (error) {
      actionFns.setError(`${error}`)
      console.log(`🚨 Failed to submit`, error)
    } finally {
      actionFns.setStatus("idle")
    }
  }

  return (
    <form {...props} onSubmit={handleSubmit}>
      {props.children}
    </form>
  )
}
