from .imports import *

# ---------------------------------------------------------------------
# Model registry — manifest is the single source of truth.
# No code-defined MODELS; every row comes from model_manifest.json.
# ---------------------------------------------------------------------


def assess_config(cls, values):
    """Build cls from values if it can be a valid instance, else False.

    Required = fields the dataclass defines with no default. Unknown keys are
    dropped, absent optionals filled from defaults. Never raises.

    tasks is special-cased: manifest rows carry primary_task/task but no
    tasks list, so we synthesize one before the required-field check — else
    every row fails and the registry comes back empty.
    """
    values = dict(values)   # local copy; we may inject tasks

    # Synthesize tasks from primary_task/task so manifest rows validate.
    if not values.get("tasks"):
        pt = values.get("primary_task") or values.get("task")
        if pt:
            values["tasks"] = [pt]
            values.setdefault("primary_task", pt)

    flds = {f.name: f for f in fields(cls)}

    for f in flds.values():
        required = f.default is MISSING and f.default_factory is MISSING
        if required and values.get(f.name) in (None, "", []):
            return False

    out = {}
    for name, f in flds.items():
        if name in values:
            out[name] = values[name]
        elif f.default is not MISSING:
            out[name] = f.default
        else:
            out[name] = f.default_factory()
    return cls(**out)


def get_model_values(config, dict_return=False, return_dict=False):
    if dict_return or return_dict:
        return config.to_dict()
    return config


def get_assessed_model_config(values, dict_return=False, return_dict=False):
    assessed = assess_config(ModelConfig, values)
    if assessed is False:
        return False
    return get_model_values(assessed, dict_return=dict_return, return_dict=return_dict)


def update_model_config_dict(model_key=None, values=None, dict_obj=None,
                             dict_return=False, return_dict=False, key=None):
    dict_obj = dict_obj if dict_obj is not None else {}
    model_key = model_key or key
    values = dict(values or {})
    values["model_key"] = model_key

    config = get_assessed_model_config(values, dict_return=dict_return, return_dict=return_dict)
    if config is False:                  # incomplete -> not attached
        logger.warning("registry: dropping incomplete row %r", model_key)
        return dict_obj

    dict_obj[model_key] = config
    return dict_obj


def get_models_raw_dict(models_dict_path=None):
    models_dict_path = models_dict_path or MODELS_DICT_PATH
    if models_dict_path and os.path.isfile(models_dict_path):
        return safe_load_from_json(models_dict_path) or {}
    return {}


def get_models_dict(models_dict_path=None, dict_return=False, return_dict=False):
    dict_return = dict_return or return_dict
    overlay = get_models_raw_dict(models_dict_path=models_dict_path)
    nudict = {}
    for model_key, values in overlay.items():
        nudict = update_model_config_dict(
            model_key=model_key, values=values, dict_obj=nudict, dict_return=dict_return
        )
    return nudict


MODEL_REGISTRY: Dict[str, ModelConfig] = get_models_dict()
MODEL_REGISTRY_DICT: Dict[str, dict] = get_models_dict(dict_return=True)


def get_model_registry(dict_return=False, return_dict=False) -> "ModelConfig | dict":
    dict_return = dict_return or return_dict
    return MODEL_REGISTRY_DICT if dict_return else MODEL_REGISTRY
