"""Append-only episodic memory store.

A memory (an ``Entry``) is the minimal unit of what an agent observed or did.
Entries are immutable once written and carry a stable id, a source reference,
and a timestamp so that downstream retrieval can always point back to the
original observation (see the provenance ledger in :mod:`agentmemory.ledger`).
"""

from __future__ import annotations

import hashlib
import time
import uuid
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional


@dataclass
class Entry:
    """A single immutable memory record.

    Parameters
    ----------
    id:
        Stable unique id. Auto-generated if omitted (SHA-1 of content+source+nonce).
    text:
        The natural-language content of the memory.
    source:
        A free-form provenance reference (URL, document id, tool call, ...).
    tags:
        Optional short labels used by tag-based retrieval.
    metadata:
        Arbitrary structured context associated with the observation.
    created_at:
        Epoch seconds. Defaults to now.
    """

    text: str
    source: str = ""
    tags: List[str] = field(default_factory=list)
    metadata: Dict[str, Any] = field(default_factory=dict)
    id: Optional[str] = None
    created_at: Optional[float] = None

    def __post_init__(self) -> None:
        if self.created_at is None:
            self.created_at = time.time()
        if not self.id:
            payload = f"{self.text}|{self.source}|{self.created_at}|{uuid.uuid4()}".encode("utf-8")
            self.id = hashlib.sha1(payload).hexdigest()[:16]

    def as_dict(self) -> Dict[str, Any]:
        return {
            "id": self.id,
            "text": self.text,
            "source": self.source,
            "tags": list(self.tags),
            "metadata": dict(self.metadata),
            "created_at": self.created_at,
        }


class MemoryStore:
    """In-memory, append-only store of :class:`Entry` objects.

    This is a teaching/demo implementation. In production the same interface
    would be backed by a durable append-only log (e.g. an object store or a
    database table) plus an index, but the semantics are identical: you can
    read any stored entry, but you cannot mutate it in place.
    """

    def __init__(self) -> None:
        self._entries: Dict[str, Entry] = {}
        self._order: List[str] = []

    def add(self, entry: Entry) -> Entry:
        """Append an entry. Returns the stored entry (id assigned)."""
        if entry.id in self._entries:
            raise ValueError(f"entry id {entry.id!r} already exists")
        self._entries[entry.id] = entry
        self._order.append(entry.id)
        return entry

    def get(self, entry_id: str) -> Optional[Entry]:
        return self._entries.get(entry_id)

    def __len__(self) -> int:
        return len(self._entries)

    def __iter__(self):
        for eid in self._order:
            yield self._entries[eid]

    def all(self) -> List[Entry]:
        return [self._entries[eid] for eid in self._order]

    def clear(self) -> None:
        self._entries.clear()
        self._order.clear()


def _tokenize(text: str) -> List[str]:
    """Small tokenizer used by scoring. Kept here for reuse."""
    import re
    return re.findall(r"[a-z0-9']+", text.lower())