from __future__ import annotations

from typing import TYPE_CHECKING, List, Optional, Tuple, overload

import numpy as np
from django.db import models
from django.db.models import CASCADE, PROTECT
from lamin_utils import logger
from lnschema_core.models import (
    Artifact,
    CanValidate,
    Feature,
    FeatureSet,
    HasParents,
    LinkORM,
    Record,
    TracksRun,
    TracksUpdates,
)

import bionty.base as bionty_base

from . import ids
from ._bionty import encode_uid, lookup2kwargs

if TYPE_CHECKING:
    from pandas import DataFrame

    from .base import PublicOntology


class StaticReference:
    def __init__(self, source_record: Source) -> None:
        self._source = source_record

    def df(self) -> DataFrame:
        return self._source.dataframe_artifact.load()


class BioRecord(Record, HasParents, CanValidate):
    """Base Record of bionty.

    BioRecord inherits all methods from :class:`~lamindb.core.Record` and provides additional methods
    including :meth:`~bionty.core.BioRecord.public` and :meth:`~bionty.core.BioRecord.from_source`.

    Notes:
        For more info, see tutorials:

        - :doc:`docs:bionty`
        - :doc:`docs:bio-registries`
    """

    class Meta:
        abstract = True

    def __init__(self, *args, **kwargs):
        # DB-facing constructor
        if len(args) == len(self._meta.concrete_fields):
            super().__init__(*args, **kwargs)
            return None

        # passing lookup result from bionty, which is a Tuple or List
        if (
            args
            and len(args) == 1
            and isinstance(args[0], (Tuple, List))
            and len(args[0]) > 0
        ):
            if isinstance(args[0], List) and len(args[0]) > 1:
                logger.warning(
                    "multiple lookup/search results are passed, only returning record from the first entry"
                )
            result = lookup2kwargs(self, *args, **kwargs)  # type:ignore
            # exclude "parents" from query arguments
            query_kwargs = {k: v for k, v in result.items() if k != "parents"}
            existing_record = self.__class__.filter(**query_kwargs).one_or_none()
            if existing_record is not None:
                from lamindb._record import init_self_from_db

                init_self_from_db(self, existing_record)
                return None
            else:
                kwargs = result  # result already has encoded id
                args = ()
        # all other cases require encoding the id
        else:
            kwargs = encode_uid(orm=self, kwargs=kwargs)

        # raise error if no organism is passed
        if hasattr(self.__class__, "organism_id"):
            if kwargs.get("organism") is None and kwargs.get("organism_id") is None:
                from .core._settings import settings

                if settings.organism is not None:
                    kwargs["organism"] = settings.organism
                else:
                    raise RuntimeError("please pass a organism!")
            elif kwargs.get("organism") is not None:
                if not isinstance(kwargs.get("organism"), Organism):
                    raise TypeError("organism must be a `bionty.Organism` record")

        # now continue with the user-facing constructor
        # set the direct parents as a private attribute
        # this is a list of strings that store the ontology id
        if "parents" in kwargs:
            parents = kwargs.pop("parents")
            # this checks if we receive a np.ndarray from pandas
            if isinstance(parents, (list, np.ndarray)) and len(parents) > 0:
                if not isinstance(parents[0], str):
                    raise ValueError(
                        "not a valid parents kwarg, got to be list of ontology ids"
                    )
                self._parents = parents

        super().__init__(*args, **kwargs)

    @classmethod
    def list_source(
        cls,
        currently_used: bool | None = None,
        in_db: bool | None = None,
        organism: str | None = None,
    ) -> Source:
        """Default source for the registry.

        Args:
            currently_used: Only returns currently used sources

        Examples:
            >>> bionty.Gene.list_source()
            >>> bionty.Gene.list_source(currently_used=True)
        """
        filters = {}
        if currently_used is not None:
            filters["currently_used"] = currently_used
        if in_db is not None:
            filters["in_db"] = in_db
        if organism is not None:
            filters["organism"] = organism  # type:ignore
        return Source.filter(entity=cls.__get_name_with_schema__(), **filters)

    @classmethod
    def import_from_source(
        cls,
        source: Source | None = None,
        ontology_ids: list[str] | None = None,
        organism: str | Record | None = None,
        ignore_conflicts: bool = True,
        update: bool = False,
    ):
        """Bulk save records from a dataframe.

        Use this method to initialize your registry with public ontology.

        Args:
            ontology_ids: List of ontology ids to save
            organism: Organism record or name
            source: Source record
            ignore_conflicts: Ignore conflicts during bulk create

        Examples:
            >>> bionty.CellType.import_from_source()
        """
        from .core._add_ontology import add_ontology_from_df, check_source_in_db

        if hasattr(cls, "ontology_id"):
            add_ontology_from_df(
                registry=cls,
                ontology_ids=ontology_ids,
                organism=organism,
                source=source,
                ignore_conflicts=ignore_conflicts,
                update=update,
            )
        else:
            import lamindb as ln

            from ._bionty import get_source_record

            public = cls.public(organism=organism, source=source)
            # TODO: consider StaticReference
            source_record = get_source_record(public)  # type:ignore
            df = public.df().reset_index()
            if hasattr(cls, "_ontology_id_field"):
                field = cls._ontology_id_field
            else:
                raise NotImplementedError(
                    f"import_from_source is not implemented for {cls.__name__}"
                )
            records = cls.from_values(
                ontology_ids or df[field],
                field=field,
                organism=organism,
                source=source_record,
            )
            ln.save(records, ignore_conflicts=ignore_conflicts)

            # make sure source.in_db is correctly set based on the DB content
            check_source_in_db(registry=cls, source=source_record, update=update)

    @classmethod
    def public(
        cls,
        organism: str | Record | None = None,
        source: Source | None = None,
    ) -> PublicOntology | StaticReference:
        """The corresponding PublicOntology object.

        Note that the source is auto-configured and tracked via :meth:`bionty.Source`.

        See Also:
            `PublicOntology <https://lamin.ai/docs/public-ontologies>`__

        Examples:
            >>> celltype_pub = bionty.CellType.public()
            >>> celltype_pub
            PublicOntology
            Entity: CellType
            Organism: all
            Source: cl, 2023-04-20
            #terms: 2698
        """
        if isinstance(organism, Organism):
            organism = organism.name

        if source is not None:
            organism = source.organism
            source_name = source.name
            version = source.version
        else:
            from .core._settings import settings

            if hasattr(cls, "organism_id"):
                if organism is None and settings.organism is not None:
                    organism = settings.organism.name
            source_name = None
            version = None

        try:
            return getattr(bionty_base, cls.__name__)(
                organism=organism, source=source_name, version=version
            )
        except (AttributeError, ValueError):
            return StaticReference(source)

    @classmethod
    def from_source(
        cls, *, mute: bool = False, **kwargs
    ) -> BioRecord | list[BioRecord] | None:
        """Create a record or records from source based on a single field value.

        Notes:
            For more info, see tutorial :doc:`docs:bionty`

            Bulk create protein records via :meth:`~docs:lamindb.core.Record.from_values`.

        Examples:
            Create a record by passing a field value:

            >>> record = bionty.Gene.from_source(symbol="TCF7", organism="human")

            Create a record from non-default source:

            >>> source = bionty.Source.filter(entity="CellType", source="cl", version="2022-08-16").one()  # noqa
            >>> record = bionty.CellType.from_source(name="T cell", source=source)

        """
        # non-relationship kwargs
        kv = {
            k: v
            for k, v in kwargs.items()
            if k not in [i.name for i in cls._meta.fields if i.is_relation]
        }
        if len(kv) > 1:
            raise AssertionError(
                "Only one field can be passed to generate record from public reference"
            )
        elif len(kv) == 0:
            return None
        else:
            k = next(iter(kv))
            v = kwargs.pop(k)
            results = cls.from_values([v], field=getattr(cls, k), mute=mute, **kwargs)
            if len(results) == 1:
                return results[0]
            elif len(results) == 0:
                return None
            else:
                return results

    # deprecated
    @classmethod
    def from_public(cls, *args, **kwargs) -> BioRecord | list[BioRecord] | None:
        """Create a record or records from public reference based on a single field value.

        Notes:
            For more info, see tutorial :doc:`docs:bionty`

            Bulk create protein records via :meth:`~docs:lamindb.core.Record.from_values`.

        Examples:
            Create a record by passing a field value:

            >>> record = bionty.Gene.from_public(symbol="TCF7", organism="human")

        """
        logger.warning("`.from_public()` is deprecated, use `.from_source()`!'")
        return cls.from_source(*args, **kwargs)

    def save(self, *args, **kwargs) -> BioRecord:
        """Save the record and its parents recursively."""
        super().save(*args, **kwargs)
        # saving records of parents
        if hasattr(self, "_parents"):
            import lamindb as ln

            # here parents is still a list of ontology ids
            parents = self._parents
            # bulk create parent records
            parents_records = self.from_values(
                parents, self.__class__.ontology_id, source=self.source
            )
            ln.save(parents_records)
            self.parents.set(parents_records)

        return self


class Organism(BioRecord, TracksRun, TracksUpdates):
    """Organism - `NCBI Taxonomy <https://www.ncbi.nlm.nih.gov/taxonomy/>`__, `Ensembl Organism <https://useast.ensembl.org/info/about/species.html>`__.

    Notes:
        For more info, see tutorials :doc:`docs:bio-registries` and :doc:`docs:organism`.


    Examples:
        >>> record = bionty.Organism.from_source(name="rabbit")
    """

    class Meta(BioRecord.Meta, TracksRun.Meta, TracksUpdates.Meta):
        abstract = False

    _name_field: str = "name"
    _ontology_id_field: str = "ontology_id"

    id: int = models.AutoField(primary_key=True)
    """Internal id, valid only in one DB instance."""
    uid: str = models.CharField(unique=True, max_length=8, default=ids.ontology)
    """A universal id (hash of selected field)."""
    name: str = models.CharField(
        max_length=64, db_index=True, default=None, unique=True
    )
    """Name of a organism, required field."""
    ontology_id: str | None = models.CharField(
        max_length=32, unique=True, db_index=True, null=True, default=None
    )
    """NCBI Taxon ID."""
    scientific_name: str | None = models.CharField(
        max_length=64, db_index=True, unique=True, null=True, default=None
    )
    """Scientific name of a organism."""
    synonyms: str | None = models.TextField(null=True, default=None)
    """Bar-separated (|) synonyms that correspond to this organism."""
    description: str | None = models.TextField(null=True, default=None)
    """Description of the organism."""
    parents: Organism = models.ManyToManyField(
        "self", symmetrical=False, related_name="children"
    )
    """Parent organism records."""
    source: Source = models.ForeignKey(
        "Source", PROTECT, null=True, related_name="organisms"
    )
    """:class:`~bionty.Source` this record associates with."""
    artifacts: Artifact = models.ManyToManyField(
        Artifact, through="ArtifactOrganism", related_name="organisms"
    )
    """Artifacts linked to the organism."""

    @overload
    def __init__(
        self,
        name: str,
        taxon_id: str | None,
        scientific_name: str | None,
    ): ...

    @overload
    def __init__(
        self,
        *db_args,
    ): ...

    def __init__(
        self,
        *args,
        **kwargs,
    ):
        super().__init__(*args, **kwargs)


class Gene(BioRecord, TracksRun, TracksUpdates):
    """Genes - `Ensembl <https://ensembl.org/>`__, `NCBI Gene <https://www.ncbi.nlm.nih.gov/gene/>`__.

    Notes:
        For more info, see tutorials :doc:`docs:bio-registries` and :doc:`docs:gene`.

        Bulk create Gene records via :meth:`~docs:lamindb.core.Record.from_values`.

    Examples:
        >>> record = bionty.Gene.from_source(symbol="TCF7", organism="human")
    """

    class Meta(BioRecord.Meta, TracksRun.Meta, TracksUpdates.Meta):
        abstract = False

    _name_field: str = "symbol"
    _ontology_id_field: str = "ensembl_gene_id"

    id: int = models.AutoField(primary_key=True)
    """Internal id, valid only in one DB instance."""
    uid: str = models.CharField(unique=True, max_length=12, default=ids.gene)
    """A universal id (hash of selected field)."""
    symbol: str | None = models.CharField(
        max_length=64, db_index=True, null=True, default=None
    )
    """A unique short form of gene name."""
    stable_id: str | None = models.CharField(
        max_length=64, db_index=True, null=True, default=None, unique=True
    )
    """Stable ID of a gene that doesn't have ensembl_gene_id, e.g. a yeast gene."""
    ensembl_gene_id: str | None = models.CharField(
        max_length=64, db_index=True, null=True, default=None, unique=True
    )
    """Ensembl gene stable ID, in the form ENS[organism prefix][feature type prefix][a unique eleven digit number]."""
    ncbi_gene_ids: str | None = models.TextField(null=True, default=None)
    """Bar-separated (|) NCBI Gene IDs that correspond to this Ensembl Gene ID.
    NCBI Gene ID, also known as Entrez Gene ID, in the form of numeric string, 1 to 9 digits.
    """
    biotype: str | None = models.CharField(
        max_length=64, db_index=True, null=True, default=None
    )
    """Type of the gene."""
    synonyms: str | None = models.TextField(null=True, default=None)
    """Bar-separated (|) synonyms that correspond to this gene."""
    description: str | None = models.TextField(null=True, default=None)
    """Description of the gene."""
    organism: Organism = models.ForeignKey(
        Organism, PROTECT, default=None, related_name="genes"
    )
    """:class:`~bionty.Organism` this gene associates with."""
    source: Source = models.ForeignKey(
        "Source", PROTECT, null=True, related_name="genes"
    )
    """:class:`~bionty.Source` this gene associates with."""
    artifacts: Artifact = models.ManyToManyField(
        Artifact, through="ArtifactGene", related_name="genes"
    )
    """Artifacts linked to the gene."""
    feature_sets: FeatureSet = models.ManyToManyField(
        FeatureSet, through="FeatureSetGene", related_name="genes"
    )
    """Featuresets linked to this gene."""

    @overload
    def __init__(
        self,
        symbol: str | None,
        stable_id: str | None,
        ensembl_gene_id: str | None,
        ncbi_gene_ids: str | None,
        biotype: str | None,
        description: str | None,
        synonyms: str | None,
        organism: Organism | None,
        source: Source | None,
    ): ...

    @overload
    def __init__(
        self,
        *db_args,
    ): ...

    def __init__(
        self,
        *args,
        **kwargs,
    ):
        super().__init__(*args, **kwargs)


class Protein(BioRecord, TracksRun, TracksUpdates):
    """Proteins - `Uniprot <https://www.uniprot.org/>`__.

    Notes:
        For more info, see tutorials :doc:`docs:bio-registries` and :doc:`docs:protein`.

        Bulk create Protein records via :meth:`~docs:lamindb.core.Record.from_values`.

    Examples:
        >>> record = bionty.Protein.from_source(name="Synaptotagmin-15B", organism="human")
        >>> record = bionty.Protein.from_source(gene_symbol="SYT15B", organism="human")
    """

    class Meta(BioRecord.Meta, TracksRun.Meta, TracksUpdates.Meta):
        abstract = False

    _name_field: str = "name"
    _ontology_id_field: str = "uniprotkb_id"

    id: int = models.AutoField(primary_key=True)
    """Internal id, valid only in one DB instance."""
    uid: str = models.CharField(unique=True, max_length=12, default=ids.protein)
    """A universal id (hash of selected field)."""
    name: str | None = models.CharField(
        max_length=256, db_index=True, null=True, default=None
    )
    """Unique name of a protein."""
    uniprotkb_id: str | None = models.CharField(
        max_length=10, db_index=True, null=True, default=None, unique=True
    )
    """UniProt protein ID, 6 alphanumeric characters, possibly suffixed by 4 more."""
    synonyms: str | None = models.TextField(null=True, default=None)
    """Bar-separated (|) synonyms that correspond to this protein."""
    description: str | None = models.TextField(null=True, default=None)
    """Description of the protein."""
    length: int | None = models.BigIntegerField(db_index=True, null=True)
    """Length of the protein sequence."""
    gene_symbol: str | None = models.CharField(
        max_length=256, db_index=True, null=True, default=None
    )
    """The primary gene symbol corresponds to this protein."""
    ensembl_gene_ids: str | None = models.TextField(null=True, default=None)
    """Bar-separated (|) Ensembl Gene IDs that correspond to this protein."""
    organism: Organism = models.ForeignKey(
        Organism, PROTECT, default=None, related_name="proteins"
    )
    """:class:`~bionty.Organism` this protein associates with."""
    source: Source = models.ForeignKey(
        "Source", PROTECT, null=True, related_name="proteins"
    )
    """:class:`~bionty.Source` this protein associates with."""
    artifacts: Artifact = models.ManyToManyField(
        Artifact, through="ArtifactProtein", related_name="proteins"
    )
    """Artifacts linked to the protein."""
    feature_sets: FeatureSet = models.ManyToManyField(
        FeatureSet, through="FeatureSetProtein", related_name="proteins"
    )
    """Featuresets linked to this protein."""

    @overload
    def __init__(
        self,
        name: str | None,
        uniprotkb_id: str | None,
        synonyms: str | None,
        length: int | None,
        gene_symbol: str | None,
        ensembl_gene_ids: str | None,
        organism: Organism | None,
        source: Source | None,
    ): ...

    @overload
    def __init__(
        self,
        *db_args,
    ): ...

    def __init__(
        self,
        *args,
        **kwargs,
    ):
        super().__init__(*args, **kwargs)


class CellMarker(BioRecord, TracksRun, TracksUpdates):
    """Cell markers - `CellMarker <http://xteam.xbio.top/CellMarker>`__.

    Notes:
        For more info, see tutorials :doc:`docs:bio-registries` and :doc:`docs:cell_marker`.

        Bulk create CellMarker records via :meth:`~docs:lamindb.core.Record.from_values`.

    Examples:
        >>> record = bionty.CellMarker.from_source(name="PD1", organism="human")
    """

    class Meta(BioRecord.Meta, TracksRun.Meta, TracksUpdates.Meta):
        abstract = False
        unique_together = (("name", "organism"),)

    _name_field: str = "name"
    _ontology_id_field: str = "name"

    id: int = models.AutoField(primary_key=True)
    """Internal id, valid only in one DB instance."""
    uid: str = models.CharField(unique=True, max_length=12, default=ids.cellmarker)
    """A universal id (hash of selected field)."""
    name: str = models.CharField(max_length=64, db_index=True)
    """Unique name of the cell marker."""
    synonyms: str | None = models.TextField(null=True, default=None)
    """Bar-separated (|) synonyms that correspond to this cell marker."""
    description: str | None = models.TextField(null=True, default=None)
    """Description of the cell marker."""
    gene_symbol: str | None = models.CharField(
        max_length=64, db_index=True, null=True, default=None
    )
    """Gene symbol that corresponds to this cell marker."""
    ncbi_gene_id: str | None = models.CharField(
        max_length=32, db_index=True, null=True, default=None
    )
    """NCBI gene id that corresponds to this cell marker."""
    uniprotkb_id: str | None = models.CharField(
        max_length=10, db_index=True, null=True, default=None
    )
    """Uniprotkb id that corresponds to this cell marker."""
    organism: Organism = models.ForeignKey(
        Organism, PROTECT, default=None, related_name="cell_markers"
    )
    """:class:`~bionty.Organism` this cell marker associates with."""
    source: Source = models.ForeignKey(
        "Source", PROTECT, null=True, related_name="cell_markers"
    )
    """:class:`~bionty.Source` this cell marker associates with."""
    artifacts: Artifact = models.ManyToManyField(
        Artifact,
        through="ArtifactCellMarker",
        related_name="cell_markers",
    )
    """Artifacts linked to the cell marker."""
    feature_sets: FeatureSet = models.ManyToManyField(
        FeatureSet, through="FeatureSetCellMarker", related_name="cell_markers"
    )
    """Featuresets linked to this cell marker."""

    @overload
    def __init__(
        self,
        name: str,
        synonyms: str | None,
        gene_symbol: str | None,
        ncbi_gene_id: str | None,
        uniprotkb_id: str | None,
        organism: Organism | None,
        source: Source | None,
    ): ...

    @overload
    def __init__(
        self,
        *db_args,
    ): ...

    def __init__(
        self,
        *args,
        **kwargs,
    ):
        super().__init__(*args, **kwargs)


class Tissue(BioRecord, TracksRun, TracksUpdates):
    """Tissues - `Uberon <http://obophenotype.github.io/uberon/>`__.

    Notes:
        For more info, see tutorials :doc:`docs:bio-registries` :doc:`docs:tissue`.

        Bulk create Tissue records via :meth:`~docs:lamindb.core.Record.from_values`.

    Examples:
        >>> record = bionty.Tissue.from_source(name="brain")
    """

    class Meta(BioRecord.Meta, TracksRun.Meta, TracksUpdates.Meta):
        abstract = False
        unique_together = (("name", "ontology_id"),)

    _name_field: str = "name"
    _ontology_id_field: str = "ontology_id"

    id: int = models.AutoField(primary_key=True)
    """Internal id, valid only in one DB instance."""
    uid: str = models.CharField(unique=True, max_length=8, default=ids.ontology)
    """A universal id (hash of selected field)."""
    name: str = models.CharField(max_length=256, db_index=True)
    """Name of the tissue."""
    ontology_id: str | None = models.CharField(
        max_length=32, db_index=True, null=True, default=None
    )
    """Ontology ID of the tissue."""
    abbr: str | None = models.CharField(
        max_length=32, db_index=True, unique=True, null=True, default=None
    )
    """A unique abbreviation of tissue."""
    synonyms: str | None = models.TextField(null=True, default=None)
    """Bar-separated (|) synonyms that correspond to this tissue."""
    description: str | None = models.TextField(null=True, default=None)
    """Description of the tissue."""
    parents: Tissue = models.ManyToManyField(
        "self", symmetrical=False, related_name="children"
    )
    """Parent tissues records."""
    source: Source = models.ForeignKey(
        "Source", PROTECT, null=True, related_name="tissues"
    )
    """:class:`~bionty.Source` this tissue associates with."""
    artifacts: Artifact = models.ManyToManyField(
        Artifact, through="ArtifactTissue", related_name="tissues"
    )
    """Artifacts linked to the tissue."""

    @overload
    def __init__(
        self,
        name: str,
        ontology_id: str | None,
        abbr: str | None,
        synonyms: str | None,
        description: str | None,
        parents: list[Tissue],
        source: Source | None,
    ): ...

    @overload
    def __init__(
        self,
        *db_args,
    ): ...

    def __init__(
        self,
        *args,
        **kwargs,
    ):
        super().__init__(*args, **kwargs)


class CellType(BioRecord, TracksRun, TracksUpdates):
    """Cell types - `Cell Ontology <https://obophenotype.github.io/cell-ontology/>`__.

    Notes:
        For more info, see tutorials :doc:`docs:bio-registries` and :doc:`docs:cell_type`.

        Bulk create CellType records via :meth:`~docs:lamindb.core.Record.from_values`.

    Examples:
        >>> record = bionty.CellType.from_source(name="T cell")
    """

    class Meta(BioRecord.Meta, TracksRun.Meta, TracksUpdates.Meta):
        abstract = False
        unique_together = (("name", "ontology_id"),)

    _name_field: str = "name"
    _ontology_id_field: str = "ontology_id"

    id: int = models.AutoField(primary_key=True)
    """Internal id, valid only in one DB instance."""
    uid: str = models.CharField(unique=True, max_length=8, default=ids.ontology)
    """A universal id (hash of selected field)."""
    name: str = models.CharField(max_length=256, db_index=True)
    """Name of the cell type."""
    ontology_id: str | None = models.CharField(
        max_length=32, db_index=True, null=True, default=None
    )
    """Ontology ID of the cell type."""
    abbr: str | None = models.CharField(
        max_length=32, db_index=True, unique=True, null=True, default=None
    )
    """A unique abbreviation of cell type."""
    synonyms: str | None = models.TextField(null=True, default=None)
    """Bar-separated (|) synonyms that correspond to this cell type."""
    description: str | None = models.TextField(null=True, default=None)
    """Description of the cell type."""
    parents: CellType = models.ManyToManyField(
        "self", symmetrical=False, related_name="children"
    )
    """Parent cell type records."""
    source: Source = models.ForeignKey(
        "Source", PROTECT, null=True, related_name="cell_types"
    )
    """:class:`~bionty.Source` this cell type associates with."""
    artifacts: Artifact = models.ManyToManyField(
        Artifact, through="ArtifactCellType", related_name="cell_types"
    )
    """Artifacts linked to the cell type."""

    @overload
    def __init__(
        self,
        name: str,
        ontology_id: str | None,
        abbr: str | None,
        synonyms: str | None,
        description: str | None,
        parents: list[CellType],
        source: Source | None,
    ): ...

    @overload
    def __init__(
        self,
        *db_args,
    ): ...

    def __init__(
        self,
        *args,
        **kwargs,
    ):
        super().__init__(*args, **kwargs)


class Disease(BioRecord, TracksRun, TracksUpdates):
    """Diseases - `Mondo <https://mondo.monarchinitiative.org/>`__, `Human Disease <https://disease-ontology.org/>`__.

    Notes:
        Bulk create Disease records via :meth:`~docs:lamindb.core.Record.from_values`.

        For more info, see tutorials: :doc:`docs:disease`.

    Examples:
        >>> record = bionty.Disease.from_source(name="Alzheimer disease")
    """

    class Meta(BioRecord.Meta, TracksRun.Meta, TracksUpdates.Meta):
        abstract = False
        unique_together = (("name", "ontology_id"),)

    _name_field: str = "name"
    _ontology_id_field: str = "ontology_id"

    id: int = models.AutoField(primary_key=True)
    """Internal id, valid only in one DB instance."""
    uid: str = models.CharField(unique=True, max_length=8, default=ids.ontology)
    """A universal id (hash of selected field)."""
    name: str = models.CharField(max_length=256, db_index=True)
    """Name of the disease."""
    ontology_id: str | None = models.CharField(
        max_length=32, db_index=True, null=True, default=None
    )
    """Ontology ID of the disease."""
    abbr: str | None = models.CharField(
        max_length=32, db_index=True, unique=True, null=True, default=None
    )
    """A unique abbreviation of disease."""
    synonyms: str | None = models.TextField(null=True, default=None)
    """Bar-separated (|) synonyms that correspond to this disease."""
    description: str | None = models.TextField(null=True, default=None)
    """Description of the disease."""
    parents: Disease = models.ManyToManyField(
        "self", symmetrical=False, related_name="children"
    )
    """Parent disease records."""
    source: Source = models.ForeignKey(
        "Source", PROTECT, null=True, related_name="diseases"
    )
    """:class:`~bionty.Source` this disease associates with."""
    artifacts: Artifact = models.ManyToManyField(
        Artifact, through="ArtifactDisease", related_name="diseases"
    )
    """Artifacts linked to the disease."""

    @overload
    def __init__(
        self,
        name: str,
        ontology_id: str | None,
        abbr: str | None,
        synonyms: str | None,
        description: str | None,
        parents: list[Disease],
        source: Source | None,
    ): ...

    @overload
    def __init__(
        self,
        *db_args,
    ): ...

    def __init__(
        self,
        *args,
        **kwargs,
    ):
        super().__init__(*args, **kwargs)


class CellLine(BioRecord, TracksRun, TracksUpdates):
    """Cell lines - `Cell Line Ontology <https://github.com/CLO-ontology/CLO>`__.

    Notes:
        For more info, see tutorials :doc:`docs:bio-registries` and :doc:`docs:cell_line`.

        Bulk create CellLine records via :meth:`~docs:lamindb.core.Record.from_values`.

    Examples:
        >>> standard_name = bionty.CellLine.public().standardize(["K562"])[0]
        >>> record = bionty.CellLine.from_source(name=standard_name)
    """

    class Meta(BioRecord.Meta, TracksRun.Meta, TracksUpdates.Meta):
        abstract = False
        unique_together = (("name", "ontology_id"),)

    _name_field: str = "name"
    _ontology_id_field: str = "ontology_id"

    id: int = models.AutoField(primary_key=True)
    """Internal id, valid only in one DB instance."""
    uid: str = models.CharField(unique=True, max_length=8, default=ids.ontology)
    """A universal id (hash of selected field)."""
    name: str = models.CharField(max_length=256, db_index=True)
    """Name of the cell line."""
    ontology_id: str | None = models.CharField(
        max_length=32, db_index=True, null=True, default=None
    )
    """Ontology ID of the cell line."""
    abbr: str | None = models.CharField(
        max_length=32, db_index=True, unique=True, null=True, default=None
    )
    """A unique abbreviation of cell line."""
    synonyms: str | None = models.TextField(null=True, default=None)
    """Bar-separated (|) synonyms that correspond to this cell line."""
    description: str | None = models.TextField(null=True, default=None)
    """Description of the cell line."""
    parents: CellLine = models.ManyToManyField(
        "self", symmetrical=False, related_name="children"
    )
    """Parent cell line records."""
    source: Source = models.ForeignKey(
        "Source", PROTECT, null=True, related_name="cell_lines"
    )
    """:class:`~bionty.Source` this cell line associates with."""
    artifacts: Artifact = models.ManyToManyField(
        Artifact, through="ArtifactCellLine", related_name="cell_lines"
    )
    """Artifacts linked to the cell line."""

    @overload
    def __init__(
        self,
        name: str,
        ontology_id: str | None,
        abbr: str | None,
        synonyms: str | None,
        description: str | None,
        parents: list[CellLine],
        source: Source | None,
    ): ...

    @overload
    def __init__(
        self,
        *db_args,
    ): ...

    def __init__(
        self,
        *args,
        **kwargs,
    ):
        super().__init__(*args, **kwargs)


class Phenotype(BioRecord, TracksRun, TracksUpdates):
    """Phenotypes - `Human Phenotype <https://hpo.jax.org/app/>`__,
    `Phecodes <https://phewascatalog.org/phecodes_icd10>`__,
    `Mammalian Phenotype <http://obofoundry.org/ontology/mp.html>`__,
    `Zebrafish Phenotype <http://obofoundry.org/ontology/zp.html>`__.

    Notes:
        For more info, see tutorials :doc:`docs:bio-registries` and :doc:`docs:phenotype`.

        Bulk create Phenotype records via :meth:`~docs:lamindb.core.Record.from_values`.

    Examples:
        >>> record = bionty.Phenotype.from_source(name="Arachnodactyly")
        >>> record.save()
    """

    class Meta(BioRecord.Meta, TracksRun.Meta, TracksUpdates.Meta):
        abstract = False
        unique_together = (("name", "ontology_id"),)

    _name_field: str = "name"
    _ontology_id_field: str = "ontology_id"

    id: int = models.AutoField(primary_key=True)
    """Internal id, valid only in one DB instance."""
    uid: str = models.CharField(unique=True, max_length=8, default=ids.ontology)
    """A universal id (hash of selected field)."""
    name: str = models.CharField(max_length=256, db_index=True)
    """Name of the phenotype."""
    ontology_id: str | None = models.CharField(
        max_length=32, db_index=True, null=True, default=None
    )
    """Ontology ID of the phenotype."""
    abbr: str | None = models.CharField(
        max_length=32, db_index=True, unique=True, null=True, default=None
    )
    """A unique abbreviation of phenotype."""
    synonyms: str | None = models.TextField(null=True, default=None)
    """Bar-separated (|) synonyms that correspond to this phenotype."""
    description: str | None = models.TextField(null=True, default=None)
    """Description of the phenotype."""
    parents: Phenotype = models.ManyToManyField(
        "self", symmetrical=False, related_name="children"
    )
    """Parent phenotype records."""
    source: Source = models.ForeignKey(
        "Source", PROTECT, null=True, related_name="phenotypes"
    )
    """:class:`~bionty.Source` this phenotype associates with."""
    artifacts: Artifact = models.ManyToManyField(
        Artifact, through="ArtifactPhenotype", related_name="phenotypes"
    )
    """Artifacts linked to the phenotype."""

    @overload
    def __init__(
        self,
        name: str,
        ontology_id: str | None,
        abbr: str | None,
        synonyms: str | None,
        description: str | None,
        parents: list[Phenotype],
        source: Source | None,
    ): ...

    @overload
    def __init__(
        self,
        *db_args,
    ): ...

    def __init__(
        self,
        *args,
        **kwargs,
    ):
        super().__init__(*args, **kwargs)


class Pathway(BioRecord, TracksRun, TracksUpdates):
    """Pathways - `Gene Ontology <https://bioportal.bioontology.org/ontologies/GO>`__,
    `Pathway Ontology <https://bioportal.bioontology.org/ontologies/PW>`__.

    Notes:
        For more info, see tutorials :doc:`docs:bio-registries` and :doc:`docs:pathway`.

        Bulk create Pathway records via :meth:`~docs:lamindb.core.Record.from_values`.

    Examples:
        >>> record = bionty.Pathway.from_source(ontology_id="GO:1903353")
        >>> record.save()
    """

    class Meta(BioRecord.Meta, TracksRun.Meta, TracksUpdates.Meta):
        abstract = False
        unique_together = (("name", "ontology_id"),)

    _name_field: str = "name"
    _ontology_id_field: str = "ontology_id"

    id: int = models.AutoField(primary_key=True)
    """Internal id, valid only in one DB instance."""
    uid: str = models.CharField(unique=True, max_length=8, default=ids.ontology)
    """A universal id (hash of selected field)."""
    name: str = models.CharField(max_length=256, db_index=True)
    """Name of the pathway."""
    ontology_id: str | None = models.CharField(
        max_length=32, db_index=True, null=True, default=None
    )
    """Ontology ID of the pathway."""
    abbr: str | None = models.CharField(
        max_length=32, db_index=True, unique=True, null=True, default=None
    )
    """A unique abbreviation of pathway."""
    synonyms: str | None = models.TextField(null=True, default=None)
    """Bar-separated (|) synonyms that correspond to this pathway."""
    description: str | None = models.TextField(null=True, default=None)
    """Description of the pathway."""
    parents: Pathway = models.ManyToManyField(
        "self", symmetrical=False, related_name="children"
    )
    """Parent pathway records."""
    source: Source = models.ForeignKey(
        "Source", PROTECT, null=True, related_name="pathways"
    )
    """:class:`~bionty.Source` this pathway associates with."""
    genes: Gene = models.ManyToManyField("Gene", related_name="pathways")
    """Genes that signifies the pathway."""
    feature_sets: FeatureSet = models.ManyToManyField(
        FeatureSet, through="FeatureSetPathway", related_name="pathways"
    )
    """Featuresets linked to the pathway."""
    artifacts: Artifact = models.ManyToManyField(
        Artifact, through="ArtifactPathway", related_name="pathways"
    )
    """Artifacts linked to the pathway."""

    @overload
    def __init__(
        self,
        name: str,
        ontology_id: str | None,
        abbr: str | None,
        synonyms: str | None,
        description: str | None,
        parents: list[Pathway],
        source: Source | None,
    ): ...

    @overload
    def __init__(
        self,
        *db_args,
    ): ...

    def __init__(
        self,
        *args,
        **kwargs,
    ):
        super().__init__(*args, **kwargs)


class ExperimentalFactor(BioRecord, TracksRun, TracksUpdates):
    """Experimental factors - `Experimental Factor Ontology <https://www.ebi.ac.uk/ols/ontologies/efo>`__.

    Notes:
        For more info, see tutorials :doc:`docs:bio-registries` and :doc:`docs:experimental_factor`.

        Bulk create ExperimentalFactor records via :meth:`~docs:lamindb.core.Record.from_values`.

    Examples:
        >>> standard_name = bionty.ExperimentalFactor.public().standardize(["scRNA-seq"])
        >>> record = bionty.ExperimentalFactor.from_source(name=standard_name)
    """

    class Meta(BioRecord.Meta, TracksRun.Meta, TracksUpdates.Meta):
        abstract = False
        unique_together = (("name", "ontology_id"),)

    _name_field: str = "name"
    _ontology_id_field: str = "ontology_id"

    id: int = models.AutoField(primary_key=True)
    """Internal id, valid only in one DB instance."""
    uid: str = models.CharField(unique=True, max_length=8, default=ids.ontology)
    """A universal id (hash of selected field)."""
    name: str = models.CharField(max_length=256, db_index=True)
    """Name of the experimental factor."""
    ontology_id: str | None = models.CharField(
        max_length=32, db_index=True, null=True, default=None
    )
    """Ontology ID of the experimental factor."""
    abbr: str | None = models.CharField(
        max_length=32, db_index=True, unique=True, null=True, default=None
    )
    """A unique abbreviation of experimental factor."""
    synonyms: str | None = models.TextField(null=True, default=None)
    """Bar-separated (|) synonyms that correspond to this experimental factor."""
    description: str | None = models.TextField(null=True, default=None)
    """Description of the experimental factor."""
    molecule: str | None = models.TextField(null=True, default=None, db_index=True)
    """Molecular experimental factor, parsed from EFO."""
    instrument: str | None = models.TextField(null=True, default=None, db_index=True)
    """Instrument used to measure the experimental factor, parsed from EFO."""
    measurement: str | None = models.TextField(null=True, default=None, db_index=True)
    """Phenotypic experimental factor, parsed from EFO."""
    parents: ExperimentalFactor = models.ManyToManyField(
        "self", symmetrical=False, related_name="children"
    )
    """Parent experimental factor records."""
    source: Source = models.ForeignKey(
        "Source", PROTECT, null=True, related_name="experimental_factors"
    )
    """:class:`~bionty.Source` this experimental_factors associates with."""
    artifacts: Artifact = models.ManyToManyField(
        Artifact,
        through="ArtifactExperimentalFactor",
        related_name="experimental_factors",
    )
    """Artifacts linked to the experimental_factors."""

    @overload
    def __init__(
        self,
        name: str,
        ontology_id: str | None,
        abbr: str | None,
        synonyms: str | None,
        description: str | None,
        parents: list[ExperimentalFactor],
        source: Source | None,
    ): ...

    @overload
    def __init__(
        self,
        *db_args,
    ): ...

    def __init__(
        self,
        *args,
        **kwargs,
    ):
        super().__init__(*args, **kwargs)


class DevelopmentalStage(BioRecord, TracksRun, TracksUpdates):
    """Developmental stages - `Human Developmental Stages <https://github.com/obophenotype/developmental-stage-ontologies/wiki/HsapDv>`__,
    `Mouse Developmental Stages <https://github.com/obophenotype/developmental-stage-ontologies/wiki/MmusDv>`__.  # noqa.

    Notes:
        For more info, see tutorials :doc:`docs:bio-registries` and :doc:`docs:developmental_stage`.

        Bulk create DevelopmentalStage records via :meth:`~docs:lamindb.core.Record.from_values`.

    Examples:
        >>> record = bionty.DevelopmentalStage.from_source(name="neurula stage")
        >>> record.save()
    """

    class Meta(BioRecord.Meta, TracksRun.Meta, TracksUpdates.Meta):
        abstract = False
        unique_together = (("name", "ontology_id"),)

    _name_field: str = "name"
    _ontology_id_field: str = "ontology_id"

    id: int = models.AutoField(primary_key=True)
    """Internal id, valid only in one DB instance."""
    uid: str = models.CharField(unique=True, max_length=8, default=ids.ontology)
    """A universal id (hash of selected field)."""
    name: str = models.CharField(max_length=256, db_index=True)
    """Name of the developmental stage."""
    ontology_id: str | None = models.CharField(
        max_length=32, db_index=True, null=True, default=None
    )
    """Ontology ID of the developmental stage."""
    abbr: str | None = models.CharField(
        max_length=32, db_index=True, unique=True, null=True, default=None
    )
    """A unique abbreviation of developmental stage."""
    synonyms: str | None = models.TextField(null=True, default=None)
    """Bar-separated (|) synonyms that correspond to this developmental stage."""
    description: str | None = models.TextField(null=True, default=None)
    """Description of the developmental stage."""
    parents: str | None = models.ManyToManyField(
        "self", symmetrical=False, related_name="children"
    )
    """Parent developmental stage records."""
    source: Source = models.ForeignKey(
        "Source", PROTECT, null=True, related_name="developmental_stages"
    )
    """:class:`~bionty.Source` this developmental stage associates with."""
    artifacts: Artifact = models.ManyToManyField(
        Artifact,
        through="ArtifactDevelopmentalStage",
        related_name="developmental_stages",
    )
    """Artifacts linked to the developmental stage."""

    @overload
    def __init__(
        self,
        name: str,
        ontology_id: str | None,
        abbr: str | None,
        synonyms: str | None,
        description: str | None,
        parents: list[DevelopmentalStage],
        source: Source | None,
    ): ...

    @overload
    def __init__(
        self,
        *db_args,
    ): ...

    def __init__(
        self,
        *args,
        **kwargs,
    ):
        super().__init__(*args, **kwargs)


class Ethnicity(BioRecord, TracksRun, TracksUpdates):
    """Ethnicity - `Human Ancestry Ontology <https://github.com/EBISPOT/hancestro>`__.

    Notes:
        For more info, see tutorials :doc:`docs:bio-registries` and :doc:`docs:ethnicity`.

        Bulk create Ethnicity records via :meth:`~docs:lamindb.core.Record.from_values`.

    Examples:
        >>> record = bionty.Ethnicity.from_source(name="European")
        >>> record.save()
    """

    class Meta(BioRecord.Meta, TracksRun.Meta, TracksUpdates.Meta):
        abstract = False
        unique_together = (("name", "ontology_id"),)

    _name_field: str = "name"
    _ontology_id_field: str = "ontology_id"

    id: int = models.AutoField(primary_key=True)
    """Internal id, valid only in one DB instance."""
    uid: str = models.CharField(unique=True, max_length=8, default=ids.ontology)
    """A universal id (hash of selected field)."""
    name: str = models.CharField(max_length=256, db_index=True)
    """Name of the ethnicity."""
    ontology_id: str | None = models.CharField(
        max_length=32, db_index=True, null=True, default=None
    )
    """Ontology ID of the ethnicity."""
    abbr: str | None = models.CharField(
        max_length=32, db_index=True, unique=True, null=True, default=None
    )
    """A unique abbreviation of ethnicity."""
    synonyms: str | None = models.TextField(null=True, default=None)
    """Bar-separated (|) synonyms that correspond to this ethnicity."""
    description: str | None = models.TextField(null=True, default=None)
    """Description of the ethnicity."""
    parents: Ethnicity = models.ManyToManyField(
        "self", symmetrical=False, related_name="children"
    )
    """Parent ethnicity records."""
    source: Source = models.ForeignKey(
        "Source", PROTECT, null=True, related_name="ethnicities"
    )
    """:class:`~bionty.Source` this ethnicity associates with."""
    artifacts: Artifact = models.ManyToManyField(
        Artifact,
        through="ArtifactEthnicity",
        related_name="ethnicities",
    )
    """Artifacts linked to the ethnicity."""

    @overload
    def __init__(
        self,
        name: str,
        ontology_id: str | None,
        abbr: str | None,
        synonyms: str | None,
        description: str | None,
        parents: list[Ethnicity],
        source: Source | None,
    ): ...

    @overload
    def __init__(
        self,
        *db_args,
    ): ...

    def __init__(
        self,
        *args,
        **kwargs,
    ):
        super().__init__(*args, **kwargs)


class Source(Record, TracksRun, TracksUpdates):
    """Versions of ontology sources.

    .. warning::

        Do not modify the records unless you know what you are doing!
    """

    _name_field: str = "name"

    class Meta(BioRecord.Meta, TracksRun.Meta, TracksUpdates.Meta):
        abstract = False
        unique_together = (("entity", "name", "organism", "version"),)

    id: int = models.AutoField(primary_key=True)
    """Internal id, valid only in one DB instance."""
    uid: str = models.CharField(unique=True, max_length=4, default=ids.source)
    """A universal id (hash of selected field)."""
    entity: str = models.CharField(max_length=256, db_index=True)
    """Entity class name."""
    organism: str = models.CharField(max_length=64, db_index=True)
    """Organism name, use 'all' if unknown or none applied."""
    name: str = models.CharField(max_length=64, db_index=True)
    """Source name, short form, CURIE prefix for ontologies."""
    version: str = models.CharField(max_length=64, db_index=True)
    """Version of the source."""
    in_db: bool = models.BooleanField(default=False, db_index=True)
    """Whether this ontology has be added to the database."""
    currently_used: bool = models.BooleanField(default=False, db_index=True)
    """Whether this record is currently used."""
    description: str | None = models.TextField(blank=True, db_index=True)
    """Source full name, long form."""
    url: str | None = models.TextField(null=True, default=None)
    """URL of the source file."""
    md5: str | None = models.TextField(null=True, default=None)
    """Hash md5 of the source file."""
    source_website: str | None = models.TextField(null=True, default=None)
    """Website of the source."""
    dataframe_artifact: Artifact = models.ForeignKey(
        Artifact, PROTECT, null=True, default=None, related_name="_source_dataframe_of"
    )
    """Dataframe artifact that corresponds to this source."""
    artifacts: Artifact = models.ManyToManyField(
        Artifact, related_name="_source_artifact_of"
    )
    """Additional files that correspond to this source."""

    @overload
    def __init__(
        self,
        entity: str,
        organism: str,
        name: str,
        version: str,
        currently_used: bool,
        description: str | None,
        url: str | None,
        md5: str | None,
        source_website: str | None,
    ): ...

    @overload
    def __init__(
        self,
        *db_args,
    ): ...

    def __init__(
        self,
        *args,
        **kwargs,
    ):
        kwargs = encode_uid(orm=self, kwargs=kwargs)
        super().__init__(*args, **kwargs)

    def set_as_currently_used(self):
        """Set this record as the currently used source.

        Examples:
            >>> record = bionty.Source.filter(uid="...").one()
            >>> record.set_as_currently_used()
        """
        # set this record as currently used
        self.currently_used = True
        self.save()
        # set all other records as not currently used
        Source.filter(
            entity=self.entity, organism=self.organism, name=self.name
        ).exclude(uid=self.uid).update(currently_used=False)
        logger.success(f"set {self} as currently used")
        logger.warning("please reload your instance to reflect the updates!")


class FeatureSetGene(Record, LinkORM):
    id: int = models.BigAutoField(primary_key=True)
    # follow the .lower() convention in link models
    featureset: FeatureSet = models.ForeignKey(
        "lnschema_core.FeatureSet", CASCADE, related_name="+"
    )
    gene: Gene = models.ForeignKey("Gene", PROTECT, related_name="+")


class FeatureSetProtein(Record, LinkORM):
    id: int = models.BigAutoField(primary_key=True)
    # follow the .lower() convention in link models
    featureset: FeatureSet = models.ForeignKey(
        "lnschema_core.FeatureSet", CASCADE, related_name="+"
    )
    protein: Protein = models.ForeignKey("Protein", PROTECT, related_name="+")


class FeatureSetCellMarker(Record, LinkORM):
    id: int = models.BigAutoField(primary_key=True)
    # follow the .lower() convention in link models
    featureset: FeatureSet = models.ForeignKey(
        "lnschema_core.FeatureSet", CASCADE, related_name="+"
    )
    # follow the .lower() convention in link models
    cellmarker: CellMarker = models.ForeignKey("CellMarker", PROTECT, related_name="+")


class FeatureSetPathway(Record, LinkORM):
    id: int = models.BigAutoField(primary_key=True)
    # follow the .lower() convention in link models
    featureset: FeatureSet = models.ForeignKey(
        "lnschema_core.FeatureSet", CASCADE, related_name="+"
    )
    pathway: Pathway = models.ForeignKey("Pathway", PROTECT, related_name="+")


class ArtifactOrganism(Record, LinkORM, TracksRun):
    id: int = models.BigAutoField(primary_key=True)
    artifact: Artifact = models.ForeignKey(
        Artifact, CASCADE, related_name="links_organism"
    )
    organism: Organism = models.ForeignKey(
        "Organism", PROTECT, related_name="links_artifact"
    )
    feature: Feature = models.ForeignKey(
        Feature, PROTECT, null=True, default=None, related_name="links_artifactorganism"
    )
    label_ref_is_name: bool | None = models.BooleanField(null=True, default=None)
    feature_ref_is_name: bool | None = models.BooleanField(null=True, default=None)


class ArtifactGene(Record, LinkORM, TracksRun):
    id: int = models.BigAutoField(primary_key=True)
    artifact: Artifact = models.ForeignKey(Artifact, CASCADE, related_name="links_gene")
    gene: Gene = models.ForeignKey("Gene", PROTECT, related_name="links_artifact")
    feature: Feature = models.ForeignKey(
        Feature, PROTECT, null=True, default=None, related_name="links_artifactgene"
    )
    gene_ref_is_symbol: bool | None = models.BooleanField(null=True, default=None)
    feature_ref_is_symbol: bool | None = models.BooleanField(null=True, default=None)


class ArtifactProtein(Record, LinkORM, TracksRun):
    id: int = models.BigAutoField(primary_key=True)
    artifact: Artifact = models.ForeignKey(
        Artifact, CASCADE, related_name="links_protein"
    )
    protein: Protein = models.ForeignKey(
        "Protein", PROTECT, related_name="links_artifact"
    )
    feature: Feature = models.ForeignKey(
        Feature, PROTECT, null=True, default=None, related_name="links_artifactprotein"
    )
    label_ref_is_name: bool | None = models.BooleanField(null=True, default=None)
    feature_ref_is_name: bool | None = models.BooleanField(null=True, default=None)


class ArtifactCellMarker(Record, LinkORM, TracksRun):
    id: int = models.BigAutoField(primary_key=True)
    artifact: Artifact = models.ForeignKey(
        Artifact, CASCADE, related_name="links_cell_marker"
    )
    # follow the .lower() convention in link models
    cellmarker: CellMarker = models.ForeignKey(
        "CellMarker", PROTECT, related_name="links_artifact"
    )
    feature: Feature = models.ForeignKey(
        Feature,
        PROTECT,
        null=True,
        default=None,
        related_name="links_artifactcellmarker",
    )
    label_ref_is_name: bool | None = models.BooleanField(null=True, default=None)
    feature_ref_is_name: bool | None = models.BooleanField(null=True, default=None)


class ArtifactTissue(Record, LinkORM, TracksRun):
    id: int = models.BigAutoField(primary_key=True)
    artifact: Artifact = models.ForeignKey(
        Artifact, CASCADE, related_name="links_tissue"
    )
    tissue: Tissue = models.ForeignKey("Tissue", PROTECT, related_name="links_artifact")
    feature: Feature = models.ForeignKey(
        Feature, PROTECT, null=True, default=None, related_name="links_artifacttissue"
    )
    label_ref_is_name: bool | None = models.BooleanField(null=True, default=None)
    feature_ref_is_name: bool | None = models.BooleanField(null=True, default=None)


class ArtifactCellType(Record, LinkORM, TracksRun):
    id: int = models.BigAutoField(primary_key=True)
    artifact: Artifact = models.ForeignKey(
        Artifact, CASCADE, related_name="links_cell_type"
    )
    # follow the .lower() convention in link models
    celltype: CellType = models.ForeignKey(
        "CellType", PROTECT, related_name="links_artifact"
    )
    feature: Feature = models.ForeignKey(
        Feature, PROTECT, null=True, default=None, related_name="links_artifactcelltype"
    )
    label_ref_is_name: bool | None = models.BooleanField(null=True, default=None)
    feature_ref_is_name: bool | None = models.BooleanField(null=True, default=None)


class ArtifactDisease(Record, LinkORM, TracksRun):
    id: int = models.BigAutoField(primary_key=True)
    artifact: Artifact = models.ForeignKey(
        Artifact, CASCADE, related_name="links_disease"
    )
    disease: Disease = models.ForeignKey(
        "Disease", PROTECT, related_name="links_artifact"
    )
    feature: Feature = models.ForeignKey(
        Feature, PROTECT, null=True, default=None, related_name="links_artifactdisease"
    )
    label_ref_is_name: bool | None = models.BooleanField(null=True, default=None)
    feature_ref_is_name: bool | None = models.BooleanField(null=True, default=None)


class ArtifactCellLine(Record, LinkORM, TracksRun):
    id: int = models.BigAutoField(primary_key=True)
    artifact: Artifact = models.ForeignKey(
        Artifact, CASCADE, related_name="links_cell_line"
    )
    # follow the .lower() convention in link models
    cellline: CellLine = models.ForeignKey(
        "CellLine", PROTECT, related_name="links_artifact"
    )
    feature: Feature = models.ForeignKey(
        Feature, PROTECT, null=True, default=None, related_name="links_artifactcellline"
    )
    label_ref_is_name: bool | None = models.BooleanField(null=True, default=None)
    feature_ref_is_name: bool | None = models.BooleanField(null=True, default=None)


class ArtifactPhenotype(Record, LinkORM, TracksRun):
    id: int = models.BigAutoField(primary_key=True)
    artifact: Artifact = models.ForeignKey(
        Artifact, CASCADE, related_name="links_phenotype"
    )
    phenotype: Phenotype = models.ForeignKey(
        "Phenotype", PROTECT, related_name="links_artifact"
    )
    feature: Feature = models.ForeignKey(
        Feature,
        PROTECT,
        null=True,
        default=None,
        related_name="links_artifactphenotype",
    )
    label_ref_is_name: bool | None = models.BooleanField(null=True, default=None)
    feature_ref_is_name: bool | None = models.BooleanField(null=True, default=None)


class ArtifactPathway(Record, LinkORM, TracksRun):
    id: int = models.BigAutoField(primary_key=True)
    artifact: Artifact = models.ForeignKey(
        Artifact, CASCADE, related_name="links_pathway"
    )
    pathway: Pathway = models.ForeignKey(
        "Pathway", PROTECT, related_name="links_artifact"
    )
    feature: Feature = models.ForeignKey(
        Feature, PROTECT, null=True, default=None, related_name="links_artifactpathway"
    )
    label_ref_is_name: bool | None = models.BooleanField(null=True, default=None)
    feature_ref_is_name: bool | None = models.BooleanField(null=True, default=None)


class ArtifactExperimentalFactor(Record, LinkORM, TracksRun):
    id: int = models.BigAutoField(primary_key=True)
    artifact: Artifact = models.ForeignKey(
        Artifact, CASCADE, related_name="links_experimental_factor"
    )
    experimentalfactor: ExperimentalFactor = models.ForeignKey(
        "ExperimentalFactor", PROTECT, related_name="links_artifact"
    )
    feature: Feature = models.ForeignKey(
        Feature,
        PROTECT,
        null=True,
        default=None,
        related_name="links_artifactexperimentalfactor",
    )
    label_ref_is_name: bool | None = models.BooleanField(null=True, default=None)
    feature_ref_is_name: bool | None = models.BooleanField(null=True, default=None)


class ArtifactDevelopmentalStage(Record, LinkORM, TracksRun):
    id: int = models.BigAutoField(primary_key=True)
    artifact: Artifact = models.ForeignKey(
        Artifact, CASCADE, related_name="links_developmental_stage"
    )
    # follow the .lower() convention in link models
    developmentalstage: DevelopmentalStage = models.ForeignKey(
        "DevelopmentalStage", PROTECT, related_name="links_artifact"
    )
    feature: Feature = models.ForeignKey(
        Feature,
        PROTECT,
        null=True,
        default=None,
        related_name="links_artifactdevelopmentalstage",
    )
    label_ref_is_name: bool | None = models.BooleanField(null=True, default=None)
    feature_ref_is_name: bool | None = models.BooleanField(null=True, default=None)


class ArtifactEthnicity(Record, LinkORM, TracksRun):
    id: int = models.BigAutoField(primary_key=True)
    artifact: Artifact = models.ForeignKey(
        Artifact, CASCADE, related_name="links_ethnicity"
    )
    ethnicity: Ethnicity = models.ForeignKey(
        "Ethnicity", PROTECT, related_name="links_artifact"
    )
    feature: Feature = models.ForeignKey(
        Feature,
        PROTECT,
        null=True,
        default=None,
        related_name="links_artifactethnicity",
    )
    label_ref_is_name: bool | None = models.BooleanField(null=True, default=None)
    feature_ref_is_name: bool | None = models.BooleanField(null=True, default=None)


# backward compat
Species = Organism
BiontySource = Source
BioRegistry = BioRecord
PublicSource = Source
