#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
# See LICENSE file for details.
"""Brain Utilities: path constants, scaffold functions, and re-exports."""

import hashlib
import json
import os
import re
import shutil
import subprocess
from datetime import date, datetime, timezone
from pathlib import Path

# Use the centralized path resolution (Law 4 - SoC)
from pulse.core.paths import get_brain_dir, get_state_dir, get_root_dir

# === PATH CONSTANTS (Flat Hippocampus — 6 regions) ===

ROOT_DIR = get_root_dir()
HIPPOCAMPUS_DIR = get_brain_dir()

# New flat structure (Feb 2026 refactor)
LESSONS_DIR = HIPPOCAMPUS_DIR / "Lessons"
FAILS_DIR = HIPPOCAMPUS_DIR / "Failures"
PATTERNS_DIR = HIPPOCAMPUS_DIR / "Patterns"
EVIDENCE_DIR = HIPPOCAMPUS_DIR / "Evidence"
KNOWLEDGE_DIR = HIPPOCAMPUS_DIR / "Semantic"  # Renamed from Knowledge/ — Mar 2026
SEMANTIC_DIR = KNOWLEDGE_DIR  # Canonical alias
PRIVATE_DIR = HIPPOCAMPUS_DIR / "Private"
ARCHIVE_DIR = HIPPOCAMPUS_DIR / "archive"

# Legacy aliases (for backward compat during transition)
DOMAINS_DIR = KNOWLEDGE_DIR
WINS_DIR = LESSONS_DIR          # Wins merged into Lessons
METADATA_DIR = EVIDENCE_DIR     # Metadata merged into Evidence
WORKING_MEMORY_DIR = EVIDENCE_DIR  # Placeholder — overridden after STATE_DIR resolves (line ~121)

# Root-level brain regions (promoted from Evidence/ — Mar 2026)
INTENTS_DIR = HIPPOCAMPUS_DIR / "Intents"
DECISIONS_DIR = HIPPOCAMPUS_DIR / "Decisions"
ANTI_PATTERNS_DIR = HIPPOCAMPUS_DIR / "Anti_Patterns"
# Evidence subdirs (operational data)
EVIDENCE_SESSIONS_DIR = EVIDENCE_DIR / "Sessions"
EVIDENCE_METRICS_DIR = EVIDENCE_DIR / "Metrics"
EVIDENCE_LEARNINGS_DIR = EVIDENCE_DIR / "Learnings"
# Legacy aliases (backward compat)
EVIDENCE_DECISIONS_DIR = DECISIONS_DIR
EVIDENCE_INTENTS_DIR = INTENTS_DIR
EVIDENCE_ANTI_PATTERNS_DIR = ANTI_PATTERNS_DIR
# Episodic Memory — top-level brain region (separate from Hippocampus)
EPISODIC_DIR = ROOT_DIR / "Episodic_Memory"
# Legacy aliases
PRIVATE_EVIDENCE_DIR = PRIVATE_DIR
PRIVATE_PATTERNS_DIR = PRIVATE_DIR
PRIVATE_LESSONS_DIR = PRIVATE_DIR

# LEGACY: .md paths used ONLY as dict lookup keys in _MD_TO_JSONL maps. No .md files exist.
LESSONS_FILE = LESSONS_DIR / "auto_lessons.md"
PATTERNS_FILE = PATTERNS_DIR / "auto_patterns.md"
FAILS_FILE = FAILS_DIR / "auto_fails.md"

# JSONL brain files (Phase 1 — structured knowledge storage)
LESSONS_JSONL = LESSONS_DIR / "auto_lessons.jsonl"
PATTERNS_JSONL = PATTERNS_DIR / "auto_patterns.jsonl"
FAILS_JSONL = FAILS_DIR / "auto_fails.jsonl"
PRIVATE_JSONL = PRIVATE_DIR / "private_lessons.jsonl"

# Decisions JSONL (N0 — conversational decision memory)
DECISIONS_JSONL = DECISIONS_DIR / "auto_decisions.jsonl"

# Map knowledge types to JSONL paths
BRAIN_JSONL_FILES = {
    "lesson": LESSONS_JSONL,
    "failure": FAILS_JSONL,
    "pattern": PATTERNS_JSONL,
    "private": PRIVATE_JSONL,
    "decision": DECISIONS_JSONL,
}
DECISIONS_FILE = EVIDENCE_DECISIONS_DIR / "auto_decisions.json"
CURRENT_SESSION_FILE = EVIDENCE_SESSIONS_DIR / "current_session.json"
AGENT_SESSIONS_FILE = EVIDENCE_SESSIONS_DIR / "agent_sessions.json"
ANTI_PATTERNS_ACTIVE_FILE = EVIDENCE_ANTI_PATTERNS_DIR / "anti_patterns_active.json"

# Declarative facts (Symposium P0 — March 2026)
FACTS_FILE = KNOWLEDGE_DIR / "auto_facts.json"
# Knowledge graph — lives at Hippocampus root (spans all regions)
GRAPH_FILE = HIPPOCAMPUS_DIR / "knowledge_graph.json"
# Episodic index (Symposium P1 — Phase 3)
EPISODIC_INDEX_FILE = EPISODIC_DIR / "episodic_index.json"

# Specific files
GROWTH_METRICS = EVIDENCE_METRICS_DIR / "growth_metrics.json"
RETRIEVAL_METRICS = EVIDENCE_METRICS_DIR / "retrieval_metrics.json"
BRAIN_MAP = EVIDENCE_DIR / "brain_map.json"
REFACTOR_LOG = EVIDENCE_DIR / "auto_refactor_log.json"
BENCHMARKS_FILE = EVIDENCE_DIR / "benchmarks.json"
WANTS_FILE = EVIDENCE_DIR / "wants_tracking.json"

# Package subdirectories (resolved relative to THIS file, not ROOT_DIR)
PKG_ROOT = Path(__file__).resolve().parent.parent  # pulse/
DAEMONS_DIR = PKG_ROOT / "daemons"
AGENTS_DIR = PKG_ROOT / "agents"
WORKERS_DIR = PKG_ROOT / "workers"
BRAIN_TOOLS_DIR = PKG_ROOT / "brain"
TASKS_DIR = PKG_ROOT / "tasks"
ADMIN_DIR = PKG_ROOT / "admin"
CONFIG_DIR = PKG_ROOT / "config"
CONFIG_FILE = CONFIG_DIR / "brain_config.json"

# Non-code paths
PREFRONTAL_DIR = ROOT_DIR / "Prefrontal_Cortex"
PREFRONTAL_WORKING = PREFRONTAL_DIR / "Working_Memory"
ACTIVE_NEURAL_STATE = PREFRONTAL_WORKING / "ACTIVE_NEURAL_STATE.md"
STATE_DIR = get_state_dir()
WORKING_MEMORY_DIR = STATE_DIR / "working_memory"  # Dimension F session buffers
TASK_BOARD_FILE = Path(os.environ.get("PULSE_TASK_BOARD", str(STATE_DIR / "task_board.json")))
AGENT_REGISTRY_FILE = STATE_DIR / "agent_registry.json"
# Dev-mode only paths (governance lives in repo, not user scaffold)
PROTOCOL_DIR = ROOT_DIR / "Corpus_Callosum" / "Protocols"
SKILLS_DIR = ROOT_DIR / ".cursor" / "skills"
CONSTITUTION_FILE = ROOT_DIR / "Corpus_Callosum" / "CONSTITUTION.md"

# State files
PID_FILE = STATE_DIR / "pulse_orchestrator.pid"
DAEMON_PIDS_FILE = STATE_DIR / "daemon_pids.json"
WORKER_PIDS_FILE = STATE_DIR / "worker_pids.json"
BRIDGE_STATE_FILE = STATE_DIR / "pulse_bridge_state.json"

# === RE-EXPORTS from brain_io ===
from .brain_io import (  # noqa: F401
    _get_lock, _NoOpLock, _acquire,
    load_json, load_json_dict, save_json, atomic_update_json, load_config,
)

# === RE-EXPORTS from brain_analysis ===
from .brain_analysis import (  # noqa: F401
    truncate_at_sentence, text_similarity, search_relevance, extract_domain,
    count_lines, count_json_items, count_md_sections,
    compute_weighted_count, compute_confidence_decay,
    detect_cross_domain_patterns, get_brain_density,
)

# === SHARED UTILITIES ===


def _make_fingerprint(text: str) -> str:
    """Canonical content fingerprint — MD5 of normalized first 200 chars, 16-hex."""
    t = re.sub(r'^\[?(claude|gemini|codex|grok|opus|sonnet|haiku|flash)\]?[:\s]+', '', text.lower().strip())
    t = re.sub(r'\d{4}-\d{2}-\d{2}[t ]\d{2}:\d{2}[:\d.]*[z+\d:]*\s*', '', t)
    t = re.sub(r'\s+', ' ', re.sub(r'[^a-z0-9 ]', '', t)).strip()
    return hashlib.md5(t[:200].encode()).hexdigest()[:16]


def now_iso() -> str:
    """Return current UTC time as ISO 8601 string."""
    return datetime.now(timezone.utc).isoformat()


def read_jsonl_entries(jsonl_path: Path) -> list[dict]:
    """Read all entries from a JSONL brain file. Returns [] on missing/empty/error."""
    if not jsonl_path.exists() or jsonl_path.stat().st_size == 0:
        return []
    entries = []
    with open(jsonl_path, "r", encoding="utf-8") as f:
        for line in f:
            line = line.strip()
            if line:
                try:
                    entries.append(json.loads(line))
                except json.JSONDecodeError:
                    continue
    return entries


def _extract_jsonl_titles(jsonl_path: Path, max_chars: int = 120, min_chars: int = 10) -> list[dict]:
    """Shared JSONL title+metadata extractor for boot loaders and neural render."""
    return [{"title": (r.get("title") or r.get("content", ""))[:max_chars],
             "confidence": r.get("confidence", 0.5), "evidence_count": r.get("evidence_count", 1),
             "domain": r.get("domain", "general"), "source": r.get("agent", "unknown"),
             "timestamp": str(r.get("timestamp") or "")[:10], "salience": r.get("salience", 2.0)}
            for r in read_jsonl_entries(jsonl_path)
            if len((r.get("title") or r.get("content", ""))[:max_chars]) >= min_chars]


def load_env(env_file: Path | None = None) -> None:
    """Load .env file into os.environ (skips already-set keys)."""
    path = env_file or (ROOT_DIR / ".env")
    if not path.exists():
        return
    with open(path, "r", encoding="utf-8") as f:
        for line in f:
            line = line.strip()
            if not line or line.startswith("#"):
                continue
            if "=" in line:
                key, value = line.split("=", 1)
                value = value.strip()
                if len(value) >= 2 and value[0] == value[-1] and value[0] in ('"', "'"):
                    value = value[1:-1]
                os.environ.setdefault(key.strip(), value)


def is_process_running(pid: int) -> bool:
    """Check if a process is alive (cross-platform)."""
    if os.name == "nt":
        try:
            out = subprocess.check_output(
                ["tasklist", "/FI", f"PID eq {pid}"],
                stderr=subprocess.DEVNULL,
                creationflags=0x08000000,
            ).decode()
            return str(pid) in out
        except Exception:
            return False
    else:
        try:
            os.kill(pid, 0)
            return True
        except OSError:
            return False


# === SCAFFOLD (stays here — uses path constants directly) ===


def _ensure_file(path: Path, content: str):
    if path.exists():
        return
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(content, encoding="utf-8")


def _ensure_json(path: Path, is_dict: bool = False):
    default = "{}" if is_dict else "[]"
    _ensure_file(path, default + "\n")


def ensure_brain_scaffold():
    """Ensures core dirs exist. Brain data lives in SQLite (pulse_brain.db).
    Only operational session dirs + non-Hippocampus dirs are created here."""
    # Operational session dir (bridge writes here)
    EVIDENCE_SESSIONS_DIR.mkdir(parents=True, exist_ok=True)
    # Non-Hippocampus dirs (state, config, working memory)
    PREFRONTAL_WORKING.mkdir(parents=True, exist_ok=True)
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    STATE_DIR.mkdir(parents=True, exist_ok=True)
    (STATE_DIR / "completed").mkdir(parents=True, exist_ok=True)
    # Operational JSON files (NOT brain knowledge — session/config only)
    for p in [EVIDENCE_SESSIONS_DIR / "current_session.json",
              EVIDENCE_SESSIONS_DIR / "agent_sessions.json"]:
        _ensure_json(p, is_dict=False)
    for p in [TASK_BOARD_FILE, AGENT_REGISTRY_FILE,
              CONFIG_DIR / "brain_config.json", STATE_DIR / "pulse_bridge_state.json"]:
        _ensure_json(p, is_dict=True)
    _ensure_file(ACTIVE_NEURAL_STATE, "# ACTIVE_NEURAL_STATE\n")

# Call to ensure directories/files exist on import
ensure_brain_scaffold()
