/**
 * BrowserRouterFactory - Creates FederationPeer for browser environments
 *
 * This is the AUTHORITATIVE factory for browser-based Matrix routing.
 * Uses FederationPeer (unified class) with TabInstanceResolver for tab isolation.
 *
 * DESIGN: Single source of truth for browser transport creation.
 */

import type { Transport } from '../transport/base';
import { InMemoryFilterBroker } from '../transport/inmem';
import { FederationPeer, TabInstanceResolver } from '../federation_peer';
import { RootId } from '../identity/root_id';

/**
 * Configuration for browser router creation.
 */
export interface BrowserRouterConfig {
  /** Base root (e.g., 'flowpad'). Tab ID will be appended for isolation. */
  baseRoot?: string;
  /** @deprecated Use baseRoot */
  baseRealm?: string;

  /** Optional explicit tab ID. If not provided, one is generated. */
  tabId?: string;

  /** Optional backbone transport for cross-root communication. */
  backboneTransport?: Transport;

  /** Optional backbone broker URL (if backbone should be auto-created). */
  backboneUrl?: string;
}

/**
 * Result of browser router creation.
 */
export interface BrowserRouterResult {
  /** The FederationPeer for routing local/remote traffic */
  router: FederationPeer;

  /** The local InMemoryFilterBroker for subscriptions */
  localBroker: InMemoryFilterBroker;

  /** The tab-isolated root ID */
  root: RootId;
  /** @deprecated Use root */
  realm: RootId;

  /** Adapter that implements transport interface for MatrixContext */
  adapter: IRouterTransportAdapter;
}

/**
 * Transport adapter interface matching MatrixContext requirements.
 */
export interface IRouterTransportAdapter {
  publish(topic: string, payload: unknown): void;
  subscribe(topic: string, handler: (payload: unknown, meta: { topic: string; ts: number }) => void): () => void;
  readonly root?: string;
  /** @deprecated Use root */
  readonly realm?: string;
  getUnderlyingClient?(): FederationPeer;
  getWireTopicInfo?(topic: string): { appTopic: string; wireTopic: string; prefix: string; root?: string; /** @deprecated */ realm?: string };
  /** Convert local bus topic (dot-separated) to canonical URI (slash-separated) */
  toCanonicalUri?(topic: string): string;
  /** Observe all messages on the local bus unconditionally. Returns unsub. */
  tap?(handler: (topic: string, payload: string) => void): () => void;
}

// Tab ID cache per base root
const tabIds = new Map<string, string>();

/**
 * Get or create a tab-isolated root ID.
 * Uses session root format: s-<durableShort>-<tabId>
 * No dots -- dots break NATS subject parsing.
 */
function getOrCreateTabRoot(baseRoot: string, explicitTabId?: string): RootId {
  const clean = baseRoot.split('.')[0];
  const short = clean.length <= 6 ? clean.padEnd(6, '0') : clean.slice(-6);

  if (explicitTabId) {
    return RootId.of(`s-${short}-${explicitTabId}`);
  }

  let tabId = tabIds.get(baseRoot);
  if (!tabId) {
    tabId = Math.random().toString(16).slice(2, 10);
    tabIds.set(baseRoot, tabId);
  }
  return RootId.of(`s-${short}-${tabId}`);
}

/**
 * Create a FederationPeer configured for browser use.
 *
 * This factory:
 * 1. Creates an InMemoryFilterBroker for local traffic
 * 2. Sets up tab isolation via TabInstanceResolver
 * 3. Creates the FederationPeer
 * 4. Returns an adapter compatible with MatrixContext
 *
 * @param config - Browser router configuration
 * @returns BrowserRouterResult with peer, broker, root, and adapter
 */
export function createBrowserRouter(config: BrowserRouterConfig = {}): BrowserRouterResult {
  const baseRoot = config.baseRoot ?? config.baseRealm ?? 'flowpad';
  const root = getOrCreateTabRoot(baseRoot, config.tabId);

  // Create local broker for in-memory traffic
  const localBroker = new InMemoryFilterBroker();

  // Create FederationPeer with TabInstanceResolver for tab isolation
  const peer = new FederationPeer({
    localRoot: root.value,
    localBus: localBroker,
    backbone: config.backboneTransport,
    instanceResolver: new TabInstanceResolver(root.value),
    // Browser doesn't need security features by default
    enforceAntiRelay: false,
    // Subscribe to base-root group channel so cross-tab requests
    // (e.g., chat-root -> flowpad-root) can reach this tab via the
    // group topic (flowpad-root.$ingress.00) in addition to the
    // tab-specific topic (flowpad-root.tab-xxx.$ingress.00).
    subscribeToGroupChannel: true,
    // No internal timeout -- let handlers like on$Prompt run as long as needed.
    // The outer request's own timeout (e.g., CLI 300s) handles expiry.
    internalTimeoutMs: 0,
  });

  // Start peer (async, but we don't await - backbone may not be set yet)
  peer.start().catch(err => {
    console.warn('[BrowserRouterFactory] Peer start error:', err);
  });

  // Create transport adapter for MatrixContext compatibility
  const adapter = createPeerAdapter(peer, localBroker, root);

  return { router: peer, localBroker, root, realm: root, adapter };
}

/**
 * Create a transport adapter that wraps FederationPeer.
 *
 * This adapter:
 * - Rewrites topics for tab isolation on publish/subscribe
 * - Delegates routing to FederationPeer
 * - Subscribes via local broker for immediate delivery
 *
 * Bridges clean component topics to FederationPeer's local bus format (dot-separated, no scheme)
 */
function createPeerAdapter(
  peer: FederationPeer,
  localBroker: InMemoryFilterBroker,
  root: RootId
): IRouterTransportAdapter {
  return {
    publish: (topic: string, payload: unknown) => {
      const payloadStr = typeof payload === 'string' ? payload : JSON.stringify(payload);
      const localRoot = root.value;

      // Topics emitted by browser components are usually clean local topics (e.g. header/$inbox).
      // Some federation paths inject root-prefixed absolute topics in replyTo.
      // Avoid double-prefixing those absolute topics.
      const isAbsoluteLocalTopic =
        topic.startsWith(localRoot + '.') ||
        topic.startsWith(localRoot + '/');

      const peerTopic = isAbsoluteLocalTopic ? topic : `${localRoot}.${topic}`;
      peer.route(peerTopic, payloadStr);
    },

    subscribe: (topic: string, handler) => {
      // Subscribe on InMemoryFilterBroker with root prefix (no scheme)
      const brokerTopic = `${root.value}.${topic}`;
      return localBroker.subscribe(brokerTopic, (t: string, p: string) => {
        const meta = { topic: t, ts: Date.now() };
        try {
          handler(JSON.parse(p), meta);
        } catch {
          handler(p, meta);
        }
      });
    },

    get root() {
      return root.value;
    },

    get realm() {
      return root.value;
    },

    getUnderlyingClient: () => peer,

    toCanonicalUri: (topic: string): string => {
      // Incoming topic is clean (e.g. daemon.accepts.$introspect)
      // Prepend root for display (no scheme)
      return `${root.value}/${topic}`;
    },

    getWireTopicInfo: (topic: string) => {
      const wireTopic = `${root.value}/${topic}`;
      return { appTopic: topic, wireTopic, prefix: '', root: root.value, realm: root.value };
    },

    tap: (handler) => localBroker.tap(handler),
  };
}

/**
 * Cache for browser routers (one per base root).
 */
const browserRouters = new Map<string, BrowserRouterResult>();

/**
 * Get or create a cached browser router.
 *
 * Use this for typical browser apps - ensures only one router per root.
 *
 * @param config - Browser router configuration
 * @returns Cached or new BrowserRouterResult
 */
export function getBrowserRouter(config: BrowserRouterConfig = {}): BrowserRouterResult {
  const baseRoot = config.baseRoot ?? config.baseRealm ?? 'flowpad';
  const cacheKey = `${baseRoot}:${config.tabId || 'auto'}`;

  let result = browserRouters.get(cacheKey);
  if (!result) {
    result = createBrowserRouter(config);
    browserRouters.set(cacheKey, result);
  }
  return result;
}

/**
 * Clear all cached browser routers.
 * Call this when cleaning up or during tests.
 */
export function clearBrowserRouters(): void {
  for (const result of browserRouters.values()) {
    result.router.stop();
  }
  browserRouters.clear();
  tabIds.clear();
}
