#
#   Imandra Inc.
#
#   fs_sync.py
#

import logging
from pathlib import Path
from typing import cast

from ..dep_tools.python import build_py_dep_graph
from ..tools.filesystem import FileSystem
from .model import Model

log = logging.getLogger(__name__)


def model_filesystem_sync(
    models: dict[str, Model],
    src_dir_abs_path: str,
    language: str,
) -> dict[str, Model]:
    """
    Go through the source code directory and update the models' dependencies/create new ones if needed.
    """

    fs = FileSystem.from_disk(Path(src_dir_abs_path))
    py_dep = build_py_dep_graph(fs)

    # Let's look for deleted nodes
    model_paths = {str(m.relative_path) for m in py_dep.nx_graph}
    for path in list(models.keys()):
        log.info(f'Checking path {path}...')
        if path not in model_paths:
            models[path].set_src_code(None)

    # Now let's look at new nodes / update code for the existing ones
    for node in py_dep.nodes:
        rel_path = str(node.relative_path)
        if rel_path not in models:
            log.info(f'Adding model for path {rel_path}')
            models[rel_path] = Model(
                rel_path=rel_path,
                src_code=node.content,
                src_language=language,
            )
            log.info(f'Creating new model with path={node.relative_path}.')
        else:
            log.info(f'Setting source code to existing node={node.relative_path}.')
            models[rel_path].set_src_code(node.content)

    # Rebuild edges from scratch
    for m in models.values():
        m.dependencies.clear()
        m.rev_dependencies.clear()

    # Now let's add dependencies + reverse dependencies
    for p1, p2 in py_dep.nx_graph.edges:
        path1, path2 = str(p1.relative_path), str(p2.relative_path)

        if path1 not in models or path2 not in models:
            errorMsg = "Attempting to add a dependency that doesn't exist!"
            log.error(errorMsg)
            raise Exception(errorMsg)

        model1 = models[path1]  # depends-on
        model2 = models[path2]  # dependency

        if model2 not in cast(list[Model], model1.dependencies):
            model1.dependencies.append(model2)

        if model1 not in cast(list[Model], model2.rev_dependencies):
            model2.rev_dependencies.append(model1)

    return models
