# SPDX-License-Identifier: Apache-2.0
"""Hand-off to a phone over a passive NFC tag (ADR-0007).

The device never transmits. It writes one NDEF URI record into the tag's
EEPROM over I2C; a phone touching the tail powers the tag and reads it.
The note travels in the URL *fragment* (after '#'), which browsers never
send to the server, so the reader page can be a plain static file.

    https://<reader>/#g1.<base64url(deflate-raw(utf-8 note))>

The note itself is Markdown, written to be read by people and by language
models alike (see v01/note-format.md): YAML front matter with the session's
facts, one `##` section per level with that level and its expiry stated
(RP §5.4), and one bullet per statement.
"""
from __future__ import annotations

import base64
import zlib

FORMAT = "g1"
TAG_BYTES = 8192            # ST25DV64KC user memory
CC_BYTES = 8                # extended capability container (> 2040 bytes)
OVERHEAD = CC_BYTES + 4 + 7 + 1   # TLV header + record header + terminator


def pack(note: str) -> str:
    c = zlib.compressobj(9, zlib.DEFLATED, -15)          # raw deflate, matches DecompressionStream('deflate-raw')
    raw = c.compress(note.encode()) + c.flush()
    return FORMAT + "." + base64.urlsafe_b64encode(raw).decode().rstrip("=")


def unpack(fragment: str) -> str:
    fmt, data = fragment.lstrip("#").split(".", 1)
    assert fmt == FORMAT
    raw = base64.urlsafe_b64decode(data + "=" * (-len(data) % 4))
    return zlib.decompress(raw, -15).decode()


def url_for(note: str, reader: str) -> str:
    return f"{reader.rstrip('/')}/#{pack(note)}"


NOTE_FORMAT = "goldfish-note/1"


def front_matter(meta: dict) -> str:
    """YAML front matter: flat `key: value` lines, values quoted only when YAML would misread them."""
    def val(v):
        v = str(v)
        return f'"{v}"' if (not v or v[0] in "-?:,[]{}#&*!|>'\"%@`" or ": " in v or " #" in v) else v
    return "---\n" + "\n".join(f"{k}: {val(v)}" for k, v in {"format": NOTE_FORMAT, **meta}.items()) + "\n---"


def section(heading: str, level: str, until: str, lines: list[str]) -> tuple[str, str]:
    """One level's section: the level and expiry on the first line, then one bullet per statement."""
    body = f"*{level} · kept until {until}*\n" + ("\n".join(f"- {x}" for x in lines) if lines else "- (none)")
    return heading, body


def fit(sections: list[tuple[str, str]], reader: str, budget: int = TAG_BYTES - OVERHEAD, preamble: str = "") -> tuple[str, list[str]]:
    """Join (heading, body) sections in priority order until the URL no longer fits.

    Callers pass outcomes first, then gist, then attributed — so what is
    dropped first is always the highest-resolution material. The preamble
    (front matter and title) is always kept.
    """
    kept, dropped, note = [], [], preamble.strip()
    for head, body in sections:
        trial = (note + f"\n\n## {head}\n{body}").strip()
        if len(url_for(trial, reader).encode()) - len("https://") <= budget:
            note = trial
            kept.append(head)
        else:
            dropped.append(head)
    if dropped:
        note += "\n\n> Left out to fit the tag: " + ", ".join(dropped) + ". Hold the eye again later to read them."
    return url_for(note, reader), dropped


def ndef_tag_image(url: str, tag_bytes: int = TAG_BYTES) -> bytes:
    """Full EEPROM image: Type 5 capability container + NDEF TLV + terminator."""
    assert url.startswith("https://")
    payload = b"\x04" + url[len("https://"):].encode()  # URI prefix 0x04 = "https://"
    if len(payload) < 256:
        record = bytes([0xD1, 0x01, len(payload)]) + b"U" + payload          # MB ME SR, TNF=well-known
    else:
        record = bytes([0xC1, 0x01]) + len(payload).to_bytes(4, "big") + b"U" + payload
    tlv = bytes([0x03]) + (bytes([len(record)]) if len(record) < 0xFF else b"\xFF" + len(record).to_bytes(2, "big")) + record + b"\xFE"
    mlen = tag_bytes // 8
    cc = bytes([0xE2, 0x40, 0x00, 0x01, 0x00, 0x00]) + mlen.to_bytes(2, "big")   # extended CC, read/write, MLEN in 8-byte units
    image = cc + tlv
    if len(image) > tag_bytes:
        raise ValueError(f"note too large for tag: {len(image)} > {tag_bytes}")
    return image
