"""Portable randomness and portable dates.

Nothing in the generator may touch a language built-in for either. A built-in
RNG picks a different alert for the same seed in the browser, and a built-in
date type is timezone-sensitive in JavaScript — CI in UTC passes and a
visitor in another timezone sees a different fire date for the same `?seed=`.
Both are hand-written here and ported line for line to TypeScript.
"""

from __future__ import annotations

MASK = 0xFFFFFFFF


def fnv1a32(text: str) -> int:
    """FNV-1a, 32-bit, over the UTF-8 bytes. Identical to wordle_five's."""
    h = 0x811C9DC5
    for byte in text.encode():
        h ^= byte
        h = (h * 0x01000193) & MASK
    return h


class XorShift32:
    """Marsaglia xorshift32. Seven lines, and the browser has the same seven.

    `below(n)` is a plain modulo. It is biased for large `n`, which does not
    matter here (every `n` is tiny) and keeps the port a one-liner —
    `Math.imul`-free, since the state is only ever shifted and xored.
    """

    def __init__(self, seed: int) -> None:
        self.state = seed & MASK or 0x9E3779B9

    def next(self) -> int:
        x = self.state
        x ^= (x << 13) & MASK
        x ^= x >> 17
        x ^= (x << 5) & MASK
        self.state = x
        return x

    def below(self, n: int) -> int:
        return self.next() % n

    def between(self, lo: int, hi: int) -> int:
        """Inclusive on both ends."""
        return lo + self.below(hi - lo + 1)

    def chance(self, percent: int) -> bool:
        return self.below(100) < percent

    def pick(self, items):
        return items[self.below(len(items))]

    def sample(self, items, k: int) -> list:
        """k distinct items, Fisher-Yates over a copy, first k taken."""
        pool = list(items)
        out = []
        for i in range(min(k, len(pool))):
            j = i + self.below(len(pool) - i)
            pool[i], pool[j] = pool[j], pool[i]
            out.append(pool[i])
        return out

    def hex4(self) -> str:
        return format(self.below(65536), "04x")


# ------------------------------------------------------------------ dates --
# Howard Hinnant's civil-from-days / days-from-civil. Integer arithmetic only.
# Day 0 is 1970-01-01.


def days_from_civil(y: int, m: int, d: int) -> int:
    y -= m <= 2
    era = (y if y >= 0 else y - 399) // 400
    yoe = y - era * 400
    mp = (m + 9) % 12
    doy = (153 * mp + 2) // 5 + d - 1
    doe = yoe * 365 + yoe // 4 - yoe // 100 + doy
    return era * 146097 + doe - 719468


def civil_from_days(z: int) -> tuple[int, int, int]:
    z += 719468
    era = (z if z >= 0 else z - 146096) // 146097
    doe = z - era * 146097
    yoe = (doe - doe // 1460 + doe // 36524 - doe // 146096) // 365
    y = yoe + era * 400
    doy = doe - (365 * yoe + yoe // 4 - yoe // 100)
    mp = (5 * doy + 2) // 153
    d = doy - (153 * mp + 2) // 5 + 1
    m = mp + 3 if mp < 10 else mp - 9
    return (y + (m <= 2), m, d)


def days_in_month(y: int, m: int) -> int:
    if m == 12:
        return days_from_civil(y + 1, 1, 1) - days_from_civil(y, 12, 1)
    return days_from_civil(y, m + 1, 1) - days_from_civil(y, m, 1)


def iso_date(day: int) -> str:
    y, m, d = civil_from_days(day)
    return f"{y:04d}-{m:02d}-{d:02d}"


def month_key(y: int, m: int) -> str:
    return f"{y:04d}-{m:02d}"


def month_of_day(day: int) -> str:
    y, m, _ = civil_from_days(day)
    return month_key(y, m)


def add_months(y: int, m: int, n: int) -> tuple[int, int]:
    index = y * 12 + (m - 1) + n
    return (index // 12, index % 12 + 1)
