'use client'

import * as React from 'react'
import { isServer } from '@tanstack/router-core/isServer'
import { useRouter } from './useRouter'
import { useHydrated } from './ClientOnly'
import type { RouterManagedTag } from '@tanstack/router-core'

const INLINE_CSS_HYDRATION_ATTR = 'data-tsr-inline-css'

interface ScriptAttrs {
  [key: string]: string | boolean | undefined
  src?: string
  suppressHydrationWarning?: boolean
}

export function Asset(
  asset: RouterManagedTag & { nonce?: string },
): React.ReactElement | null {
  const { attrs, children, nonce } = asset

  switch (asset.tag) {
    case 'title':
      return (
        <title {...attrs} suppressHydrationWarning>
          {children}
        </title>
      )
    case 'meta':
      return <meta {...attrs} suppressHydrationWarning />
    case 'link':
      return (
        <link
          {...attrs}
          precedence={
            attrs?.precedence ??
            (attrs?.rel === 'stylesheet' ? 'default' : undefined)
          }
          nonce={nonce}
          suppressHydrationWarning
        />
      )
    case 'style':
      if (
        asset.inlineCss &&
        (process.env.TSS_INLINE_CSS_ENABLED === 'true' ||
          (process.env.TSS_INLINE_CSS_ENABLED === undefined && isServer))
      ) {
        return (
          <InlineCssStyle attrs={attrs} nonce={nonce}>
            {children}
          </InlineCssStyle>
        )
      }

      return (
        <style
          {...attrs}
          dangerouslySetInnerHTML={{ __html: children as string }}
          nonce={nonce}
        />
      )
    case 'script':
      return <Script attrs={attrs}>{children}</Script>
    default:
      return null
  }
}

function InlineCssStyle({
  attrs,
  children,
  nonce,
}: {
  attrs?: Record<string, any>
  children?: RouterManagedTag['children']
  nonce?: string
}) {
  const isInlineCssPlaceholder = children === undefined
  const [hydratedInlineCss] = React.useState(() => {
    if (!isInlineCssPlaceholder || typeof document === 'undefined') {
      return undefined
    }

    return (
      document.querySelector<HTMLStyleElement>(
        `style[${INLINE_CSS_HYDRATION_ATTR}]`,
      )?.textContent ?? undefined
    )
  })
  const html = isInlineCssPlaceholder
    ? (hydratedInlineCss ?? '')
    : (children ?? '')

  return (
    <style
      {...attrs}
      {...{ [INLINE_CSS_HYDRATION_ATTR]: '' }}
      dangerouslySetInnerHTML={{ __html: html }}
      nonce={nonce}
      suppressHydrationWarning
    />
  )
}

function Script({
  attrs,
  children,
}: {
  attrs?: ScriptAttrs
  children?: string
}) {
  const router = useRouter()
  const hydrated = useHydrated()
  const dataScript =
    typeof attrs?.type === 'string' &&
    attrs.type !== '' &&
    attrs.type !== 'text/javascript' &&
    attrs.type !== 'module'

  if (
    process.env.NODE_ENV !== 'production' &&
    attrs?.src &&
    typeof children === 'string' &&
    children.trim().length
  ) {
    console.warn(
      '[TanStack Router] <Script> received both `src` and `children`. The `children` content will be ignored. Remove `children` or remove `src`.',
    )
  }

  React.useEffect(() => {
    if (dataScript) return

    if (attrs?.src) {
      const normSrc = (() => {
        try {
          const base = document.baseURI || window.location.href
          return new URL(attrs.src, base).href
        } catch {
          return attrs.src
        }
      })()
      const existingScript = Array.from(
        document.querySelectorAll('script[src]'),
      ).find((el) => (el as HTMLScriptElement).src === normSrc)

      if (existingScript) {
        return
      }

      const script = document.createElement('script')

      for (const [key, value] of Object.entries(attrs)) {
        if (
          key !== 'suppressHydrationWarning' &&
          value !== undefined &&
          value !== false
        ) {
          script.setAttribute(
            key,
            typeof value === 'boolean' ? '' : String(value),
          )
        }
      }

      document.head.appendChild(script)

      return () => {
        if (script.parentNode) {
          script.parentNode.removeChild(script)
        }
      }
    }

    if (typeof children === 'string') {
      const typeAttr =
        typeof attrs?.type === 'string' ? attrs.type : 'text/javascript'
      const nonceAttr =
        typeof attrs?.nonce === 'string' ? attrs.nonce : undefined
      const existingScript = Array.from(
        document.querySelectorAll('script:not([src])'),
      ).find((el) => {
        if (!(el instanceof HTMLScriptElement)) return false
        const sType = el.getAttribute('type') ?? 'text/javascript'
        const sNonce = el.getAttribute('nonce') ?? undefined
        return (
          el.textContent === children &&
          sType === typeAttr &&
          sNonce === nonceAttr
        )
      })

      if (existingScript) {
        return
      }

      const script = document.createElement('script')
      script.textContent = children

      if (attrs) {
        for (const [key, value] of Object.entries(attrs)) {
          if (
            key !== 'suppressHydrationWarning' &&
            value !== undefined &&
            value !== false
          ) {
            script.setAttribute(
              key,
              typeof value === 'boolean' ? '' : String(value),
            )
          }
        }
      }

      document.head.appendChild(script)

      return () => {
        if (script.parentNode) {
          script.parentNode.removeChild(script)
        }
      }
    }

    return undefined
  }, [attrs, children, dataScript])

  // --- Server rendering ---
  if (isServer ?? router.isServer) {
    if (attrs?.src) {
      return <script {...attrs} suppressHydrationWarning />
    }

    if (typeof children === 'string') {
      return (
        <script
          {...attrs}
          dangerouslySetInnerHTML={{ __html: children }}
          suppressHydrationWarning
        />
      )
    }

    return null
  }

  // --- Client rendering ---

  // Data scripts (e.g. application/ld+json) are rendered in the tree;
  // the useEffect intentionally skips them.
  if (dataScript && typeof children === 'string') {
    return (
      <script
        {...attrs}
        suppressHydrationWarning
        dangerouslySetInnerHTML={{ __html: children }}
      />
    )
  }

  // During hydration (before useEffect has fired), render the script element
  // to match the server-rendered HTML and avoid structural hydration mismatches.
  // After hydration, return null — the useEffect handles imperative injection.
  if (!hydrated) {
    if (attrs?.src) {
      return <script {...attrs} suppressHydrationWarning />
    }

    if (typeof children === 'string') {
      return (
        <script
          {...attrs}
          dangerouslySetInnerHTML={{ __html: children }}
          suppressHydrationWarning
        />
      )
    }
  }

  return null
}
