import { useCallback, useEffect, useMemo, useState } from 'react'

import FieldError from '@/components/common/FieldError'
import FieldWarning from '@/components/common/FieldWarning'
import PostCreationDataFields from '@/components/posts/PostCreationDataFields'
import PostDurationField from '@/components/posts/PostDurationField'
import PostTextField from '@/components/posts/PostTextField'
import PostImportThumbnailPreview from '@/components/posts/import/PostImportThumbnailPreview'
import { hasThumbnailBaseValue, hasVideoTag } from '@/lib/postImportRows'

import type { FC } from 'react'

import type { DialogueFormControls } from '@/lib/dialogues/useDialogue'
import type { PostImportEditableDraft, PostImportRow } from '@/lib/postImportTypes'

type Draft = PostImportEditableDraft

type Props = {
  row:          PostImportRow
  controls:     DialogueFormControls
  onSave:       (args: { draft:          Draft
			 resetRequested: boolean }) => Promise<{
			     saved: boolean
			     row:   PostImportRow | null }> }

const THUMBNAIL_MISSING_WARNING = 'サムネールなし'

const buildDraft = (row: PostImportRow): Draft => ({
    url: row.url,
    title: String (row.attributes.title ?? ''),
    thumbnailBase: String (row.attributes.thumbnailBase ?? ''),
    originalCreatedFrom: String (row.attributes.originalCreatedFrom ?? ''),
    originalCreatedBefore: String (row.attributes.originalCreatedBefore ?? ''),
    tags: String (row.attributes.tags ?? ''),
    parentPostIds: String (row.attributes.parentPostIds ?? ''),
    duration: String (row.attributes.duration ?? ''),
    thumbnailFile: row.thumbnailFile })

const buildResetDraft = (row: PostImportRow): Draft => ({
    url: row.resetSnapshot.url,
    title: String (row.resetSnapshot.attributes.title ?? ''),
    thumbnailBase: String (row.resetSnapshot.attributes.thumbnailBase ?? ''),
    originalCreatedFrom: String (row.resetSnapshot.attributes.originalCreatedFrom ?? ''),
    originalCreatedBefore: String (row.resetSnapshot.attributes.originalCreatedBefore ?? ''),
    tags: String (row.resetSnapshot.attributes.tags ?? ''),
    parentPostIds: String (row.resetSnapshot.attributes.parentPostIds ?? ''),
    duration: String (row.resetSnapshot.attributes.duration ?? ''),
    thumbnailFile: undefined })

const groupedMessages = (...values: (string[] | undefined)[]): string[] =>
    [...new Set (values.flatMap (value => value ?? []))]

const sameDraft = (left:  Draft, right: Draft): boolean =>
    left.url === right.url
    && left.title === right.title
    && left.thumbnailBase === right.thumbnailBase
    && left.originalCreatedFrom === right.originalCreatedFrom
    && left.originalCreatedBefore === right.originalCreatedBefore
    && left.tags === right.tags
    && left.parentPostIds === right.parentPostIds
    && left.duration === right.duration
    && left.thumbnailFile === right.thumbnailFile

const sameProvenance = (
    current: PostImportRow['provenance'],
    reset:   PostImportRow['resetSnapshot']['provenance'],
): boolean =>
    Object.keys (reset).every (field => current[field] === reset[field])

const sameTagSources = (
    current: PostImportRow['tagSources'],
    reset:   PostImportRow['resetSnapshot']['tagSources'],
): boolean =>
    (current?.automatic ?? '') === reset.automatic
    && (current?.manual ?? '') === reset.manual

const sameWarnings = (
    current: PostImportRow,
    reset:   PostImportRow['resetSnapshot'],
): boolean =>
    JSON.stringify (current.fieldWarnings) === JSON.stringify (reset.fieldWarnings)
    && JSON.stringify (current.baseWarnings) === JSON.stringify (reset.baseWarnings)

const thumbnailWarnings = (
    messages:       string[] | undefined,
    thumbnailBase:  string,
    thumbnailFile:  File | undefined,
): string[] => {
  const others = (messages ?? []).filter (message => message !== THUMBNAIL_MISSING_WARNING)
  return hasThumbnailBaseValue (thumbnailBase) || thumbnailFile != null
      ? others
      : [...new Set ([...others, THUMBNAIL_MISSING_WARNING])]
}


const PostImportRowForm: FC<Props> = (
    { row,
      controls,
      onSave },
) => {
  const [draft, setDraft] = useState<Draft> (() => buildDraft (row))
  const [messageRow, setMessageRow] = useState<PostImportRow | null> (null)
  const [saving, setSaving] = useState (false)
  const [resetRequested, setResetRequested] = useState (false)
  const [committedThumbnailBase, setCommittedThumbnailBase] = useState (
      () => String (row.attributes.thumbnailBase ?? ''))

  useEffect (() => {
    const nextDraft = buildDraft (row)
    setDraft (nextDraft)
    setMessageRow (null)
    setResetRequested (false)
    setCommittedThumbnailBase (String (row.attributes.thumbnailBase ?? ''))
  }, [row])

  const displayRow = messageRow ?? row
  const resetDraft = useMemo (
      () => buildResetDraft (row),
      [row])
  const durationVisible = hasVideoTag (draft.tags)
  const currentThumbnailWarnings = thumbnailWarnings (
      displayRow.fieldWarnings.thumbnailBase,
      draft.thumbnailBase,
      draft.thumbnailFile)
  const resetDisabled =
      saving
      || (sameDraft (draft, resetDraft)
          && sameProvenance (row.provenance, row.resetSnapshot.provenance)
          && sameTagSources (row.tagSources, row.resetSnapshot.tagSources)
          && row.metadataUrl === row.resetSnapshot.metadataUrl
          && sameWarnings (displayRow, row.resetSnapshot))

  const update = <Key extends keyof Draft,> (
      key:   Key,
      value: Draft[Key],
  ) => {
    if (messageRow != null)
      setMessageRow (null)
    setDraft (current => ({ ...current, [key]: value }))
  }

  const reset = useCallback (async (): Promise<boolean> => {
    if (resetDisabled)
      return false

    const confirmed = await controls.confirm ({
	title: '変更をリセットしますか?',
	confirmText: 'リセット',
	cancelText: '取消',
	variant: 'danger' })
    if (!(confirmed))
      return false

    setDraft (resetDraft)
    setResetRequested (true)
    setMessageRow (null)
    setCommittedThumbnailBase (resetDraft.thumbnailBase)
    return false
  }, [controls, resetDisabled, resetDraft])

  const save = useCallback (async (): Promise<boolean> => {
    setSaving (true)
    try
    {
      const result = await onSave ({ draft, resetRequested })
      if (result.saved)
        return true

      if (result.row != null)
        setMessageRow (result.row)
      return false
    }
    finally
    {
      setSaving (false)
    }
  }, [draft, onSave, resetRequested])

  useEffect (() => {
    controls.setActions ([{
	label: '変更をリセット',
	placement: 'start',
	variant: 'danger',
	disabled: resetDisabled,
	onSelect: reset },
	{
	  label: '編輯内容を保存',
	  disabled: saving,
	  onSelect: save }])
  }, [controls, resetDisabled, reset, save, saving])

  return (
      <>
	<div className="px-6 pb-6">
	  <div className="space-y-4">
	    <div className="grid gap-6 md:grid-cols-[7rem_minmax(0,1fr)]">
	      <div className="space-y-3 md:sticky md:top-0 md:self-start">
		<PostImportThumbnailPreview
		    url={committedThumbnailBase}
		    file={
			hasThumbnailBaseValue (committedThumbnailBase)
			? undefined
			: draft.thumbnailFile}
		    className="h-28 w-28"/>
	      </div>

	      <div className="space-y-4">
		<PostCreationDataFields
		    url={{
			value: draft.url,
			onChange: value => update ('url', value),
			disabled: saving,
			warnings: displayRow.fieldWarnings.url,
			errors: groupedMessages (
			    displayRow.validationErrors.url,
			    displayRow.importErrors?.url) }}
		    thumbnailField={
			<>
			  <PostTextField
			      label="サムネール"
			      value={draft.thumbnailBase}
			      disabled={saving}
			      warnings={currentThumbnailWarnings}
			      errors={groupedMessages (
				  displayRow.validationErrors.thumbnailBase,
				  displayRow.importErrors?.thumbnailBase)}
			      onBlur={() => {
				if (draft.thumbnailBase.trim () !== committedThumbnailBase.trim ())
				  setCommittedThumbnailBase (draft.thumbnailBase)
			      }}
			      onChange={value => update ('thumbnailBase', value)}/>
			  {!(hasThumbnailBaseValue (draft.thumbnailBase)) && (
			      <input
				  type="file"
				  accept="image/*"
				  disabled={saving}
				  onChange={event => {
				    const file = event.target.files?.[0]
				    update ('thumbnailFile', file)
				  }}/>)}
			</>}
		    core={{
			title: {
			    value: draft.title,
			    onChange: value => update ('title', value),
			    disabled: saving,
			    warnings: displayRow.fieldWarnings.title,
			    errors: groupedMessages (
				displayRow.validationErrors.title,
				displayRow.importErrors?.title) },
			originalCreated: {
			    disabled: saving,
			    originalCreatedFrom: draft.originalCreatedFrom || null,
			    setOriginalCreatedFrom: value =>
				update ('originalCreatedFrom', value ?? ''),
			    originalCreatedBefore: draft.originalCreatedBefore || null,
			    setOriginalCreatedBefore: value =>
				update ('originalCreatedBefore', value ?? ''),
			    errors: {
				originalCreatedAt: groupedMessages (
				    displayRow.validationErrors.originalCreatedAt,
				    displayRow.importErrors?.originalCreatedAt),
				originalCreatedFrom: groupedMessages (
				    displayRow.validationErrors.originalCreatedFrom,
				    displayRow.importErrors?.originalCreatedFrom),
				originalCreatedBefore: groupedMessages (
				    displayRow.validationErrors.originalCreatedBefore,
				    displayRow.importErrors?.originalCreatedBefore) } },
			tags: {
			    value: draft.tags,
			    onChange: value => update ('tags', value),
			    disabled: saving,
			    warnings: displayRow.fieldWarnings.tags,
			    errors: groupedMessages (
				displayRow.validationErrors.tags,
				displayRow.importErrors?.tags),
			    rows: 4 },
			parentPostIds: {
			    value: draft.parentPostIds,
			    onChange: value => update ('parentPostIds', value),
			    disabled: saving,
			    errors: groupedMessages (
				displayRow.validationErrors.parentPostIds,
				displayRow.importErrors?.parentPostIds) } }}
		    extraFields={
			durationVisible
			? (
			    <PostDurationField
				value={draft.duration}
				onChange={value => update ('duration', value)}
				disabled={saving}
				errors={groupedMessages (
				    displayRow.validationErrors.videoMs,
				    displayRow.importErrors?.videoMs)}/>)
			: null}/>
		<FieldWarning messages={displayRow.baseWarnings}/>
		<FieldError messages={displayRow.validationErrors.base}/>
		<FieldError messages={displayRow.importErrors?.base}/>
	      </div>
	    </div>
	  </div>
	</div>
      </>)
}

export default PostImportRowForm
export { buildDraft }
export type { Draft as PostImportRowDraft }
