""" The Changelist Map.
"""
from typing import Generator
from uuid import uuid4

from changelist_sort.data.changelist_data import ChangelistData
from changelist_sort.data.list_key import ListKey, compute_key


class ChangelistMap:
    """ The Map containing all of the Changelists.
    """

    def __init__(self):
        self.mapping: dict[str, ChangelistData] = {}
        self.changelist_ids: set[str] = set[str]()

    def insert(self, changelist: ChangelistData) -> bool:
        """ Insert a Changelist into the Map.
            - Uses the Changelist Simple name as a key.

        Parameters:
        - changelist (ChangelistData): The Changelist Data to insert into the Map.

        Returns:
        bool - True when Changelist Name and Id are not already in the Map.
        """
        if changelist.id in self.changelist_ids or\
            changelist.list_key.key in self.mapping:
            return False
        self.changelist_ids.add(changelist.id)
        self.mapping[changelist.list_key.key] = changelist
        return True

    def search(self, key: str) -> ChangelistData | None:
        """ Search the Map dict for the Changelist with the given simple name.
            - Expects the Changelist Simple Name to match the key.
        """
        return self.mapping.get(key)

    def contains_id(self, id: str) -> bool:
        """ Determine whether the Map contains the given id.
        """
        return id in self.changelist_ids

    def get_lists(self) -> list[ChangelistData]:
        """ Obtain all Changelists in the Map as a List.
        """
        return list(self.mapping.values())

    def generate_lists(self) -> Generator[ChangelistData, None, None]:
        """ Generate Changelists from the Map.
        """
        for k, cl in self.mapping.items():
            yield cl

    def generate_nonempty_lists(self) -> Generator[ChangelistData, None, None]:
        """ Obtain only non-empty Changelists in the Map.
        """
        for k, cl in self.mapping.items():
            if len(cl.changes) > 0:
                yield cl

    def _generate_new_id(self) -> str:
        """ Create a new Changelist Id that does not appear in this map.
        """
        test_id: str = str(uuid4())
        while self.contains_id(test_id):
            test_id = str(uuid4())
        return test_id

    def create_changelist(self, cl_name: ListKey | str) -> ChangelistData:
        """ Create a new empty Changelist with a new Id, and insert it into the Map.

        Parameters:
        - name (str): The Name of the new Changelist, or the ListKey.

        Returns:
        ChangelistData - The Changelist that was recently created and added to the Map.
        """
        if isinstance(cl_name, ListKey):
            cl_key = cl_name
        elif isinstance(cl_name, str):
            cl_key = compute_key(cl_name)
        else:
            raise TypeError
        new_cl = ChangelistData(
            id=self._generate_new_id(),
            list_key=cl_key,
            changes=[],
            comment='',
            is_default=False,
        )
        if self.insert(new_cl):
            return new_cl
        if (existing_cl := self.search(new_cl.list_key.key)) is not None:
            return existing_cl
        exit(f"Failed to create new Changelist(name={new_cl.list_key.changelist_name})")
