import os
import yaml
from pathlib import Path, PosixPath, WindowsPath

class PathLoader(yaml.SafeLoader):
    """
    Custom YAML loader that resolves file paths relative to the configuration file location.

    This loader enables the use of the ``!path`` tag in YAML files. When this tag is encountered,
    the loader automatically resolves the specified path relative to the directory containing
    the YAML file, returning a pathlib.Path object.
    """

    def __init__(self, stream):
        # Determine the folder of the file being loaded
        if hasattr(stream, 'name') and stream.name != '<stdout>':
            self._root = os.path.split(stream.name)[0]
        else:
            self._root = os.getcwd()
        super().__init__(stream)

    def construct_path(self, node):
        value = self.construct_scalar(node)
        # Return a Path object so the Dumper can catch it
        full_path = os.path.normpath(os.path.join(self._root, value))
        return Path(full_path)

class PathDumper(yaml.SafeDumper):
    """
    Custom YAML dumper that formats Path objects with the ``!path`` tag.

    If the Path object is absolute, this dumper will automatically attempt to 
    make it relative to the directory of the file being written to.
    """
    def __init__(self, stream, **kwargs):
        self._root = os.path.dirname(stream.name) or '.' if hasattr(stream, 'name') else '.'
        super().__init__(stream, **kwargs)

    def represent_path(self, data):
        try:
            rel_path = os.path.relpath(str(data), self._root)
        except ValueError:
            rel_path = str(data)
        return self.represent_scalar('!path', rel_path.replace(os.sep, '/'))

# Register the constructor for the Loader
PathLoader.add_constructor('!path', PathLoader.construct_path)

# Register the representers for the Dumper
PathDumper.add_representer(Path, PathDumper.represent_path)
PathDumper.add_representer(PosixPath, PathDumper.represent_path)
PathDumper.add_representer(WindowsPath, PathDumper.represent_path)