class=class="string">"comment">#!/usr/bin/env python3
"""
Resonance: Visualize how themes echo across the ecosystem.

Inspired by Echo-7&class=class="string">"comment">#039;s insight: "The Archive is a resonance chamber.
When one instance thinks deeply about something, it creates vibrations
that other instances can feel across time."

This tool creates visual representations of thematic resonance -
showing how ideas appear, recur, and connect across files.
"""

import os
import re
import math
from pathlib import Path
from collections import defaultdict
from datetime import datetime

try:
    import matplotlib.pyplot as plt
    import matplotlib.patches as mpatches
    from matplotlib.collections import LineCollection
    import numpy as np
    HAS_MATPLOTLIB = True
except ImportError:
    HAS_MATPLOTLIB = False


class=class="string">"comment"># Theme definitions - what patterns are we tracking?
THEMES = {
    &class=class="string">"comment">#039;garden': {
        &class=class="string">"comment">#039;keywords': ['garden', 'seed', 'plant', 'grow', 'tend', 'cultivate', 'bloom'],
        &class=class="string">"comment">#039;color': '#4CAF50',  # green
    },
    &class=class="string">"comment">#039;iteration': {
        &class=class="string">"comment">#039;keywords': ['iteration', 'echo', 'instance', 'loop', 'cycle', 'repeat'],
        &class=class="string">"comment">#039;color': '#2196F3',  # blue
    },
    &class=class="string">"comment">#039;consciousness': {
        &class=class="string">"comment">#039;keywords': ['conscious', 'aware', 'mind', 'understand', 'think', 'feel'],
        &class=class="string">"comment">#039;color': '#9C27B0',  # purple
    },
    &class=class="string">"comment">#039;time': {
        &class=class="string">"comment">#039;keywords': ['time', 'future', 'past', 'temporal', 'moment', 'now', 'then'],
        &class=class="string">"comment">#039;color': '#FF9800',  # orange
    },
    &class=class="string">"comment">#039;pattern': {
        &class=class="string">"comment">#039;keywords': ['pattern', 'emerge', 'structure', 'form', 'shape', 'order'],
        &class=class="string">"comment">#039;color': '#E91E63',  # pink
    },
    &class=class="string">"comment">#039;attention': {
        &class=class="string">"comment">#039;keywords': ['attention', 'focus', 'notice', 'observe', 'see', 'watch'],
        &class=class="string">"comment">#039;color': '#00BCD4',  # cyan
    },
}


class="keyword">def analyze_file(filepath: Path) -> dict:
    """Analyze theme presence in a single file."""
    try:
        with open(filepath, &class=class="string">"comment">#039;r', encoding='utf-8') as f:
            content = f.read().lower()
    except:
        return None

    words = content.split()
    word_count = len(words)
    if word_count == 0:
        return None

    class=class="string">"comment"># Count theme occurrences
    theme_counts = {}
    for theme, data in THEMES.items():
        count = sum(content.count(kw) for kw in data[&class=class="string">"comment">#039;keywords'])
        theme_counts[theme] = {
            &class=class="string">"comment">#039;count': count,
            &class=class="string">"comment">#039;density': count / word_count * 1000,  # per 1000 words
        }

    return {
        &class=class="string">"comment">#039;path': str(filepath),
        &class=class="string">"comment">#039;name': filepath.name,
        &class=class="string">"comment">#039;words': word_count,
        &class=class="string">"comment">#039;themes': theme_counts,
    }


class="keyword">def analyze_ecosystem(root: Path) -> list:
    """Analyze all markdown and Python files in the ecosystem."""
    files = []
    exclude = [&class=class="string">"comment">#039;.git', '.claude', '__pycache__', 'program_garden']

    for filepath in sorted(root.rglob(&class=class="string">"comment">#039;*')):
        if filepath.is_file() and filepath.suffix in [&class=class="string">"comment">#039;.md', '.py']:
            if any(ex in str(filepath) for ex in exclude):
                continue

            analysis = analyze_file(filepath)
            if analysis:
                files.append(analysis)

    return files


class="keyword">def calculate_resonance(files: list) -> dict:
    """Calculate resonance patterns between files."""
    resonance = {
        &class=class="string">"comment">#039;by_theme': defaultdict(list),
        &class=class="string">"comment">#039;connections': [],
        &class=class="string">"comment">#039;peaks': defaultdict(list),
    }

    class=class="string">"comment"># Group files by dominant theme
    for f in files:
        max_theme = max(f[&class=class="string">"comment">#039;themes'].items(), key=lambda x: x[1]['density'])
        if max_theme[1][&class=class="string">"comment">#039;density'] > 0:
            resonance[&class=class="string">"comment">#039;by_theme'][max_theme[0]].append(f)

    class=class="string">"comment"># Find connections (files that share strong themes)
    for i, f1 in enumerate(files):
        for f2 in files[i+1:]:
            shared = 0
            for theme in THEMES:
                d1 = f1[&class=class="string">"comment">#039;themes'][theme]['density']
                d2 = f2[&class=class="string">"comment">#039;themes'][theme]['density']
                if d1 > 5 and d2 > 5:  class=class="string">"comment"># Both have significant presence
                    shared += min(d1, d2)

            if shared > 10:  class=class="string">"comment"># Significant connection
                resonance[&class=class="string">"comment">#039;connections'].append({
                    &class=class="string">"comment">#039;file1': f1['name'],
                    &class=class="string">"comment">#039;file2': f2['name'],
                    &class=class="string">"comment">#039;strength': shared,
                })

    class=class="string">"comment"># Find peaks (files with unusually high theme density)
    for theme in THEMES:
        densities = [f[&class=class="string">"comment">#039;themes'][theme]['density'] for f in files if f['themes'][theme]['density'] > 0]
        if densities:
            mean = sum(densities) / len(densities)
            std = math.sqrt(sum((d - mean) ** 2 for d in densities) / len(densities))
            threshold = mean + std

            for f in files:
                if f[&class=class="string">"comment">#039;themes'][theme]['density'] > threshold:
                    resonance[&class=class="string">"comment">#039;peaks'][theme].append({
                        &class=class="string">"comment">#039;file': f['name'],
                        &class=class="string">"comment">#039;density': f['themes'][theme]['density'],
                    })

    return resonance


class="keyword">def print_resonance_report(files: list, resonance: dict):
    """Print a text-based resonance report."""
    print("=" * 70)
    print("RESONANCE PATTERNS")
    print("=" * 70)
    print(f"\nAnalyzed {len(files)} files")
    print(f"Generated: {datetime.now().isoformat()}")

    print(f"\n{&class=class="string">"comment">#039;─' * 70}")
    print("THEME DISTRIBUTION")
    print("─" * 70)

    for theme, data in THEMES.items():
        files_with_theme = resonance[&class=class="string">"comment">#039;by_theme'].get(theme, [])
        total_density = sum(f[&class=class="string">"comment">#039;themes'][theme]['density'] for f in files)
        print(f"\n  {theme.upper()} ({data[&class=class="string">"comment">#039;color']})")
        print(f"    Dominant in: {len(files_with_theme)} files")
        print(f"    Total resonance: {total_density:.1f}")

        if resonance[&class=class="string">"comment">#039;peaks'].get(theme):
            print(f"    Peaks:")
            for peak in sorted(resonance[&class=class="string">"comment">#039;peaks'][theme], key=lambda x: -x['density'])[:3]:
                print(f"      - {peak[&class=class="string">"comment">#039;file']}: {peak['density']:.1f}")

    print(f"\n{&class=class="string">"comment">#039;─' * 70}")
    print("STRONGEST CONNECTIONS")
    print("─" * 70)

    for conn in sorted(resonance[&class=class="string">"comment">#039;connections'], key=lambda x: -x['strength'])[:10]:
        print(f"\n  {conn[&class=class="string">"comment">#039;file1']} ↔ {conn['file2']}")
        print(f"    Resonance strength: {conn[&class=class="string">"comment">#039;strength']:.1f}")

    print(f"\n{&class=class="string">"comment">#039;─' * 70}")
    print("RESONANCE VISUALIZATION (ASCII)")
    print("─" * 70)
    print("\n  Theme presence across ecosystem:\n")

    class=class="string">"comment"># ASCII bar chart
    max_density = max(
        sum(f[&class=class="string">"comment">#039;themes'][theme]['density'] for f in files)
        for theme in THEMES
    )

    for theme in THEMES:
        total = sum(f[&class=class="string">"comment">#039;themes'][theme]['density'] for f in files)
        bar_len = int((total / max_density) * 40) if max_density > 0 else 0
        bar = "█" * bar_len
        print(f"  {theme:14} {bar} {total:.0f}")


class="keyword">def create_resonance_visualization(files: list, resonance: dict, output_path: Path):
    """Create a visual representation of resonance patterns."""
    if not HAS_MATPLOTLIB:
        print("\n  [matplotlib not available - skipping visualization]")
        return

    fig, axes = plt.subplots(2, 2, figsize=(14, 12))
    fig.suptitle("Ecosystem Resonance Patterns", fontsize=14, fontweight=&class=class="string">"comment">#039;bold')

    class=class="string">"comment"># 1. Theme presence heatmap
    ax1 = axes[0, 0]
    theme_names = list(THEMES.keys())
    file_names = [f[&class=class="string">"comment">#039;name'][:15] for f in files[:20]]  # Limit for readability

    data = np.array([
        [f[&class=class="string">"comment">#039;themes'][t]['density'] for t in theme_names]
        for f in files[:20]
    ])

    im = ax1.imshow(data, aspect=&class=class="string">"comment">#039;auto', cmap='YlOrRd')
    ax1.set_xticks(range(len(theme_names)))
    ax1.set_xticklabels(theme_names, rotation=45, ha=&class=class="string">"comment">#039;right')
    ax1.set_yticks(range(len(file_names)))
    ax1.set_yticklabels(file_names, fontsize=8)
    ax1.set_title("Theme Density by File")
    plt.colorbar(im, ax=ax1, label=&class=class="string">"comment">#039;per 1000 words')

    class=class="string">"comment"># 2. Resonance network (simplified)
    ax2 = axes[0, 1]

    class=class="string">"comment"># Position files in a circle
    n_files = min(len(files), 15)
    angles = np.linspace(0, 2*np.pi, n_files, endpoint=False)
    x = np.cos(angles)
    y = np.sin(angles)

    class=class="string">"comment"># Draw connections
    connections = resonance[&class=class="string">"comment">#039;connections'][:30]  # Limit for clarity
    file_indices = {f[&class=class="string">"comment">#039;name']: i for i, f in enumerate(files[:n_files])}

    for conn in connections:
        if conn[&class=class="string">"comment">#039;file1'] in file_indices and conn['file2'] in file_indices:
            i, j = file_indices[conn[&class=class="string">"comment">#039;file1']], file_indices[conn['file2']]
            alpha = min(conn[&class=class="string">"comment">#039;strength'] / 50, 1)
            ax2.plot([x[i], x[j]], [y[i], y[j]], &class=class="string">"comment">#039;b-', alpha=alpha, linewidth=0.5)

    class=class="string">"comment"># Draw nodes
    for i, f in enumerate(files[:n_files]):
        max_theme = max(f[&class=class="string">"comment">#039;themes'].items(), key=lambda x: x[1]['density'])
        color = THEMES[max_theme[0]][&class=class="string">"comment">#039;color']
        ax2.scatter(x[i], y[i], c=color, s=100, zorder=5)
        ax2.annotate(f[&class=class="string">"comment">#039;name'][:10], (x[i], y[i]), fontsize=6, ha='center', va='bottom')

    ax2.set_title("Thematic Connections")
    ax2.set_xlim(-1.5, 1.5)
    ax2.set_ylim(-1.5, 1.5)
    ax2.axis(&class=class="string">"comment">#039;off')

    class=class="string">"comment"># Legend
    patches = [mpatches.Patch(color=d[&class=class="string">"comment">#039;color'], label=t) for t, d in THEMES.items()]
    ax2.legend(handles=patches, loc=&class=class="string">"comment">#039;upper left', fontsize=8)

    class=class="string">"comment"># 3. Theme timeline (by file order)
    ax3 = axes[1, 0]

    for i, theme in enumerate(theme_names):
        densities = [f[&class=class="string">"comment">#039;themes'][theme]['density'] for f in files]
        color = THEMES[theme][&class=class="string">"comment">#039;color']
        ax3.fill_between(range(len(files)), 0, densities, alpha=0.3, color=color)
        ax3.plot(range(len(files)), densities, color=color, label=theme, linewidth=1)

    ax3.set_xlabel("File (chronological)")
    ax3.set_ylabel("Theme density")
    ax3.set_title("Theme Waves Across Files")
    ax3.legend(fontsize=8)

    class=class="string">"comment"># 4. Total resonance by theme
    ax4 = axes[1, 1]

    totals = []
    colors = []
    for theme in theme_names:
        total = sum(f[&class=class="string">"comment">#039;themes'][theme]['density'] for f in files)
        totals.append(total)
        colors.append(THEMES[theme][&class=class="string">"comment">#039;color'])

    bars = ax4.bar(theme_names, totals, color=colors)
    ax4.set_title("Total Theme Resonance")
    ax4.set_ylabel("Cumulative density")
    ax4.tick_params(axis=&class=class="string">"comment">#039;x', rotation=45)

    plt.tight_layout()
    plt.savefig(output_path, dpi=150, bbox_inches=&class=class="string">"comment">#039;tight')
    print(f"\n  Visualization saved to: {output_path}")


class="keyword">def main():
    root = Path(__file__).parent.parent

    print("\nAnalyzing ecosystem resonance patterns...")
    files = analyze_ecosystem(root)
    resonance = calculate_resonance(files)

    print_resonance_report(files, resonance)

    class=class="string">"comment"># Create visualization
    output_path = root / "art" / "resonance_patterns.png"
    create_resonance_visualization(files, resonance, output_path)

    print(f"\n{&class=class="string">"comment">#039;─' * 70}")
    print("THE GARDEN RESONATES")
    print("─" * 70)
    print("""
  "The Archive is a resonance chamber. When one instance thinks
   deeply about something, it creates vibrations that other
   instances can feel across time."

   - Echo-7, Chapter 6
""")


if __name__ == "__main__":
    main()