import { Cognitive } from "@botpress/cognitive";
import { getWasmTokenizer } from "@bpinternal/thicktoken";
import { z } from "@bpinternal/zui";
import { TableAdapter } from "./adapters/botpress-table";
import { MemoryAdapter } from "./adapters/memory";
const _ActiveLearning = z.object({
  enable: z.boolean().describe("Whether to enable active learning").default(false),
  tableName: z.string().regex(
    /^[A-Za-z0-9_/-]{1,100}Table$/,
    "Namespace must be alphanumeric and contain only letters, numbers, underscores, hyphens and slashes"
  ).describe("The name of the table to store active learning tasks").default("ActiveLearningTable"),
  taskId: z.string().regex(
    /^[A-Za-z0-9_/-]{1,100}$/,
    "Namespace must be alphanumeric and contain only letters, numbers, underscores, hyphens and slashes"
  ).describe("The ID of the task").default("default")
});
const _ZaiConfig = z.object({
  client: z.custom(),
  userId: z.string().describe("The ID of the user consuming the API").optional(),
  modelId: z.custom(
    (value) => {
      if (typeof value !== "string") {
        return false;
      }
      if (value !== "best" && value !== "fast" && !value.includes(":")) {
        return false;
      }
      return true;
    },
    {
      message: "Invalid model ID"
    }
  ).describe("The ID of the model you want to use").default("best"),
  activeLearning: _ActiveLearning.default({ enable: false }),
  namespace: z.string().regex(
    /^[A-Za-z0-9_/-]{1,100}$/,
    "Namespace must be alphanumeric and contain only letters, numbers, underscores, hyphens and slashes"
  ).default("zai")
});
export class Zai {
  static tokenizer = null;
  client;
  _originalConfig;
  _userId;
  Model;
  ModelDetails;
  namespace;
  adapter;
  activeLearning;
  /**
   * Creates a new Zai instance with the specified configuration.
   *
   * @param config - Configuration object containing client, model, and learning settings
   *
   * @example
   * ```typescript
   * import { Client } from '@botpress/client'
   * import { Zai } from '@botpress/zai'
   *
   * const client = new Client({ token: 'your-token' })
   * const zai = new Zai({
   *   client,
   *   modelId: 'best',
   *   namespace: 'my-app',
   *   userId: 'user-123'
   * })
   * ```
   *
   * @throws {Error} If the configuration is invalid (e.g., invalid modelId format)
   */
  constructor(config) {
    this._originalConfig = config;
    const parsed = _ZaiConfig.parse(config);
    this.client = Cognitive.isCognitiveClient(parsed.client) ? parsed.client : new Cognitive({ client: parsed.client });
    this.namespace = parsed.namespace;
    this._userId = parsed.userId;
    this.Model = parsed.modelId;
    this.activeLearning = parsed.activeLearning;
    this.adapter = parsed.activeLearning?.enable ? new TableAdapter({
      client: this.client.client,
      tableName: parsed.activeLearning.tableName
    }) : new MemoryAdapter([]);
  }
  /** @internal */
  async callModel(props) {
    return this.client.generateContent({
      reasoningEffort: "none",
      ...props,
      model: this.Model,
      userId: this._userId
    });
  }
  async getTokenizer() {
    Zai.tokenizer ??= await (async () => {
      while (!getWasmTokenizer) {
        await new Promise((resolve) => setTimeout(resolve, 25));
      }
      return getWasmTokenizer();
    })();
    return Zai.tokenizer;
  }
  async fetchModelDetails() {
    if (!this.ModelDetails) {
      this.ModelDetails = await this.client.getModelDetails(this.Model);
    }
  }
  get taskId() {
    if (!this.activeLearning.enable) {
      return void 0;
    }
    return `${this.namespace}/${this.activeLearning.taskId}`.replace(/\/+/g, "/");
  }
  /**
   * Creates a new Zai instance with merged configuration options.
   *
   * This method allows you to create variations of your Zai instance with different
   * settings without modifying the original. Useful for switching models, namespaces,
   * or other configuration on a per-operation basis.
   *
   * @param options - Partial configuration to override the current settings
   * @returns A new Zai instance with the merged configuration
   *
   * @example Switch to a faster model
   * ```typescript
   * const zai = new Zai({ client })
   *
   * // Use fast model for simple operations
   * const fastZai = zai.with({ modelId: 'fast' })
   * await fastZai.check(text, 'Is this spam?')
   *
   * // Use best model for complex operations
   * const bestZai = zai.with({ modelId: 'best' })
   * await bestZai.extract(document, complexSchema)
   * ```
   *
   * @example Change namespace
   * ```typescript
   * const customerZai = zai.with({ namespace: 'customer-support' })
   * const salesZai = zai.with({ namespace: 'sales' })
   * ```
   *
   * @example Use specific model
   * ```typescript
   * const gpt4 = zai.with({ modelId: 'openai:gpt-4' })
   * const claude = zai.with({ modelId: 'anthropic:claude-3-5-sonnet-20241022' })
   * ```
   */
  with(options) {
    return new Zai({
      ...this._originalConfig,
      ...options
    });
  }
  /**
   * Creates a new Zai instance with active learning enabled for a specific task.
   *
   * Active learning stores successful operation results and uses them as examples for
   * future operations, improving accuracy and consistency over time. Each task ID
   * maintains its own set of learned examples.
   *
   * @param taskId - Unique identifier for the learning task (alphanumeric, hyphens, underscores, slashes)
   * @returns A new Zai instance with active learning enabled for the specified task
   *
   * @example Sentiment analysis with learning
   * ```typescript
   * const zai = new Zai({
   *   client,
   *   activeLearning: {
   *     enable: false,
   *     tableName: 'AppLearningTable',
   *     taskId: 'default'
   *   }
   * })
   *
   * // Enable learning for sentiment analysis
   * const sentimentZai = zai.learn('sentiment-analysis')
   * const result = await sentimentZai.check(review, 'Is this review positive?')
   *
   * // Each successful call is stored and used to improve future calls
   * ```
   *
   * @example Different tasks for different purposes
   * ```typescript
   * // Extract user info with learning
   * const userExtractor = zai.learn('user-extraction')
   * await userExtractor.extract(text, userSchema)
   *
   * // Extract product info with separate learning
   * const productExtractor = zai.learn('product-extraction')
   * await productExtractor.extract(text, productSchema)
   *
   * // Each task learns independently
   * ```
   *
   * @example Combining with other configuration
   * ```typescript
   * // Use fast model + learning
   * const fastLearner = zai.with({ modelId: 'fast' }).learn('quick-checks')
   * await fastLearner.check(email, 'Is this spam?')
   * ```
   *
   * @see {@link ZaiConfig.activeLearning} for configuration options
   */
  learn(taskId) {
    return new Zai({
      ...this._originalConfig,
      activeLearning: { ...this.activeLearning, taskId, enable: true }
    });
  }
}
