# SPDX-License-Identifier: LGPL-3.0-or-later

from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, Dict, Optional

from ..pyotherside_events import ModelItemSet
from ..utils import serialize_value_for_qml

if TYPE_CHECKING:
    from .model import Model


@dataclass
class ModelItem:
    """Base class for items stored inside a `Model`.

    This class must be subclassed and not used directly.
    All subclasses must be dataclasses.

    Subclasses are also expected to implement `__lt__()`,
    to provide support for comparisons with the `<`, `>`, `<=`, `=>` operators
    and thus allow a `Model` to keep its data sorted.
    """

    id: Any = field()


    def __new__(cls, *_args, **_kwargs) -> "ModelItem":
        cls.parent_model: Optional[Model] = None
        return super().__new__(cls)


    def __setattr__(self, name: str, value) -> None:
        """If this item is in a `Model`, alert it of attribute changes."""

        if (
            name == "parent_model" or
            not self.parent_model or
            getattr(self, name) == value
        ):
            super().__setattr__(name, value)
            return

        super().__setattr__(name, value)
        self._notify_parent_model({name: self.serialize_field(name)})


    def _notify_parent_model(self, changed_fields: Dict[str, Any]) -> None:
        parent = self.parent_model

        if not parent or not parent.sync_id or not changed_fields:
            return

        with parent.write_lock:
            index_then = parent._sorted_data.index(self)
            parent._sorted_data.sort()
            index_now = parent._sorted_data.index(self)

            ModelItemSet(parent.sync_id, index_then, index_now, changed_fields)

        for sync_id, proxy in parent.proxies.items():
            if sync_id != parent.sync_id:
                proxy.source_item_set(parent, self.id, self, changed_fields)


    def __delattr__(self, name: str) -> None:
        raise NotImplementedError()


    def serialize_field(self, field: str) -> Any:
        return serialize_value_for_qml(
            getattr(self, field),
            json_list_dicts=True,
        )


    @property
    def serialized(self) -> Dict[str, Any]:
        """Return this item as a dict ready to be passed to QML."""

        return {
            name: self.serialize_field(name) for name in dir(self)
            if not (
                name.startswith("_") or name in ("parent_model", "serialized")
            )
        }


    def set_fields(self, **fields: Any) -> None:
        """Set multiple fields's values at once.

        The parent model will be resorted only once, and one `ModelItemSet`
        event will be sent informing QML of all the changed fields.
        """

        for name, value in fields.copy().items():
            if getattr(self, name) == value:
                del fields[name]
            else:
                super().__setattr__(name, value)
                fields[name] = self.serialize_field(name)

        self._notify_parent_model(fields)
