"""Mica: a tiny Tamagotchi-style cat that runs in the browser with Pyodide."""

from __future__ import annotations

import json
import time
from dataclasses import asdict, dataclass
from datetime import date
from random import choice

from js import document, localStorage, setInterval, setTimeout
from pyodide.ffi import create_proxy


SAVE_KEY = "mica-python-pet-v1"


@dataclass
class PetState:
    fullness: int = 72
    happiness: int = 78
    energy: int = 68
    last_update: float = 0.0
    visit_streak: int = 0
    last_visit_day: str = ""
    care_moments: int = 0

    def clamp(self) -> None:
        self.fullness = max(0, min(100, self.fullness))
        self.happiness = max(0, min(100, self.happiness))
        self.energy = max(0, min(100, self.energy))

    def apply_elapsed_time(self) -> None:
        now = time.time()
        if self.last_update:
            minutes = min((now - self.last_update) / 60, 8 * 60)
            self.fullness -= int(minutes / 4)
            self.happiness -= int(minutes / 6)
            self.energy -= int(minutes / 8)
        self.last_update = now
        self.clamp()

    @property
    def mood(self) -> str:
        if self.energy < 25:
            return "sleepy"
        if self.fullness < 25:
            return "hungry"
        if self.happiness < 25:
            return "lonely"
        if min(self.fullness, self.happiness, self.energy) > 78:
            return "delighted"
        return "content"


def load_state() -> PetState:
    raw = localStorage.getItem(SAVE_KEY)
    if raw:
        try:
            saved = json.loads(str(raw))
            state = PetState(**{key: saved[key] for key in asdict(PetState()) if key in saved})
            state.apply_elapsed_time()
            record_visit(state)
            return state
        except (ValueError, TypeError, KeyError):
            pass
    state = PetState(last_update=time.time())
    record_visit(state)
    return state


def record_visit(state: PetState) -> None:
    today = date.today()
    if state.last_visit_day == today.isoformat():
        return
    try:
        previous = date.fromisoformat(state.last_visit_day)
        state.visit_streak = state.visit_streak + 1 if (today - previous).days == 1 else 1
    except ValueError:
        state.visit_streak = 1
    state.last_visit_day = today.isoformat()


pet = load_state()

MOOD_MESSAGES = {
    "sleepy": "Mica is blinking slowly. A nap would be lovely.",
    "hungry": "Mica is politely staring at the snack cupboard.",
    "lonely": "Mica misses you. A little attention would help.",
    "delighted": "Mica is thriving — purr engine fully activated!",
    "content": "Mica is comfortable and curious.",
}

MEMORY_NOTES = {
    "sleepy": "Mica remembers the quiet visits best. A rest would make today complete.",
    "hungry": "The diary says snacks have excellent timing.",
    "lonely": "Mica has saved a little space on this page for your attention.",
    "delighted": "Today gets a gold star and a very loud purr.",
    "content": "Another calm page in Mica’s care diary.",
}

REACTIONS = {
    "play": ("Mica pounces after the toy.", "play", {"fullness": -5, "happiness": 20, "energy": -10}),
    "rest": ("The room grows quiet. Mica curls up.", "sleep", {"fullness": -3, "happiness": 2, "energy": 26}),
    "pet": ("Mica leans into your hand and purrs.", "wave", {"fullness": 0, "happiness": 12, "energy": 1}),
}

FOODS = {
    "fish": ("Mica trots to the bowl. Fish is the favourite.", "fish", {"fullness": 22, "happiness": 3, "energy": 1}),
    "chicken": ("Mica settles in for a proper meal.", "chicken", {"fullness": 16, "happiness": 3, "energy": 4}),
    "milk": ("Mica laps at the milk, then looks content.", "milk", {"fullness": 9, "happiness": 8, "energy": 2}),
}


def element(selector: str):
    return document.querySelector(selector)


def save() -> None:
    pet.last_update = time.time()
    localStorage.setItem(SAVE_KEY, json.dumps(asdict(pet)))


def render(message: str | None = None, reaction: str = "") -> None:
    values = {
        "hunger": pet.fullness,
        "happy": pet.happiness,
        "energy": pet.energy,
    }
    for name, value in values.items():
        element(f"#{name}").value = value
        element(f"#{name}-value").textContent = f"{value}%"

    mood = pet.mood
    element("#scene").dataset.mood = mood
    element("#scene").dataset.time = "night" if time.localtime().tm_hour < 7 or time.localtime().tm_hour >= 19 else "day"
    element("#pet-status").textContent = message or MOOD_MESSAGES[mood]
    element("#reaction").textContent = reaction
    element("#fullness-word").textContent = "fed" if pet.fullness >= 65 else ("peckish" if pet.fullness >= 30 else "hungry")
    element("#happiness-word").textContent = "happy" if pet.happiness >= 65 else ("quiet" if pet.happiness >= 30 else "lonely")
    element("#energy-word").textContent = "awake" if pet.energy >= 65 else ("resting" if pet.energy >= 30 else "sleepy")
    element("#streak-value").textContent = f"{pet.visit_streak} day{'s' if pet.visit_streak != 1 else ''}"
    element("#care-value").textContent = str(pet.care_moments)
    element("#memory-note").textContent = MEMORY_NOTES[mood]
    state = {"sleepy": "state-sleeping", "hungry": "state-curious", "lonely": "state-curious", "delighted": "state-happy", "content": "state-idle"}[mood]
    element("#mica").className = f"mica-sprite {state}"


def apply_changes(message: str, activity: str, changes: dict[str, int]) -> None:
    pet.care_moments += 1
    for stat, amount in changes.items():
        setattr(pet, stat, getattr(pet, stat) + amount)
    pet.clamp()
    save()
    render(message)
    element("#scene").dataset.activity = activity
    activity_state = {"play": "state-happy", "sleep": "state-sleeping", "wave": "state-waving", "eating": "state-walking-one"}.get(activity)
    if activity_state:
        element("#mica").className = f"mica-sprite {activity_state}"
    if pet.care_moments % 5 == 0:
        element("#scene").dataset.celebrate = "true"
        element("#reaction").textContent = "✦ purr milestone ✦"
    setTimeout(_finish_proxy, 1700)


def handle_click(event) -> None:
    global selected_index
    action = str(event.currentTarget.dataset.action)
    if action in MENU_ACTIONS:
        selected_index = MENU_ACTIONS.index(action)
        update_menu()
    if action == "feed":
        tray = element("#food-tray")
        is_open = bool(tray.hidden)
        tray.hidden = not is_open
        event.currentTarget.setAttribute("aria-expanded", str(is_open).lower())
        if is_open:
            element("#pet-status").textContent = "What should Mica have?"
        return
    apply_changes(*REACTIONS[action])


def handle_food(event) -> None:
    food = str(event.currentTarget.dataset.food)
    message, _, changes = FOODS[food]
    bowl = element("#food-bowl")
    bowl.hidden = False
    bowl.dataset.food = food
    element("#mica").className = "mica-sprite state-walking-one"
    apply_changes(message, "eating", changes)
    element("#food-tray").hidden = True
    element('[data-action="feed"]').setAttribute("aria-expanded", "false")


def close_tray(event=None) -> None:
    element("#food-tray").hidden = True
    element('[data-action="feed"]').setAttribute("aria-expanded", "false")


def finish_activity() -> None:
    element("#scene").removeAttribute("data-activity")
    element("#scene").removeAttribute("data-celebrate")
    element("#food-bowl").hidden = True
    render(element("#pet-status").textContent)


MENU_ACTIONS = ["feed", "play", "rest", "pet"]
selected_index = 0


def update_menu() -> None:
    for index, action in enumerate(MENU_ACTIONS):
        button = element(f'[data-action="{action}"]:not(.mica-character)')
        selected = index == selected_index
        button.classList.toggle("is-selected", selected)
        if selected:
            button.setAttribute("aria-current", "true")
        else:
            button.removeAttribute("aria-current")


def handle_device(event) -> None:
    global selected_index
    control = str(event.currentTarget.dataset.device)
    if control == "next":
        selected_index = (selected_index + 1) % len(MENU_ACTIONS)
        update_menu()
    elif control == "select":
        element(f'[data-action="{MENU_ACTIONS[selected_index]}"]:not(.mica-character)').click()
    else:
        close_tray()


def tick() -> None:
    pet.fullness -= 1
    if pet.fullness < 35:
        pet.happiness -= 1
    if pet.energy < 30:
        pet.happiness -= 1
    pet.clamp()
    save()
    render()


_proxies = []
_finish_proxy = create_proxy(finish_activity)
_proxies.append(_finish_proxy)
for button in document.querySelectorAll("[data-action]"):
    proxy = create_proxy(handle_click)
    _proxies.append(proxy)
    button.addEventListener("click", proxy)
    button.disabled = False

for button in document.querySelectorAll("[data-food]"):
    proxy = create_proxy(handle_food)
    _proxies.append(proxy)
    button.addEventListener("click", proxy)
    button.disabled = False

_close_proxy = create_proxy(close_tray)
_proxies.append(_close_proxy)
element("[data-close-tray]").addEventListener("click", _close_proxy)

_device_proxy = create_proxy(handle_device)
_proxies.append(_device_proxy)
for button in document.querySelectorAll("[data-device]"):
    button.addEventListener("click", _device_proxy)

update_menu()
close_tray()

_tick_proxy = create_proxy(tick)
_proxies.append(_tick_proxy)
setInterval(_tick_proxy, 60_000)

save()
render(choice(("Mica woke up!", "Mica is ready for company.", "A curious cat appears.")))
