"""
NOTES — Phase 6, and the slice with the largest single JSON column in the app.

The contract is `front/src/renderer/src/features/notes/data/api.js` and the
field set is `features/notes/model.js`. Where this file disagrees with either,
this file is wrong.

Five things this model states deliberately:

1. **`sheets` IS ONE JSON FIELD — sheets and blocks both.** Not a `Sheet` table
   with a `Block` table under it. The client's own `data/api.js` argues it in
   three points and the third settles it: a note is saved as a WHOLE on a
   debounce, because the editor writes the document it has rather than a diff.
   Rows would mean computing that diff on the client to turn one keystroke into
   the right INSERT/UPDATE/DELETE set — the hardest code in the feature, buying
   nothing anybody asked for. A block has no identity outside its note, nothing
   links to one, and the operation blocks get most is reorder, which as rows is
   an UPDATE per block with a position column to maintain and as a list is the
   list.

   The migration to do LATER, deliberately, is a global search that ranks by
   which block matched. That is a query across blocks, and it is the one thing
   a JSON column cannot serve.

2. **`cover_off` IS A SEPARATE FACT FROM `cover` BEING NULL.** They are two
   different states sharing one value otherwise: `cover: null` means "use the
   page's gradient", which is how every note gets a cover without a field, and
   `cover_off` is the explicit "no cover at all". One column could not say both,
   and collapsing them makes "remove the cover" silently mean "restore the
   default".

3. **`source` HAS TO ROUND-TRIP.** `{kind: 'book', bookId}` for a quotes note —
   it is how a book that was unlinked and re-linked finds its own note again
   instead of creating a second one. Lose it and the reconcile falls back to
   matching on the title, which a rename defeats. Deliberately generic
   `{kind, …}` rather than a `book_id` column: the next feature to write a note
   on somebody's behalf needs the same field.

4. **`date` IS A DAY AND IS NOT A DEADLINE.** Named `date`, not `due_date`, and
   that is the client's naming for the reason written at the field: nothing on
   this page goes red for being in the past.

5. **COVER IMAGE BYTES DO NOT LIVE HERE.** `cover` is metadata only
   (`{type, id, posY}`); the bytes stay in the renderer's local storage under
   `note:<id>`, the same split page banners already use. When the backend gains
   file storage this becomes an upload — a separate decision with real cost on
   a 20 GB box, not a side effect of wiring an endpoint.

`updated_at` comes from `TimeStampedModel` and is `auto_now`, which matters more
here than anywhere else: **the grid's default sort is by it.** A client that
could set it would be a client that could silently reorder the page.
"""

from django.db import models

from core.models import TimeStampedModel

from .workspace import Page


class Note(TimeStampedModel):
    page = models.ForeignKey(Page, on_delete=models.CASCADE, related_name='notes')

    title = models.CharField(max_length=300, blank=True)

    # `{id, color}` from the app's icon registry. Null means the note has never
    # been given one, which is not the same as a stored icon that happens to
    # match the default.
    icon = models.JSONField(null=True, blank=True)

    # `{type, id, posY}`. Null means "the page's gradient" — see `cover_off`.
    cover = models.JSONField(null=True, blank=True)
    cover_off = models.BooleanField(default=False)

    # Tag IDS, against the page-scoped vocabulary in that page's settings blob.
    tags = models.JSONField(default=list, blank=True)

    # A DAY, and not a deadline. See the module docstring.
    date = models.DateField(null=True, blank=True)

    pinned = models.BooleanField(default=False)

    # `[{id, name, blocks: [...]}]`. The whole document, in one column.
    sheets = models.JSONField(default=list, blank=True)

    # The note's own record of the calendar event it was added to. ONE
    # DIRECTIONAL — the event does not point back, which is why this is not
    # derived from `Event.link_page`. Real foreign keys with `SET_NULL`, like a
    # task's: deleting the event turns the connection off by itself.
    calendar_page = models.ForeignKey(
        Page,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='linked_notes',
    )
    calendar_event = models.ForeignKey(
        'lifey_api.Event',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='linked_notes',
    )

    # What MADE this note, when something other than a person did.
    source = models.JSONField(null=True, blank=True)

    is_sample = models.BooleanField(default=False)

    class Meta:
        # Newest edit first, which is the grid's own default sort. Stated on the
        # model so the list endpoint and the page agree without the client
        # having to re-sort what it was handed.
        ordering = ['-updated_at', '-id']
        indexes = [
            models.Index(fields=['page', '-updated_at']),
            models.Index(fields=['page', 'pinned']),
        ]

    def __str__(self):
        return self.title or f'(untitled note {self.pk})'
