#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""
Multi-Level Abstraction Hierarchy (Phase 4, Architecture 7).

Level 0: Raw entries (lessons, failures, patterns) — exists
Level 1: Schemas (cross-session patterns, confidence > 0.5) — exists
Level 2: Meta-schemas (this) — clusters of schemas with shared structure
Level 3: Principles — universal meta-schemas (all domains)

Schemas that share structural properties get grouped into meta-schemas.
Meta-schemas compress knowledge: instead of 10 individual schemas, agents
see 2-3 meta-schemas that contextualize them.

Runs during consolidation. Meta-schemas are derived views, not permanent
entities — they're recomputed each cycle.
"""
import json
import logging
from collections import defaultdict
from datetime import datetime, timezone

from pulse.core.paths import get_state_dir

log = logging.getLogger("pulse.meta_schemas")

META_SCHEMAS_FILE = get_state_dir() / "memory" / "meta_schemas.json"
PRINCIPLES_FILE = get_state_dir() / "memory" / "principles.json"
MIN_SCHEMAS_PER_CLUSTER = 3  # Need 3+ schemas to form a meta-schema
MIN_DOMAINS_FOR_PRINCIPLE = 3  # Need 3+ domains to promote to principle
QUALITY_GATE = 0.4  # Minimum avg confidence for meta-schema


def build_meta_schemas(verbose: bool = False) -> dict:
    """Build Level 2 meta-schemas by clustering Level 1 schemas.

    Clusters schemas by type AND shared evidence links.
    Returns {"meta_schemas": [...], "principles": [...]}.
    """
    schemas = _load_schemas()
    if len(schemas) < MIN_SCHEMAS_PER_CLUSTER:
        return {"meta_schemas": [], "principles": [], "status": "insufficient_schemas"}

    # Cluster by type
    by_type = defaultdict(list)
    for s in schemas:
        schema_type = s.get("type", "unknown")
        by_type[schema_type].append(s)

    meta_schemas = []
    principles = []

    for schema_type, members in by_type.items():
        if len(members) < MIN_SCHEMAS_PER_CLUSTER:
            continue

        # Compute cluster statistics
        avg_conf = sum(m.get("confidence", 0) for m in members) / len(members)
        if avg_conf < QUALITY_GATE:
            continue

        domains = set()
        for m in members:
            if isinstance(m.get("domains"), list):
                domains.update(m["domains"])
            elif m.get("domain"):
                domains.add(m["domain"])

        # Build meta-schema
        meta = {
            "id": f"MS-{schema_type}-{len(members)}",
            "type": schema_type,
            "description": f"{len(members)} {schema_type} schemas across {len(domains)} domains",
            "schema_count": len(members),
            "constituent_ids": [m.get("name", m.get("id", "?")) for m in members],
            "domains_covered": sorted(domains),
            "avg_confidence": round(avg_conf, 3),
            "computed_at": datetime.now(timezone.utc).isoformat(),
        }
        meta_schemas.append(meta)

        # Level 3: Promote to principle if universal (3+ domains)
        if len(domains) >= MIN_DOMAINS_FOR_PRINCIPLE and avg_conf >= 0.5:
            principle = {
                "id": f"P-{schema_type}",
                "source_meta_schema": meta["id"],
                "type": schema_type,
                "domains": sorted(domains),
                "domain_count": len(domains),
                "confidence": round(avg_conf, 3),
                "description": f"Universal {schema_type} principle ({len(domains)} domains, {len(members)} schemas)",
                "computed_at": datetime.now(timezone.utc).isoformat(),
            }
            principles.append(principle)

    # Save
    _save_meta_schemas(meta_schemas)
    _save_principles(principles)

    if verbose:
        print(f"  [ABSTRACTION] {len(meta_schemas)} meta-schemas, {len(principles)} principles")

    return {
        "meta_schemas": len(meta_schemas),
        "principles": len(principles),
        "status": "ok",
    }


def get_meta_schemas() -> list:
    """Load computed meta-schemas."""
    if not META_SCHEMAS_FILE.exists():
        return []
    try:
        return json.loads(META_SCHEMAS_FILE.read_text(encoding="utf-8"))
    except Exception:
        return []


def get_principles() -> list:
    """Load computed principles (Level 3)."""
    if not PRINCIPLES_FILE.exists():
        return []
    try:
        return json.loads(PRINCIPLES_FILE.read_text(encoding="utf-8"))
    except Exception:
        return []


def _load_schemas() -> list:
    """Load Level 1 schemas from Hippocampus."""
    try:
        from pulse.core.brain_utils import HIPPOCAMPUS_DIR
        schemas_file = HIPPOCAMPUS_DIR / "Patterns" / "schemas.json"
        if not schemas_file.exists():
            return []
        data = json.loads(schemas_file.read_text(encoding="utf-8"))
        if isinstance(data, list):
            return data
        if isinstance(data, dict) and "schemas" in data:
            return data["schemas"]
        return []
    except Exception:
        return []


def _save_meta_schemas(meta_schemas: list):
    META_SCHEMAS_FILE.parent.mkdir(parents=True, exist_ok=True)
    try:
        META_SCHEMAS_FILE.write_text(
            json.dumps(meta_schemas, indent=2, ensure_ascii=False), encoding="utf-8")
    except Exception as e:
        log.debug("Failed to save meta-schemas: %s", e)


def _save_principles(principles: list):
    PRINCIPLES_FILE.parent.mkdir(parents=True, exist_ok=True)
    try:
        PRINCIPLES_FILE.write_text(
            json.dumps(principles, indent=2, ensure_ascii=False), encoding="utf-8")
    except Exception as e:
        log.debug("Failed to save principles: %s", e)
