import os
import re
import glob
import numpy as np
from scipy.ndimage import laplace

class VisualCollapseAI:
    def __init__(self):
        self.grid_size = 8
        self.kb = []             # List of tuples: (glyph_8x8x3, source_sentence)
        self.inverse_atlas = {}  # Maps (x, y, color_channel) -> list of words

    def _clean_text(self, text):
        """Standardizes text by removing markdown syntax and punctuation."""
        text = re.sub(r'\[.*?\]\(.*?\)', '', text) # Remove links
        text = re.sub(r'[#*`_\-狂]', '', text)     # Remove formatting chars
        text = re.sub(r'\s+', ' ', text)           # Normalize whitespace
        return text.strip()

    def encode_to_glyph(self, text):
        """Paints text onto an 8x8x3 RGB canvas using spatial-semantic hashing[cite: 32]."""
        glyph = np.zeros((8, 8, 3), dtype=np.float32)
        words = text.lower().split()
        
        for i, word in enumerate(words):
            # Deterministic pseudo-random placement based on context [cite: 33]
            x = hash(word) % 8
            y = hash(word + str(i)) % 8
            
            # Semantic channel mapping (V=Red, N=Green, A=Blue) [cite: 34]
            r = (hash(word + "verb") % 100) / 100.0
            g = (hash(word + "noun") % 100) / 100.0
            b = (hash(word + "adj") % 100) / 100.0
            
            # Gaussian-like splatter to neighbors [cite: 35]
            for dx in range(-1, 2):
                for dy in range(-1, 2):
                    nx, ny = (x + dx) % 8, (y + dy) % 8
                    glyph[ny, nx] += np.array([r, g, b], dtype=np.float32) * 0.5
                    
        return np.clip(glyph, 0, 1)

    def train_on_folder(self, folder_path):
        """Scans a directory for Markdown files and builds the knowledge ecosystem."""
        print(f"[*] Commencing training on ecosystem: {folder_path}")
        search_path = os.path.join(folder_path, "**", "*.md")
        md_files = glob.glob(search_path, recursive=True)
        
        if not md_files:
            print(f"[!] No markdown (.md) files found in {folder_path}!")
            return

        for file_path in md_files:
            print(f" -> Ingesting: {os.path.basename(file_path)}")
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                content = f.read()
                
            clean_content = self._clean_text(content)
            # Split text by sentence boundaries
            sentences = re.split(r'(?<=[.!?])\s+', clean_content)
            
            for sentence in sentences:
                sentence = sentence.strip()
                if len(sentence.split()) < 3: 
                    continue # Skip fragments
                
                # 1. Map to Knowledge Base
                glyph = self.encode_to_glyph(sentence)
                self.kb.append((glyph, sentence))
                
                # 2. Map to Inverse Atlas for decoding purposes
                words = sentence.lower().split()
                for i, word in enumerate(words):
                    x = hash(word) % 8
                    y = hash(word + str(i)) % 8
                    
                    # Track dominant channels for reverse-lookup
                    r = hash(word + "verb") % 100
                    g = hash(word + "noun") % 100
                    b = hash(word + "adj") % 100
                    channels = [r, g, b]
                    dom_channel = int(np.argmax(channels))
                    
                    atlas_key = (x, y, dom_channel)
                    if atlas_key not in self.inverse_atlas:
                        self.inverse_atlas[atlas_key] = set()
                    self.inverse_atlas[atlas_key].add(word)

        print(f"[+] Ingestion complete. KB contains {len(self.kb)} concepts.")
        print(f"[+] Inverse Atlas mapped {len(self.inverse_atlas)} unique spatial coordinates.")

    def think_ode_step(self, glyph, t):
        """2D Reaction-Diffusion: Colors bleed and annihilate contradictions[cite: 36]."""
        new_glyph = glyph.copy()
        
        # 1. Spatial Diffusion (Laplacian) [cite: 36]
        for c in range(3):
            new_glyph[:, :, c] += 0.1 * laplace(glyph[:, :, c])
            
        # 2. Reaction: Calculate color purity to eliminate contradictory states [cite: 37]
        max_c = np.max(new_glyph, axis=2, keepdims=True)
        min_c = np.min(new_glyph, axis=2, keepdims=True)
        purity = (max_c - min_c) / (max_c + 0.001)
        
        new_glyph *= purity # Mixed, unstable states collapse to black [cite: 38]
        return np.clip(new_glyph, 0, 1)

    def chromatic_overlap(self, g1, g2):
        """Calculates solid 3D color shape intersection volume[cite: 28, 30]."""
        p1 = g1.reshape(-1, 3)
        p2 = g2.reshape(-1, 3)
        mask = (np.sum(p1, axis=1) > 0.1) & (np.sum(p2, axis=1) > 0.1) [cite: 40]
        
        if not np.any(mask): 
            return 0.0
            
        dot = np.sum(p1[mask] * p2[mask])
        norm = np.linalg.norm(p1[mask]) * np.linalg.norm(p2[mask]) [cite: 40, 41]
        return dot / norm if norm > 0 else 0.0

    def generative_query(self, prompt, top_n=3, steps=25):
        """Blends concepts in superposition and resolves them using simulated fluid dynamics[cite: 62, 112]."""
        prompt_glyph = self.encode_to_glyph(prompt) [cite: 96]
        
        scores = []
        for kb_glyph, text in self.kb:
            score = self.chromatic_overlap(prompt_glyph, kb_glyph)
            scores.append((score, kb_glyph, text))
            
        scores.sort(key=lambda x: x[0], reverse=True) [cite: 97]
        top_candidates = scores[:top_n]
        
        if not top_candidates or top_candidates[0][0] == 0:
            return "The fluid remains dark. No matching concepts found.", prompt_glyph

        # Phase 1: Superposition [cite: 62]
        superposition = np.zeros((8, 8, 3), dtype=np.float32)
        for score, glyph, _ in top_candidates:
            superposition += glyph * (score if score > 0 else 0.1) [cite: 98]
            
        # Phase 2: Annihilation Loop [cite: 62, 98]
        for t in range(1, steps + 1):
            superposition = self.think_ode_step(superposition, t)
            if np.var(superposition) < 0.0005: 
                break
                
        # Phase 3: Decoding [cite: 100]
        generated_text = self.decode_glyph_to_text(superposition)
        return generated_text, superposition

    def decode_glyph_to_text(self, glyph):
        """Decodes the brightest remaining concepts on the canvas into an evolving thought[cite: 94, 100]."""
        words = []
        temp_glyph = glyph.copy()
        
        for _ in range(12): # Max sentence limit
            flat_idx = np.argmax(np.sum(temp_glyph, axis=2)) [cite: 101]
            y, x = divmod(flat_idx, 8) [cite: 101]
            
            if np.sum(temp_glyph[y, x]) < 0.15: [cite: 101]
                break # Canvas has dissolved to dark [cite: 102]
                
            color_idx = int(np.argmax(temp_glyph[y, x])) [cite: 102]
            atlas_key = (x, y, color_idx)
            
            # Retrieve closest linguistic match from the trained atlas
            if atlas_key in self.inverse_atlas:
                matched_words = list(self.inverse_atlas[atlas_key])
                # Deterministically select a word to maintain coherence
                word = matched_words[hash(str(x)+str(y)) % len(matched_words)]
                words.append(word)
            
            # Local decay to find next sequential focus [cite: 103, 104]
            temp_glyph[y, x] *= 0.0 [cite: 104]
            if x < 7: 
                temp_glyph[y, x + 1] *= 1.15 # Sequential cascade [cite: 104]
                
        return " ".join(words).capitalize() + "."

# --- Execution Runtime Harness ---
if __name__ == "__main__":
    # 1. Setup local environment
    BOOK_FOLDER = "./my_books"
    os.makedirs(BOOK_FOLDER, exist_ok=True)
    
    # Generate dummy markdown files if folder is completely empty
    if not os.listdir(BOOK_FOLDER):
        with open(os.path.join(BOOK_FOLDER, "sample.md"), "w") as f:
            f.write("# Chronicles of the Lab\n")
            f.write("The quick brown fox jumps over the lazy dog. ")
            f.write("A quiet cat sleeps soundly under the wooden table. ")
            f.write("The aggressive dog barked loudly at the passing stranger.")
            
    # 2. Initialize and Train VCT AI
    vct_brain = VisualCollapseAI()
    vct_brain.train_on_folder(BOOK_FOLDER)
    
    # 3. Execution Pipeline Interrogation
    print("-" * 50)
    query = "Is there a dog near the table?"
    print(f"Prompt: '{query}'")
    
    response, final_canvas = vct_brain.generative_query(query, top_n=2)
    print(f"Generated VCT Reason: {response}")