# Copyright (C) 2012 Anaconda, Inc
# SPDX-License-Identifier: BSD-3-Clause
"""Common path utilities."""

from __future__ import annotations

import os
import re
from functools import cache
from logging import getLogger
from os.path import (
    abspath,
    expanduser,
    expandvars,
    normcase,
    split,
)
from typing import TYPE_CHECKING
from urllib.parse import urlsplit

from ... import CondaError
from ...deprecations import deprecated
from ..compat import on_win
from .directories import (
    explode_directories,
    get_all_directories,
    get_leaf_directories,
    tokenized_startswith,
)
from .python import (
    get_major_minor_version,
    get_python_noarch_target_path,
    get_python_short_path,
    get_python_site_packages_short_path,
    missing_pyc_files,
    parse_entry_point_def,
    pyc_path,
)
from .windows import (
    unix_path_to_win,
    win_path_backout,
    win_path_double_escape,
    win_path_ok,
    win_path_to_unix,
)

if TYPE_CHECKING:
    from collections.abc import Iterable

    PathType = str | os.PathLike[str]
    PathsType = Iterable[PathType]

__all__ = [
    "explode_directories",
    "get_all_directories",
    "get_leaf_directories",
    "get_major_minor_version",
    "get_python_noarch_target_path",
    "get_python_short_path",
    "get_python_site_packages_short_path",
    "missing_pyc_files",
    "parse_entry_point_def",
    "pyc_path",
    "tokenized_startswith",
    "unix_path_to_win",
    "win_path_backout",
    "win_path_double_escape",
    "win_path_ok",
    "win_path_to_unix",
]

log = getLogger(__name__)

PATH_MATCH_REGEX = (
    r"\./"  # ./
    r"|\.\."  # ..
    r"|~"  # ~
    r"|/"  # /
    r"|[a-zA-Z]:[/\\]"  # drive letter, colon, forward or backslash
    r"|\\\\"  # windows UNC path
    r"|//"  # windows UNC path
)

# Repodata file extensions (static, not plugin-dependent)
KNOWN_REPODATA_EXTENSIONS = (
    ".json",
    ".jlap",
    ".json.zst",
)

deprecated.constant(
    "26.9",
    "27.3",
    "KNOWN_EXTENSIONS",
    (".conda", ".tar.bz2", *KNOWN_REPODATA_EXTENSIONS),
    addendum="Use `conda.common.path.strip_pkg_extension` instead.",
)


def is_path(value):
    if "://" in value:
        return False
    return re.match(PATH_MATCH_REGEX, value)


def expand(path):
    return abspath(expanduser(expandvars(path)))


def paths_equal(path1, path2):
    """
    Examples:
        >>> paths_equal('/a/b/c', '/a/b/c/d/..')
        True

    """
    if on_win:
        return normcase(abspath(path1)) == normcase(abspath(path2))
    else:
        return abspath(path1) == abspath(path2)


@cache
def url_to_path(url):
    """Convert a file:// URL to a path.

    Relative file URLs (i.e. `file:relative/path`) are not supported.
    """
    if is_path(url):
        return url
    if not url.startswith("file://"):  # pragma: no cover
        raise CondaError(
            f"You can only turn absolute file: urls into paths (not {url})"
        )
    _, netloc, path, _, _ = urlsplit(url)
    from ..url import percent_decode

    path = percent_decode(path)
    if netloc not in ("", "localhost", "127.0.0.1", "::1"):
        if not netloc.startswith("\\\\"):
            # The only net location potentially accessible is a Windows UNC path
            netloc = "//" + netloc
    else:
        netloc = ""
        # Handle Windows drive letters if present
        if re.match("^/([a-z])[:|]", path, re.I):
            path = path[1] + ":" + path[3:]
    return netloc + path


BIN_DIRECTORY = "Scripts" if on_win else "bin"


def ensure_pad(name, pad="_"):
    """

    Examples:
        >>> ensure_pad('conda')
        '_conda_'
        >>> ensure_pad('_conda')
        '__conda_'
        >>> ensure_pad('')
        ''

    """
    if not name or name[0] == name[-1] == pad:
        return name
    else:
        return f"{pad}{name}{pad}"


def right_pad_os_sep(path):
    return path if path.endswith(os.sep) else path + os.sep


def split_filename(path_or_url):
    dn, fn = split(path_or_url)
    return (dn or None, fn) if "." in fn else (path_or_url, None)


def strip_pkg_extension(path: str) -> tuple[str, str | None]:
    """
    Split path into (base, extension) for known extensions.

    Package extensions are determined dynamically from registered plugins.
    Repodata extensions (.json, .jlap, .json.zst) are also recognized.

    :param path: Path to split.
    :return: Tuple of (base_path, extension) where extension is None if not found.

    Examples:
        >>> strip_pkg_extension("/path/_license-1.1-py27_1.tar.bz2")
        ('/path/_license-1.1-py27_1', '.tar.bz2')
        >>> strip_pkg_extension("/path/_license-1.1-py27_1.conda")
        ('/path/_license-1.1-py27_1', '.conda')
        >>> strip_pkg_extension("/path/repodata.json")
        ('/path/repodata', '.json')
        >>> strip_pkg_extension("/path/_license-1.1-py27_1")
        ('/path/_license-1.1-py27_1', None)
    """
    from ...base.context import context

    # Check package extensions first (dynamic from plugins)
    if ext := context.plugin_manager.has_package_extension(path):
        return path[: -len(ext)], ext
    # Then additional extensions (static)
    for ext in KNOWN_REPODATA_EXTENSIONS:
        if path.endswith(ext):
            return path[: -len(ext)], ext
    return path, None


@deprecated(
    "26.9",
    "27.3",
    addendum="Use `conda.base.context.context.plugin_manager.has_package_extension` instead.",
)
def is_package_file(path):
    """
    Examples:
        >>> is_package_file("/path/_license-1.1-py27_1.tar.bz2")
        True
        >>> is_package_file("/path/_license-1.1-py27_1.conda")
        True
        >>> is_package_file("/path/_license-1.1-py27_1")
        False
    """
    # NOTE: not using CONDA_TARBALL_EXTENSION_V1 or CONDA_TARBALL_EXTENSION_V2 to comply with
    #       import rules and to avoid a global lookup.
    return path[-6:] == ".conda" or path[-8:] == ".tar.bz2"


def path_identity(paths: PathType | PathsType | None) -> str | tuple[str, ...] | None:
    if paths is None:
        return None
    elif isinstance(paths, (str, os.PathLike)):
        return os.path.normpath(paths)
    else:
        return tuple(os.path.normpath(path) for path in paths)
