/**
 * Layer 5: Compute — Deterministic transforms that run locally when possible,
 * delegate to server when not.
 *
 * | Transform           | Local? | Example                        |
 * |---------------------|--------|--------------------------------|
 * | Markdown → HTML     | Yes    | Notes rendering                |
 * | Search indexing      | Yes    | Build index from docs          |
 * | Vote tallies         | Yes    | Derived from CRDT state        |
 * | PDF generation       | No     | Server delegate (Typst)        |
 * | Image thumbnailing   | No     | Server delegate (Sharp)        |
 *
 * Server fallback: POST /:space/api/compute/:transformId
 */

// ============================================================================
// TYPES
// ============================================================================

/**
 * A transform: a named, deterministic function that converts input → output.
 */
export interface Transform<In = unknown, Out = unknown> {
  /** Unique identifier (e.g. "markdown-to-html", "pdf-generate") */
  id: string;
  /** Whether this transform can run in the browser */
  canRunLocally: boolean;
  /** Execute the transform */
  execute(input: In): Promise<Out>;
}

export interface ComputeEngineOptions {
  /** Base URL for server-side compute endpoint (e.g. "/demo/api/compute") */
  serverBaseUrl?: string;
  /** Auth token for server requests */
  authToken?: string;
}

// ============================================================================
// ComputeEngine
// ============================================================================

export class ComputeEngine {
  #transforms = new Map<string, Transform<any, any>>();
  #serverBaseUrl: string | null;
  #authToken: string | null;

  constructor(opts: ComputeEngineOptions = {}) {
    this.#serverBaseUrl = opts.serverBaseUrl ?? null;
    this.#authToken = opts.authToken ?? null;
  }

  /**
   * Register a transform.
   */
  register<In, Out>(transform: Transform<In, Out>): void {
    this.#transforms.set(transform.id, transform);
  }

  /**
   * Run a transform. Runs locally if possible, delegates to server otherwise.
   */
  async run<In, Out>(transformId: string, input: In): Promise<Out> {
    const transform = this.#transforms.get(transformId);

    if (transform?.canRunLocally) {
      return transform.execute(input) as Promise<Out>;
    }

    if (transform && !transform.canRunLocally && this.#serverBaseUrl) {
      return this.#delegateToServer<In, Out>(transformId, input);
    }

    if (!transform && this.#serverBaseUrl) {
      // Transform not registered locally — try server
      return this.#delegateToServer<In, Out>(transformId, input);
    }

    throw new Error(
      `Transform "${transformId}" not available: ${
        transform ? 'requires server but no serverBaseUrl configured' : 'not registered'
      }`
    );
  }

  /**
   * Check if a transform is registered and can run locally.
   */
  canRunLocally(transformId: string): boolean {
    const t = this.#transforms.get(transformId);
    return !!t && t.canRunLocally;
  }

  /**
   * Check if a transform is registered.
   */
  has(transformId: string): boolean {
    return this.#transforms.has(transformId);
  }

  /**
   * List all registered transform IDs.
   */
  list(): string[] {
    return Array.from(this.#transforms.keys());
  }

  /**
   * Update auth token (e.g. after login/refresh).
   */
  setAuthToken(token: string): void {
    this.#authToken = token;
  }

  /**
   * Update server base URL.
   */
  setServerBaseUrl(url: string): void {
    this.#serverBaseUrl = url;
  }

  // ---------- Private ----------

  async #delegateToServer<In, Out>(transformId: string, input: In): Promise<Out> {
    if (!this.#serverBaseUrl) {
      throw new Error('No server base URL configured for compute delegation');
    }

    const url = `${this.#serverBaseUrl}/${transformId}`;
    const headers: Record<string, string> = {
      'Content-Type': 'application/json',
    };
    if (this.#authToken) {
      headers['Authorization'] = `Bearer ${this.#authToken}`;
    }

    const response = await fetch(url, {
      method: 'POST',
      headers,
      body: JSON.stringify({ input }),
    });

    if (!response.ok) {
      throw new Error(`Compute server error: ${response.status} ${response.statusText}`);
    }

    const result = await response.json();
    return result.output as Out;
  }
}

// ============================================================================
// BUILT-IN TRANSFORMS
// ============================================================================

/**
 * Helper to create a local transform.
 */
export function localTransform<In, Out>(
  id: string,
  fn: (input: In) => Promise<Out> | Out
): Transform<In, Out> {
  return {
    id,
    canRunLocally: true,
    execute: async (input) => fn(input),
  };
}

/**
 * Helper to declare a server-only transform (acts as a type contract).
 */
export function serverTransform<In, Out>(id: string): Transform<In, Out> {
  return {
    id,
    canRunLocally: false,
    execute: async () => {
      throw new Error(`Transform "${id}" must run on server`);
    },
  };
}
