from pathlib import Path
from typing import (
    Callable,
    Type,
    Union,
    Dict,
    List,
    Any,
    Optional,
)


from . import (
    AutoHandler,
    prompt_namedtuple,
    namedtuple_sequence_dumps,
    namedtuple_sequence_load,
)
from .typehelpers import PrimitiveType, NT, T, PromptFunctionorValue


def _normalize(_path: Union[Path, str]) -> Path:
    p: Path
    if isinstance(_path, str):
        p = Path(_path)
    else:
        p = _path
    return p.expanduser().absolute()


# doesnt expose underlying kwargs from namedtuple_sequence_dump
# and namedtuple_sequence_load on purpose -- so that its less
# likely its mistyped. Can always use the underlying
# functions if you'd prefer to do that.


def dump_to(
    items: List[NT],
    path: Union[Path, str],
    attr_serializers: Optional[Dict[str, Callable[[T], PrimitiveType]]] = None,
    type_serializers: Optional[Dict[Type, Callable[[Any], PrimitiveType]]] = None,
) -> None:
    """
    Takes a list of NamedTuples (or subclasses) and a path to a file.
    Serializes the items into a string, using the attr_serializers and
    type_serializers to handle custom types if specified.

    If serialization succeeds, writes to the file.
    """
    p = _normalize(path)
    # serialize to string before opening file
    # if serialization fails, file is left alone
    nt_string: str = namedtuple_sequence_dumps(
        items, attr_serializers=attr_serializers, type_serializers=type_serializers
    )
    with p.open(mode="w") as f:
        f.write(nt_string)


# args are slightly reordered here, comapared to json.load
# to be consistent with dump_to
def load_from(
    to: Type[NT],
    path: Union[Path, str],
    attr_deserializers: Optional[Dict[str, Callable[[PrimitiveType], Any]]] = None,
    type_deserializers: Optional[Dict[Type, Callable[[PrimitiveType], Any]]] = None,
    allow_empty: bool = False,
) -> List[NT]:
    """
    Takes a type to load and a path to a file containing JSON.
    Reads from the file, and deserializes the items into a list,
    using the attr_deserializers and type_deserializers to handle
    custom types if specified.

    If allow_empty is True, this returns an empty list if the file
    didn't exist

    Returns the list of items read from the file.
    """
    p: Path = _normalize(path)
    try:
        with p.open(mode="r") as f:
            items: List[NT] = namedtuple_sequence_load(
                f,
                to,
                attr_deserializers=attr_deserializers,
                type_deserializers=type_deserializers,
            )
    except FileNotFoundError as fne:
        if allow_empty:
            return []
        else:
            raise fne
    return items


def load_prompt_and_writeback(
    to: Type[NT],
    path: Union[Path, str],
    *,
    create_file: bool = True,
    attr_validators: Optional[Dict[str, AutoHandler]] = None,
    type_validators: Optional[Dict[Type[T], AutoHandler]] = None,
    attr_use_values: Optional[Dict[str, PromptFunctionorValue]] = None,
    type_use_values: Optional[Dict[Type[T], PromptFunctionorValue]] = None,
    attr_serializers: Optional[Dict[str, Callable[[T], PrimitiveType]]] = None,
    type_serializers: Optional[Dict[Type, Callable[[Any], PrimitiveType]]] = None,
    attr_deserializers: Optional[Dict[str, Callable[[PrimitiveType], Any]]] = None,
    type_deserializers: Optional[Dict[Type, Callable[[PrimitiveType], Any]]] = None,
    prompt_function: Optional[Callable[..., NT]] = None,
) -> List[NT]:
    """
    An entry point to entire library, essentially.

    Load the NamedTuples from the JSON file specified by 'path' and 'to'

    If the file doesn't exist and the create_file flag is True, ignores the
    FileNotFound error when trying to read from the file.

    After reading the NamedTuples from the file, prompts you to add a new item,
    adds that to the list it reads in, and writes back to the file

    The 'prompt_function' overrides the default 'prompt_namedtuple', if you
    want to change how to prompt/the underling functions generated by namedtuple_prompt_funcs

    accepts validators, serializers and deserializers for each step of the process.
    """
    p: Path = _normalize(path)
    # read from file
    items: List[NT] = []
    try:
        items = load_from(to, p, attr_deserializers, type_deserializers)
    except FileNotFoundError as fne:
        if not create_file:
            raise fne
        # warn(f"File at {p} didn't exist, using empty list")
    # prompt for the new item
    chosen_prompt_function = prompt_function or prompt_namedtuple
    new_item: NT = chosen_prompt_function(
        to,
        attr_validators=attr_validators,
        type_validators=type_validators,
        attr_use_values=attr_use_values,
        type_use_values=type_use_values,
    )
    items.append(new_item)
    # dump back to file
    dump_to(items, p, attr_serializers, type_serializers)
    return items

from pathlib import Path
from typing import (
    Callable,
    Type,
    Union,
    Dict,
    List,
    Any,
    Optional,
)


from . import (
    AutoHandler,
    prompt_namedtuple,
    namedtuple_sequence_dumps,
    namedtuple_sequence_load,
)
from .typehelpers import PrimitiveType, NT, T, PromptFunctionorValue


def _normalize(_path: Union[Path, str]) -> Path:
    p: Path
    if isinstance(_path, str):
        p = Path(_path)
    else:
        p = _path
    return p.expanduser().absolute()


# doesnt expose underlying kwargs from namedtuple_sequence_dump
# and namedtuple_sequence_load on purpose -- so that its less
# likely its mistyped. Can always use the underlying
# functions if you'd prefer to do that.


def dump_to(
    items: List[NT],
    path: Union[Path, str],
    attr_serializers: Optional[Dict[str, Callable[[T], PrimitiveType]]] = None,
    type_serializers: Optional[Dict[Type, Callable[[Any], PrimitiveType]]] = None,
) -> None:
    """
    Takes a list of NamedTuples (or subclasses) and a path to a file.
    Serializes the items into a string, using the attr_serializers and
    type_serializers to handle custom types if specified.

    If serialization succeeds, writes to the file.
    """
    p = _normalize(path)
    # serialize to string before opening file
    # if serialization fails, file is left alone
    nt_string: str = namedtuple_sequence_dumps(
        items, attr_serializers=attr_serializers, type_serializers=type_serializers
    )
    with p.open(mode="w") as f:
        f.write(nt_string)


# args are slightly reordered here, comapared to json.load
# to be consistent with dump_to
def load_from(
    to: Type[NT],
    path: Union[Path, str],
    attr_deserializers: Optional[Dict[str, Callable[[PrimitiveType], Any]]] = None,
    type_deserializers: Optional[Dict[Type, Callable[[PrimitiveType], Any]]] = None,
    allow_empty: bool = False,
) -> List[NT]:
    """
    Takes a type to load and a path to a file containing JSON.
    Reads from the file, and deserializes the items into a list,
    using the attr_deserializers and type_deserializers to handle
    custom types if specified.

    If allow_empty is True, this returns an empty list if the file
    didn't exist

    Returns the list of items read from the file.
    """
    p: Path = _normalize(path)
    try:
        with p.open(mode="r") as f:
            items: List[NT] = namedtuple_sequence_load(
                f,
                to,
                attr_deserializers=attr_deserializers,
                type_deserializers=type_deserializers,
            )
    except FileNotFoundError as fne:
        if allow_empty:
            return []
        else:
            raise fne
    return items


def load_prompt_and_writeback(
    to: Type[NT],
    path: Union[Path, str],
    *,
    create_file: bool = True,
    attr_validators: Optional[Dict[str, AutoHandler]] = None,
    type_validators: Optional[Dict[Type[T], AutoHandler]] = None,
    attr_use_values: Optional[Dict[str, PromptFunctionorValue]] = None,
    type_use_values: Optional[Dict[Type[T], PromptFunctionorValue]] = None,
    attr_serializers: Optional[Dict[str, Callable[[T], PrimitiveType]]] = None,
    type_serializers: Optional[Dict[Type, Callable[[Any], PrimitiveType]]] = None,
    attr_deserializers: Optional[Dict[str, Callable[[PrimitiveType], Any]]] = None,
    type_deserializers: Optional[Dict[Type, Callable[[PrimitiveType], Any]]] = None,
    prompt_function: Optional[Callable[..., NT]] = None,
) -> List[NT]:
    """
    An entry point to entire library, essentially.

    Load the NamedTuples from the JSON file specified by 'path' and 'to'

    If the file doesn't exist and the create_file flag is True, ignores the
    FileNotFound error when trying to read from the file.

    After reading the NamedTuples from the file, prompts you to add a new item,
    adds that to the list it reads in, and writes back to the file

    The 'prompt_function' overrides the default 'prompt_namedtuple', if you
    want to change how to prompt/the underling functions generated by namedtuple_prompt_funcs

    accepts validators, serializers and deserializers for each step of the process.
    """
    p: Path = _normalize(path)
    # read from file
    items: List[NT] = []
    try:
        items = load_from(to, p, attr_deserializers, type_deserializers)
    except FileNotFoundError as fne:
        if not create_file:
            raise fne
        # warn(f"File at {p} didn't exist, using empty list")
    # prompt for the new item
    chosen_prompt_function = prompt_function or prompt_namedtuple
    new_item: NT = chosen_prompt_function(
        to,
        attr_validators=attr_validators,
        type_validators=type_validators,
        attr_use_values=attr_use_values,
        type_use_values=type_use_values,
    )
    items.append(new_item)
    # dump back to file
    dump_to(items, p, attr_serializers, type_serializers)
    return items

# doesnt expose underlying kwargs from namedtuple_sequence_dump # and namedtuple_sequence_load on purpose -- so that its less # likely its mistyped. Can always use the underlying # functions if you'd prefer to do that. Takes a list of NamedTuples (or subclasses) and a path to a file.     Serializes the items into a string, using the attr_serializers and     type_serializers to handle custom types if specified.      If serialization succeeds, writes to the file. # serialize to string before opening file # if serialization fails, file is left alone # args are slightly reordered here, comapared to json.load # to be consistent with dump_to Takes a type to load and a path to a file containing JSON.     Reads from the file, and deserializes the items into a list,     using the attr_deserializers and type_deserializers to handle     custom types if specified.      If allow_empty is True, this returns an empty list if the file     didn't exist      Returns the list of items read from the file. An entry point to entire library, essentially.      Load the NamedTuples from the JSON file specified by 'path' and 'to'      If the file doesn't exist and the create_file flag is True, ignores the     FileNotFound error when trying to read from the file.      After reading the NamedTuples from the file, prompts you to add a new item,     adds that to the list it reads in, and writes back to the file      The 'prompt_function' overrides the default 'prompt_namedtuple', if you     want to change how to prompt/the underling functions generated by namedtuple_prompt_funcs      accepts validators, serializers and deserializers for each step of the process. # read from file # warn(f"File at {p} didn't exist, using empty list") # prompt for the new item # dump back to file