"""NMEA 0183 helpers: checksum validation and AIS sentence detection.

Pure functions, no Qt/QGIS dependencies, so they're easy to unit-test on their
own. A valid NMEA sentence looks like:

    !AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C
    ^                                              ^^
    start char ($ or !)                  '*' + 2-hex checksum

The checksum is the XOR of every character between the start char and the '*'.
"""


def strip_tag_block(sentence: str) -> str:
    r"""Remove a leading NMEA 4.0 / IEC 61162-1 TAG block if present.

    Many AIS aggregators and multiplexers (e.g. the Norwegian Coastal
    Administration feed) prefix each sentence with a tag block carrying source /
    timestamp metadata, like::

        \s:SRC,c:1700000000*HH\!AIVDM,1,1,,B,....,0*5C

    The payload only starts at the second backslash. Strip it so the sentence can
    be classified, checksum-validated and decoded normally; sentences without a
    tag block are returned unchanged."""
    s = sentence.lstrip()
    if s.startswith("\\"):
        end = s.find("\\", 1)
        if end != -1:
            return s[end + 1:].lstrip()
    return sentence


def nmea_checksum(sentence: str) -> str:
    """Return the two-character uppercase hex checksum for a raw NMEA sentence."""
    s = sentence.strip()
    if s and s[0] in "$!":
        s = s[1:]
    star = s.find("*")
    if star != -1:
        s = s[:star]
    cs = 0
    for ch in s:
        cs ^= ord(ch)
    return f"{cs:02X}"


def is_valid_nmea(sentence: str) -> bool:
    """True if the sentence is well-formed and its checksum matches."""
    s = sentence.strip()
    if not s or s[0] not in "$!" or "*" not in s:
        return False
    body, _, given = s.partition("*")
    given = given.strip().upper()
    if len(given) < 2:
        return False
    return nmea_checksum(s) == given[:2]


def is_ais_sentence(sentence: str) -> bool:
    """True for AIS VDM/VDO sentences from any talker (e.g. !AIVDM, !ABVDM)."""
    s = sentence.strip().upper()
    if not s.startswith("!"):
        return False
    head = s.split(",", 1)[0]  # e.g. "!AIVDM"
    return head.endswith("VDM") or head.endswith("VDO")


def classify(sentence: str) -> str:
    """Return 'ais', 'nmea', or 'other' for a single line of text."""
    s = sentence.strip()
    if not s or s[0] not in "$!":
        return "other"
    return "ais" if is_ais_sentence(s) else "nmea"


_GPS_TYPES = ("RMC", "GGA", "GLL", "VTG", "GSV", "GSA", "ZDA")


def is_gps_sentence(sentence: str) -> bool:
    """True for common GNSS position/time sentences (e.g. $GPRMC, $GNGGA)."""
    s = sentence.strip().upper()
    if not s.startswith("$"):
        return False
    head = s.split(",", 1)[0]  # e.g. "$GPRMC" -> talker 'GP', type 'RMC'
    return len(head) >= 6 and head[3:6] in _GPS_TYPES
