import { useState } from 'react'
import { Alert } from '@/components/ui/Alert'
import { Button } from '@/components/ui/Button'
import { TextArea } from '@/components/ui/TextArea'
import { TextField } from '@/components/ui/TextField'
import { errorMessage } from '@/lib/api'
import {
  formatObservedAt,
  today,
  useCreateJournalEntry,
  useDeleteJournalEntry,
  useJournal,
  useUpdateJournalEntry,
  type JournalEntry,
} from '@/lib/journal'
import type { EditorObject } from './types'
import { objectDisplayName } from './kinds'

/**
 * The garden's journal: write an entry, read the season back.
 *
 * Notes get written standing in the garden holding a phone, usually one-handed.
 * If it takes more than a couple of taps from looking at a bed to typing a
 * sentence, the log stays empty — so the composer is open by default rather than
 * behind an "add" button, and selecting a bed pre-scopes it to that bed.
 */
export function JournalPanel({
  gardenId,
  canEdit,
  currentUserId,
  isOwner,
  objects,
  scopeObjectId,
  onScopeChange,
}: {
  gardenId: number
  canEdit: boolean
  currentUserId?: number
  isOwner: boolean
  objects: EditorObject[]
  /** Which bed the panel is filtered to, if any. */
  scopeObjectId: number | null
  onScopeChange: (id: number | null) => void
}) {
  const filter = scopeObjectId != null ? { objectId: scopeObjectId } : {}
  const journal = useJournal(gardenId, filter)
  const entries = journal.data?.pages.flatMap((p) => p.entries) ?? []
  const scopedObject = objects.find((o) => o.id === scopeObjectId) ?? null

  return (
    <div className="flex flex-col gap-3">
      <div className="flex items-center justify-between gap-2">
        <h2 className="text-sm font-semibold text-fg">Journal</h2>
        {objects.length > 0 && (
          <select
            value={scopeObjectId ?? ''}
            onChange={(e) => {
              const id = Number(e.target.value)
              onScopeChange(e.target.value === '' || !Number.isFinite(id) ? null : id)
            }}
            className="max-w-[9rem] truncate rounded-md border border-border bg-surface px-1.5 py-1 text-xs text-fg outline-none focus-visible:ring-2 focus-visible:ring-accent/40"
          >
            <option value="">Whole garden</option>
            {objects.map((o) => (
              <option key={o.id} value={o.id}>
                {objectDisplayName(o)}
              </option>
            ))}
          </select>
        )}
      </div>

      {canEdit && (
        <Composer
          gardenId={gardenId}
          objectId={scopeObjectId}
          scopeLabel={scopedObject ? objectDisplayName(scopedObject) : null}
        />
      )}

      {journal.isPending && <p className="text-sm text-muted">Loading…</p>}
      {journal.isError && entries.length === 0 && (
        <Alert>{errorMessage(journal.error, 'Could not load the journal.')}</Alert>
      )}

      {journal.isSuccess && entries.length === 0 && (
        <p className="text-sm text-muted">
          {scopedObject
            ? `Nothing written about ${objectDisplayName(scopedObject)} yet.`
            : 'Nothing written yet. This is where what happened goes — when something went in, what came up, what the frost got. Next year you get to read it back.'}
        </p>
      )}

      <ol className="flex flex-col gap-2">
        {entries.map((e) => (
          <Entry
            key={e.id}
            entry={e}
            gardenId={gardenId}
            objects={objects}
            canDelete={canEdit && (e.authorId === currentUserId || isOwner)}
            canRewrite={canEdit && e.authorId === currentUserId}
          />
        ))}
      </ol>

      {journal.hasNextPage && (
        <Button
          variant="ghost"
          className="text-sm"
          disabled={journal.isFetchingNextPage}
          onClick={() => void journal.fetchNextPage()}
        >
          {journal.isFetchingNextPage ? 'Loading…' : 'Load older'}
        </Button>
      )}
      {journal.isError && entries.length > 0 && (
        <p className="text-xs text-red-700 dark:text-red-400">
          {errorMessage(journal.error, "Couldn't load older entries.")}
        </p>
      )}
    </div>
  )
}

function Composer({
  gardenId,
  objectId,
  scopeLabel,
}: {
  gardenId: number
  objectId: number | null
  scopeLabel: string | null
}) {
  const create = useCreateJournalEntry(gardenId)
  const [body, setBody] = useState('')
  // Editable so an observation can be backdated: you write up Saturday on Sunday.
  const [observedAt, setObservedAt] = useState(today())
  const [error, setError] = useState<string | null>(null)

  const submit = () => {
    const text = body.trim()
    if (!text) return
    setError(null)
    create.mutate(
      { body: text, observedAt, objectId: objectId ?? undefined },
      {
        onSuccess: () => {
          setBody('')
          setObservedAt(today())
        },
        onError: (err) => setError(errorMessage(err, "Couldn't save that note.")),
      },
    )
  }

  return (
    <div className="flex flex-col gap-2 rounded-lg border border-border p-2">
      <TextArea
        label={scopeLabel ? `Note about ${scopeLabel}` : 'Note'}
        name="journalBody"
        rows={3}
        placeholder="What happened?"
        value={body}
        onChange={(e) => setBody(e.target.value)}
      />
      <div className="flex items-end gap-2">
        <div className="flex-1">
          <TextField
            label="Observed"
            name="observedAt"
            type="date"
            value={observedAt}
            onChange={(e) => setObservedAt(e.target.value)}
          />
        </div>
        <Button className="shrink-0" disabled={create.isPending || body.trim() === ''} onClick={submit}>
          {create.isPending ? 'Saving…' : 'Save note'}
        </Button>
      </div>
      {error && <p className="text-xs text-red-700 dark:text-red-400">{error}</p>}
    </div>
  )
}

function Entry({
  entry,
  gardenId,
  objects,
  canDelete,
  canRewrite,
}: {
  entry: JournalEntry
  gardenId: number
  objects: EditorObject[]
  /** May remove it: the author, or the garden owner. */
  canDelete: boolean
  /** May rewrite the text: the author only — rewriting someone else's
   *  observation under their name is a different act from removing it. */
  canRewrite: boolean
}) {
  const update = useUpdateJournalEntry(gardenId)
  const del = useDeleteJournalEntry(gardenId)
  const [editing, setEditing] = useState(false)
  const [draft, setDraft] = useState(entry.body)
  const [error, setError] = useState<string | null>(null)
  const about = objects.find((o) => o.id === entry.objectId) ?? null

  // Re-sync the draft when the entry changes underneath — a refetch after
  // someone else's edit, or this entry's own successful save. Skipped while
  // editing so a background refetch can't clobber what's being typed; the
  // version guard is what catches a genuine collision.
  const [syncedBody, setSyncedBody] = useState(entry.body)
  if (!editing && entry.body !== syncedBody) {
    setSyncedBody(entry.body)
    setDraft(entry.body)
  }

  const save = () => {
    const text = draft.trim()
    if (!text) {
      // Silence here reads as a broken button; say why nothing happened.
      setError('An entry needs some text. Delete it instead if that\'s what you meant.')
      return
    }
    setError(null)
    update.mutate(
      { id: entry.id, version: entry.version, body: text },
      {
        onSuccess: () => {
          setEditing(false)
          setError(null)
        },
        onError: (err) => setError(errorMessage(err, "Couldn't save that edit.")),
      },
    )
  }

  return (
    <li className="rounded-lg border border-border px-2.5 py-2 text-sm">
      {editing ? (
        <div className="flex flex-col gap-2">
          <TextArea label="Note" name={`entry-${entry.id}`} rows={3} value={draft} onChange={(e) => setDraft(e.target.value)} />
          <div className="flex justify-end gap-1">
            <Button
              variant="ghost"
              className="px-2 py-1 text-xs"
              onClick={() => {
                setDraft(entry.body)
                setEditing(false)
                setError(null)
              }}
            >
              Cancel
            </Button>
            <Button className="px-2 py-1 text-xs" disabled={update.isPending} onClick={save}>
              {update.isPending ? 'Saving…' : 'Save'}
            </Button>
          </div>
        </div>
      ) : (
        <p className="whitespace-pre-wrap text-fg">{entry.body}</p>
      )}

      <p className="mt-1 flex flex-wrap items-center gap-x-1.5 gap-y-1 text-xs text-muted">
        <time dateTime={entry.observedAt}>{formatObservedAt(entry.observedAt)}</time>
        <span aria-hidden>·</span>
        <span>{entry.authorName}</span>
        {about && (
          <>
            <span aria-hidden>·</span>
            <span className="rounded bg-border/60 px-1 py-px">{objectDisplayName(about)}</span>
          </>
        )}
        {entry.plantingId != null && <span className="rounded bg-border/60 px-1 py-px">planting</span>}
      </p>

      {(canRewrite || canDelete) && !editing && (
        <div className="mt-1 flex justify-end gap-1">
          {canRewrite && (
            <button
              type="button"
              onClick={() => setEditing(true)}
              className="rounded px-1.5 py-0.5 text-xs text-muted outline-none hover:text-fg focus-visible:ring-2 focus-visible:ring-accent/40"
            >
              Edit
            </button>
          )}
          {canDelete && (
            <button
              type="button"
              disabled={del.isPending}
              onClick={() => {
                setError(null) // clear a previous failure so a retry isn't read as another
                del.mutate(entry.id, {
                  onError: (err) => setError(errorMessage(err, "Couldn't delete that note.")),
                })
              }}
              className="rounded px-1.5 py-0.5 text-xs text-red-700 outline-none hover:underline focus-visible:ring-2 focus-visible:ring-accent/40 dark:text-red-400"
            >
              Delete
            </button>
          )}
        </div>
      )}
      {error && <p className="mt-1 text-xs text-red-700 dark:text-red-400">{error}</p>}
    </li>
  )
}
