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

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

import type { FC } from 'react'

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

type TheatreInfo = {
    hostFlg:        boolean
    postId:         number | null
    postStartedAt:  string | null
    watchingUsers:  { id: number; name: string }[] }

type TheatreCommentField = 'content'

const INITIAL_THEATRE_INFO =
    { hostFlg: false,
      postId: null,
      postStartedAt: null,
      watchingUsers: [] as { id: number; name: string }[] } as const


const TheatreDetailPage: FC = () => {
  const { id } = useParams ()

  const commentsRef = useRef<HTMLDivElement> (null)
  const embedRef = useRef<NiconicoViewerHandle> (null)
  const loadingRef = useRef (false)
  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 [status, setStatus] = useState (200)
  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)
  const { fieldErrors, clearValidationErrors, applyValidationError } =
      useValidationErrors<TheatreCommentField> ()

  useEffect (() => {
    loadingRef.current = loading
  }, [loading])

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

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

  useEffect (() => {
    lastCommentNoRef.current = comments[0]?.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)
      {
	setStatus (isApiError (error) ? error.response?.status ?? 200 : 200)
      }
    }) ()

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

  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 => [...newComments, ...prev])
	  }

	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) || loadingRef.current || 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)
  }

  if (status >= 400)
    return <ErrorScreen status={status}/>

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

	<div className="hidden md:block">
	  {post && <TagDetailSidebar post={post}/>}
	</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-auto h-auto border border-black dark:border-white rounded mx-2"
	      onSubmit={async e => {
		e.preventDefault ()

		if (!(content))
		  return

		try
		{
		  setSending (true)
		  clearValidationErrors ()
		  await apiPost (`/theatres/${ id }/comments`, { content })
		  setContent ('')
		  commentsRef.current?.scrollTo ({ top: 0, behavior: 'smooth' })
		}
		catch (error)
		{
		  applyValidationError (error)
		}
		finally
		{
		  setSending (false)
		}
	      }}>
	    <input
		className={inputClass ((fieldErrors.content ?? []).length > 0)}
		type="text"
		placeholder="ここにコメントを入力"
		value={content}
		onChange={e => setContent (e.target.value)}
		disabled={sending}/>
	    <FieldError messages={fieldErrors.content}/>

	    <div
		ref={commentsRef}
		className="overflow-x-hidden overflow-y-scroll text-wrap w-full
			   h-[32vh] md:h-[64vh] border rounded">
	      {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 || `名もなきニジラー（#${ comment.user.id }）`)
			  : '運営'}
		    </div>
		    <div className="w-full text-sm text-right">
		      {dateString (comment.createdAt)}
		    </div>
		  </div>))}
	    </div>
	  </form>

	  <div className="w-auto h-auto border border-black dark:border-white rounded mx-2 mt-4">
	    <div className="p-2">
		現在の同接数：{theatreInfo.watchingUsers.length}
	    </div>

	    <div className="overflow-x-hidden overflow-y-scroll text-wrap w-full h-32
			    border rounded">
	      <ul className="list-inside list-disc">
		{theatreInfo.watchingUsers.map (user => (
		    <li key={user.id} className="px-4 py-1 text-sm">
		      {user.name || `名もなきニジラー（#${ user.id }）`}
		    </li>))}
	      </ul>
	    </div>
	  </div>
	</SidebarComponent>

	<div className="md:hidden">
	  {post && <TagDetailSidebar post={post} sp/>}
	</div>
      </div>)
}

export default TheatreDetailPage
