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

import FieldError from '@/components/common/FieldError'
import Form from '@/components/common/Form'
import FormField from '@/components/common/FormField'
import PageTitle from '@/components/common/PageTitle'
import TextArea from '@/components/common/TextArea'
import MainArea from '@/components/layout/MainArea'
import { Button } from '@/components/ui/button'
import { toast } from '@/components/ui/use-toast'
import { SITE_TITLE } from '@/config'
import {
    appendPostNewSessionId,
    serialisePostNewState,
} from '@/lib/postNewQueryState'
import { canEditContent } from '@/lib/users'
import { countImportSourceLines,
	 generatePostImportSessionId,
	 cleanupExpiredPostImportSessions,
	 initialisePreviewRows,
	 loadPostImportSession,
	 loadPostImportSourceDraft,
	 resultRepairMode,
	 serialisedPostImportRowsEqual,
	 savePostImportSession,
	 savePostImportSourceDraft,
	 validateImportSource } from '@/lib/postImportSession'
import Forbidden from '@/pages/Forbidden'

import type { FC } from 'react'

import type { PostImportRow } from '@/lib/postImportSession'
import type { User } from '@/types'

type Props = { user: User | null }

const MAX_ROWS = 100
const SOURCE_ERROR_ID = 'post-import-source-error'
const SOURCE_ISSUES_ID = 'post-import-source-issues'

const buildInitialRows = (source: string): PostImportRow[] =>
    source
        .split (/\r\n|\n|\r/)
        .map ((line, index) => ({
            sourceRow: index + 1,
            url: line.trim () }))
        .filter (row => row.url !== '')
        .map (row => ({
            sourceRow: row.sourceRow,
            url: row.url,
            attributes: {
                title: '',
                thumbnailBase: '',
                originalCreatedFrom: '',
                originalCreatedBefore: '',
                duration: '',
                videoMs: '',
                tags: '',
                parentPostIds: '' },
            fieldWarnings: { },
            baseWarnings: [],
            validationErrors: { },
            provenance: {
                url: 'manual',
                title: 'automatic',
                thumbnailBase: 'automatic',
                originalCreatedFrom: 'automatic',
                originalCreatedBefore: 'automatic',
                duration: 'automatic',
                videoMs: 'automatic',
                tags: 'automatic',
                parentPostIds: 'automatic' },
            tagSources: {
                automatic: '',
                manual: '' },
            status: 'pending' as const,
            displayTags: [],
            resetSnapshot: {
                url: row.url,
                attributes: {
                    title: '',
                    thumbnailBase: '',
                    originalCreatedFrom: '',
                    originalCreatedBefore: '',
                    duration: '',
                    videoMs: '',
                    tags: '',
                    parentPostIds: '' },
                provenance: {
                    url: 'manual',
                    title: 'automatic',
                    thumbnailBase: 'automatic',
                    originalCreatedFrom: 'automatic',
                    originalCreatedBefore: 'automatic',
                    duration: 'automatic',
                    videoMs: 'automatic',
                    tags: 'automatic',
                    parentPostIds: 'automatic' },
                tagSources: {
                    automatic: '',
                    manual: '' },
                displayTags: [],
                fieldWarnings: { },
                baseWarnings: [] } }))


const PostImportSourcePage: FC<Props> = ({ user }) => {
  const editable = canEditContent (user)
  const navigate = useNavigate ()
  const [source, setSource] = useState ('')
  const [loading, setLoading] = useState (false)
  const [sourceIssues, setSourceIssues] = useState<ReturnType<typeof validateImportSource>> ([])
  const [sourceError, setSourceError] = useState<string | null> (null)
  const saveTimer = useRef<number | null> (null)
  const editedRef = useRef (false)

  const lineCount = countImportSourceLines (source)
  const messages = sourceError != null ? [sourceError] : []
  const sourceDescribedBy =
      [sourceError != null ? SOURCE_ERROR_ID : null,
       sourceIssues.length > 0 ? SOURCE_ISSUES_ID : null]
      .filter (value => value != null)
      .join (' ')

  const showStorageError = (message: string) =>
      toast ({ description: message })

  useEffect (() => {
    cleanupExpiredPostImportSessions (message =>
	toast ({ title: '保存済みデータを整理できませんでした', description: message }))

    const draft = loadPostImportSourceDraft (message =>
	toast ({ title: '保存済み入力を復元できませんでした', description: message }))

    if (!(editedRef.current))
      {
	setSource (current =>
	    current === ''
	    ? draft.source
	    : current)
      }
  }, [])

  useEffect (() => {
    if (saveTimer.current != null)
      window.clearTimeout (saveTimer.current)

    saveTimer.current = window.setTimeout (() => {
      savePostImportSourceDraft (source, message =>
	  toast ({ title: '入力内容を保存できませんでした', description: message }))
    }, 300)

    return () => {
      if (saveTimer.current != null)
	window.clearTimeout (saveTimer.current)
    }
  }, [source])

  const preview = async () => {
    if (lineCount === 0)
      {
	setSourceIssues ([])
	setSourceError ('URL を入力してください．')
	return
      }

    const issues = validateImportSource (source)
    if (issues.length > 0)
      {
	setSourceIssues (issues)
	setSourceError (null)
	return
      }

    setLoading (true)
    setSourceIssues ([])
    setSourceError (null)
    try
    {
      const nextRows = initialisePreviewRows (buildInitialRows (source))
      const serialised = await serialisePostNewState (nextRows)
      const sessionId = (() => {
	try
	{
	  return generatePostImportSessionId ()
	}
	catch
	{
	  return null
	}
      }) ()
      const session = {
	  source,
	  rows: nextRows,
	  repairMode: resultRepairMode (nextRows) }
      const saved =
	  sessionId == null
	  ? false
	  : savePostImportSession (sessionId, session, showStorageError)
      const loadedSession =
	  sessionId == null
	  ? null
	  : loadPostImportSession (sessionId, showStorageError)
      const sessionRoundTripSucceeded =
	  saved
	  && serialisedPostImportRowsEqual (
	      loadedSession?.rows ?? [],
	      nextRows)
      const canNavigate =
	  serialised?.complete === true
	  || sessionRoundTripSucceeded
      if (!(canNavigate))
	{
	  showStorageError ('ブラウザへ保存できませんでした．')
	  return
	}

      const nextPath = (() => {
	if (serialised != null)
	  {
	    return sessionId == null
		? serialised.path
		: appendPostNewSessionId (serialised.path, sessionId)
	  }
	if (sessionRoundTripSucceeded && sessionId != null)
	  return `/posts/new?session_id=${ sessionId }`
	return null
      }) ()
      if (nextPath == null)
	{
	  showStorageError ('ブラウザへ保存できませんでした．')
	  return
	}

      navigate (nextPath)
    }
    catch (error)
    {
      console.error (error)
      showStorageError ('ブラウザへ保存できませんでした．')
    }
    finally
    {
      setLoading (false)
    }
  }

  if (!(editable))
	return <Forbidden/>

  return (
      <MainArea>
	<Helmet>
	  <title>{`広場に投稿を追加 | ${ SITE_TITLE }`}</title>
	</Helmet>

	<Form className="max-w-4xl">
	  <PageTitle>広場に投稿を追加</PageTitle>
	  <FormField label="URL リスト">
	    {() => (
		<TextArea
		    value={source}
		    rows={14}
		    invalid={messages.length > 0 || sourceIssues.length > 0}
		    aria-describedby={sourceDescribedBy || undefined}
		    className="h-80 font-mono text-sm"
		    onBlur={() => {
		      savePostImportSourceDraft (source, message =>
			  toast ({ title: '入力内容を保存できませんでした',
				   description: message }))
		    }}
		    onChange={ev => {
		      editedRef.current = true
		      setSource (ev.target.value)
		      setSourceError (null)
		      setSourceIssues ([])
		    }}/>)}
	  </FormField>
	  <div id={messages.length > 0 ? SOURCE_ERROR_ID : undefined}>
	    <FieldError messages={messages}/>
	  </div>
	  <ul
	      id={SOURCE_ISSUES_ID}
	      className="space-y-2 text-sm text-red-700 dark:text-red-300">
	    {sourceIssues.map (issue => (
		<li key={`${ issue.sourceRow }-${ issue.message }-${ issue.url }`}>
		  <div>{issue.sourceRow} 行目: {issue.message}</div>
		  <div className="break-all font-mono text-xs">{issue.url}</div>
		</li>))}
	  </ul>

	  <div className="relative z-10 flex items-center justify-between gap-3">
	    <div className="text-sm text-neutral-600 dark:text-neutral-300">
	      入力行数 {lineCount} / {MAX_ROWS}
	    </div>
	    <Button
		type="button"
		className="pointer-events-auto shrink-0"
		onClick={preview}
		disabled={loading}>
	      次へ
	    </Button>
	  </div>
	</Form>
      </MainArea>)
}

export default PostImportSourcePage
