import { FileImage, Upload, X } from 'lucide-react';
import { useEffect, useRef, useState, useId } from 'react';
import {
    Attachment,
    AttachmentAction,
    AttachmentActions,
    AttachmentContent,
    AttachmentDescription,
    AttachmentMedia,
    AttachmentTitle,
    AttachmentTrigger,
} from '@/components/ui/attachment';
import { uploadFile, UploadError } from '@/lib/upload';

type FileUploadProps = {
    value: string | null;
    onChange: (key: string | null) => void;
    folder?: string;
    accept?: string;
    maxSize?: number;
    onUploadingChange?: (uploading: boolean) => void;
    existingUrl?: string | null;
    onFileMeta?: (meta: { size: number; type: string } | null) => void;
};

function formatFileSize(bytes: number): string {
    if (bytes < 1024) {
        return `${bytes} B`;
    }

    if (bytes < 1024 * 1024) {
        return `${(bytes / 1024).toFixed(1)} KB`;
    }

    return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}

function acceptToLabels(accept: string): string[] {
    const mimeMap: Record<string, string> = {
        'image/jpeg': 'JPG',
        'image/png': 'PNG',
        'image/webp': 'WebP',
        'image/gif': 'GIF',
        'image/svg+xml': 'SVG',
        'application/pdf': 'PDF',
        'video/mp4': 'MP4',
        'application/zip': 'ZIP',
    };

    return accept
        .split(',')
        .map(
            (mime) =>
                mimeMap[mime.trim()] ||
                mime.trim().split('/').pop()?.toUpperCase() ||
                'File',
        )
        .filter((v, i, a) => a.indexOf(v) === i);
}

function formatMaxSize(bytes: number): string {
    if (bytes < 1024 * 1024) {
        return `${(bytes / 1024).toFixed(0)}KB`;
    }

    return `${(bytes / (1024 * 1024)).toFixed(0)}MB`;
}

export function FileUpload({
    value,
    onChange,
    folder,
    accept = 'image/jpeg,image/png,image/webp,image/gif',
    maxSize = 10 * 1024 * 1024,
    onUploadingChange,
    existingUrl,
    onFileMeta,
}: FileUploadProps) {
    const inputRef = useRef<HTMLInputElement>(null);
    const uploadId = useId();
    const [uploading, setUploading] = useState(false);
    const [error, setError] = useState<string | null>(null);
    const [fileName, setFileName] = useState<string | null>(null);
    const [fileSize, setFileSize] = useState<number | null>(null);
    const [preview, setPreview] = useState<string | null>(null);

    const onUploadingChangeRef = useRef(onUploadingChange);
    onUploadingChangeRef.current = onUploadingChange;

    useEffect(() => {
        onUploadingChangeRef.current?.(uploading);
    }, [uploading]);

    async function handleFileChange(e: React.ChangeEvent<HTMLInputElement>) {
        const file = e.target.files?.[0];

        if (!file) {
            return;
        }

        setError(null);
        setUploading(true);
        setFileName(file.name);
        setFileSize(file.size);

        const objectUrl = URL.createObjectURL(file);
        setPreview(objectUrl);

        try {
            const key = await uploadFile(file, folder);
            onChange(key);
            onFileMeta?.({ size: file.size, type: file.type });
        } catch (err) {
            const message =
                err instanceof UploadError
                    ? err.message
                    : 'Gagal mengunggah file.';
            setError(message);
            setPreview(null);
            setFileName(null);
            setFileSize(null);
            onFileMeta?.(null);
        } finally {
            setUploading(false);

            if (inputRef.current) {
                inputRef.current.value = '';
            }
        }
    }

    function handleRemove(e: React.MouseEvent) {
        e.stopPropagation();
        onChange(null);
        setFileName(null);
        setFileSize(null);
        setPreview(null);
        setError(null);
        onFileMeta?.(null);

        if (inputRef.current) {
            inputRef.current.value = '';
        }
    }

    const state = uploading
        ? 'uploading'
        : error
          ? 'error'
          : value
            ? 'done'
            : 'idle';

    return (
        <>
            <input
                ref={inputRef}
                type="file"
                accept={accept}
                onChange={handleFileChange}
                className="hidden"
                id={uploadId}
            />

            <Attachment
                state={state}
                orientation="horizontal"
                className="w-full"
            >
                <AttachmentTrigger
                    onClick={() => inputRef.current?.click()}
                    aria-label={
                        value ? 'Ganti file' : 'Pilih file untuk diunggah'
                    }
                />

                <AttachmentMedia
                    variant={preview || existingUrl ? 'image' : 'icon'}
                >
                    {preview ? (
                        <img src={preview} alt={fileName ?? 'Preview'} />
                    ) : existingUrl && value ? (
                        <img src={existingUrl} alt="Bukti" />
                    ) : uploading ? (
                        <Upload className="animate-pulse" />
                    ) : (
                        <FileImage />
                    )}
                </AttachmentMedia>

                <AttachmentContent>
                    {value ? (
                        <>
                            <AttachmentTitle>{fileName}</AttachmentTitle>
                            <AttachmentDescription>
                                {fileSize
                                    ? formatFileSize(fileSize)
                                    : 'Terupload'}
                            </AttachmentDescription>
                        </>
                    ) : uploading ? (
                        <>
                            <AttachmentTitle>Mengunggah...</AttachmentTitle>
                            <AttachmentDescription>
                                Memproses file
                            </AttachmentDescription>
                        </>
                    ) : error ? (
                        <>
                            <AttachmentTitle>Gagal</AttachmentTitle>
                            <AttachmentDescription>
                                {error}
                            </AttachmentDescription>
                        </>
                    ) : (
                        <>
                            <AttachmentTitle>Unggah File</AttachmentTitle>
                            <AttachmentDescription>
                                {`${acceptToLabels(accept).join(', ')} · Maks ${formatMaxSize(maxSize)}`}
                            </AttachmentDescription>
                        </>
                    )}
                </AttachmentContent>

                {value && (
                    <AttachmentActions>
                        <AttachmentAction
                            aria-label="Hapus file"
                            onClick={handleRemove}
                        >
                            <X />
                        </AttachmentAction>
                    </AttachmentActions>
                )}
            </Attachment>
        </>
    );
}
