import { ImagePlusIcon, RefreshCwIcon, Trash2Icon } from 'lucide-react'
import { useCallback, useEffect, useState } from 'react'
import { useDropzone } from 'react-dropzone'
import type { FileRejection } from 'react-dropzone'

import { FieldError } from '@/components/FieldError'
import { Button } from '@/components/ui/button'
import { FieldDescription, FieldLabel } from '@/components/ui/field'
import { formatNumber } from '@/lib/format'
import { t } from '@/lib/i18n'
import { cn } from '@/lib/utils'

interface Props {
  file: File | null
  /** Hasta cuántos bytes acepta el servidor. Lo dice él, no la pantalla. */
  maxBytes: number
  /** El error que devolvió el servidor, si lo hubo. */
  error?: string
  onChange: (file: File | null) => void
}

function megabytes(bytes: number): string {
  return formatNumber(Math.round(bytes / 1024 / 1024))
}

function fileSize(bytes: number): string {
  if (bytes < 1024) return t('keyFile.bytes', { value: formatNumber(bytes) })
  if (bytes < 1024 * 1024) {
    return t('keyFile.kilobytes', { value: formatNumber(Math.round((bytes / 1024) * 10) / 10) })
  }
  return t('ai.generator.photo.megabytes', {
    value: formatNumber(Math.round((bytes / 1024 / 1024) * 10) / 10),
  })
}

/**
 * La foto del patio, opcional, con una zona para arrastrarla o elegirla.
 *
 * Es `react-dropzone` y no un `<input type="file">` a secas, por decisión del
 * owner: el campo nativo se ve distinto en cada navegador y no muestra lo que
 * se eligió. La zona entera es un botón —se enfoca, se abre con Enter o
 * espacio— así que arrastrar es un atajo y no el único camino (RT-15).
 *
 * Lo que rechaza la librería —otro tipo de archivo, más pesado que el tope—
 * se dice acá mismo con el mismo código que usaría el servidor, para que el
 * mensaje sea uno solo esté donde esté la comprobación.
 */
export function PhotoDropzone({ file, maxBytes, error, onChange }: Props) {
  const [preview, setPreview] = useState<string | null>(null)
  const [rejected, setRejected] = useState<string | undefined>()

  useEffect(() => {
    if (!file) {
      setPreview(null)
      return
    }
    const url = URL.createObjectURL(file)
    setPreview(url)
    return () => URL.revokeObjectURL(url)
  }, [file])

  const onDrop = useCallback(
    (accepted: File[], rejections: FileRejection[]) => {
      const rejection = rejections[0]
      if (rejection) {
        const code = rejection.errors[0]?.code
        setRejected(
          code === 'file-too-large'
            ? t('errors.IMAGE_TOO_LARGE', { megabytes: megabytes(maxBytes) })
            : t('errors.IMAGE_INVALID')
        )
        onChange(null)
        return
      }
      setRejected(undefined)
      onChange(accepted[0] ?? null)
    },
    [maxBytes, onChange]
  )

  const { getRootProps, getInputProps, isDragActive, open } = useDropzone({
    onDrop,
    accept: { 'image/jpeg': [], 'image/png': [], 'image/webp': [] },
    maxSize: maxBytes,
    multiple: false,
    // La zona no abre el diálogo al hacer clic cuando ya hay foto: ahí lo que
    // se ofrece son «reemplazar» y «quitar», y un clic accidental sobre la
    // vista previa no tiene por qué abrir nada.
    noClick: file !== null,
  })

  const message = rejected ?? error
  const errorId = 'error-image'

  return (
    <div className="flex flex-col gap-2">
      <FieldLabel htmlFor="image">{t('ai.generator.photo.label')}</FieldLabel>

      <div
        {...getRootProps({
          className: cn(
            'flex min-h-40 items-center justify-center rounded-lg border border-dashed p-4 transition-colors duration-150 outline-none focus-visible:ring-3 focus-visible:ring-ring/50 motion-reduce:transition-none',
            file === null && 'cursor-pointer',
            isDragActive ? 'border-foreground bg-accent/40' : 'border-border',
            message && 'border-destructive'
          ),
          'aria-describedby': [message ? errorId : '', 'help-image'].filter(Boolean).join(' '),
          'aria-invalid': message ? true : undefined,
        })}
      >
        <input id="image" name="image" {...getInputProps()} />

        {file && preview ? (
          <div className="flex w-full flex-col items-center gap-4 sm:flex-row">
            <img
              src={preview}
              alt={t('ai.generator.photo.previewAlt')}
              className="bg-muted h-32 w-48 shrink-0 rounded-md border object-cover"
            />
            <div className="flex min-w-0 flex-1 flex-col gap-1">
              <p className="truncate font-medium" title={file.name}>
                {file.name}
              </p>
              <p className="text-muted-foreground text-sm">{fileSize(file.size)}</p>
              <div className="mt-2 flex flex-wrap gap-2">
                <Button type="button" size="sm" variant="outline" onClick={open}>
                  <RefreshCwIcon aria-hidden />
                  {t('ai.generator.photo.replace')}
                </Button>
                <Button
                  type="button"
                  size="sm"
                  variant="ghost"
                  onClick={() => {
                    setRejected(undefined)
                    onChange(null)
                  }}
                >
                  <Trash2Icon aria-hidden />
                  {t('ai.generator.photo.remove')}
                </Button>
              </div>
            </div>
          </div>
        ) : (
          <div className="flex flex-col items-center gap-2 text-center">
            <span className="bg-muted text-muted-foreground flex size-10 items-center justify-center rounded-md">
              <ImagePlusIcon aria-hidden className="size-5" />
            </span>
            <p className="font-medium">
              {isDragActive ? t('ai.generator.photo.dropNow') : t('ai.generator.photo.drop')}
            </p>
            <p className="text-muted-foreground text-sm">{t('ai.generator.photo.orClick')}</p>
          </div>
        )}
      </div>

      <FieldDescription id="help-image">
        {t('ai.generator.photo.help', { megabytes: megabytes(maxBytes) })}
      </FieldDescription>
      <FieldError id={errorId} message={message} />
    </div>
  )
}
