import { useEffect, useRef, useState } from 'react'
import { Helmet } from 'react-helmet-async'
import { useParams } from 'react-router-dom'

import PostEmbed from '@/components/PostEmbed'
import PrefetchLink from '@/components/PrefetchLink'
import TagDetailSidebar from '@/components/TagDetailSidebar'
import MainArea from '@/components/layout/MainArea'
import SidebarComponent from '@/components/layout/SidebarComponent'
import { SITE_TITLE } from '@/config'
import { apiGet, apiPatch, apiPost, apiPut } from '@/lib/api'
import { fetchPost } from '@/lib/posts'
import { dateString } from '@/lib/utils'

import type { FC } from 'react'

import type { NiconicoMetadata,
	      NiconicoViewerHandle,
	      Post,
	      Theatre,
	      TheatreComment } from '@/types'

type TheatreInfo = {
    hostFlg:        boolean
    postId:         number | null
    postStartedAt:  string | null }

const INITIAL_THEATRE_INFO = { hostFlg: false, postId: null, postStartedAt: null } as const


export default (() => {
  const { id } = useParams ()

  const commentsRef = useRef<HTMLDivElement> (null)
  const embedRef = useRef<NiconicoViewerHandle> (null)
  const theatreInfoRef = useRef<TheatreInfo> (INITIAL_THEATRE_INFO)
  const videoLengthRef = useRef (0)
  const lastCommentNoRef = useRef (0)

  const [comments, setComments] = useState<TheatreComment[]> ([])
  const [content, setContent] = useState ('')
  const [loading, setLoading] = useState (false)
  const [sending, setSending] = useState (false)
  const [theatre, setTheatre] = useState<Theatre | null> (null)
  const [theatreInfo, setTheatreInfo] = useState<TheatreInfo> (INITIAL_THEATRE_INFO)
  const [post, setPost] = useState<Post | null> (null)
  const [videoLength, setVideoLength] = useState (0)

  useEffect (() => {
    theatreInfoRef.current = theatreInfo
  }, [theatreInfo])

  useEffect (() => {
    videoLengthRef.current = videoLength
  }, [videoLength])

  useEffect (() => {
    lastCommentNoRef.current = comments.at (-1)?.no ?? 0
  }, [comments])

  useEffect (() => {
    if (!(id))
      return

    let cancelled = false

    setComments ([])
    setTheatre (null)
    setPost (null)
    setTheatreInfo (INITIAL_THEATRE_INFO)
    setVideoLength (0)
    lastCommentNoRef.current = 0

    void (async () => {
      try
      {
	const data = await apiGet<Theatre> (`/theatres/${ id }`)
	if (!(cancelled))
	  setTheatre (data)
      }
      catch (error)
      {
	console.error (error)
      }
    }) ()

    return () => {
      cancelled = true
    }
  }, [id])

  useEffect (() => {
    commentsRef.current?.scrollTo ({
	top: commentsRef.current.scrollHeight,
	behavior: 'smooth' })
  }, [commentsRef])

  useEffect (() => {
    if (!(id))
      return

    let cancelled = false
    let running = false

    const tick = async () => {
      if (running)
	return

      running = true

      try
      {
	const newComments = await apiGet<TheatreComment[]> (
	    `/theatres/${ id }/comments`,
	    { params: { no_gt: lastCommentNoRef.current } })

	if (!(cancelled) && newComments.length > 0)
	  {
	    lastCommentNoRef.current = newComments[newComments.length - 1].no
	    setComments (prev => [...prev, ...newComments])
	  }

	const currentInfo = theatreInfoRef.current
	const ended =
	    currentInfo.hostFlg
	    && currentInfo.postStartedAt
	    && ((Date.now () - (new Date (currentInfo.postStartedAt)).getTime ())
	        > videoLengthRef.current + 3_000)

	if (ended)
	  {
	    if (!(cancelled))
	      setTheatreInfo (prev => ({ ...prev, postId: null, postStartedAt: null }))

	    return
	  }

	const nextInfo = await apiPut<TheatreInfo> (`/theatres/${ id }/watching`)
	if (!(cancelled))
	  setTheatreInfo (nextInfo)
      }
      catch (error)
      {
	console.error (error)
      }
      finally
      {
	running = false
      }
    }

    tick ()
    const interval = setInterval (() => tick (), 1_500)

    return () => {
      cancelled = true
      clearInterval (interval)
    }
  }, [id])

  useEffect (() => {
    if (!(id) || !(theatreInfo.hostFlg) || loading || theatreInfo.postId != null)
      return

    let cancelled = false

    void (async () => {
      setLoading (true)

      try
      {
	await apiPatch<void> (`/theatres/${ id }/next_post`)
      }
      catch (error)
      {
	console.error (error)
      }
      finally
      {
	if (!(cancelled))
	  setLoading (false)
      }
    }) ()

    return () => {
      cancelled = true
    }
  }, [id, theatreInfo.hostFlg, theatreInfo.postId])

  useEffect (() => {
    setVideoLength (0)

    if (theatreInfo.postId == null)
      return

    let cancelled = false

    void (async () => {
      try
      {
	const nextPost = await fetchPost (String (theatreInfo.postId))
	if (!(cancelled))
	  setPost (nextPost)
      }
      catch (error)
      {
	console.error (error)
      }
    }) ()

    return () => {
      cancelled = true
    }
  }, [theatreInfo.postId])

  const syncPlayback = (meta: NiconicoMetadata) => {
    if (!(theatreInfo.postStartedAt))
      return

    const targetTime = Math.min (
	Math.max (0, Date.now () - (new Date (theatreInfo.postStartedAt)).getTime ()),
	videoLength)

    const drift = Math.abs (meta.currentTime - targetTime)

    if (drift > 5_000)
      embedRef.current?.seek (targetTime)
  }

  return (
      <div className="md:flex md:flex-1">
	<Helmet>
	  {theatre && (
	      <title>
		{'上映会場'
		 + (theatre.name ? `『${ theatre.name }』` : ` #${ theatre.id }`)
		 + ` | ${ SITE_TITLE }`}
	      </title>)}
	</Helmet>

	<div className="hidden md:block">
	  <TagDetailSidebar post={post ?? null}/>
	</div>

	<MainArea>
	  {post ? (
	      <>
		<PostEmbed
		    key={post.id}
		    ref={embedRef}
		    post={post}
		    onLoadComplete={info => {
		      embedRef.current?.play ()
		      setVideoLength (info.lengthInSeconds * 1_000)
		    }}
		    onMetadataChange={syncPlayback}/>
		<div className="m-2">
		  <>再生中：</>
		  <PrefetchLink to={`/posts/${ post.id }`} className="font-bold">
		    {post.title || post.url}
		  </PrefetchLink>
		</div>
	      </>) : 'Loading...'}
	</MainArea>

	<SidebarComponent>
	  <form
	      className="w-full h-5/6"
	      onSubmit={async e => {
		e.preventDefault ()

		if (!(content))
		  return

		try
		{
		  setSending (true)
		  await apiPost (`/theatres/${ id }/comments`, { content })
		  setContent ('')
		  commentsRef.current?.scrollTo ({
		      top: commentsRef.current.scrollHeight,
		      behavior: 'smooth' })
		}
		finally
		{
		  setSending (false)
		}
	      }}>
	    <div
		ref={commentsRef}
		className="overflow-x-hidden overflow-y-scroll text-wrap
			   border border-black dark:border-white w-full h-[80vh]">
	      {comments.map (comment => (
		  <div key={comment.no} className="p-2">
		    <div className="w-full">
		      {comment.content}
		    </div>
		    <div className="w-full text-sm text-right">
		      by {comment.user ? (comment.user.name || '名もなきニジラー') : '運営'}
		    </div>
		    <div className="w-full text-sm text-right">
		      {dateString (comment.createdAt)}
		    </div>
		  </div>))}
	    </div>
	    <input
		className="w-full p-2 border border-black dark:border-white"
		type="text"
		value={content}
		onChange={e => setContent (e.target.value)}
		disabled={sending}/>
	  </form>
	</SidebarComponent>

	<div className="md:hidden">
	  <TagDetailSidebar post={post ?? null}/>
	</div>
      </div>)
}) satisfies FC
