from __future__ import annotations

import os
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Any, Dict

from gradio import processing_utils


class Serializable(ABC):
    @abstractmethod
    def serialize(
        self, x: Any, load_dir: str = "", encryption_key: bytes | None = None
    ):
        """
        Convert data from human-readable format to serialized format for a browser.
        """
        pass

    @abstractmethod
    def deserialize(
        x: Any, save_dir: str | None = None, encryption_key: bytes | None = None
    ):
        """
        Convert data from serialized format for a browser to human-readable format.
        """
        pass


class SimpleSerializable(Serializable):
    def serialize(
        self, x: Any, load_dir: str = "", encryption_key: bytes | None = None
    ) -> Any:
        """
        Convert data from human-readable format to serialized format. For SimpleSerializable components, this is a no-op.
        Parameters:
            x: Input data to serialize
            load_dir: Ignored
            encryption_key: Ignored
        """
        return x

    def deserialize(
        self, x: Any, save_dir: str | None = None, encryption_key: bytes | None = None
    ):
        """
        Convert data from serialized format to human-readable format. For SimpleSerializable components, this is a no-op.
        Parameters:
            x: Input data to deserialize
            save_dir: Ignored
            encryption_key: Ignored
        """
        return x


class ImgSerializable(Serializable):
    def serialize(
        self, x: str, load_dir: str = "", encryption_key: bytes | None = None
    ) -> str:
        """
        Convert from human-friendly version of a file (string filepath) to a seralized
        representation (base64).
        Parameters:
            x: String path to file to serialize
            load_dir: Path to directory containing x
            encryption_key: Used to encrypt the file
        """
        if x is None or x == "":
            return None
        return processing_utils.encode_url_or_file_to_base64(
            os.path.join(load_dir, x), encryption_key=encryption_key
        )

    def deserialize(
        self, x: str, save_dir: str | None = None, encryption_key: bytes | None = None
    ) -> str:
        """
        Convert from serialized representation of a file (base64) to a human-friendly
        version (string filepath). Optionally, save the file to the directory specified by save_dir
        Parameters:
            x: Base64 representation of image to deserialize into a string filepath
            save_dir: Path to directory to save the deserialized image to
            encryption_key: Used to decrypt the file
        """
        if x is None or x == "":
            return None
        file = processing_utils.decode_base64_to_file(
            x, dir=save_dir, encryption_key=encryption_key
        )
        return file.name


class FileSerializable(Serializable):
    def serialize(
        self, x: str, load_dir: str = "", encryption_key: bytes | None = None
    ) -> Any:
        """
        Convert from human-friendly version of a file (string filepath) to a
        seralized representation (base64)
        Parameters:
            x: String path to file to serialize
            load_dir: Path to directory containing x
            encryption_key: Used to encrypt the file
        """
        if x is None or x == "":
            return None
        filename = os.path.join(load_dir, x)
        return {
            "name": filename,
            "data": processing_utils.encode_url_or_file_to_base64(
                filename, encryption_key=encryption_key
            ),
            "orig_name": Path(filename).name,
            "is_file": False,
        }

    def deserialize(
        self, x: Dict, save_dir: str | None = None, encryption_key: bytes | None = None
    ):
        """
        Convert from serialized representation of a file (base64) to a human-friendly
        version (string filepath). Optionally, save the file to the directory specified by `save_dir`
        Parameters:
            x: Base64 representation of file to deserialize into a string filepath
            save_dir: Path to directory to save the deserialized file to
            encryption_key: Used to decrypt the file
        """
        if x is None:
            return None
        if isinstance(x, str):
            file = processing_utils.decode_base64_to_file(
                x, dir=save_dir, encryption_key=encryption_key
            )
        elif isinstance(x, dict):
            if x.get("is_file", False):
                file = processing_utils.create_tmp_copy_of_file(x["name"], dir=save_dir)
            else:
                file = processing_utils.decode_base64_to_file(
                    x["data"], dir=save_dir, encryption_key=encryption_key
                )
        else:
            raise ValueError(
                f"A FileSerializable component cannot only deserialize a string or a dict, not a: {type(x)}"
            )
        return file.name


class JSONSerializable(Serializable):
    def serialize(
        self, x: str, load_dir: str = "", encryption_key: bytes | None = None
    ) -> str:
        """
        Convert from a a human-friendly version (string path to json file) to a
        serialized representation (json string)
        Parameters:
            x: String path to json file to read to get json string
            load_dir: Path to directory containing x
            encryption_key: Ignored
        """
        if x is None or x == "":
            return None
        return processing_utils.file_to_json(os.path.join(load_dir, x))

    def deserialize(
        self,
        x: str | Dict,
        save_dir: str | None = None,
        encryption_key: bytes | None = None,
    ) -> str:
        """
        Convert from serialized representation (json string) to a human-friendly
        version (string path to json file).  Optionally, save the file to the directory specified by `save_dir`
        Parameters:
            x: Json string
            save_dir: Path to save the deserialized json file to
            encryption_key: Ignored
        """
        if x is None:
            return None
        return processing_utils.dict_or_str_to_json_file(x, dir=save_dir).name
