import { useCallback, useEffect, useRef, useState } from 'react'
import YoutubeEmbed from 'react-youtube'

import NicoViewer from '@/components/NicoViewer'
import TwitterEmbed from '@/components/TwitterEmbed'
import { useDialogue } from '@/components/dialogues/DialogueProvider'
import { Button } from '@/components/ui/button'
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'

import type { FC, RefObject } from 'react'

import type { NiconicoMetadata, NiconicoVideoInfo, NiconicoViewerHandle, Post } from '@/types'
import type { YouTubePlayer } from 'react-youtube'

type YouTubeEvent<T = unknown> = {
    data:    T
    target:  YouTubePlayer }

type Props = {
    ref?:               RefObject<NiconicoViewerHandle | null>
    post:               Post
    onLoadComplete?:    (info: NiconicoVideoInfo) => void
    onMetadataChange?:  (meta: NiconicoMetadata) => void
    onVideoReady?:      (durationMs: number) => void
    onPlaybackChange?:  (currentTimeMs: number) => number | void
    onError?:           (data: unknown) => void }


const PostEmbed: FC<Props> = (
    { ref,
      post,
      onLoadComplete,
      onMetadataChange,
      onVideoReady,
      onPlaybackChange,
      onError },
) => {
  const dialogue = useDialogue ()
  const behaviourSettings = useClientBehaviourSettings ()
  const embedAutoLoadMode = behaviourSettings.embedAutoLoad ?? 'auto'
  const [manualLoadRequested, setManualLoadRequested] = useState (false)
  const [framed, setFramed] = useState (false)
  const [youtubePlayer, setYoutubePlayer] = useState<YouTubePlayer | null> (null)
  const niconicoVideoReadyRef = useRef (false)

  const notifyNiconicoVideoReady = useCallback ((durationMs: number) => {
    if (niconicoVideoReadyRef.current
	|| !(Number.isFinite (durationMs))
	|| durationMs <= 0)
      return

    niconicoVideoReadyRef.current = true
    onVideoReady?.(durationMs)
  }, [onVideoReady])

  const reportYoutubePlayback = useCallback (async (player: YouTubePlayer) => {
    const currentTime = await player.getCurrentTime ()
    const currentTimeMs = currentTime * 1_000
    const targetTimeMs = onPlaybackChange?.(currentTimeMs)

    if (typeof targetTimeMs !== 'number')
      return

    if (Math.abs (currentTimeMs - targetTimeMs) > 5_000)
      await player.seekTo (targetTimeMs / 1_000, true)
  }, [onPlaybackChange])

  const handleYoutubeReady = async (event: YouTubeEvent) => {
    setYoutubePlayer (event.target)

    try
    {
      await event.target.playVideo ()

      const duration = await event.target.getDuration ()
      const durationMs = duration * 1_000
      onVideoReady?.(durationMs)

      if (!(Number.isFinite (durationMs)) || durationMs <= 0)
	return

      await reportYoutubePlayback (event.target)
    }
    catch (error)
    {
      onError?.({ platform: 'youtube', error })
    }
  }

  const handleYoutubeStateChange = (event: YouTubeEvent<number>) => {
    void reportYoutubePlayback (event.target)
  }

  const handleYoutubeError = (event: YouTubeEvent<number>) => {
    onError?.({ platform: 'youtube', code: event.data })
  }

  const handleNiconicoLoadComplete = (info: NiconicoVideoInfo) => {
    notifyNiconicoVideoReady (info.lengthInSeconds * 1_000)
    onLoadComplete?.(info)
  }

  const handleNiconicoMetadataChange = (meta: NiconicoMetadata) => {
    notifyNiconicoVideoReady (meta.duration)
    onPlaybackChange?.(meta.currentTime)
    onMetadataChange?.(meta)
  }

  useEffect (() => {
    niconicoVideoReadyRef.current = false
  }, [post.url])

  useEffect (() => {
    setManualLoadRequested (false)
  }, [embedAutoLoadMode, post.url])

  useEffect (() => {
    if (!(youtubePlayer) || !(onPlaybackChange))
      return

    const timer = setInterval (
	() => void reportYoutubePlayback (youtubePlayer),
	1_000)

    return () => clearInterval (timer)
  }, [onPlaybackChange, reportYoutubePlayback, youtubePlayer])

  const url = new URL (post.url)
  const shouldLoadEmbed =
      embedAutoLoadMode === 'auto'
      || (
	  embedAutoLoadMode === 'manual'
	  && manualLoadRequested
      )

  const externalLink = (
      <a href={post.url} target="_blank" rel="noreferrer">
	外部ページを開く
      </a>)

  const manualLoadControl = (
      <div className="flex flex-wrap items-center gap-3">
	<Button
	    type="button"
	    variant="outline"
	    onClick={() => setManualLoadRequested (true)}>
	  埋め込みを読込
	</Button>
	{externalLink}
      </div>)

  switch (url.hostname.split ('.').slice (-2).join ('.'))
  {
  case 'nicovideo.jp':
    {
      const mVideoId = url.pathname.match (/(?<=\/watch\/)[a-zA-Z0-9]+?(?=\/|$)/)
      if (!(mVideoId))
	break

      const [videoId] = mVideoId

      if (embedAutoLoadMode === 'off')
	return externalLink
      if (shouldLoadEmbed === false)
	return manualLoadControl

      return (
	  <NicoViewer
	      ref={ref}
	      id={videoId}
	      width={640}
	      height={360}
	      onLoadComplete={handleNiconicoLoadComplete}
	      onMetadataChange={handleNiconicoMetadataChange}
	      onError={onError}/>)
    }

  case 'twitter.com':
  case 'x.com':
    {
      const mUserId = url.pathname.match (/(?<=\/)[^/]+?(?=\/|$|\?)/)
      const mStatusId = url.pathname.match (/(?<=\/status\/)\d+?(?=\/|$|\?)/)
      if (!(mUserId) || !(mStatusId))
	break

      const [userId] = mUserId
      const [statusId] = mStatusId

      if (embedAutoLoadMode === 'off')
	return externalLink
      if (shouldLoadEmbed === false)
	return manualLoadControl

      return <TwitterEmbed userId={userId} statusId={statusId}/>
    }

  case 'youtube.com':
    {
      const videoId = url.searchParams.get ('v')
      if (!(videoId))
	break

      if (embedAutoLoadMode === 'off')
	return externalLink
      if (shouldLoadEmbed === false)
	return manualLoadControl

      return (
	  <YoutubeEmbed videoId={videoId} opts={{ playerVars: {
	      playsinline:  1,
	      autoplay:     1,
	      mute:         0,
	      loop:         1,
	      width:        '640',
	      height:       '360' } }}
	      onReady={handleYoutubeReady}
	      onStateChange={handleYoutubeStateChange}
	      onError={handleYoutubeError}/>)
    }
  }

  return (
      <>
	{framed
	 ? (
	    <iframe
		src={post.url}
		title={post.title || post.url}
		width={640}
		height={360}/>)
	 : (
	    <div>
	      {embedAutoLoadMode === 'off'
	       ? externalLink
	       : embedAutoLoadMode === 'manual' && shouldLoadEmbed === false
		 ? manualLoadControl
		 : (
	      <a href="#" onClick={async e => {
		e.preventDefault ()

		setFramed (await dialogue.confirm ({
		    title: '未確認の外部ページを表示します。',
		    description: (
			<div>
			  <p>悪意のあるスクリプトが実行される可能性があります。</p>
			  <p>表示しますか?</p>
			</div>),
		    confirmText: '表示' }))
	      }}>
		外部ページを表示
	      </a>)}
	    </div>)}
      </>)
}

export default PostEmbed
