# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# src/pdftl/utils/io_helpers.py

"""Helper functions for robust file I/O operations."""

import sys
from contextlib import AbstractContextManager as ContextManager
from contextlib import nullcontext
from typing import IO, TYPE_CHECKING, Any, Literal, overload

if TYPE_CHECKING:
    import pikepdf


def smart_pikepdf_open(filename: str | None, password: str | None = None) -> "pikepdf.Pdf":
    import io

    import pikepdf

    if filename is None:
        data = sys.stdin.buffer.read()
        source = io.BytesIO(data)
    else:
        source = filename
    if password:
        return pikepdf.open(source, password=password)
    return pikepdf.open(source)


@overload
def smart_open_maybe_dash(
    filename: str | None, mode: Literal["w", "a"] = "w", encoding: str = "utf-8"
) -> ContextManager[IO[str]]: ...


@overload
def smart_open_maybe_dash(
    filename: str | None, mode: Literal["wb", "ab"], encoding: str | None = None
) -> ContextManager[IO[bytes]]: ...


def smart_open_maybe_dash(
    filename: str | None, mode: str = "w", encoding: str | None = "utf-8"
) -> ContextManager[Any]:
    return smart_open(filename if filename != "-" else None, mode, encoding)  # type: ignore


# Overload for Text Mode (default, "w", or "a")
@overload
def smart_open(
    filename: str | None, mode: Literal["r", "w", "a"] = "w", encoding: str = "utf-8"
) -> ContextManager[IO[str]]: ...


# Overload for Binary Mode ("wb" or "ab")
@overload
def smart_open(
    filename: str | None, mode: Literal["rb", "wb", "ab"], encoding: str | None = None
) -> ContextManager[IO[bytes]]: ...


def smart_open(
    filename: str | None, mode: str = "w", encoding: str | None = "utf-8"
) -> ContextManager[Any]:
    """
    Context manager that opens a filename or stdout (if the
    filename is None).
    """
    if filename is None:
        if "w" in mode:
            # If binary mode is requested, use the buffer underlying stdout
            if "b" in mode:
                return nullcontext(sys.stdout.buffer)
            return nullcontext(sys.stdout)
        if "b" in mode:
            return nullcontext(sys.stdin.buffer)
        return nullcontext(sys.stdin)

    if "b" in mode:
        # Binary mode doesn't take an encoding argument
        return open(filename, mode)  # pylint: disable=unspecified-encoding

    return open(filename, mode, encoding=encoding)


def can_read_file(filename: str) -> bool:
    """Test if we can read a file by attempting to open it."""
    try:
        from pathlib import Path

        p = Path(filename)

        if not p.is_file():
            return False

        with p.open("rb") as _:
            pass
        return True

    except (OSError, FileNotFoundError):
        return False
