# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import annotations

from abc import abstractmethod
from enum import Enum
from typing import Protocol


class MediaFileKind(Enum):
    # st.image, st.video, st.audio files
    MEDIA = "media"

    # st.download_button files
    DOWNLOADABLE = "downloadable"


class MediaFileStorageError(Exception):
    """Exception class for errors raised by MediaFileStorage.

    When running in "development mode", the full text of these errors
    is displayed in the frontend, so errors should be human-readable
    (and actionable).

    When running in "release mode", errors are redacted on the
    frontend; we instead show a generic "Something went wrong!" message.
    """


class MediaFileStorage(Protocol):
    @abstractmethod
    def load_and_get_id(
        self,
        path_or_data: str | bytes,
        mimetype: str,
        kind: MediaFileKind,
        filename: str | None = None,
    ) -> str:
        """Load the given file path or bytes into the manager and return
        an ID that uniquely identifies it.

        It's an error to pass a URL to this function. (Media stored at
        external URLs can be served directly to the Streamlit frontend;
        there's no need to store this data in MediaFileStorage.)

        Parameters
        ----------
        path_or_data
            A path to a file, or the file's raw data as bytes.

        mimetype
            The media's mimetype. Used to set the Content-Type header when
            serving the media over HTTP.

        kind
            The kind of file this is: either MEDIA, or DOWNLOADABLE.

        filename : str or None
            Optional filename. Used to set the filename in the response header.

        Returns
        -------
        str
            The unique ID of the media file.

        Raises
        ------
        MediaFileStorageError
            Raised if the media can't be loaded (for example, if a file
            path is invalid).

        """
        raise NotImplementedError

    @abstractmethod
    def get_url(self, file_id: str) -> str:
        """Return a URL for a file in the manager.

        Parameters
        ----------
        file_id
            The file's ID, returned from load_media_and_get_id().

        Returns
        -------
        str
            A URL that the frontend can load the file from. Because this
            URL may expire, it should not be cached!

        Raises
        ------
        MediaFileStorageError
            Raised if the manager doesn't contain an object with the given ID.

        """
        raise NotImplementedError

    @abstractmethod
    def delete_file(self, file_id: str) -> None:
        """Delete a file from the manager.

        This should be called when a given file is no longer referenced
        by any connected client, so that the MediaFileStorage can free its
        resources.

        Calling `delete_file` on a file_id that doesn't exist is allowed,
        and is a no-op. (This means that multiple `delete_file` calls with
        the same file_id is not an error.)

        Note: implementations can choose to ignore `delete_file` calls -
        this function is a *suggestion*, not a *command*. Callers should
        not rely on file deletion happening immediately (or at all).

        Parameters
        ----------
        file_id
            The file's ID, returned from load_media_and_get_id().

        Returns
        -------
        None

        Raises
        ------
        MediaFileStorageError
            Raised if file deletion fails for any reason. Note that these
            failures will generally not be shown on the frontend (file
            deletion usually occurs on session disconnect).

        """
        raise NotImplementedError
