import os
from pathlib import Path
from typing import List,Dict, Optional

from get_files import getTemplateFileNames
from speedbuild.frameworks.django.utils.var_utils import get_assigned_variables
from tools.read_file import read_file

from pydantic import BaseModel

class Chunk(BaseModel):
    name: str
    code: str
    dependencies: Optional[List[str]]

def load_all_chunks(files: List[str]) -> Dict[str, Chunk]:
    all_chunks = []

    for path in files:
        raw = read_file(path)

        for item in raw:

            # cid = item['name']

            # Create a globally unique ID for the chunk
            # global_id = f"{path}:::{cid}"

            all_chunks.append({"source":path, **item})

    return all_chunks

def sortChunks(chunks):
    processed = set()
    arranged = []
    prev_batch = []
    cycles = []

    while len(chunks) > 0:
        process_next_batch = []
        for chunk in chunks:
            # print("processing ",chunk['name'],"\n")
            if "dependencies" not in chunk.keys():
                arranged.append(chunk)
                continue
            
            add_to_process = True
            for dep in chunk['dependencies']:
                dep_source = dep['source']

                if dep_source in [".",".sb_utils",".sb_app"] and dep['name'] not in processed:
                    add_to_process = False
                    print(dep,"stuck")
                    chunk_name = get_assigned_variables(chunk['code'],True)
                    print(chunk_name,"\n\n")
                    break

            if add_to_process:
                arranged.append(chunk)
                chunk_name = get_assigned_variables(chunk['code'],True)
                print(chunk_name)
                processed.add(chunk_name)
            else:
                process_next_batch.append(chunk)

        # if prev_batch == process_next_batch:
        #     # cycle detected
        #     start = process_next_batch.pop(0)
        #     end = process_next_batch.
        #     pass

        chunks = process_next_batch

        # print("proceesed this batch ",processed)
    return arranged


def sortChunks(chunks):
    """
    Sorts a collection of code "chunks" into an order that satisfies declared dependencies.

    The function implements a simple dependency resolution pass: on each iteration it selects
    chunks whose dependencies have all been "processed" and appends them to the output order.
    A chunk is considered "processed" when an assigned name can be determined from its code
    (using get_assigned_variables(chunk["code"], True)); that name (if a single string) is
    recorded and used to satisfy other chunks' dependency checks. If get_assigned_variables
    returns a set (multiple assignments) it is treated as no single assigned name (i.e. None).

    Args:
        chunks (list of dict): A list of chunk dictionaries. Each chunk is expected to contain
            at least the keys:
                - "name": a unique identifier for the chunk (not directly used for ordering
                  by the core algorithm, but typically present),
                - "code": source code string passed to get_assigned_variables(...),
                - "dependencies": a list of dependency descriptors; each dependency is expected
                  to be a mapping containing a "name" key whose value is the name of another chunk.
            Example chunk: {"name": "A", "code": "x = ...", "dependencies": [{"name": "B"}]}

    Returns:
        list of dict: A new list containing the same chunk dictionaries arranged in an order
        intended to satisfy dependencies (i.e. a chunk appears after all chunks whose names
        are listed in its "dependencies" and were successfully detected as processed).

    Side effects:
        - Prints the detected assigned name (from get_assigned_variables) for each chunk when it
          is appended to the arranged output.
        - Calls get_assigned_variables(chunk["code"], True) for each chunk to infer assignment name.

    Behavior and notes:
        - The algorithm is iterative: on each pass it collects chunks whose dependencies are
          already processed and moves them into the output. If an entire pass makes no progress,
          the remaining chunks are assumed to be in a circular dependency and are appended
          to the output as-is to break the cycle.
        - Dependency entries are expected to be mappings with a "name" key (the code checks
          dep["name"] against the processed set).
        - If a chunk's inferred assigned name is a set (multiple assignments) it is not added
          to the processed set and so will not satisfy other chunks' dependencies by name.
        - Important implementation note: during the cycle-break path the code attempts to
          add names of the remaining chunks to the processed set, but the variable used to do
          so may be incorrectly scoped/retained from an earlier loop iteration. This can cause
          incorrect entries (or missed entries) in the processed set; treat this as a potential
          bug if strict correctness on circular dependency handling is required.

    Examples:
        - Use this function when you have a collection of interdependent code chunks (e.g.
          generated code blocks) and you need a best-effort linear order that places providers
          before consumers. It does not perform full topological sorting with robust cycle
          resolution — remaining cycles are appended in arbitrary order.
    """
    processed = set()
    arranged = []


    while len(chunks) > 0:
        next_round = []
        made_progress = False

        for chunk in chunks:
            cname = get_assigned_variables(chunk["code"], True)

            if isinstance(cname,set):
                cname = None

            deps = chunk.get("dependencies", [])

            # Check whether all dependencies are processed
            all_done = all(dep["name"] in processed for dep in deps)

            if all_done:
                arranged.append(chunk)
 
                if cname:
                    processed.add(cname)
                made_progress = True
            else:
                next_round.append(chunk)

        # ---- BREAK CYCLE ----
        if not made_progress:
            # Any remaining chunks are stuck in a circular dependency.
            # Process them anyway (but only once).
            arranged.extend(next_round)
            for chunk in next_round:
                if cname:
                    processed.add(cname)
            break

        chunks = next_round

    return arranged


home = str(Path.home())
root_address = os.path.join(home,".sb_zip","speed_build_markConsultationAsPaid","lts")

files = getTemplateFileNames(root_address)

data = load_all_chunks(files)
arranged = sortChunks(data)

# # print(arranged)

# for i in arranged:
#     print(i)
#     print("\n","#"*30)