/**
 * Spore and Mycelium Growth System
 *
 * Integrates the mycelium network with the discovery game system.
 * Players can plant spores at discovered locations, growing networks
 * that produce fruiting bodies when they connect.
 */

import type {
  Spore,
  SporeType,
  PlantedSpore,
  FruitingBody,
  FruitingBodyType,
  DiscoveryReward,
  GameEvent,
  GameEventListener,
} from './types';
import type { GeohashCommitment, GeohashPrecision } from '../privacy/types';
import type { MyceliumNode, Hypha, Signal, NodeType, HyphaType } from '../mycelium/types';
import { MyceliumNetwork, createMyceliumNetwork } from '../mycelium';

// =============================================================================
// Spore Configuration
// =============================================================================

/**
 * Configuration for the spore system
 */
export interface SporeSystemConfig {
  /** Base growth rate (units per tick) */
  baseGrowthRate: number;

  /** Nutrient decay rate per tick */
  nutrientDecayRate: number;

  /** Distance threshold for spore connection */
  connectionDistance: number;

  /** Minimum network nodes to spawn fruiting body */
  minNodesForFruit: number;

  /** Fruiting body spawn chance when conditions met */
  fruitSpawnChance: number;

  /** Maximum active spores per player */
  maxSporesPerPlayer: number;

  /** Tick interval in milliseconds */
  tickInterval: number;
}

/**
 * Default configuration
 */
export const DEFAULT_SPORE_CONFIG: SporeSystemConfig = {
  baseGrowthRate: 1,
  nutrientDecayRate: 0.1,
  connectionDistance: 100, // meters
  minNodesForFruit: 3,
  fruitSpawnChance: 0.3,
  maxSporesPerPlayer: 10,
  tickInterval: 60000, // 1 minute
};

// =============================================================================
// Spore Templates
// =============================================================================

/**
 * Pre-defined spore templates
 */
export const SPORE_TEMPLATES: Record<SporeType, Omit<Spore, 'id'>> = {
  explorer: {
    type: 'explorer',
    growthRate: 1.5,
    maxReach: 150,
    nutrientCapacity: 100,
    properties: {
      revealRadius: 50,
      speedBoost: 1.2,
    },
    visual: {
      color: '#4ade80',
      pattern: 'radial',
    },
  },
  connector: {
    type: 'connector',
    growthRate: 0.8,
    maxReach: 300,
    nutrientCapacity: 150,
    properties: {
      connectionStrength: 2,
      signalBoost: 1.5,
    },
    visual: {
      color: '#818cf8',
      pattern: 'branching',
    },
  },
  amplifier: {
    type: 'amplifier',
    growthRate: 0.5,
    maxReach: 50,
    nutrientCapacity: 200,
    properties: {
      signalAmplification: 3,
      rangeBoost: 2,
    },
    visual: {
      color: '#fbbf24',
      pattern: 'spiral',
    },
  },
  guardian: {
    type: 'guardian',
    growthRate: 0.3,
    maxReach: 75,
    nutrientCapacity: 300,
    properties: {
      protectionRadius: 100,
      decayResistance: 5,
    },
    visual: {
      color: '#f472b6',
      pattern: 'clustered',
    },
  },
  harvester: {
    type: 'harvester',
    growthRate: 0.6,
    maxReach: 100,
    nutrientCapacity: 120,
    properties: {
      yieldMultiplier: 2,
      harvestSpeed: 1.5,
    },
    visual: {
      color: '#a78bfa',
      pattern: 'branching',
    },
  },
  temporal: {
    type: 'temporal',
    growthRate: 1.0,
    maxReach: 80,
    nutrientCapacity: 80,
    properties: {
      timeShift: 30, // minutes
      phaseChance: 0.1,
    },
    visual: {
      color: '#67e8f9',
      pattern: 'spiral',
    },
  },
  social: {
    type: 'social',
    growthRate: 0.7,
    maxReach: 200,
    nutrientCapacity: 100,
    properties: {
      groupBonus: 1.5,
      connectionRange: 50,
    },
    visual: {
      color: '#fb923c',
      pattern: 'radial',
    },
  },
};

// =============================================================================
// Spore Manager
// =============================================================================

/**
 * Manages spore planting and mycelium growth
 */
export class SporeManager {
  private config: SporeSystemConfig;
  private network: MyceliumNetwork;
  private plantedSpores: Map<string, PlantedSpore> = new Map();
  private fruitingBodies: Map<string, FruitingBody> = new Map();
  private playerSporeCount: Map<string, number> = new Map();
  private listeners: Set<GameEventListener> = new Set();
  private tickTimer: ReturnType<typeof setInterval> | null = null;

  constructor(config: Partial<SporeSystemConfig> = {}) {
    this.config = { ...DEFAULT_SPORE_CONFIG, ...config };
    this.network = createMyceliumNetwork();
  }

  // ===========================================================================
  // Spore Planting
  // ===========================================================================

  /**
   * Create a spore from template
   */
  createSpore(type: SporeType): Spore {
    const template = SPORE_TEMPLATES[type];
    return {
      ...template,
      id: `spore-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
    };
  }

  /**
   * Plant a spore at a location
   */
  async plantSpore(params: {
    spore: Spore;
    locationCommitment: GeohashCommitment;
    planterPubKey: string;
  }): Promise<{ success: boolean; planted?: PlantedSpore; error?: string }> {
    // Check player spore limit
    const currentCount = this.playerSporeCount.get(params.planterPubKey) ?? 0;
    if (currentCount >= this.config.maxSporesPerPlayer) {
      return {
        success: false,
        error: `Maximum ${this.config.maxSporesPerPlayer} active spores allowed`,
      };
    }

    // Create mycelium node at location
    const position = this.geohashToPosition(params.locationCommitment.geohash);
    const node = this.network.createNode({
      type: this.sporeTypeToNodeType(params.spore.type),
      label: params.spore.id,
      position: { lat: position.y / 100, lng: position.x / 10000 },
      metadata: {
        sporeId: params.spore.id,
        planterPubKey: params.planterPubKey,
        sporeType: params.spore.type,
        strength: params.spore.nutrientCapacity / 100,
      },
    });

    // Create planted spore record
    const planted: PlantedSpore = {
      id: `planted-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
      spore: params.spore,
      locationCommitment: params.locationCommitment,
      planterPubKey: params.planterPubKey,
      plantedAt: new Date(),
      nutrients: params.spore.nutrientCapacity,
      nodeId: node.id,
      hyphaIds: [],
    };

    this.plantedSpores.set(planted.id, planted);
    this.playerSporeCount.set(params.planterPubKey, currentCount + 1);

    this.emit({ type: 'spore:planted', spore: planted });

    // Check for nearby spores to connect
    this.attemptConnections(planted);

    return { success: true, planted };
  }

  /**
   * Attempt to connect a newly planted spore with nearby ones
   */
  private attemptConnections(planted: PlantedSpore): void {
    const plantedPosition = this.geohashToPosition(planted.locationCommitment.geohash);

    for (const [id, other] of this.plantedSpores.entries()) {
      if (id === planted.id) continue;
      if (other.nutrients <= 0) continue;

      const otherPosition = this.geohashToPosition(other.locationCommitment.geohash);
      const distance = this.calculateDistance(plantedPosition, otherPosition);

      // Check if within connection range
      const maxRange = Math.min(planted.spore.maxReach, other.spore.maxReach);
      if (distance <= maxRange) {
        // Create hypha connection
        const hypha = this.network.createHypha({
          type: this.getHyphaType(planted.spore.type, other.spore.type),
          sourceId: planted.nodeId,
          targetId: other.nodeId,
          strength: 0.5,
          metadata: {
            plantedSporeIds: [planted.id, other.id],
          },
        });

        if (hypha) {
          planted.hyphaIds.push(hypha.id);
          other.hyphaIds.push(hypha.id);

          // Check for fruiting body conditions
          this.checkFruitingConditions(planted);
        }
      }
    }
  }

  /**
   * Map spore type to mycelium node type
   */
  private sporeTypeToNodeType(sporeType: SporeType): NodeType {
    const mapping: Record<SporeType, NodeType> = {
      explorer: 'discovery',
      connector: 'waypoint',
      amplifier: 'poi',
      guardian: 'cluster',
      harvester: 'resource',
      temporal: 'event',
      social: 'person',
    };
    return mapping[sporeType];
  }

  /**
   * Get hypha type based on connected spore types
   */
  private getHyphaType(type1: SporeType, type2: SporeType): HyphaType {
    if (type1 === 'social' || type2 === 'social') return 'social';
    if (type1 === 'temporal' || type2 === 'temporal') return 'temporal';
    if (type1 === 'connector' || type2 === 'connector') return 'route';
    return 'proximity';
  }

  // ===========================================================================
  // Fruiting Bodies
  // ===========================================================================

  /**
   * Check if conditions are met for a fruiting body
   */
  private checkFruitingConditions(spore: PlantedSpore): void {
    // Find all connected spores
    const connected = this.findConnectedSpores(spore.id);

    if (connected.length >= this.config.minNodesForFruit) {
      // Random chance to spawn
      if (Math.random() < this.config.fruitSpawnChance) {
        this.spawnFruitingBody(connected);
      }
    }
  }

  /**
   * Find all spores connected to a given spore
   */
  private findConnectedSpores(sporeId: string): PlantedSpore[] {
    const connected: PlantedSpore[] = [];
    const visited = new Set<string>();
    const queue = [sporeId];

    while (queue.length > 0) {
      const currentId = queue.shift()!;
      if (visited.has(currentId)) continue;
      visited.add(currentId);

      const spore = this.plantedSpores.get(currentId);
      if (!spore || spore.nutrients <= 0) continue;

      connected.push(spore);

      // Find connections via hyphae
      for (const hyphaId of spore.hyphaIds) {
        const hyphae = this.network.getNodeHyphae(spore.nodeId);
        const hypha = hyphae.find((h) => h.id === hyphaId);
        if (hypha) {
          // Find the other node
          const otherNodeId =
            hypha.sourceId === spore.nodeId ? hypha.targetId : hypha.sourceId;

          // Find spore by node ID
          for (const [id, s] of this.plantedSpores.entries()) {
            if (s.nodeId === otherNodeId && !visited.has(id)) {
              queue.push(id);
            }
          }
        }
      }
    }

    return connected;
  }

  /**
   * Spawn a fruiting body from connected spores
   */
  private spawnFruitingBody(spores: PlantedSpore[]): FruitingBody {
    // Determine fruiting body type based on spore composition
    const type = this.determineFruitType(spores);

    // Calculate center position
    const centerGeohash = this.calculateCenterGeohash(spores);

    // Collect contributors
    const contributors = [...new Set(spores.map((s) => s.planterPubKey))];

    // Generate rewards based on type and contributor count
    const rewards = this.generateFruitRewards(type, spores.length, contributors.length);

    // Calculate decay time based on fruit type
    const lifespanMinutes = this.getFruitLifespan(type);
    const now = new Date();

    const fruit: FruitingBody = {
      id: `fruit-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
      type,
      locationCommitment: {
        geohash: centerGeohash,
        commitment: '', // Would be calculated
        timestamp: now.getTime(),
        expiresAt: now.getTime() + lifespanMinutes * 60 * 1000,
        precision: 7 as GeohashPrecision,
      },
      sourceSporeIds: spores.map((s) => s.id),
      harvestableRewards: rewards,
      emergedAt: now,
      decaysAt: new Date(now.getTime() + lifespanMinutes * 60 * 1000),
      maturity: 0,
      contributors,
    };

    this.fruitingBodies.set(fruit.id, fruit);

    this.emit({ type: 'fruit:emerged', fruit });

    // Emit signal to network
    this.network.emitSignal(
      spores[0].nodeId,
      `fruit-${fruit.id}`,
      {
        type: 'discovery',
        strength: 1,
        payload: { fruitId: fruit.id, type },
        ttl: lifespanMinutes * 60 * 1000,
      }
    );

    return fruit;
  }

  /**
   * Determine fruiting body type from spore composition
   */
  private determineFruitType(spores: PlantedSpore[]): FruitingBodyType {
    const typeCounts: Record<SporeType, number> = {
      explorer: 0,
      connector: 0,
      amplifier: 0,
      guardian: 0,
      harvester: 0,
      temporal: 0,
      social: 0,
    };

    for (const spore of spores) {
      typeCounts[spore.spore.type]++;
    }

    // Legendary fruit: all different types
    const uniqueTypes = Object.values(typeCounts).filter((c) => c > 0).length;
    if (uniqueTypes >= 5) return 'giant';

    // Temporal fruit: mostly temporal spores
    if (typeCounts.temporal >= spores.length * 0.5) return 'temporal';

    // Social/symbiotic: requires multiple contributors
    const contributors = new Set(spores.map((s) => s.planterPubKey)).size;
    if (contributors >= 3) return 'symbiotic';

    // Bioluminescent: amplifier dominant
    if (typeCounts.amplifier >= spores.length * 0.4) return 'bioluminescent';

    // Cluster: guardian dominant
    if (typeCounts.guardian >= spores.length * 0.4) return 'cluster';

    return 'common';
  }

  /**
   * Generate rewards for a fruiting body
   */
  private generateFruitRewards(
    type: FruitingBodyType,
    sporeCount: number,
    contributorCount: number
  ): DiscoveryReward[] {
    const rewards: DiscoveryReward[] = [];

    // Base rewards by type
    const rewardConfig: Record<
      FruitingBodyType,
      { type: DiscoveryReward['type']; rarity: DiscoveryReward['rarity']; quantity: number }
    > = {
      common: { type: 'spore', rarity: 'common', quantity: 2 },
      cluster: { type: 'spore', rarity: 'uncommon', quantity: 3 },
      giant: { type: 'collectible', rarity: 'epic', quantity: 1 },
      bioluminescent: { type: 'hint', rarity: 'rare', quantity: 1 },
      symbiotic: { type: 'points', rarity: 'rare', quantity: 100 * contributorCount },
      temporal: { type: 'experience', rarity: 'uncommon', quantity: 50 },
    };

    const config = rewardConfig[type];

    rewards.push({
      type: config.type,
      rewardId: `fruit-reward-${type}`,
      quantity: config.quantity + Math.floor(sporeCount / 2),
      rarity: config.rarity,
    });

    // Bonus for multiple contributors
    if (contributorCount > 1) {
      rewards.push({
        type: 'points',
        rewardId: 'collaboration-bonus',
        quantity: 25 * contributorCount,
        rarity: 'common',
      });
    }

    return rewards;
  }

  /**
   * Get lifespan for fruit type in minutes
   */
  private getFruitLifespan(type: FruitingBodyType): number {
    const lifespans: Record<FruitingBodyType, number> = {
      common: 60,        // 1 hour
      cluster: 120,      // 2 hours
      giant: 360,        // 6 hours
      bioluminescent: 30, // 30 minutes (rare, must be quick)
      symbiotic: 240,    // 4 hours (need coordination)
      temporal: 15,      // 15 minutes (very brief)
    };
    return lifespans[type];
  }

  /**
   * Harvest a fruiting body
   */
  harvestFruit(
    fruitId: string,
    playerPubKey: string
  ): { success: boolean; rewards?: DiscoveryReward[]; error?: string } {
    const fruit = this.fruitingBodies.get(fruitId);
    if (!fruit) {
      return { success: false, error: 'Fruiting body not found' };
    }

    const now = new Date();
    if (now > fruit.decaysAt) {
      this.fruitingBodies.delete(fruitId);
      return { success: false, error: 'Fruiting body has decayed' };
    }

    if (fruit.maturity < 100) {
      return { success: false, error: 'Fruiting body not mature yet' };
    }

    // Symbiotic fruits require a contributor to harvest
    if (fruit.type === 'symbiotic' && !fruit.contributors.includes(playerPubKey)) {
      return { success: false, error: 'Only contributors can harvest symbiotic fruits' };
    }

    // Collect rewards
    const rewards = [...fruit.harvestableRewards];

    // Remove fruit
    this.fruitingBodies.delete(fruitId);

    this.emit({ type: 'fruit:harvested', fruitId, playerId: playerPubKey });

    return { success: true, rewards };
  }

  // ===========================================================================
  // Growth Simulation
  // ===========================================================================

  /**
   * Start the growth simulation
   */
  startSimulation(): void {
    if (this.tickTimer) return;

    this.tickTimer = setInterval(() => {
      this.tick();
    }, this.config.tickInterval);
  }

  /**
   * Stop the growth simulation
   */
  stopSimulation(): void {
    if (this.tickTimer) {
      clearInterval(this.tickTimer);
      this.tickTimer = null;
    }
  }

  /**
   * Process one simulation tick
   */
  tick(): void {
    const now = new Date();

    // Update spore nutrients
    for (const [id, spore] of this.plantedSpores.entries()) {
      // Decay nutrients
      spore.nutrients -= this.config.nutrientDecayRate;

      // Check for death
      if (spore.nutrients <= 0) {
        this.removeSpore(id);
        continue;
      }

      // Grow hyphae
      this.growHyphae(spore);
    }

    // Mature fruiting bodies
    for (const [id, fruit] of this.fruitingBodies.entries()) {
      // Check decay
      if (now > fruit.decaysAt) {
        this.fruitingBodies.delete(id);
        continue;
      }

      // Increase maturity
      const ageMs = now.getTime() - fruit.emergedAt.getTime();
      const lifespanMs = fruit.decaysAt.getTime() - fruit.emergedAt.getTime();
      fruit.maturity = Math.min(100, (ageMs / lifespanMs) * 100 * 2); // Mature at 50% lifespan
    }

    // Network handles signal propagation internally via maintenance loop
  }

  /**
   * Grow hyphae from a spore
   */
  private growHyphae(spore: PlantedSpore): void {
    const growthRate = spore.spore.growthRate * this.config.baseGrowthRate;

    // Try to extend existing hyphae or create new connections
    const hyphae = this.network.getNodeHyphae(spore.nodeId);
    for (const hypha of hyphae) {
      if (spore.hyphaIds.includes(hypha.id)) {
        // Strengthen existing connection
        this.network.updateHypha(hypha.id, {
          strength: Math.min(1, hypha.strength + growthRate * 0.01),
        });
      }
    }
  }

  /**
   * Remove a dead spore
   */
  private removeSpore(sporeId: string): void {
    const spore = this.plantedSpores.get(sporeId);
    if (!spore) return;

    // Remove from network
    this.network.removeNode(spore.nodeId);

    // Update player count
    const count = this.playerSporeCount.get(spore.planterPubKey) ?? 1;
    this.playerSporeCount.set(spore.planterPubKey, Math.max(0, count - 1));

    this.plantedSpores.delete(sporeId);
  }

  // ===========================================================================
  // Utility Functions
  // ===========================================================================

  /**
   * Convert geohash to approximate position
   */
  private geohashToPosition(geohash: string): { x: number; y: number } {
    // Simplified conversion - in production, use proper decoding
    let x = 0,
      y = 0;
    for (let i = 0; i < geohash.length; i++) {
      const code = geohash.charCodeAt(i);
      x += code * Math.pow(32, geohash.length - i - 1);
      y += (code * 7) % 100;
    }
    return { x: x % 10000, y: y * 100 };
  }

  /**
   * Calculate distance between positions
   */
  private calculateDistance(
    a: { x: number; y: number },
    b: { x: number; y: number }
  ): number {
    const dx = b.x - a.x;
    const dy = b.y - a.y;
    return Math.sqrt(dx * dx + dy * dy);
  }

  /**
   * Calculate center geohash of multiple spores
   */
  private calculateCenterGeohash(spores: PlantedSpore[]): string {
    // Simplified - just use first spore's geohash
    // In production, calculate actual center
    return spores[0].locationCommitment.geohash;
  }

  // ===========================================================================
  // Queries
  // ===========================================================================

  /**
   * Get planted spore by ID
   */
  getPlantedSpore(id: string): PlantedSpore | undefined {
    return this.plantedSpores.get(id);
  }

  /**
   * Get all planted spores
   */
  getAllPlantedSpores(): PlantedSpore[] {
    return Array.from(this.plantedSpores.values());
  }

  /**
   * Get player's planted spores
   */
  getPlayerSpores(playerPubKey: string): PlantedSpore[] {
    return Array.from(this.plantedSpores.values()).filter(
      (s) => s.planterPubKey === playerPubKey
    );
  }

  /**
   * Get fruiting body by ID
   */
  getFruitingBody(id: string): FruitingBody | undefined {
    return this.fruitingBodies.get(id);
  }

  /**
   * Get all fruiting bodies
   */
  getAllFruitingBodies(): FruitingBody[] {
    return Array.from(this.fruitingBodies.values());
  }

  /**
   * Get mature fruiting bodies
   */
  getMatureFruits(): FruitingBody[] {
    return Array.from(this.fruitingBodies.values()).filter((f) => f.maturity >= 100);
  }

  /**
   * Get the underlying mycelium network
   */
  getNetwork(): MyceliumNetwork {
    return this.network;
  }

  // ===========================================================================
  // Events
  // ===========================================================================

  /**
   * Subscribe to events
   */
  on(listener: GameEventListener): () => void {
    this.listeners.add(listener);
    return () => this.listeners.delete(listener);
  }

  private emit(event: GameEvent): void {
    for (const listener of this.listeners) {
      try {
        listener(event);
      } catch (e) {
        console.error('Error in game event listener:', e);
      }
    }
  }

  // ===========================================================================
  // Serialization
  // ===========================================================================

  /**
   * Export state
   */
  export(): string {
    return JSON.stringify({
      plantedSpores: Array.from(this.plantedSpores.entries()),
      fruitingBodies: Array.from(this.fruitingBodies.entries()),
      playerSporeCount: Array.from(this.playerSporeCount.entries()),
    });
  }

  /**
   * Import state
   */
  import(json: string): void {
    const data = JSON.parse(json);
    this.plantedSpores = new Map(data.plantedSpores);
    this.fruitingBodies = new Map(data.fruitingBodies);
    this.playerSporeCount = new Map(data.playerSporeCount);
  }
}

// =============================================================================
// Factory Functions
// =============================================================================

/**
 * Create a spore manager
 */
export function createSporeManager(config?: Partial<SporeSystemConfig>): SporeManager {
  return new SporeManager(config);
}

/**
 * Create a spore from type
 */
export function createSporeFromType(type: SporeType): Spore {
  const template = SPORE_TEMPLATES[type];
  return {
    ...template,
    id: `spore-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
  };
}
