import { Camera, FileText, ImagePlus, X } from 'lucide-react';
import { useEffect, useRef, useState } from 'react';
import { cn } from '@/lib/utils';

/** What one chosen file looks like while the form is still open. */
type Chosen = {
    file: File;
    /** Object URL for the preview; revoked when the file is removed. */
    url: string;
    isImage: boolean;
};

const ACCEPT = 'image/*,.pdf,.heic,.heif';

/**
 * Photos, screenshots and PDFs, chosen before the form is submitted.
 *
 * Phones put the camera behind `capture`, so the same control takes a photo of
 * a roof and a screenshot of an inverter display. The chosen files stay in a
 * real file input, which is what the form posts; the thumbnail strip is only a
 * preview, so what you see is what will be attached.
 *
 * `name` is plural for a ticket being logged (`attachments[]`) and singular for
 * a comment, which carries one file.
 */
export function AttachmentPicker({
    name = 'attachments[]',
    multiple = true,
    max = 6,
    hint,
    id = 'attachments',
}: {
    name?: string;
    multiple?: boolean;
    max?: number;
    hint?: string;
    id?: string;
}) {
    const input = useRef<HTMLInputElement>(null);
    const chosenRef = useRef<Chosen[]>([]);
    const [chosen, setChosen] = useState<Chosen[]>([]);
    const [dragging, setDragging] = useState(false);

    // The ref mirrors the state so the unmount cleanup can release the object
    // URLs without re-running on every change and revoking previews still on
    // screen.
    useEffect(() => {
        chosenRef.current = chosen;
    }, [chosen]);

    useEffect(
        () => () => {
            chosenRef.current.forEach((entry) =>
                URL.revokeObjectURL(entry.url),
            );
        },
        [],
    );

    const describe = (files: File[]): Chosen[] =>
        files.map((file) => ({
            file,
            url: URL.createObjectURL(file),
            isImage:
                file.type.startsWith('image/') ||
                /\.(jpe?g|png|webp|gif|avif|heic|heif)$/i.test(file.name),
        }));

    /**
     * Push the chosen files back into the input. This is the step that makes
     * them part of the form: removing a thumbnail removes the file from what
     * will be posted, not just from the strip.
     */
    const commit = (files: File[]) => {
        chosenRef.current.forEach((entry) => URL.revokeObjectURL(entry.url));

        const next = describe(files);

        chosenRef.current = next;
        setChosen(next);

        if (input.current !== null) {
            const transfer = new DataTransfer();
            files.forEach((file) => transfer.items.add(file));
            input.current.files = transfer.files;
        }
    };

    const add = (incoming: FileList | null) => {
        if (incoming === null) {
            return;
        }

        const existing = chosen.map((entry) => entry.file);
        const files = multiple
            ? [...existing, ...Array.from(incoming)].slice(0, max)
            : Array.from(incoming).slice(0, 1);

        commit(files);
    };

    const remove = (index: number) => {
        commit(
            chosen
                .filter((_, position) => position !== index)
                .map((entry) => entry.file),
        );
    };

    return (
        <div className="flex flex-col gap-3">
            <label
                htmlFor={id}
                onDragOver={(event) => {
                    event.preventDefault();
                    setDragging(true);
                }}
                onDragLeave={() => setDragging(false)}
                onDrop={(event) => {
                    event.preventDefault();
                    setDragging(false);
                    add(event.dataTransfer.files);
                }}
                className={cn(
                    'flex cursor-pointer flex-col items-center justify-center gap-1.5 rounded-lg border border-dashed px-4 py-6 text-center transition-colors',
                    dragging
                        ? 'border-solar bg-solar/5'
                        : 'border-input hover:border-ring/60 hover:bg-accent/40',
                )}
            >
                <span className="flex size-9 items-center justify-center rounded-full bg-muted text-muted-foreground">
                    <ImagePlus className="size-4" aria-hidden />
                </span>
                <span className="text-sm font-medium">
                    Choose a photo or screenshot
                </span>
                <span className="text-xs text-muted-foreground">
                    Drop files here, or tap to use the camera — JPG, PNG, WEBP,
                    HEIC or PDF, up to 10 MB
                    {multiple ? ` each, ${max} at a time` : ''}
                </span>
                {hint ? (
                    <span className="text-xs text-muted-foreground">
                        {hint}
                    </span>
                ) : null}
            </label>

            <input
                ref={input}
                id={id}
                type="file"
                name={name}
                multiple={multiple}
                accept={ACCEPT}
                className="sr-only"
                onChange={(event) => add(event.target.files)}
            />

            {chosen.length > 0 ? (
                <ul className="flex flex-wrap gap-2">
                    {chosen.map((entry, index) => (
                        <li
                            key={`${entry.file.name}-${index}`}
                            className="relative w-24 overflow-hidden rounded-md border border-hairline bg-muted/40"
                        >
                            {entry.isImage ? (
                                <img
                                    src={entry.url}
                                    alt={entry.file.name}
                                    className="h-20 w-full object-cover"
                                    // HEIC is a photo but no browser shows it;
                                    // the caption still names the file.
                                    onError={(event) => {
                                        event.currentTarget.style.display =
                                            'none';
                                    }}
                                />
                            ) : (
                                <span className="flex h-20 items-center justify-center text-muted-foreground">
                                    <FileText className="size-6" aria-hidden />
                                </span>
                            )}

                            <span className="block truncate px-1.5 py-1 text-[10px]">
                                {entry.file.name}
                            </span>

                            <button
                                type="button"
                                onClick={() => remove(index)}
                                aria-label={`Remove ${entry.file.name}`}
                                className="absolute top-1 right-1 flex size-5 items-center justify-center rounded-full bg-background/90 text-foreground shadow-xs ring-1 ring-hairline hover:bg-destructive hover:text-white"
                            >
                                <X className="size-3" aria-hidden />
                            </button>
                        </li>
                    ))}
                </ul>
            ) : null}

            {chosen.length > 0 ? (
                <p className="flex items-center gap-1.5 text-xs text-muted-foreground">
                    <Camera className="size-3.5" aria-hidden />
                    {chosen.length} file{chosen.length === 1 ? '' : 's'} ready
                    to attach
                </p>
            ) : null}
        </div>
    );
}
