"""
LoomCare Receipt Schemas - Consumer Health Journal with Clinical Provenance

Constitutional receipts for LoomCare: the patient-facing health journal that is
"emotionally safe today and clinically meaningful tomorrow."

These receipts form the fossilization layer - every log entry, visit prep, and
interaction creates an immutable audit trail that can later serve clinical purposes.

Constitutional Invariants:
1. No AI Impersonation - AI never pretends to be a doctor
2. Doctor-in-Loop - All clinical features gated on real MD connection
3. Red-Flag Safety - Urgent patterns trigger immediate prompts
4. Truth = Replay - Receipts allow perfect reconstruction
5. Dignity-First UX - Errors degrade gracefully, never blame patient

Tri-Temporal Design:
- record_time: When the receipt was created (system clock)
- valid_time: When the event actually happened (patient-reported)
- observation_time: When the system observed/processed the event
"""
from __future__ import annotations

import hashlib
import logging
from datetime import datetime, timezone
from enum import Enum
from typing import Any, Dict, List, Optional

from pydantic import Field

from receipts.base import BaseReceipt, Domain
from receipts.compat.pyd import BaseModel, ConfigDictLike

logger = logging.getLogger(__name__)


# -----------------------------------------------------------------------------
# Enums
# -----------------------------------------------------------------------------

class LogEntryType(str, Enum):
    """Types of entries a patient can log."""
    SYMPTOM = "symptom"           # Physical symptom (headache, fatigue, etc.)
    MEDICATION = "medication"      # Med taken, dosage, time
    MOOD = "mood"                  # Emotional state
    FOOD = "food"                  # Diet/nutrition
    ACTIVITY = "activity"          # Exercise, movement
    SLEEP = "sleep"                # Sleep quality/duration
    NOTE = "note"                  # Free-form journal entry
    QUESTION = "question"          # Question for doctor
    MEASUREMENT = "measurement"    # Vital signs, weight, etc.
    APPOINTMENT = "appointment"    # Doctor visit, test, procedure
    LAB_RESULT = "lab_result"      # Self-reported lab/test result
    PHOTO = "photo"                # Image attachment (rash, wound, etc.)


class RedFlagSeverity(str, Enum):
    """Severity classification for detected red flags."""
    URGENT = "urgent"       # Immediate action needed (call 911, go to ER)
    HIGH = "high"           # Prompt medical attention (call doctor today)
    MODERATE = "moderate"   # Schedule appointment soon
    LOW = "low"             # Worth mentioning at next visit


class VisitPrepStatus(str, Enum):
    """Status of visit preparation generation."""
    REQUESTED = "requested"    # User requested prep
    GENERATING = "generating"  # AI processing timeline
    COMPLETED = "completed"    # Prep ready for user review
    SANITIZED = "sanitized"    # Output cleaned of diagnosis language
    DELIVERED = "delivered"    # Shown to user
    EXPORTED = "exported"      # Downloaded as PDF


# -----------------------------------------------------------------------------
# Supporting Models
# -----------------------------------------------------------------------------

class RedFlagDetection(BaseModel):
    """A detected red flag in patient input."""
    
    pattern_matched: str = Field(description="The pattern or keyword that triggered detection")
    severity: RedFlagSeverity = Field(description="How urgent this flag is")
    category: str = Field(description="Category: chest_pain, suicidal, breathing, etc.")
    recommendation: str = Field(description="What to show the user")
    source_text: str = Field(description="The text that triggered the flag")
    confidence: float = Field(ge=0.0, le=1.0, description="Detection confidence")
    
    model_config = ConfigDictLike(frozen=True)


class TimelineReference(BaseModel):
    """Reference to a log entry used in visit prep."""
    
    log_receipt_id: str = Field(description="Receipt ID of the referenced log entry")
    log_content_hash: str = Field(description="Hash of the log content at time of reference")
    entry_type: LogEntryType = Field(description="Type of the referenced entry")
    timestamp: datetime = Field(description="When the original entry was logged")
    
    model_config = ConfigDictLike(frozen=True)


class SanitizationEdit(BaseModel):
    """Record of a sanitization edit made to AI output."""
    
    original_text: str = Field(description="Text that was removed/modified")
    replacement_text: str = Field(description="What it was replaced with (or empty)")
    reason: str = Field(description="Why this was sanitized: diagnosis_language, treatment_advice, etc.")
    position: int = Field(ge=0, description="Character position where edit occurred")
    
    model_config = ConfigDictLike(frozen=True)


# -----------------------------------------------------------------------------
# Receipt Schemas
# -----------------------------------------------------------------------------

class LoomCareLogReceipt(BaseReceipt):
    """
    Receipt for every patient log entry.
    
    This is the fossilization layer - every symptom, mood, medication, and note
    becomes an immutable record with cryptographic provenance. Receipts can later
    be aggregated for clinical use when a doctor connection is established.
    
    Tri-Temporal:
    - valid_time (inherited): When the patient says the event happened
    - record_time: When this receipt was created
    - observation_time: When the system processed the input
    """
    
    receipt_type: str = "LoomCareLogReceipt"
    domain: str = "loomcare"
    domain_namespace: Domain = Field(default=Domain.CLINICAL, exclude=True)
    
    # Patient identification (hashed for privacy)
    patient_id_hash: str = Field(
        description="SHA-256 hash of patient identifier - never store raw patient ID"
    )
    
    # Entry content
    entry_type: LogEntryType = Field(description="Type of log entry")
    content_hash: str = Field(
        description="SHA-256 hash of the raw text content for integrity verification"
    )
    text_preview: str = Field(
        default="",
        max_length=100,
        description="First 100 chars for quick reference (redactable)"
    )
    tags: List[str] = Field(default_factory=list, description="User-applied tags")
    
    # Doctor sharing
    for_doctor: bool = Field(
        default=False,
        description="Patient explicitly marked this for doctor to see"
    )
    
    # Attachments (hashes only - actual files stored separately)
    attachment_hashes: List[str] = Field(
        default_factory=list,
        description="SHA-256 hashes of any attached files"
    )
    
    # Red flag detection
    red_flags: List[RedFlagDetection] = Field(
        default_factory=list,
        description="Any red flags detected in this entry"
    )
    warning_shown: Optional[str] = Field(
        default=None,
        description="Warning message shown to user (if red flag detected)"
    )
    
    # Tri-temporal timestamps
    record_time: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc),
        description="When this receipt was created (system clock)"
    )
    observation_time: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc),
        description="When the system observed/processed this event"
    )
    
    # Provenance
    client_version: Optional[str] = Field(
        default=None,
        description="Version of the LoomCare client that created this entry"
    )
    session_id: Optional[str] = Field(
        default=None,
        description="Session identifier for grouping related entries"
    )
    
    @classmethod
    def compute_content_hash(cls, text: str) -> str:
        """Compute SHA-256 hash of content for integrity verification."""
        return hashlib.sha256(text.encode("utf-8")).hexdigest()
    
    @classmethod
    def compute_patient_hash(cls, patient_id: str, salt: str = "") -> str:
        """Hash patient ID for privacy-preserving storage."""
        combined = f"{salt}:{patient_id}" if salt else patient_id
        return hashlib.sha256(combined.encode("utf-8")).hexdigest()


class VisitPrepRequestReceipt(BaseReceipt):
    """
    Receipt emitted when a patient requests visit preparation.
    
    Tracks the request and what timeline data will be used to generate the prep.
    Creates provenance chain: request → generation → output.
    """
    
    receipt_type: str = "VisitPrepRequestReceipt"
    domain: str = "loomcare"
    domain_namespace: Domain = Field(default=Domain.CLINICAL, exclude=True)
    
    # Patient identification
    patient_id_hash: str = Field(
        description="SHA-256 hash of patient identifier"
    )
    
    # Request details
    reason: Optional[str] = Field(
        default=None,
        description="Patient's stated reason for visit"
    )
    reason_hash: Optional[str] = Field(
        default=None,
        description="Hash of the reason text"
    )
    
    # Timeline window
    timeline_window_start: Optional[datetime] = Field(
        default=None,
        description="Start of timeline window to include"
    )
    timeline_window_end: Optional[datetime] = Field(
        default=None,
        description="End of timeline window to include"
    )
    
    # References to source data
    timeline_refs: List[TimelineReference] = Field(
        default_factory=list,
        description="References to log entries used in this prep"
    )
    timeline_entry_count: int = Field(
        default=0,
        ge=0,
        description="Number of timeline entries being summarized"
    )
    
    # Request metadata
    request_time: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc),
        description="When the prep was requested"
    )
    status: VisitPrepStatus = Field(
        default=VisitPrepStatus.REQUESTED,
        description="Current status of this request"
    )
    
    @classmethod
    def compute_reason_hash(cls, reason: str) -> str:
        """Hash the reason for integrity verification."""
        return hashlib.sha256(reason.encode("utf-8")).hexdigest()


class VisitPrepGeneratedReceipt(BaseReceipt):
    """
    Receipt emitted when visit prep is generated by AI.
    
    Full provenance for AI-generated content including:
    - Model and prompt used
    - Raw output hash (before sanitization)
    - Final output hash (after sanitization)
    - All sanitization edits made
    
    Constitutional: AI output is NEVER presented as medical advice.
    """
    
    receipt_type: str = "VisitPrepGeneratedReceipt"
    domain: str = "loomcare"
    domain_namespace: Domain = Field(default=Domain.CLINICAL, exclude=True)
    
    # Link to request
    request_receipt_id: str = Field(
        description="Receipt ID of the VisitPrepRequestReceipt that triggered this"
    )
    
    # Patient identification
    patient_id_hash: str = Field(
        description="SHA-256 hash of patient identifier"
    )
    
    # AI provenance
    model_id: str = Field(description="Model identifier (e.g., 'gpt-4o-mini')")
    model_version: Optional[str] = Field(default=None, description="Model version if available")
    prompt_hash: str = Field(
        description="SHA-256 hash of the prompt sent to the model"
    )
    prompt_template_id: Optional[str] = Field(
        default=None,
        description="Identifier for the prompt template used"
    )
    
    # Output hashes
    raw_output_hash: str = Field(
        description="SHA-256 hash of raw AI output before sanitization"
    )
    sanitized_output_hash: str = Field(
        description="SHA-256 hash of output after sanitization"
    )
    
    # Sanitization record
    sanitization_edits: List[SanitizationEdit] = Field(
        default_factory=list,
        description="All edits made during sanitization"
    )
    sanitization_rules_applied: List[str] = Field(
        default_factory=list,
        description="Which sanitization rules were triggered"
    )
    
    # Output structure
    sections_generated: List[str] = Field(
        default_factory=list,
        description="Sections included: summary, questions, meds_bullets, symptoms_bullets"
    )
    
    # Timing
    generation_started: datetime = Field(
        description="When generation began"
    )
    generation_completed: datetime = Field(
        description="When generation finished"
    )
    latency_ms: int = Field(
        ge=0,
        description="Total generation latency in milliseconds"
    )
    
    # Status
    status: VisitPrepStatus = Field(
        default=VisitPrepStatus.COMPLETED,
        description="Status after generation"
    )
    
    # Constitutional attestation
    ai_disclaimer_included: bool = Field(
        default=True,
        description="Whether AI-generated disclaimer was included"
    )
    no_diagnosis_verified: bool = Field(
        default=False,
        description="Whether output was verified to contain no diagnosis language"
    )
    no_treatment_verified: bool = Field(
        default=False,
        description="Whether output was verified to contain no treatment advice"
    )
    
    # Safety Gates (Part 3 requirement: AI summaries gated on safety checks)
    safety_gates_passed: bool = Field(
        default=False,
        description="Whether all safety gates passed before delivery to user"
    )
    coherence_score: Optional[float] = Field(
        default=None,
        ge=-1.0,
        le=1.0,
        description="Coherence delta (ΔC) for this generation (-1 to +1)"
    )
    hallucination_check_result: Optional[str] = Field(
        default=None,
        description="Result of hallucination detection: PASS, WARN, FAIL, or SKIPPED"
    )
    
    # Grounding verification
    grounding_sources: List[str] = Field(
        default_factory=list,
        description="Receipt IDs of timeline entries that ground claims in the summary"
    )
    ungrounded_claims: List[str] = Field(
        default_factory=list,
        description="Claims in summary not grounded by timeline data (potential hallucinations)"
    )
    grounding_ratio: Optional[float] = Field(
        default=None,
        ge=0.0,
        le=1.0,
        description="Ratio of grounded claims to total claims (0.0 to 1.0)"
    )
    
    # Refusal tracking (if safety gate fails)
    refusal_emitted: bool = Field(
        default=False,
        description="Whether a RefusalStone was emitted due to safety failure"
    )
    refusal_receipt_id: Optional[str] = Field(
        default=None,
        description="Receipt ID of the RefusalStone if safety gate failed"
    )
    
    @classmethod
    def compute_hash(cls, text: str) -> str:
        """Compute SHA-256 hash for output integrity."""
        return hashlib.sha256(text.encode("utf-8")).hexdigest()



# -----------------------------------------------------------------------------
# Factory Functions
# -----------------------------------------------------------------------------

def create_log_receipt(
    patient_id: str,
    entry_type: LogEntryType,
    text: str,
    *,
    tags: Optional[List[str]] = None,
    for_doctor: bool = False,
    valid_time: Optional[datetime] = None,
    red_flags: Optional[List[RedFlagDetection]] = None,
    warning_shown: Optional[str] = None,
    attachments: Optional[List[str]] = None,
    client_version: Optional[str] = None,
    session_id: Optional[str] = None,
    salt: str = "",
) -> LoomCareLogReceipt:
    """
    Create a LoomCareLogReceipt with proper hashing and timestamps.
    
    Args:
        patient_id: Raw patient identifier (will be hashed)
        entry_type: Type of log entry
        text: The actual log text
        tags: User-applied tags
        for_doctor: Whether to share with doctor
        valid_time: When the event actually happened (defaults to now)
        red_flags: Any detected red flags
        warning_shown: Warning message shown to user
        attachments: List of attachment file hashes
        client_version: Client app version
        session_id: Session ID for grouping
        salt: Salt for patient ID hashing
    
    Returns:
        LoomCareLogReceipt ready for emission
    """
    import uuid
    
    now = datetime.now(timezone.utc)
    
    return LoomCareLogReceipt(
        receipt_id=f"lc-log-{uuid.uuid4().hex[:12]}",
        patient_id_hash=LoomCareLogReceipt.compute_patient_hash(patient_id, salt),
        entry_type=entry_type,
        content_hash=LoomCareLogReceipt.compute_content_hash(text),
        text_preview=text[:100] if text else "",
        tags=tags or [],
        for_doctor=for_doctor,
        attachment_hashes=attachments or [],
        red_flags=red_flags or [],
        warning_shown=warning_shown,
        valid_time=valid_time or now,
        record_time=now,
        observation_time=now,
        client_version=client_version,
        session_id=session_id,
    )


def create_visit_prep_request_receipt(
    patient_id: str,
    timeline_refs: List[TimelineReference],
    *,
    reason: Optional[str] = None,
    timeline_window_start: Optional[datetime] = None,
    timeline_window_end: Optional[datetime] = None,
    salt: str = "",
) -> VisitPrepRequestReceipt:
    """
    Create a VisitPrepRequestReceipt when user requests visit preparation.
    
    Args:
        patient_id: Raw patient identifier (will be hashed)
        timeline_refs: References to timeline entries being used
        reason: Patient's stated reason for visit
        timeline_window_start: Start of timeline window
        timeline_window_end: End of timeline window
        salt: Salt for patient ID hashing
    
    Returns:
        VisitPrepRequestReceipt ready for emission
    """
    import uuid
    
    now = datetime.now(timezone.utc)
    
    return VisitPrepRequestReceipt(
        receipt_id=f"lc-vpreq-{uuid.uuid4().hex[:12]}",
        patient_id_hash=LoomCareLogReceipt.compute_patient_hash(patient_id, salt),
        reason=reason,
        reason_hash=VisitPrepRequestReceipt.compute_reason_hash(reason) if reason else None,
        timeline_window_start=timeline_window_start,
        timeline_window_end=timeline_window_end,
        timeline_refs=timeline_refs,
        timeline_entry_count=len(timeline_refs),
        request_time=now,
        status=VisitPrepStatus.REQUESTED,
        valid_time=now,
    )


def create_visit_prep_generated_receipt(
    request_receipt_id: str,
    patient_id: str,
    *,
    model_id: str,
    prompt: str,
    raw_output: str,
    sanitized_output: str,
    sanitization_edits: Optional[List[SanitizationEdit]] = None,
    sanitization_rules_applied: Optional[List[str]] = None,
    sections_generated: Optional[List[str]] = None,
    generation_started: datetime,
    generation_completed: datetime,
    model_version: Optional[str] = None,
    prompt_template_id: Optional[str] = None,
    no_diagnosis_verified: bool = False,
    no_treatment_verified: bool = False,
    # Safety gate parameters (NEW)
    safety_gates_passed: bool = False,
    coherence_score: Optional[float] = None,
    hallucination_check_result: Optional[str] = None,
    grounding_sources: Optional[List[str]] = None,
    ungrounded_claims: Optional[List[str]] = None,
    grounding_ratio: Optional[float] = None,
    refusal_emitted: bool = False,
    refusal_receipt_id: Optional[str] = None,
    salt: str = "",
) -> VisitPrepGeneratedReceipt:
    """
    Create a VisitPrepGeneratedReceipt after AI generates visit prep.
    
    Args:
        request_receipt_id: Receipt ID of the triggering request
        patient_id: Raw patient identifier (will be hashed)
        model_id: Model identifier
        prompt: The prompt sent to the model
        raw_output: Raw AI output before sanitization
        sanitized_output: Output after sanitization
        sanitization_edits: Edits made during sanitization
        sanitization_rules_applied: Rules that were triggered
        sections_generated: Which sections were generated
        generation_started: When generation began
        generation_completed: When generation finished
        model_version: Model version if available
        prompt_template_id: Template ID if using templates
        no_diagnosis_verified: Whether output verified to contain no diagnosis
        no_treatment_verified: Whether output verified to contain no treatment advice
        safety_gates_passed: Whether all safety gates passed
        coherence_score: Coherence delta (ΔC) for this generation
        hallucination_check_result: PASS, WARN, FAIL, or SKIPPED
        grounding_sources: Receipt IDs of timeline entries that ground claims
        ungrounded_claims: Claims not grounded by timeline data
        grounding_ratio: Ratio of grounded to total claims
        refusal_emitted: Whether a RefusalStone was emitted
        refusal_receipt_id: Receipt ID of RefusalStone if emitted
        salt: Salt for patient ID hashing
    
    Returns:
        VisitPrepGeneratedReceipt ready for emission
    """
    import uuid
    
    latency_ms = int((generation_completed - generation_started).total_seconds() * 1000)
    
    return VisitPrepGeneratedReceipt(
        receipt_id=f"lc-vpgen-{uuid.uuid4().hex[:12]}",
        request_receipt_id=request_receipt_id,
        patient_id_hash=LoomCareLogReceipt.compute_patient_hash(patient_id, salt),
        model_id=model_id,
        model_version=model_version,
        prompt_hash=VisitPrepGeneratedReceipt.compute_hash(prompt),
        prompt_template_id=prompt_template_id,
        raw_output_hash=VisitPrepGeneratedReceipt.compute_hash(raw_output),
        sanitized_output_hash=VisitPrepGeneratedReceipt.compute_hash(sanitized_output),
        sanitization_edits=sanitization_edits or [],
        sanitization_rules_applied=sanitization_rules_applied or [],
        sections_generated=sections_generated or ["summary", "questions", "meds_bullets", "symptoms_bullets"],
        generation_started=generation_started,
        generation_completed=generation_completed,
        latency_ms=latency_ms,
        status=VisitPrepStatus.SANITIZED if sanitization_edits else VisitPrepStatus.COMPLETED,
        valid_time=generation_completed,
        ai_disclaimer_included=True,
        no_diagnosis_verified=no_diagnosis_verified,
        no_treatment_verified=no_treatment_verified,
        # Safety gates (NEW)
        safety_gates_passed=safety_gates_passed,
        coherence_score=coherence_score,
        hallucination_check_result=hallucination_check_result,
        grounding_sources=grounding_sources or [],
        ungrounded_claims=ungrounded_claims or [],
        grounding_ratio=grounding_ratio,
        refusal_emitted=refusal_emitted,
        refusal_receipt_id=refusal_receipt_id,
    )


# -----------------------------------------------------------------------------
# Edit/Delete Receipt Types (Append-Only Mutation Semantics)
# -----------------------------------------------------------------------------

class LogUpdateType(str, Enum):
    """Types of updates to existing log entries."""
    CORRECTION = "correction"    # Fixing an error in original entry
    ADDITION = "addition"        # Adding detail to original entry
    RETRACTION = "retraction"    # Partial retraction of original content
    CLARIFICATION = "clarification"  # Clarifying ambiguous content


class LogDeletionType(str, Enum):
    """Reasons for soft-deleting a log entry."""
    USER_REQUEST = "user_request"  # User explicitly requested deletion
    PRIVACY = "privacy"            # Privacy concern (e.g., shared device)
    ERROR = "error"                # Entry was made in error
    DUPLICATE = "duplicate"        # Duplicate of another entry
    TEST_DATA = "test_data"        # Test/demo data to be cleaned up


class LoomCareLogUpdateReceipt(BaseReceipt):
    """
    Receipt for editing an existing log entry (append-only semantics).
    
    Constitutional Invariant: Original entries are NEVER modified in place.
    Updates create a new receipt with lineage pointing to the original.
    
    Lineage Chain:
    - target_receipt_id: The original entry being updated
    - supersedes_receipt_id: If chained edits, the previous update receipt
    
    Tri-Temporal Preservation:
    - original_valid_time: When the event originally happened (preserved)
    - edit_time: When this edit was made (new)
    - record_time: When this receipt was created (system clock)
    """
    
    receipt_type: str = "LoomCareLogUpdateReceipt"
    domain: str = "loomcare"
    domain_namespace: Domain = Field(default=Domain.CLINICAL, exclude=True)
    
    # Patient identification
    patient_id_hash: str = Field(
        description="SHA-256 hash of patient identifier"
    )
    
    # Lineage (CRITICAL for audit trail)
    target_receipt_id: str = Field(
        description="Receipt ID of the original entry being updated"
    )
    target_content_hash: str = Field(
        description="Hash of original content at time of update (for verification)"
    )
    supersedes_receipt_id: Optional[str] = Field(
        default=None,
        description="If chained edits, receipt ID of the previous update"
    )
    
    # Change tracking
    update_type: LogUpdateType = Field(
        description="Type of update: correction, addition, retraction, clarification"
    )
    old_content_hash: str = Field(
        description="Hash of content before this edit"
    )
    new_content_hash: str = Field(
        description="Hash of content after this edit"
    )
    new_text_preview: str = Field(
        default="",
        max_length=100,
        description="First 100 chars of new content for quick reference"
    )
    reason: Optional[str] = Field(
        default=None,
        description="User-provided reason for the edit"
    )
    
    # Field-level diff (optional, for structured updates)
    fields_changed: List[str] = Field(
        default_factory=list,
        description="List of fields that were modified (e.g., ['text', 'tags'])"
    )
    
    # Tri-temporal preservation
    original_valid_time: datetime = Field(
        description="When the event originally happened (preserved from original)"
    )
    edit_time: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc),
        description="When this edit was made"
    )
    record_time: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc),
        description="When this receipt was created (system clock)"
    )
    
    # Provenance
    client_version: Optional[str] = Field(
        default=None,
        description="Version of the LoomCare client that made this edit"
    )
    session_id: Optional[str] = Field(
        default=None,
        description="Session identifier"
    )


class LoomCareLogTombstoneReceipt(BaseReceipt):
    """
    Soft-delete receipt - marks a log entry as logically deleted.
    
    Constitutional Invariant: Data is NEVER physically deleted.
    Tombstones mark entries as invisible in normal queries while
    preserving full audit trail for compliance and recovery.
    
    Recovery: If reversible=True, a future receipt can "un-delete"
    by pointing to this tombstone and marking status="recovered".
    """
    
    receipt_type: str = "LoomCareLogTombstoneReceipt"
    domain: str = "loomcare"
    domain_namespace: Domain = Field(default=Domain.CLINICAL, exclude=True)
    
    # Patient identification
    patient_id_hash: str = Field(
        description="SHA-256 hash of patient identifier"
    )
    
    # Target (what's being tombstoned)
    target_receipt_id: str = Field(
        description="Receipt ID of the entry being tombstoned"
    )
    target_content_hash: str = Field(
        description="Hash of content at time of deletion (for verification)"
    )
    target_entry_type: LogEntryType = Field(
        description="Type of the entry being tombstoned"
    )
    
    # Deletion semantics
    deletion_type: LogDeletionType = Field(
        description="Reason category for the deletion"
    )
    reason: str = Field(
        min_length=1,
        description="Required: human-readable reason for deletion"
    )
    reversible: bool = Field(
        default=True,
        description="Whether this deletion can be undone"
    )
    
    # Audit
    deleted_by: str = Field(
        description="Who requested deletion: 'user', 'system', or specific user hash"
    )
    deletion_requested_at: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc),
        description="When deletion was requested"
    )
    record_time: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc),
        description="When this tombstone receipt was created"
    )
    
    # Provenance
    client_version: Optional[str] = Field(
        default=None,
        description="Version of the LoomCare client"
    )


# -----------------------------------------------------------------------------
# Factory Functions for Edit/Delete
# -----------------------------------------------------------------------------

def create_log_update_receipt(
    patient_id: str,
    target_receipt_id: str,
    target_content_hash: str,
    update_type: LogUpdateType,
    old_content_hash: str,
    new_text: str,
    original_valid_time: datetime,
    *,
    supersedes_receipt_id: Optional[str] = None,
    reason: Optional[str] = None,
    fields_changed: Optional[List[str]] = None,
    client_version: Optional[str] = None,
    session_id: Optional[str] = None,
    salt: str = "",
) -> LoomCareLogUpdateReceipt:
    """
    Create a LoomCareLogUpdateReceipt for editing an existing entry.
    
    Args:
        patient_id: Raw patient identifier (will be hashed)
        target_receipt_id: Receipt ID of the original entry
        target_content_hash: Hash of original content (for verification)
        update_type: Type of update (correction, addition, etc.)
        old_content_hash: Hash of content before this edit
        new_text: The new content after edit
        original_valid_time: When the event originally happened
        supersedes_receipt_id: If chained edits, previous update receipt ID
        reason: User-provided reason for edit
        fields_changed: List of fields modified
        client_version: Client app version
        session_id: Session ID
        salt: Salt for patient ID hashing
    
    Returns:
        LoomCareLogUpdateReceipt ready for emission
    """
    import uuid
    
    now = datetime.now(timezone.utc)
    
    return LoomCareLogUpdateReceipt(
        receipt_id=f"lc-upd-{uuid.uuid4().hex[:12]}",
        patient_id_hash=LoomCareLogReceipt.compute_patient_hash(patient_id, salt),
        target_receipt_id=target_receipt_id,
        target_content_hash=target_content_hash,
        supersedes_receipt_id=supersedes_receipt_id,
        update_type=update_type,
        old_content_hash=old_content_hash,
        new_content_hash=LoomCareLogReceipt.compute_content_hash(new_text),
        new_text_preview=new_text[:100] if new_text else "",
        reason=reason,
        fields_changed=fields_changed or [],
        original_valid_time=original_valid_time,
        edit_time=now,
        record_time=now,
        valid_time=now,
        client_version=client_version,
        session_id=session_id,
    )


def create_log_tombstone_receipt(
    patient_id: str,
    target_receipt_id: str,
    target_content_hash: str,
    target_entry_type: LogEntryType,
    deletion_type: LogDeletionType,
    reason: str,
    *,
    deleted_by: str = "user",
    reversible: bool = True,
    client_version: Optional[str] = None,
    salt: str = "",
) -> LoomCareLogTombstoneReceipt:
    """
    Create a LoomCareLogTombstoneReceipt for soft-deleting an entry.
    
    Args:
        patient_id: Raw patient identifier (will be hashed)
        target_receipt_id: Receipt ID of the entry to tombstone
        target_content_hash: Hash of content at deletion time
        target_entry_type: Type of the entry being deleted
        deletion_type: Reason category for deletion
        reason: Required human-readable reason
        deleted_by: Who requested deletion
        reversible: Whether deletion can be undone
        client_version: Client app version
        salt: Salt for patient ID hashing
    
    Returns:
        LoomCareLogTombstoneReceipt ready for emission
    """
    import uuid
    
    now = datetime.now(timezone.utc)
    
    return LoomCareLogTombstoneReceipt(
        receipt_id=f"lc-tomb-{uuid.uuid4().hex[:12]}",
        patient_id_hash=LoomCareLogReceipt.compute_patient_hash(patient_id, salt),
        target_receipt_id=target_receipt_id,
        target_content_hash=target_content_hash,
        target_entry_type=target_entry_type,
        deletion_type=deletion_type,
        reason=reason,
        reversible=reversible,
        deleted_by=deleted_by,
        deletion_requested_at=now,
        record_time=now,
        valid_time=now,
        client_version=client_version,
    )
