"""Filesystem related utilities useful to kdlj2."""

import os
import posixpath
from collections import namedtuple

import pathspec

FileBrowserRequest = namedtuple("FileBrowserRequest", ["template", "path", "items"])


def parentpath(path: str) -> str:
    """
    Find the parent of a relative path, safely.

    If path tries to go up from the relative path reference point, it is
    normalized to '.'.
    """
    if not path or path == ".":
        return "."

    return os.path.dirname(path)


def get_gitignore_spec(src: str = "") -> pathspec.PathSpec | None:
    """
    Load a spec of paths to ignore based on a .gitignore in the source path.

    Returns None if no gitignore file is found.
    """
    gitignore_path = os.path.join(src, os.getcwd(), ".gitignore")
    if os.path.exists(gitignore_path):
        with open(gitignore_path, "r") as f:
            return pathspec.PathSpec.from_lines("gitwildmatch", f.read().splitlines())
    return None


def handle_files_route(
    requested_path: str, source_path: str = "", use_gitignore: bool = True
) -> FileBrowserRequest | None:
    """
    Create a FileBrowserRequest from a file request's GET path.

    Automatically discerns between directories and files, producing different
    types of FileBrowserRequest for each.

    Uses safety guardrails to avoid accessing files outside the scope of the source
    path.

    Automatically ignores files in the gitignore spec if specified.

    Returns None if the path does not exist.
    """
    safe_path = os.path.relpath("/" + requested_path, "/")
    abs_path = os.path.join(os.getcwd(), source_path, safe_path)

    spec: pathspec.PathSpec | None = None

    if use_gitignore:
        spec = get_gitignore_spec()

    if os.path.isdir(abs_path):
        items = []
        for name in os.listdir(abs_path):
            is_dir = os.path.isdir(os.path.join(abs_path, name))

            if name == ".git" or name == ".gitignore":
                continue

            rel_item_path = os.path.join(safe_path, name) + ("/" if is_dir else "")

            if use_gitignore and spec and spec.match_file(rel_item_path):
                continue

            items.append(
                {
                    "name": name,
                    "url": f"/_files/{posixpath.join(safe_path, name)}",
                    "type": "directory" if is_dir else "file",
                }
            )

        return FileBrowserRequest(
            template="dirview",
            path=safe_path,
            items=items,
        )

    if os.path.isfile(abs_path):
        return FileBrowserRequest(
            template=None,
            path=safe_path,
            items=None,
        )

    return None
