import { useQuery } from '@tanstack/react-query'
import { AnimatePresence, motion } from 'framer-motion'
import { Fragment, useEffect, useLayoutEffect, useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'

import Separator from '@/components/MenuSeparator'
import PrefetchLink from '@/components/PrefetchLink'
import TopNavUser from '@/components/TopNavUser'
import { WikiIdBus } from '@/lib/eventBus/WikiIdBus'
import { materialsKeys, tagsKeys, wikiKeys } from '@/lib/queryKeys'
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
import { canEditContent } from '@/lib/users'
import { fetchTag, fetchTagByName } from '@/lib/tags'
import { fetchMaterial } from '@/lib/materials'
import { cn } from '@/lib/utils'
import { fetchWikiPage } from '@/lib/wiki'

import type { FC, MouseEvent } from 'react'

import type { Material, Menu, MenuVisibleItem, Tag, User } from '@/types'

type Props = { user: User | null }


export const menuOutline = (
    { tag, material, wikiId, user, pathName }: {
	tag?: Tag | null
	material?: Material | null
	wikiId: number | null
	user: User | null
	pathName: string },
): Menu => {
  const postCount = tag?.postCount ?? material?.tag?.postCount ?? 0
  const editable = canEditContent (user)

  const wikiPageFlg = Boolean (/^\/wiki\/(?!new|changes)[^/]+/.test (pathName) && wikiId)
  const wikiTitle = pathName.split ('/')[2] ?? ''

  const tagFlg = /^\/tags\/\d+/.test (pathName)

  const materialFlg = /^\/materials\/\d+/.test (pathName)

  return [
      { name: '広場', to: '/posts', subMenu: [
	  { name: '一覧', to: '/posts' },
	  { name: '検索', to: '/posts/search' },
	  { name: '追加', to: '/posts/new', visible: editable },
	  { name: '全体履歴', to: '/posts/changes' },
	  { name: 'ヘルプ', to: '/wiki/ヘルプ:広場' }] },
      { name: 'タグ', to: '/tags', subMenu: [
	  { name: 'マスタ', to: '/tags' },
	  { name: 'ニコニコ連携', to: '/tags/nico' },
	  { name: '全体履歴', to: '/tags/changes' },
	  { name: 'ヘルプ', to: '/wiki/ヘルプ:タグ' },
	  { component: <Separator/>, visible: tagFlg },
	  { name: `広場 (${ postCount || 0 })`,
	    to: `/posts?tags=${ encodeURIComponent (tag?.name ?? '') }`,
	    visible: tagFlg },
	  { name: '履歴', to: `/tags/changes?id=${ tag?.id }`,
	    visible: tagFlg && tag?.category !== 'nico' }] },
      { name: '素材', to: '/materials', visible: true, subMenu: [
	  { name: '一覧', to: '/materials' },
	  { name: '追加', to: '/materials/new', visible: editable },
	  { name: '抑止', to: '/materials/suppressions',
	    visible: user?.role === 'admin' },
	  { name: '全体履歴', to: '/materials/changes' },
	  { name: 'ヘルプ', to: '/wiki/ヘルプ:素材管理' },
	  { component: <Separator/>, visible: materialFlg },
	  { name: `広場 (${ postCount || 0 })`,
	    to: `/posts?tags=${ encodeURIComponent (material?.tag?.name ?? '') }`,
	    visible: materialFlg && Boolean (material?.tag) },
	  { name: '履歴', to: `/materials/changes?material_id=${ material?.id }`,
	    visible: materialFlg }] },
      { name: 'Wiki', to: '/wiki/ヘルプ:ホーム', base: '/wiki', subMenu: [
	  { name: '検索', to: '/wiki' },
	  { name: '新規', to: '/wiki/new', visible: editable },
	  { name: '全体履歴', to: '/wiki/changes' },
	  { name: 'ヘルプ', to: '/wiki/ヘルプ:Wiki' },
	  { component: <Separator/>, visible: wikiPageFlg },
	  { name: `広場 (${ postCount || 0 })`, to: `/posts?tags=${ wikiTitle }`,
	    visible: wikiPageFlg },
	  { name: '履歴', to: `/wiki/changes?id=${ wikiId }`, visible: wikiPageFlg },
	  { name: '編輯', to: `/wiki/${ wikiId || wikiTitle }/edit`,
	    visible: wikiPageFlg && editable }] },
      { name: 'おたのしみ', visible: false, subMenu: [
	  { name: '上映会 (β)', to: '/theatres/1' },
	  { name: 'グカネータ (β)', to: '/gekanator' }] },
      { name: 'ユーザ', to: '/users/settings', visible: false, subMenu: [
	  { name: '一覧', to: '/users', visible: false },
	  { name: 'お前', to: `/users/${ user?.id }`, visible: false },
	  { name: '設定', to: '/users/settings', visible: Boolean (user) }] },
      { name: '法規', visible: false, subMenu: [
	  { name: '利用規約', to: '/tos' }] }]
}


const TopNav: FC<Props> = ({ user }) => {
  const location = useLocation ()
  const behaviourSettings = useClientBehaviourSettings ()
  const animationMode = behaviourSettings.animation ?? 'normal'
  const animationsOff = animationMode === 'off'
  const reducedAnimations = animationMode === 'reduced'

  const dirRef = useRef<(-1) | 1> (1)
  const itemsRef = useRef<(HTMLAnchorElement | null)[]> ([])
  const navRef = useRef<HTMLDivElement | null> (null)

  const measure = (idx: number) => {
    const nav = navRef.current
    const el = itemsRef.current[idx < 0 ? visibleMenu.length : idx]

    if (!(nav) || !(el))
      {
	setHL ({ left: 0, width: 0, visible: true })
	return
      }

    const navRect = nav.getBoundingClientRect ()
    const elRect = el.getBoundingClientRect ()

    setHL ({ left:    elRect.left - navRect.left,
	     width:   elRect.width,
	     visible: true })
  }

  const [hl, setHL] = useState<{ left: number; width: number; visible: boolean }> ({
      left:     0,
      width:    0,
      visible:  false })
  const [menuOpen, setMenuOpen] = useState (false)
  const [moreVsbl, setMoreVsbl] = useState (false)
  const [openItemIdx, setOpenItemIdx] = useState (-1)
  const [wikiId, setWikiId] = useState<number | null> (WikiIdBus.get ())

  const wikiIdStr = String (wikiId ?? '')

  const { data: wikiPage } = useQuery ({
      enabled: Boolean (wikiIdStr),
      queryKey: wikiKeys.show (wikiIdStr, { }),
      queryFn: () => fetchWikiPage (wikiIdStr, { }) })

  const tagFlg = /^\/tags\/\d+/.test (location.pathname)
  const materialFlg = /^\/materials\/\d+/.test (location.pathname)
  const effectiveTitle = (((tagFlg || materialFlg)
			   ? location.pathname.split ('/')[2]
			   : wikiPage?.title)
			  ?? '')

  const { data: tag } = useQuery ({
      enabled: Boolean (effectiveTitle),
      queryKey: tagsKeys.show (effectiveTitle),
      queryFn: () => (tagFlg ? fetchTag : fetchTagByName) (effectiveTitle) })

  const { data: material } = useQuery ({
      enabled: Boolean (effectiveTitle),
      queryKey: materialsKeys.show (effectiveTitle),
      queryFn: () => fetchMaterial (effectiveTitle) })

  const menu = menuOutline ({ tag, material, wikiId, user, pathName: location.pathname })
  const visibleMenu = menu.filter ((item): item is MenuVisibleItem => item.visible ?? true)
  const moreMenu = menu.filter (item =>
      !(item.visible ?? true)
      || item.subMenu.filter (subItem => subItem.visible ?? true).length > 0)
  const activeIdx =
      visibleMenu.findIndex (item => location.pathname.startsWith (item.base || item.to))
  const submenuHeight = moreVsbl ? 40 * moreMenu.length : (activeIdx < 0 ? 0 : 40)
  const topNavTransition =
      animationsOff
      ? { duration: 0 }
      : reducedAnimations
	? { duration: .08, ease: 'linear' as const }
	: { duration: .2, ease: 'easeOut' as const }
  const opacityTransition =
      animationsOff
      ? { duration: 0 }
      : reducedAnimations
	? { duration: .08 }
	: { duration: .12 }
  const highlightTransitionClass =
      animationsOff
      ? undefined
      : reducedAnimations
	? 'transition-[transform,width] duration-75 ease-linear'
	: 'transition-[transform,width] duration-200 ease-out'

  const prevActiveIdxRef = useRef<number> (activeIdx)

  if (activeIdx !== prevActiveIdxRef.current)
    {
      dirRef.current = activeIdx > prevActiveIdxRef.current ? 1 : -1
      prevActiveIdxRef.current = activeIdx
    }

  const dir = dirRef.current

  useLayoutEffect (() => {
    const raf = requestAnimationFrame (() => measure (moreVsbl ? -1 : activeIdx))
    const onResize = () => requestAnimationFrame (() => measure (moreVsbl ? -1 : activeIdx))

    addEventListener ('resize', onResize)
    return () => {
      cancelAnimationFrame (raf)
      removeEventListener ('resize', onResize)
    }
  })

  useEffect (() => {
    const unsubscribe = WikiIdBus.subscribe (setWikiId)
    return () => unsubscribe ()
  }, [])

  useEffect (() => {
    setMenuOpen (false)
    setOpenItemIdx (activeIdx)
  }, [activeIdx, location])

  return (
      <>
	<nav className="top-nav-root px-3 flex justify-between items-center w-full">
	  <div className="flex items-center gap-2 h-12">
	    <PrefetchLink
		to="/posts"
		className="top-nav-brand-link mx-4 text-xl font-bold"
		onClick={() => {
		  scroll (0, 0)
		}}>
	      ぼざクリ タグ広場
	    </PrefetchLink>

	    <div ref={navRef} className="relative hidden md:flex h-12 items-center">
	      <div aria-hidden
		   className={cn ('absolute inset-y-0 h-12',
				  'top-nav-highlight',
				  highlightTransitionClass)}
		   style={{ width:      hl.width,
			    transform:  `translateX(${ hl.left }px)`,
			    opacity:    hl.visible ? 1 : 0 }}/>

	      {visibleMenu.map ((item, i) => (
		  <motion.div
		      key={item.to}
		      {...(animationsOff
			   ? { }
			   : { layoutId: `menu-${ item.name }` })}
		      animate={{ opacity: moreVsbl ? 0 : 1 }}
		      transition={{ opacity: opacityTransition,
				    layout: topNavTransition }}
		      style={{ pointerEvents: moreVsbl ? 'none' : 'auto' }}
		      onMouseEnter={() => setMoreVsbl (false)}>
		    <PrefetchLink
			to={item.to}
			ref={(el: (HTMLAnchorElement | null)) => {
			  itemsRef.current[i] = el
			}}
			className={cn ('top-nav-menu-link relative z-10 flex h-full items-center px-5',
				       (i === openItemIdx) && 'font-bold')}>
		      {item.name}
		    </PrefetchLink>
		  </motion.div>))}
	      <PrefetchLink
		  to="/more"
		  ref={(el: (HTMLAnchorElement | null)) => {
		    itemsRef.current[visibleMenu.length] = el
		  }}
		  onClick={() => setMoreVsbl (false)}
		  onMouseEnter={() => {
		    setMoreVsbl (true)
		    measure (-1)
		  }}
		  className={cn ('top-nav-menu-link relative z-10 flex h-full items-center px-5',
				 (openItemIdx < 0 || moreVsbl) && 'font-bold')}>
		その他 &raquo;
	      </PrefetchLink>
	    </div>
	  </div>

	  <TopNavUser user={user}/>

	  <button
	     type="button"
	     className="top-nav-mobile-toggle md:hidden ml-auto border-0 bg-transparent pr-4"
	     onClick={() => {
	       setMenuOpen (!(menuOpen))
	     }}>
	    {menuOpen ? '×' : 'Menu'}
	  </button>
	</nav>

	<AnimatePresence initial={false}>
	  <motion.div
	      key="submenu-shell"
	      {...(animationsOff ? { }
		   : { layout: true })}
	      className="relative z-20 hidden md:block overflow-hidden top-nav-submenu"
	      animate={{ height: submenuHeight }}
	      onMouseLeave={() => {
		if (moreVsbl)
		  setMoreVsbl (false)
	      }}
	      transition={{ layout: topNavTransition }}
	      onAnimationComplete={() => {
		measure (moreVsbl ? -1 : activeIdx)
	      }}>
	  {moreVsbl
	   ? (
	      moreMenu.map ((item, i) => (
		  <div key={i} className="relative h-[40px]">
		    <div className="absolute inset-0 flex items-center px-3">
		      <motion.div
			  transition={topNavTransition}
			  {...(animationsOff
			       ? { }
			       : ((item.visible ?? true)
			       ? { layoutId: `menu-${ item.name }` }
			       : { initial: { x: 40, y: -40, opacity: 0 },
				   animate: { x: 0, y: 0, opacity: 1 },
				   exit: { x: 40, y: -40, opacity: 0 } }))}
			  className="z-10 h-full flex items-center px-3 font-bold w-28">
			<h2>{item.name}</h2>
		      </motion.div>
		      {item.subMenu
		       .filter (subItem => subItem.visible ?? true)
		       .map ((subItem, j) => (
			  'component' in subItem
			  ? (
			      <motion.div
				  key={`c-${ i }-${ j }`}
				  transition={topNavTransition}
				  {...(animationsOff
				       ? { }
				       : ((visibleMenu[activeIdx]?.name
					=== item.name)
				       ? { layoutId: `submenu-${ item.name }-${ j }` }
				       : { initial: { y: -40, opacity: 0 },
					   animate: { y: 0, opacity: 1 },
					   exit: { y: -40, opacity: 0 } }))}>
				{subItem.component}
			      </motion.div>)
			  : (
			      <motion.div
				  key={`l-${ i }-${ j }`}
				  transition={topNavTransition}
				  {...(animationsOff
				       ? { }
				       : ((visibleMenu[activeIdx]?.name
					=== item.name)
				       ? { layoutId: `submenu-${ item.name }-${ j }` }
				       : { initial: { y: -40, opacity: 0 },
					   animate: { y: 0, opacity: 1 },
					   exit: { y: -40, opacity: 0 } }))}>
				<PrefetchLink
				    to={subItem.to}
				    target={subItem.to.slice (0, 2) === '//' ? '_blank' : undefined}
				    onClick={() => setMoreVsbl (false)}
				    className="top-nav-menu-link h-full flex items-center px-3">
				  {subItem.name}
				</PrefetchLink>
			      </motion.div>)))}
		    </div>
		  </div>)))
	   : ((visibleMenu[activeIdx]?.subMenu ?? []).length > 0
	      && (
		  <div className="relative h-[40px]">
		    {animationsOff
		     ? (
			 <div className="absolute inset-0 flex items-center px-3">
			   {(visibleMenu[activeIdx]?.subMenu ?? [])
			    .filter (item => item.visible ?? true)
			    .map ((item, i) => (
			       'component' in item
			       ? (
				   <div key={`c-${ i }`}>
				     {item.component}
				   </div>)
			       : (
				   <div key={`l-${ i }`}>
				     <PrefetchLink
					 to={item.to}
					 target={item.to.slice (0, 2) === '//'
						 ? '_blank'
						 : undefined}
					 className="top-nav-menu-link h-full flex items-center px-3">
				       {item.name}
				     </PrefetchLink>
				   </div>)))}
			 </div>)
		     : (
			 <AnimatePresence initial={false} custom={dir}>
			   <motion.div
			       key={activeIdx}
			       custom={dir}
			       variants={{ enter: (d: -1 | 1) => ({ y: d * 24, opacity: 0 }),
					   centre: { y: 0, opacity: 1 },
					   exit: (d: -1 | 1) => ({ y: (-d) * 24, opacity: 0 }) }}
			       className="absolute inset-0 flex items-center px-3"
			       initial="enter"
			       animate="centre"
			       exit="exit"
			       transition={topNavTransition}>
			     {(visibleMenu[activeIdx]?.subMenu ?? [])
			      .filter (item => item.visible ?? true)
			      .map ((item, i) => (
				 'component' in item
				 ? (
				     <motion.div
					 key={`c-${ i }`}
					 transition={{ layout: topNavTransition }}
					 layoutId={`submenu-${ visibleMenu[activeIdx].name }-${ i }`}>
				       {item.component}
				     </motion.div>)
				 : (
				     <motion.div
					 key={`l-${ i }`}
					 transition={{ layout: topNavTransition }}
					 layoutId={`submenu-${ visibleMenu[activeIdx].name }-${ i }`}>
				       <PrefetchLink
					   to={item.to}
					   target={item.to.slice (0, 2) === '//'
						   ? '_blank'
						   : undefined}
					   className="top-nav-menu-link h-full flex items-center px-3">
					 {item.name}
				       </PrefetchLink>
				     </motion.div>)))}
			   </motion.div>
			 </AnimatePresence>)}
		  </div>))}
	  </motion.div>
	</AnimatePresence>

	{animationsOff
	 ? (
	     menuOpen && (
		 <div
		     className={cn ('top-nav-root top-nav-mobile-menu flex flex-col md:hidden',
				    'items-start')}>
		   <Separator/>
		   {visibleMenu.map ((item, i) => (
		       <Fragment key={i}>
			 <PrefetchLink
			     to={i === openItemIdx ? item.to : '#'}
			     className={cn ('top-nav-mobile-row w-full min-h-[40px] flex items-center pl-8',
					((i === openItemIdx)
					 && 'top-nav-mobile-active font-bold'))}
			     onClick={(ev: MouseEvent<HTMLAnchorElement>) => {
			       if (i !== openItemIdx)
				 {
				   ev.preventDefault ()
				   setOpenItemIdx (i)
				 }
			     }}>
			   {item.name}
			 </PrefetchLink>

			 {i === openItemIdx && (
			     <div className="top-nav-submenu w-full">
			       {item.subMenu
				.filter (subItem => subItem.visible ?? true)
				.map ((subItem, j) => (
				   'component' in subItem
				   ? (
				       <Fragment key={`sp-c-${ i }-${ j }`}>
					 {subItem.component}
				       </Fragment>)
				   : (
				       <PrefetchLink
					   key={`sp-l-${ i }-${ j }`}
					   to={subItem.to}
					   target={subItem.to.slice (0, 2) === '//'
						   ? '_blank'
						   : undefined}
					   className="top-nav-mobile-row w-full min-h-[36px] flex items-center pl-12">
					 {subItem.name}
				       </PrefetchLink>)))}
			     </div>)}
		       </Fragment>))}
		   <PrefetchLink
		       to="/more"
		       ref={(el: (HTMLAnchorElement | null)) => {
			 itemsRef.current[visibleMenu.length] = el
		       }}
		       className={cn ('top-nav-mobile-row w-full min-h-[40px] flex items-center pl-8',
				      ((openItemIdx < 0)
				       && 'top-nav-mobile-active font-bold'))}>
		     その他 &raquo;
		   </PrefetchLink>
		   <TopNavUser user={user} sp/>
		   <Separator/>
		 </div>))
	 : (
	     <AnimatePresence initial={false}>
	       {menuOpen && (
		   <motion.div
		       key="spmenu"
		       className={cn ('top-nav-root top-nav-mobile-menu flex flex-col md:hidden',
				      'items-start')}
		       variants={{ closed: { clipPath: 'inset(0 0 100% 0)',
					     height:   0 },
				   open:   { clipPath: 'inset(0 0 0% 0)',
					     height:   'auto' } }}
		       initial="closed"
		       animate="open"
		       exit="closed"
		       transition={topNavTransition}>
		     <Separator/>
		     {visibleMenu.map ((item, i) => (
			 <Fragment key={i}>
			   <PrefetchLink
			       to={i === openItemIdx ? item.to : '#'}
			       className={cn ('top-nav-mobile-row w-full min-h-[40px] flex items-center pl-8',
					  ((i === openItemIdx)
					   && 'top-nav-mobile-active font-bold'))}
			       onClick={(ev: MouseEvent<HTMLAnchorElement>) => {
				 if (i !== openItemIdx)
				   {
				     ev.preventDefault ()
				     setOpenItemIdx (i)
				   }
			       }}>
			     {item.name}
			   </PrefetchLink>

			   <AnimatePresence initial={false}>
			     {i === openItemIdx && (
				 <motion.div
				     key={`sp-sub-${ i }`}
				     className="top-nav-submenu w-full"
				     variants={{ closed: { clipPath: 'inset(0 0 100% 0)',
							   height:   0,
							   opacity:  0 },
						 open:   { clipPath: 'inset(0 0 0% 0)',
							   height:   'auto',
							   opacity:  1 } }}
				     initial="closed"
				     animate="open"
				     exit="closed"
				     transition={topNavTransition}>
				   {item.subMenu
				    .filter (subItem => subItem.visible ?? true)
				    .map ((subItem, j) => (
				       'component' in subItem
				       ? (
					   <Fragment key={`sp-c-${ i }-${ j }`}>
					     {subItem.component}
					   </Fragment>)
				       : (
					   <PrefetchLink
					       key={`sp-l-${ i }-${ j }`}
					       to={subItem.to}
					       target={subItem.to.slice (0, 2) === '//'
						       ? '_blank'
						       : undefined}
					       className="top-nav-mobile-row w-full min-h-[36px] flex items-center pl-12">
					     {subItem.name}
					   </PrefetchLink>)))}
				 </motion.div>)}
			   </AnimatePresence>
			 </Fragment>))}
		     <PrefetchLink
			 to="/more"
			 ref={(el: (HTMLAnchorElement | null)) => {
			   itemsRef.current[visibleMenu.length] = el
			 }}
			 className={cn ('top-nav-mobile-row w-full min-h-[40px] flex items-center pl-8',
					((openItemIdx < 0)
					 && 'top-nav-mobile-active font-bold'))}>
		       その他 &raquo;
		     </PrefetchLink>
		     <TopNavUser user={user} sp/>
		     <Separator/>
		   </motion.div>)}
	     </AnimatePresence>)}
      </>)
}

export default TopNav
