import { Plus, X, XCircle } from "@styled-icons/boxicons-regular";
import { Pencil } from "@styled-icons/boxicons-solid";
import Axios, { AxiosRequestConfig } from "axios";

import styles from "./FileUploads.module.scss";
import classNames from "classnames";
import { Text } from "preact-i18n";
import { useContext, useEffect, useState } from "preact/hooks";

import { determineFileSize } from "../../lib/fileSize";

import IconButton from "../../components/ui/IconButton";
import Preloader from "../../components/ui/Preloader";

import { useIntermediate } from "../intermediate/Intermediate";
import { AppContext } from "./RevoltClient";
import { takeError } from "./util";

type Props = {
    maxFileSize: number;
    remove: () => Promise<void>;
    fileType: "backgrounds" | "icons" | "avatars" | "attachments" | "banners";
} & (
    | { behaviour: "ask"; onChange: (file: File) => void }
    | { behaviour: "upload"; onUpload: (id: string) => Promise<void> }
    | {
          behaviour: "multi";
          onChange: (files: File[]) => void;
          append?: (files: File[]) => void;
      }
) &
    (
        | {
              style: "icon" | "banner";
              defaultPreview?: string;
              previewURL?: string;
              width?: number;
              height?: number;
          }
        | {
              style: "attachment";
              attached: boolean;
              uploading: boolean;
              cancel: () => void;
              size?: number;
          }
    );

export async function uploadFile(
    autumnURL: string,
    tag: string,
    file: File,
    config?: AxiosRequestConfig,
) {
    const formData = new FormData();
    formData.append("file", file);

    const res = await Axios.post(`${autumnURL}/${tag}`, formData, {
        headers: {
            "Content-Type": "multipart/form-data",
        },
        ...config,
    });

    return res.data.id;
}

export function grabFiles(
    maxFileSize: number,
    cb: (files: File[]) => void,
    tooLarge: () => void,
    multiple?: boolean,
) {
    const input = document.createElement("input");
    input.type = "file";
    input.multiple = multiple ?? false;

    input.onchange = async (e) => {
        const files = (e.currentTarget as HTMLInputElement)?.files;
        if (!files) return;
        for (const file of files) {
            if (file.size > maxFileSize) {
                return tooLarge();
            }
        }

        cb(Array.from(files));
    };

    input.click();
}

export function FileUploader(props: Props) {
    const { fileType, maxFileSize, remove } = props;
    const { openScreen } = useIntermediate();
    const client = useContext(AppContext);

    const [uploading, setUploading] = useState(false);

    function onClick() {
        if (uploading) return;

        grabFiles(
            maxFileSize,
            async (files) => {
                setUploading(true);

                try {
                    if (props.behaviour === "multi") {
                        props.onChange(files);
                    } else if (props.behaviour === "ask") {
                        props.onChange(files[0]);
                    } else {
                        await props.onUpload(
                            await uploadFile(
                                client.configuration!.features.autumn.url,
                                fileType,
                                files[0],
                            ),
                        );
                    }
                } catch (err) {
                    return openScreen({ id: "error", error: takeError(err) });
                } finally {
                    setUploading(false);
                }
            },
            () => openScreen({ id: "error", error: "FileTooLarge" }),
            props.behaviour === "multi",
        );
    }

    function removeOrUpload() {
        if (uploading) return;

        if (props.style === "attachment") {
            if (props.attached) {
                props.remove();
            } else {
                onClick();
            }
        } else if (props.previewURL) {
            props.remove();
        } else {
            onClick();
        }
    }

    if (props.behaviour === "multi" && props.append) {
        useEffect(() => {
            // File pasting.
            function paste(e: ClipboardEvent) {
                const items = e.clipboardData?.items;
                if (typeof items === "undefined") return;
                if (props.behaviour !== "multi" || !props.append) return;

                const files = [];
                for (const item of items) {
                    if (!item.type.startsWith("text/")) {
                        const blob = item.getAsFile();
                        if (blob) {
                            if (blob.size > props.maxFileSize) {
                                openScreen({
                                    id: "error",
                                    error: "FileTooLarge",
                                });
                            }

                            files.push(blob);
                        }
                    }
                }

                props.append(files);
            }

            // Let the browser know we can drop files.
            function dragover(e: DragEvent) {
                e.stopPropagation();
                e.preventDefault();
                if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
            }

            // File dropping.
            function drop(e: DragEvent) {
                e.preventDefault();
                if (props.behaviour !== "multi" || !props.append) return;

                const dropped = e.dataTransfer?.files;
                if (dropped) {
                    const files = [];
                    for (const item of dropped) {
                        if (item.size > props.maxFileSize) {
                            openScreen({ id: "error", error: "FileTooLarge" });
                        }

                        files.push(item);
                    }

                    props.append(files);
                }
            }

            document.addEventListener("paste", paste);
            document.addEventListener("dragover", dragover);
            document.addEventListener("drop", drop);

            return () => {
                document.removeEventListener("paste", paste);
                document.removeEventListener("dragover", dragover);
                document.removeEventListener("drop", drop);
            };
        }, [props.append]);
    }

    if (props.style === "icon" || props.style === "banner") {
        const { style, previewURL, defaultPreview, width, height } = props;
        return (
            <div
                className={classNames(styles.uploader, {
                    [styles.icon]: style === "icon",
                    [styles.banner]: style === "banner",
                })}
                data-uploading={uploading}>
                <div
                    className={styles.image}
                    style={{
                        backgroundImage:
                            style === "icon"
                                ? `url('${previewURL ?? defaultPreview}')`
                                : previewURL
                                ? `linear-gradient( rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5) ), url('${previewURL}')`
                                : "black",
                        width,
                        height,
                    }}
                    onClick={onClick}>
                    {uploading ? (
                        <div className={styles.uploading}>
                            <Preloader type="ring" />
                        </div>
                    ) : (
                        <div className={styles.edit}>
                            <Pencil size={30} />
                        </div>
                    )}
                </div>
                <div className={styles.modify}>
                    <span onClick={removeOrUpload}>
                        {uploading ? (
                            <Text id="app.main.channel.uploading_file" />
                        ) : props.previewURL ? (
                            <Text id="app.settings.actions.remove" />
                        ) : (
                            <Text id="app.settings.actions.upload" />
                        )}
                    </span>
                    <span className={styles.small}>
                        <Text
                            id="app.settings.actions.max_filesize"
                            fields={{
                                filesize: determineFileSize(maxFileSize),
                            }}
                        />
                    </span>
                </div>
            </div>
        );
    } else if (props.style === "attachment") {
        const { attached, uploading, cancel, size } = props;
        return (
            <IconButton
                onClick={() => {
                    if (uploading) return cancel();
                    if (attached) return remove();
                    onClick();
                }}
                rotate={uploading || attached ? "45deg" : undefined}>
                <Plus size={size} />
            </IconButton>
        );
    }

    return null;
}