import json
import re
import io

from json import (
    JSONDecoder,
    JSONEncoder,
    JSONDecodeError,
)


class JSONXOXDecodeError(JSONDecodeError):
    pass


def _jsonx_to_json(content: str) -> str:
    """With an extended JSON string as input (JSON that can contain C-style single and
    multi-line comments as well as trailing commas) and return a standard JSON string
    that has had these non-standard elements removed."""

    if not isinstance(content, str):
        raise TypeError("The 'content' argument must have a string value!")

    if len(content) > 0:
        flags = re.MULTILINE | re.DOTALL

        try:
            # Remove multi-line comments that begin with /* and end with */
            content = re.sub(r"\/\*.*?\*\/", "", content, flags=flags)

            # Remove single-line comments which begin with // and end at a newline, } or ]
            content = re.sub(r"\s*(#|\/\/)([^\n\}\]]*)", "", content, flags=flags)

            # Remove any trailing commas that are present before block closures
            content = re.sub(r"\,(\s*)(\]|\})", r"\1\2", content, flags=flags)
        except Exception as exception:
            raise JSONXOXDecodeError(
                "An exception occurred while cleaning the JSON: %s" % (str(exception))
            ) from exception

    return content.strip()


def load(handle, **kwargs) -> object | None:
    """An extended json.load() method which supports loading JSON files with single and
    multi-line comments as well as trailing commas."""

    if not isinstance(handle, io.TextIOWrapper):
        raise RuntimeError("The input must be compatible with io.TextIOWrapper!")

    content = handle.read().strip()

    if len(content) > 0:
        if content := _jsonx_to_json(content):
            try:
                if isinstance(data := json.loads(content, **kwargs), object):
                    return data
            except JSONDecodeError as exception:
                raise JSONXOXDecodeError(str(exception)) from exception

    return None


def loads(content: str, **kwargs) -> object | None:
    """An extended json.loads() method which supports loading JSON files with single and
    multi-line comments as well as trailing commas."""

    if not isinstance(content, str):
        raise TypeError("The 'content' argument must have a string value!")

    if len(content) > 0:
        if content := _jsonx_to_json(content):
            try:
                if isinstance(data := json.loads(content, **kwargs), object):
                    return data
            except JSONDecodeError as exception:
                raise JSONXOXDecodeError(str(exception)) from exception

    return None


def dump(*args, **kwargs):
    """A passthrough method for the json standard library's .dump() method."""

    return json.dump(*args, **kwargs)


def dumps(*args, **kwargs):
    """A passthrough method for the json standard library's .dumps() method."""

    return json.dumps(*args, **kwargs)


__all__ = [
    "load",
    "loads",
    "dump",
    "dumps",
    "JSONDecoder",
    "JSONEncoder",
    "JSONDecodeError",
    "JSONXOXDecodeError",
]
