// frontend\src\components\Journal.tsx

import { type ComponentType, type SVGProps, useMemo, useState } from 'react'
import {
  ArrowRightIcon,
  ChatBubbleLeftEllipsisIcon,
  ClockIcon,
  MagnifyingGlassIcon,
  PaperAirplaneIcon,
  PencilSquareIcon,
  PlusCircleIcon,
} from '@heroicons/react/20/solid'
import Avatar from './Avatar'
import { Badge, type BadgeTone } from './Badge'
import Button from './Button'
import NotificationDot from './NotificationDot'
import Textarea from './Textarea'

type JournalIcon = ComponentType<SVGProps<SVGSVGElement>>

export type JournalChange = {
  label: string
  oldValue?: string
  newValue?: string
}

export type JournalItem = {
  id: string | number
  type?: 'created' | 'edited' | 'journal' | 'comment'

  content: string
  description?: string
  changes?: JournalChange[]

  date: string
  datetime?: string
  dateTime?: string

  editedDate?: string
  editedDatetime?: string
  editedDateTime?: string

  canEdit?: boolean
  hasNotification?: boolean

  icon?: JournalIcon
  iconBackground?: string

  person?: {
    name: string
    href?: string
    imageUrl?: string
  }

  imageUrl?: string
}

type JournalProps = {
  items?: JournalItem[]
  currentUserAvatar?: string
  currentUserName?: string
  showEntryForm?: boolean
  showSearch?: boolean
  searchQuery?: string
  onSearchQueryChange?: (value: string) => void
  isSubmitting?: boolean
  emptyText?: string
  placeholder?: string
  submitLabel?: string
  onEntrySubmit?: (content: string) => void | Promise<void>
  onEntryUpdate?: (itemId: string | number, content: string) => void | Promise<void>
  className?: string
}

function classNames(...classes: Array<string | false | null | undefined>) {
  return classes.filter(Boolean).join(' ')
}

function getItemPersonName(item: JournalItem) {
  return item.person?.name ?? 'Unbekannt'
}

function getItemDateTime(item: JournalItem) {
  return item.dateTime ?? item.datetime ?? ''
}

function getItemEditedDateTime(item: JournalItem) {
  return item.editedDateTime ?? item.editedDatetime ?? ''
}

function getItemImageUrl(item: JournalItem) {
  return item.person?.imageUrl ?? item.imageUrl
}

function getItemDescription(item: JournalItem) {
  return item.description ?? item.content
}

function getChangeValue(value?: string) {
  return value?.trim() || '—'
}

function getChangeKey(change: JournalChange, index: number) {
  return `${change.label}-${index}`
}

function getItemSearchText(item: JournalItem) {
  const changeParts = (item.changes ?? []).flatMap((change) => [
    change.label,
    change.oldValue,
    change.newValue,
  ])

  return [
    getItemPersonName(item),
    getItemDescription(item),
    ...changeParts,
  ]
    .filter(Boolean)
    .join(' ')
    .toLowerCase()
}

function getJournalIcon(item: JournalItem) {
  if (item.icon) {
    return item.icon
  }

  if (item.type === 'created') {
    return PlusCircleIcon
  }

  if (item.type === 'edited') {
    return PencilSquareIcon
  }

  return ChatBubbleLeftEllipsisIcon
}

function getJournalIconBackground(item: JournalItem) {
  if (item.iconBackground) {
    return item.iconBackground
  }

  if (item.type === 'created') {
    return 'bg-green-500'
  }

  if (item.type === 'edited') {
    return 'bg-blue-500'
  }

  return 'bg-indigo-500'
}

function getJournalType(item: JournalItem): {
  label: string
  tone: BadgeTone
} {
  if (item.type === 'created') {
    return { label: 'Erstellt', tone: 'green' }
  }

  if (item.type === 'edited') {
    return { label: 'Änderung', tone: 'blue' }
  }

  if (item.type === 'comment') {
    return { label: 'Kommentar', tone: 'gray' }
  }

  return { label: 'Journal', tone: 'indigo' }
}

export default function Journal({
  items = [],
  currentUserAvatar,
  currentUserName = 'Du',
  showEntryForm = true,
  showSearch = true,
  searchQuery,
  onSearchQueryChange,
  isSubmitting = false,
  emptyText = 'Noch keine Journal-Einträge vorhanden.',
  placeholder = 'Journal-Eintrag hinzufügen...',
  submitLabel = 'Eintrag hinzufügen',
  onEntrySubmit,
  onEntryUpdate,
  className,
}: JournalProps) {
  const [entry, setEntry] = useState('')
  const [editingItemId, setEditingItemId] = useState<string | number | null>(null)
  const [editEntry, setEditEntry] = useState('')
  const [internalSearchQuery, setInternalSearchQuery] = useState('')
  const activeSearchQuery = searchQuery ?? internalSearchQuery

  function updateSearchQuery(value: string) {
    if (onSearchQueryChange) {
      onSearchQueryChange(value)
      return
    }

    setInternalSearchQuery(value)
  }

  const filteredItems = useMemo(() => {
    const normalizedQuery = activeSearchQuery.trim().toLowerCase()

    if (!normalizedQuery) {
      return items
    }

    return items.filter((item) => getItemSearchText(item).includes(normalizedQuery))
  }, [items, activeSearchQuery])

  async function handleSubmit() {
    const content = entry.trim()

    if (!content || isSubmitting) {
      return
    }

    await onEntrySubmit?.(content)
    setEntry('')
  }

  function startEditing(item: JournalItem) {
    setEditingItemId(item.id)
    setEditEntry(getItemDescription(item))
  }

  function cancelEditing() {
    setEditingItemId(null)
    setEditEntry('')
  }

  async function handleUpdate(itemId: string | number) {
    const content = editEntry.trim()

    if (!content || isSubmitting) {
      return
    }

    await onEntryUpdate?.(itemId, content)
    cancelEditing()
  }

  return (
    <div className={className}>
      <div className="mb-6 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
        <div>
          <h3 className="text-sm font-semibold text-gray-950 dark:text-white">
            Journal
          </h3>
          <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
            Verlauf, Änderungen und manuelle Einträge.
          </p>
        </div>
      </div>

      {showEntryForm && (
        <div className="mb-8">
          <Textarea
            variant="avatar-actions"
            id="journalEntry"
            name="journalEntry"
            label="Journal-Eintrag hinzufügen"
            srOnlyLabel
            rows={3}
            value={entry}
            onChange={(event) => setEntry(event.target.value)}
            placeholder={placeholder}
            titlePlaceholder="Titel (optional)"
            avatarProps={{ src: currentUserAvatar, name: currentUserName }}
            submitButton={
              <Button
                type="button"
                variant="primary"
                color="indigo"
                size="sm"
                isLoading={isSubmitting}
                disabled={!entry.trim() || isSubmitting}
                onClick={() => void handleSubmit()}
                leadingIcon={
                  <PaperAirplaneIcon aria-hidden="true" className="size-4" />
                }
              >
                {submitLabel}
              </Button>
            }
          />
        </div>
      )}

      {showSearch && items.length > 0 && (
        <div className="mb-4 flex justify-end">
          <div className="relative w-80 max-w-full">
            <MagnifyingGlassIcon
              aria-hidden="true"
              className="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-gray-400 dark:text-gray-500"
            />

            <input
              type="search"
              value={activeSearchQuery}
              onChange={(event) => updateSearchQuery(event.target.value)}
              placeholder="Journal durchsuchen..."
              aria-label="Journal durchsuchen"
              className="block w-full rounded-md bg-white py-1.5 pr-3 pl-9 text-sm text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:placeholder:text-gray-500 dark:focus:outline-indigo-500"
            />
          </div>
        </div>
      )}

      {items.length === 0 ? (
        <div className="rounded-2xl border border-dashed border-gray-300 bg-gray-50/70 px-6 py-10 text-center dark:border-white/15 dark:bg-white/[0.03]">
          <div className="mx-auto flex size-11 items-center justify-center rounded-2xl bg-white text-gray-400 shadow-sm ring-1 ring-gray-200 dark:bg-white/5 dark:text-gray-500 dark:ring-white/10">
            <ChatBubbleLeftEllipsisIcon
              aria-hidden="true"
              className="size-6"
            />
          </div>
          <p className="mt-3 text-sm font-semibold text-gray-900 dark:text-white">
            Noch keine Aktivitäten
          </p>
          <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
            {emptyText}
          </p>
        </div>
      ) : filteredItems.length === 0 ? (
        <div className="rounded-2xl border border-dashed border-gray-300 bg-gray-50/70 px-6 py-10 text-center dark:border-white/15 dark:bg-white/[0.03]">
          <div className="mx-auto flex size-11 items-center justify-center rounded-2xl bg-white text-gray-400 shadow-sm ring-1 ring-gray-200 dark:bg-white/5 dark:text-gray-500 dark:ring-white/10">
            <MagnifyingGlassIcon
              aria-hidden="true"
              className="size-6"
            />
          </div>
          <p className="mt-3 text-sm font-semibold text-gray-900 dark:text-white">
            Keine Treffer
          </p>
          <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
            Es wurden keine Einträge zu „{activeSearchQuery.trim()}" gefunden.
          </p>
        </div>
      ) : (
        <div className="flow-root">
          <ul role="list" className="space-y-4">
            {filteredItems.map((item, itemIndex) => {
              const personName = getItemPersonName(item)
              const imageUrl = getItemImageUrl(item)
              const dateTime = getItemDateTime(item)
              const Icon = getJournalIcon(item)
              const itemType = getJournalType(item)
              const isManualEntry = item.type === 'journal' || item.type === 'comment'
              const changes = item.changes ?? []
              const hasChanges = !isManualEntry && changes.length > 0

              return (
                <li key={item.id} className="relative">
                  {itemIndex !== filteredItems.length - 1 && (
                    <span
                      aria-hidden="true"
                      className="absolute top-10 bottom-[-1rem] left-5 w-px bg-gradient-to-b from-gray-300 to-gray-200 dark:from-white/20 dark:to-white/5"
                    />
                  )}

                  <div className="relative flex items-start gap-3 sm:gap-4">
                    <div className="relative z-10 size-10 shrink-0">
                      <Avatar
                        src={imageUrl}
                        name={personName}
                        size={10}
                        className="ring-4 ring-white dark:ring-gray-900"
                      />

                      <span
                        className={classNames(
                          getJournalIconBackground(item),
                          'absolute -right-1 -bottom-1 z-20 flex size-5 items-center justify-center rounded-full shadow-sm ring-2 ring-white dark:ring-gray-900',
                        )}
                      >
                        <Icon aria-hidden="true" className="size-3 text-white" />
                      </span>
                    </div>

                    <article className="min-w-0 flex-1 rounded-lg bg-white p-4 shadow-xs outline-1 -outline-offset-1 outline-gray-300 dark:bg-gray-900/70 dark:outline-white/10">
                      <header className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
                        <div className="min-w-0">
                          <div className="flex flex-wrap items-center gap-2">
                            {item.person?.href ? (
                              <a
                                href={item.person.href}
                                className="truncate text-sm font-semibold text-gray-950 hover:text-indigo-600 dark:text-white dark:hover:text-indigo-300"
                              >
                                {personName}
                              </a>
                            ) : (
                              <span className="truncate text-sm font-semibold text-gray-950 dark:text-white">
                                {personName}
                              </span>
                            )}

                            {hasChanges && (
                              <Badge
                                tone="indigo"
                                variant="border"
                                shape="circle"
                                size="small"
                              >
                                {changes.length}{' '}
                                {changes.length === 1 ? 'Änderung' : 'Änderungen'}
                              </Badge>
                            )}

                            {item.hasNotification && (
                              <NotificationDot
                                title="Neuer Journal-Eintrag"
                                label="Neuer Journal-Eintrag"
                                className="size-2"
                              />
                            )}
                          </div>

                          {!isManualEntry && (
                            <p className="mt-1 text-sm leading-6 text-gray-600 dark:text-gray-300">
                              {getItemDescription(item)}
                            </p>
                          )}
                        </div>

                        <time
                          dateTime={dateTime}
                          className="inline-flex w-fit shrink-0 items-center gap-1.5 rounded-full bg-gray-50 px-2.5 py-1 text-xs font-medium whitespace-nowrap text-gray-500 ring-1 ring-gray-200 ring-inset dark:bg-white/5 dark:text-gray-400 dark:ring-white/10"
                        >
                          <ClockIcon aria-hidden="true" className="size-3.5" />
                          {item.date}
                        </time>
                      </header>

                      {isManualEntry && editingItemId === item.id ? (
                        <div className="mt-4 rounded-xl bg-gray-50/70 p-3 ring-1 ring-gray-200 ring-inset dark:bg-white/[0.03] dark:ring-white/10">
                            <Textarea
                              label="Journal-Eintrag bearbeiten"
                              srOnlyLabel
                              rows={3}
                              value={editEntry}
                              onChange={(event) => setEditEntry(event.target.value)}
                              className="min-h-24"
                            />

                            <div className="mt-3 flex justify-end gap-x-2">
                              <Button
                                type="button"
                                variant="secondary"
                                color="gray"
                                size="sm"
                                disabled={isSubmitting}
                                onClick={cancelEditing}
                              >
                                Abbrechen
                              </Button>

                              <Button
                                type="button"
                                variant="primary"
                                color="indigo"
                                size="sm"
                                isLoading={isSubmitting}
                                disabled={!editEntry.trim() || isSubmitting}
                                onClick={() => void handleUpdate(item.id)}
                              >
                                Speichern
                              </Button>
                            </div>
                        </div>
                      ) : isManualEntry ? (
                        <div className="mt-4 rounded-xl bg-gray-50/80 p-3.5 text-sm text-gray-700 ring-1 ring-gray-200 ring-inset dark:bg-white/[0.04] dark:text-gray-200 dark:ring-white/10">
                          <div className="flex flex-col gap-3 sm:flex-row sm:items-start">
                            <div className="min-w-0 flex-1 whitespace-pre-wrap break-words leading-6">
                              {getItemDescription(item)}
                            </div>

                            {item.canEdit && onEntryUpdate && (
                              <Button
                                type="button"
                                variant="secondary"
                                color="indigo"
                                size="sm"
                                disabled={isSubmitting}
                                onClick={() => startEditing(item)}
                                leadingIcon={
                                  <PencilSquareIcon
                                    aria-hidden="true"
                                    className="size-4"
                                  />
                                }
                              >
                                Bearbeiten
                              </Button>
                            )}
                          </div>

                          {item.editedDate && (
                            <div className="mt-3 flex items-center gap-x-1.5 border-t border-gray-200/70 pt-3 text-[11px] leading-4 text-gray-400 dark:border-white/10 dark:text-gray-500">
                              <PencilSquareIcon aria-hidden="true" className="size-3.5" />
                              <span>
                                Zuletzt bearbeitet:{' '}
                                <time dateTime={getItemEditedDateTime(item)}>
                                  {item.editedDate}
                                </time>
                              </span>
                            </div>
                          )}
                        </div>
                      ) : hasChanges ? (
                        <div className="mt-4 space-y-3">
                          {changes.map((change, changeIndex) => (
                            <section
                              key={getChangeKey(change, changeIndex)}
                              className="rounded-xl border-1 border-gray-200/70 bg-gray-50/70 p-3.5 dark:border-indigo-500/60 dark:bg-white/[0.03]"
                            >
                              <h5 className="flex items-center gap-1.5 text-xs font-semibold text-gray-900 dark:text-white">
                                <PencilSquareIcon
                                  aria-hidden="true"
                                  className="size-3.5 text-indigo-500 dark:text-indigo-400"
                                />
                                {change.label}
                              </h5>

                              <div className="mt-2.5 grid grid-cols-1 items-stretch gap-2 lg:grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)]">
                                <div className="min-w-0 rounded-lg bg-white p-3 ring-1 ring-gray-200 ring-inset dark:bg-gray-950/30 dark:ring-white/10">
                                  <div className="text-[10px] font-semibold tracking-wider text-gray-400 uppercase dark:text-gray-500">
                                    Vorher
                                  </div>
                                  <div className="mt-1.5 whitespace-pre-wrap break-words text-sm text-gray-500 line-through dark:text-gray-400">
                                    {getChangeValue(change.oldValue)}
                                  </div>
                                </div>

                                <div className="flex items-center justify-center">
                                  <span className="flex size-7 items-center justify-center rounded-full bg-white text-indigo-500 shadow-xs ring-1 ring-indigo-200 dark:bg-white/5 dark:text-indigo-400 dark:ring-indigo-400/30">
                                    <ArrowRightIcon
                                      aria-hidden="true"
                                      className="size-3.5 rotate-90 lg:rotate-0"
                                    />
                                  </span>
                                </div>

                                <div className="min-w-0 rounded-lg bg-indigo-50 p-3 ring-1 ring-indigo-300 dark:bg-indigo-500/15 dark:ring-indigo-400/40">
                                  <div className="text-[10px] font-semibold tracking-wider text-indigo-600 uppercase dark:text-indigo-300">
                                    Nachher
                                  </div>
                                  <div className="mt-1.5 whitespace-pre-wrap break-words text-sm font-semibold text-gray-950 dark:text-white">
                                    {getChangeValue(change.newValue)}
                                  </div>
                                </div>
                              </div>
                            </section>
                          ))}
                        </div>
                      ) : null}
                    </article>
                  </div>
                </li>
              )
            })}
          </ul>
        </div>
      )}
    </div>
  )
}
