"""Tests for BindableFloat / BindableColor update semantics.

The key behaviour pinned here: when ``apply_update`` receives a plain
primitive (number for float, ``[R,G,B]`` list for color) it must clear
``source_id`` -- that is how the client signals "unbind, use the static
value now". Previously the implementation preserved ``source_id`` on
plain-primitive updates, which silently dropped unbind requests.
"""

from ledgrab.storage.bindable import BindableColor, BindableFloat


class TestBindableFloatApplyUpdate:
    def test_plain_number_unbinds_when_previously_bound(self):
        bf = BindableFloat(value=0.5, source_id="vs_abc")
        updated = bf.apply_update(0.8)
        assert updated.value == 0.8
        assert updated.source_id == ""
        assert not updated.is_bound

    def test_plain_number_leaves_unbound_unbound(self):
        bf = BindableFloat(value=0.5, source_id="")
        updated = bf.apply_update(0.8)
        assert updated.value == 0.8
        assert updated.source_id == ""

    def test_dict_with_source_id_binds(self):
        bf = BindableFloat(value=0.5, source_id="")
        updated = bf.apply_update({"value": 0.8, "source_id": "vs_new"})
        assert updated.value == 0.8
        assert updated.source_id == "vs_new"
        assert updated.is_bound

    def test_dict_with_empty_source_id_unbinds(self):
        bf = BindableFloat(value=0.5, source_id="vs_abc")
        updated = bf.apply_update({"value": 0.8, "source_id": ""})
        assert updated.value == 0.8
        assert updated.source_id == ""

    def test_none_leaves_unchanged(self):
        bf = BindableFloat(value=0.5, source_id="vs_abc")
        updated = bf.apply_update(None)
        assert updated is bf


class TestBindableColorApplyUpdate:
    def test_plain_list_unbinds_when_previously_bound(self):
        bc = BindableColor(color=[10, 20, 30], source_id="vs_abc")
        updated = bc.apply_update([100, 150, 200])
        assert updated.color == [100, 150, 200]
        assert updated.source_id == ""
        assert not updated.is_bound

    def test_plain_list_leaves_unbound_unbound(self):
        bc = BindableColor(color=[10, 20, 30], source_id="")
        updated = bc.apply_update([100, 150, 200])
        assert updated.color == [100, 150, 200]
        assert updated.source_id == ""

    def test_dict_with_source_id_binds(self):
        bc = BindableColor(color=[10, 20, 30], source_id="")
        updated = bc.apply_update({"color": [100, 150, 200], "source_id": "vs_new"})
        assert updated.color == [100, 150, 200]
        assert updated.source_id == "vs_new"
        assert updated.is_bound

    def test_dict_with_empty_source_id_unbinds(self):
        bc = BindableColor(color=[10, 20, 30], source_id="vs_abc")
        updated = bc.apply_update({"color": [100, 150, 200], "source_id": ""})
        assert updated.color == [100, 150, 200]
        assert updated.source_id == ""

    def test_none_leaves_unchanged(self):
        bc = BindableColor(color=[10, 20, 30], source_id="vs_abc")
        updated = bc.apply_update(None)
        assert updated is bc
