"""Pydantic schemas for MASHLipidDroplet inputs.

Falls back to dataclasses if pydantic is unavailable so the MVP runs
on stdlib + pandas/numpy/scipy/matplotlib alone.
"""

from __future__ import annotations

from typing import Optional, List

try:
    from pydantic import BaseModel, Field, validator  # type: ignore

    HAS_PYDANTIC = True
except Exception:  # pragma: no cover
    HAS_PYDANTIC = False

    from dataclasses import dataclass, field

    def Field(default=None, **_kwargs):  # type: ignore
        return default

    def validator(*_a, **_kw):  # type: ignore
        def _wrap(fn):
            return fn

        return _wrap

    class BaseModel:  # type: ignore
        def __init__(self, **kwargs):
            for k, v in kwargs.items():
                setattr(self, k, v)

        def dict(self):  # noqa: A003
            return {k: getattr(self, k) for k in self.__annotations__}


# ---------------------------------------------------------------------------
# Allowed vocabularies (kept loose; we only warn on unknowns)
# ---------------------------------------------------------------------------
CELL_TYPES = {
    "primary_mouse",
    "primary_rat",
    "primary_human",
    "HepG2",
    "Huh7",
    "iPSC",
    "HepaRG",
    "spheroid",
    "organoid",
    "STAM_cryosection",
    "HFD_cryosection",
    "MCD_cryosection",
    "CDAA_HFD_cryosection",
}

STAINS = {
    "BODIPY_493_503",
    "LipidTOX",
    "NileRed",
    "OilRedO",
    "PLIN1",
    "PLIN2",
    "PLIN3",
    "PLIN5",
    "MitoTracker",
    "ER_Tracker",
    "DAPI",
}

LOADING_AGENTS = {
    "palmitate",
    "oleate",
    "FFA_mix_palm_oleic",
    "none",
}

VENDORS = {
    "Operetta_CLS",
    "ImageXpress_Micro",
    "INCell",
    "Nikon_Ti2",
    "Zeiss",
    "Leica",
}


class LDObject(BaseModel):
    """One row per lipid droplet object."""

    well: str
    cell_id: str
    ld_id: str
    area_um2: float
    diameter_um: float
    circularity: float
    max_intensity: float
    integrated_intensity: float
    distance_to_mito_um: float
    manders_m1: float
    manders_m2: float
    pearson: float
    plin1_intensity: float
    plin2_intensity: float
    plin3_intensity: float
    plin5_intensity: float


class PlateMapRow(BaseModel):
    well: str
    drug: str
    dose_uM: float
    replicate: int


class CellMetaRow(BaseModel):
    well: str
    cell_count: int
    cell_type: str


# ---------------------------------------------------------------------------
# Validators (lightweight)
# ---------------------------------------------------------------------------
def validate_ld_table(df) -> List[str]:
    """Return list of problems found in the LD object table.

    Empty list = OK.
    """

    required = [
        "well",
        "cell_id",
        "ld_id",
        "area_um2",
        "diameter_um",
        "circularity",
        "max_intensity",
        "integrated_intensity",
        "distance_to_mito_um",
        "manders_m1",
        "manders_m2",
        "pearson",
        "plin1_intensity",
        "plin2_intensity",
        "plin3_intensity",
        "plin5_intensity",
    ]
    problems: List[str] = []
    for col in required:
        if col not in df.columns:
            problems.append(f"missing column: {col}")
    if "diameter_um" in df.columns:
        if (df["diameter_um"] < 0).any():
            problems.append("negative diameters present")
    if "manders_m1" in df.columns:
        bad = ((df["manders_m1"] < 0) | (df["manders_m1"] > 1)).sum()
        if bad:
            problems.append(f"{bad} rows with manders_m1 outside [0,1]")
    return problems


def validate_plate_map(df) -> List[str]:
    required = ["well", "drug", "dose_uM", "replicate"]
    problems = []
    for col in required:
        if col not in df.columns:
            problems.append(f"plate_map missing column: {col}")
    return problems
