/**
 * RootRegistry - Track known roots and their endpoints
 *
 * Sources:
 * 1. Static config: .matrix/roots.json
 * 2. Runtime registration
 * 3. DNS discovery (future)
 */

export type EndpointType = 'ws' | 'wss' | 'memory';

export interface RootEndpoint {
  type: EndpointType;
  url: string;
  priority: number;
  shard?: string;
}

/** @deprecated Use RootEndpoint */
export type RealmEndpoint = RootEndpoint;

export interface RootConfig {
  roots: Record<string, { endpoints: RootEndpoint[] }>;
}

/** @deprecated Use RootConfig */
export type RealmConfig = RootConfig;

type WatchCallback = (endpoints: RootEndpoint[]) => void;

export class RootRegistry {
  private _endpoints: Map<string, RootEndpoint[]> = new Map();
  private _watchers: Map<string, Set<WatchCallback>> = new Map();
  private _cache: Map<string, { endpoint: RootEndpoint; expires: number }> = new Map();
  private _cacheTtlMs = 60000; // 1 minute

  /**
   * Register endpoints for a root.
   * Merges with existing endpoints, updates by (type, url) key.
   */
  register(root: string, endpoints: RootEndpoint[]): void {
    const existing = this._endpoints.get(root) || [];

    for (const ep of endpoints) {
      const idx = existing.findIndex(e => e.type === ep.type && e.url === ep.url);
      if (idx >= 0) {
        existing[idx] = ep; // update
      } else {
        existing.push(ep); // add
      }
    }

    // Sort by priority
    existing.sort((a, b) => a.priority - b.priority);
    this._endpoints.set(root, existing);

    // Invalidate cache
    this._cache.delete(root);

    // Notify watchers
    this._notifyWatchers(root, existing);
  }

  /**
   * Deregister a root or specific endpoint.
   */
  deregister(root: string, endpoint?: RootEndpoint): void {
    if (!endpoint) {
      // Remove entire root
      this._endpoints.delete(root);
      this._cache.delete(root);
      this._notifyWatchers(root, []);
      return;
    }

    const existing = this._endpoints.get(root);
    if (!existing) return;

    const filtered = existing.filter(
      e => !(e.type === endpoint.type && e.url === endpoint.url)
    );

    if (filtered.length === 0) {
      this._endpoints.delete(root);
    } else {
      this._endpoints.set(root, filtered);
    }

    this._cache.delete(root);
    this._notifyWatchers(root, filtered);
  }

  /**
   * Resolve the highest priority endpoint for a root.
   * Returns null if root not registered.
   */
  resolve(root: string): RootEndpoint | null {
    // Check cache
    const cached = this._cache.get(root);
    if (cached && cached.expires > Date.now()) {
      return cached.endpoint;
    }

    const endpoints = this._endpoints.get(root);
    if (!endpoints || endpoints.length === 0) {
      return null;
    }

    const best = endpoints[0]; // Already sorted by priority

    // Cache result
    this._cache.set(root, {
      endpoint: best,
      expires: Date.now() + this._cacheTtlMs,
    });

    return best;
  }

  /**
   * Get all endpoints for a root, sorted by priority.
   */
  resolveAll(root: string): RootEndpoint[] {
    return this._endpoints.get(root) || [];
  }

  /**
   * Watch for endpoint changes on a root.
   * Returns unsubscribe function.
   */
  watch(root: string, callback: WatchCallback): () => void {
    let watchers = this._watchers.get(root);
    if (!watchers) {
      watchers = new Set();
      this._watchers.set(root, watchers);
    }
    watchers.add(callback);

    return () => {
      watchers!.delete(callback);
      if (watchers!.size === 0) {
        this._watchers.delete(root);
      }
    };
  }

  /**
   * Load root configuration from JSON file.
   */
  async loadConfig(path: string): Promise<void> {
    try {
      // Browser: fetch, Node: fs.readFile
      let config: RootConfig;

      if (typeof window !== 'undefined') {
        const resp = await fetch(path);
        if (!resp.ok) {
          console.warn(`RootRegistry: config not found at ${path}`);
          return;
        }
        config = await resp.json();
      } else {
        // Node.js environment - use dynamic import
        // @ts-ignore - Node.js module import
        const fs = await import('fs/promises');
        const content = await fs.readFile(path, 'utf-8');
        config = JSON.parse(content);
      }

      // Register all roots from config
      // Accept both 'roots' and legacy 'realms' key
      const rootEntries = (config as any).roots ?? (config as any).realms;
      if (rootEntries) {
        for (const [root, data] of Object.entries(rootEntries)) {
          this.register(root, (data as { endpoints: RootEndpoint[] }).endpoints);
        }
      }
    } catch (err) {
      console.warn(`RootRegistry: failed to load config: ${err}`);
    }
  }

  /**
   * Check if a root is registered.
   */
  has(root: string): boolean {
    return this._endpoints.has(root);
  }

  /**
   * Get all registered root names.
   */
  roots(): string[] {
    return Array.from(this._endpoints.keys());
  }

  /** @deprecated Use roots() */
  realms(): string[] {
    return this.roots();
  }

  private _notifyWatchers(root: string, endpoints: RootEndpoint[]): void {
    const watchers = this._watchers.get(root);
    if (watchers) {
      for (const cb of watchers) {
        try {
          cb(endpoints);
        } catch (err) {
          console.error('RootRegistry watcher error:', err);
        }
      }
    }
  }
}

/** @deprecated Use RootRegistry */
export const RealmRegistry = RootRegistry;
/** @deprecated Use RootRegistry */
export type RealmRegistry = RootRegistry;

// Singleton instance for convenience
export const globalRootRegistry = new RootRegistry();
/** @deprecated Use globalRootRegistry */
export const globalRealmRegistry = globalRootRegistry;
