"use strict";

import { Platform } from 'react-native';
import RNLlama from './NativeRNLlama';
import './jsi';
import { BUILD_NUMBER, BUILD_COMMIT } from './version';
export const RNLLAMA_MTMD_DEFAULT_MEDIA_MARKER = '<__media__>';
const logListeners = [];
const emitNativeLog = (level, text) => {
  logListeners.forEach(listener => listener(level, text));
};
const jsiBindingKeys = ['llamaInitContext', 'llamaReleaseContext', 'llamaReleaseAllContexts', 'llamaModelInfo', 'llamaGetBackendDevicesInfo', 'llamaLoadSession', 'llamaSaveSession', 'llamaTokenize', 'llamaDetokenize', 'llamaGetFormattedChat', 'llamaEmbedding', 'llamaRerank', 'llamaBench', 'llamaToggleNativeLog', 'llamaSetContextLimit', 'llamaCompletion', 'llamaStopCompletion', 'llamaApplyLoraAdapters', 'llamaRemoveLoraAdapters', 'llamaGetLoadedLoraAdapters', 'llamaInitMultimodal', 'llamaIsMultimodalEnabled', 'llamaGetMultimodalSupport', 'llamaReleaseMultimodal', 'llamaInitVocoder', 'llamaIsVocoderEnabled', 'llamaGetFormattedAudioCompletion', 'llamaGetAudioCompletionGuideTokens', 'llamaDecodeAudioTokens', 'llamaReleaseVocoder', 'llamaClearCache', 'llamaEnableParallelMode', 'llamaQueueCompletion', 'llamaCancelRequest', 'llamaQueueEmbedding', 'llamaQueueRerank', 'llamaGetParallelStatus', 'llamaSubscribeParallelStatus', 'llamaUnsubscribeParallelStatus'];
let jsiBindings = null;
const bindJsiFromGlobal = () => {
  const bindings = {};
  const missing = [];
  jsiBindingKeys.forEach(key => {
    const value = global[key];
    if (typeof value === 'function') {
      ;
      bindings[key] = value;
      delete global[key];
    } else {
      missing.push(key);
    }
  });
  if (missing.length > 0) {
    throw new Error(`[RNLlama] Missing JSI bindings: ${missing.join(', ')}`);
  }
  jsiBindings = bindings;
};
const getJsi = () => {
  if (!jsiBindings) {
    throw new Error('JSI bindings not installed');
  }
  return jsiBindings;
};

// JSI Installation
let isJsiInstalled = false;
export const installJsi = async () => {
  if (isJsiInstalled) return;
  if (typeof global.llamaInitContext !== 'function') {
    const installed = await RNLlama.install();
    if (!installed && typeof global.llamaInitContext !== 'function') {
      throw new Error('JSI bindings not installed');
    }
  }
  bindJsiFromGlobal();
  isJsiInstalled = true;
};
const validCacheTypes = ['f16', 'f32', 'bf16', 'q8_0', 'q4_0', 'q4_1', 'iq4_nl', 'q5_0', 'q5_1'];

/**
 * Parameters for parallel completion requests.
 * Extends CompletionParams with parallel-mode specific options like state management.
 */

const getJsonSchema = responseFormat => {
  if (responseFormat?.type === 'json_schema') {
    return responseFormat.json_schema?.schema;
  }
  if (responseFormat?.type === 'json_object') {
    return responseFormat.schema || {};
  }
  return null;
};
export class LlamaContext {
  gpu = false;
  reasonNoGPU = '';
  /**
   * Parallel processing namespace for non-blocking queue operations
   */
  parallel = {
    /**
     * Queue a completion request for parallel processing (non-blocking)
     * @param params Parallel completion parameters (includes state management)
     * @param onToken Callback fired for each generated token
     * @returns Promise resolving to object with requestId, promise (resolves to completion result), and stop function
     */
    completion: async (params, onToken) => {
      const {
        llamaQueueCompletion,
        llamaCancelRequest
      } = getJsi();
      const nativeParams = {
        ...params,
        prompt: params.prompt || '',
        emit_partial_completion: true // Always emit for queued requests
      };

      // Process messages same as completion()
      if (params.messages) {
        const formattedResult = await this.getFormattedChat(params.messages, params.chat_template || params.chatTemplate, {
          jinja: params.jinja,
          tools: params.tools,
          parallel_tool_calls: params.parallel_tool_calls,
          tool_choice: params.tool_choice,
          response_format: params.response_format,
          enable_thinking: params.enable_thinking,
          reasoning_format: params.reasoning_format,
          add_generation_prompt: params.add_generation_prompt,
          now: params.now,
          chat_template_kwargs: params.chat_template_kwargs,
          force_pure_content: params.force_pure_content
        });
        if (formattedResult.type === 'jinja') {
          const jinjaResult = formattedResult;
          nativeParams.prompt = jinjaResult.prompt || '';
          if (typeof jinjaResult.chat_format === 'number') nativeParams.chat_format = jinjaResult.chat_format;
          if (jinjaResult.grammar) nativeParams.grammar = jinjaResult.grammar;
          if (typeof jinjaResult.grammar_lazy === 'boolean') nativeParams.grammar_lazy = jinjaResult.grammar_lazy;
          if (jinjaResult.grammar_triggers) nativeParams.grammar_triggers = jinjaResult.grammar_triggers;
          if (jinjaResult.preserved_tokens) nativeParams.preserved_tokens = jinjaResult.preserved_tokens;
          if (jinjaResult.additional_stops) {
            if (!nativeParams.stop) nativeParams.stop = [];
            nativeParams.stop.push(...jinjaResult.additional_stops);
          }
          if (jinjaResult.has_media) {
            nativeParams.media_paths = jinjaResult.media_paths;
          }
          if (typeof jinjaResult.generation_prompt === 'string') nativeParams.generation_prompt = jinjaResult.generation_prompt;
          if (typeof jinjaResult.thinking_forced_open === 'boolean') nativeParams.thinking_forced_open = jinjaResult.thinking_forced_open;
          if (typeof jinjaResult.thinking_start_tag === 'string') nativeParams.thinking_start_tag = jinjaResult.thinking_start_tag;
          if (typeof jinjaResult.thinking_end_tag === 'string') nativeParams.thinking_end_tag = jinjaResult.thinking_end_tag;
          if (jinjaResult.chat_parser) nativeParams.chat_parser = jinjaResult.chat_parser;
        } else if (formattedResult.type === 'llama-chat') {
          const llamaChatResult = formattedResult;
          nativeParams.prompt = llamaChatResult.prompt || '';
          if (llamaChatResult.has_media) {
            nativeParams.media_paths = llamaChatResult.media_paths;
          }
        }
      } else {
        nativeParams.prompt = params.prompt || '';
      }
      if (!nativeParams.media_paths && params.media_paths) {
        nativeParams.media_paths = params.media_paths;
      }
      if (params.response_format && !nativeParams.grammar) {
        const jsonSchema = getJsonSchema(params.response_format);
        if (jsonSchema) nativeParams.json_schema = JSON.stringify(jsonSchema);
      }
      if (!nativeParams.prompt) throw new Error('Prompt is required');
      return new Promise(async (resolveOuter, rejectOuter) => {
        try {
          let resolveResult;
          let rejectResult;
          const resultPromise = new Promise((res, rej) => {
            resolveResult = res;
            rejectResult = rej;
          });
          const {
            requestId
          } = await llamaQueueCompletion(this.id, nativeParams, (tokenResult, reqId) => {
            if (onToken) onToken(reqId, tokenResult);
          }, result => {
            if (result.error) {
              rejectResult(new Error(result.error));
            } else {
              resolveResult(result);
            }
          });
          resolveOuter({
            requestId,
            promise: resultPromise,
            stop: async () => {
              await llamaCancelRequest(this.id, requestId);
            }
          });
        } catch (e) {
          rejectOuter(e);
        }
      });
    },
    /**
     * Queue an embedding request for parallel processing (non-blocking)
     * @param text Text to embed
     * @param params Optional embedding parameters
     * @returns Promise resolving to object with requestId and promise (resolves to embedding result)
     */
    embedding: async (text, params) => new Promise(async (resolveOuter, rejectOuter) => {
      const {
        llamaQueueEmbedding
      } = getJsi();
      try {
        let resolveResult;
        const resultPromise = new Promise(res => {
          resolveResult = res;
        });
        const {
          requestId
        } = await llamaQueueEmbedding(this.id, text, params || {}, embedding => {
          resolveResult({
            embedding
          });
        });
        resolveOuter({
          requestId,
          promise: resultPromise
        });
      } catch (e) {
        rejectOuter(e);
      }
    }),
    /**
     * Queue rerank requests for parallel processing (non-blocking)
     * @param query The query text to rank documents against
     * @param documents Array of document texts to rank
     * @param params Optional reranking parameters
     * @returns Promise resolving to object with requestId and promise (resolves to rerank results)
     */
    rerank: async (query, documents, params) => new Promise(async (resolveOuter, rejectOuter) => {
      const {
        llamaQueueRerank
      } = getJsi();
      try {
        let resolveResult;
        const resultPromise = new Promise(res => {
          resolveResult = res;
        });
        const {
          requestId
        } = await llamaQueueRerank(this.id, query, documents, params || {}, results => {
          const sortedResults = results.map(result => ({
            ...result,
            document: documents[result.index]
          })).sort((a, b) => b.score - a.score);
          resolveResult(sortedResults);
        });
        resolveOuter({
          requestId,
          promise: resultPromise
        });
      } catch (e) {
        rejectOuter(e);
      }
    }),
    enable: config => getJsi().llamaEnableParallelMode(this.id, {
      enabled: true,
      ...config
    }),
    disable: () => getJsi().llamaEnableParallelMode(this.id, {
      enabled: false
    }),
    configure: config => getJsi().llamaEnableParallelMode(this.id, {
      enabled: true,
      ...config
    }),
    /**
     * Get current parallel processing status (one-time snapshot)
     * @returns Promise resolving to current parallel status
     */
    getStatus: async () => {
      const {
        llamaGetParallelStatus
      } = getJsi();
      return llamaGetParallelStatus(this.id);
    },
    /**
     * Subscribe to parallel processing status changes
     * @param callback Called whenever parallel status changes
     * @returns Object with remove() method to unsubscribe
     */
    subscribeToStatus: async callback => {
      const {
        llamaSubscribeParallelStatus,
        llamaUnsubscribeParallelStatus
      } = getJsi();
      const {
        subscriberId
      } = await llamaSubscribeParallelStatus(this.id, callback);
      return {
        remove: () => {
          llamaUnsubscribeParallelStatus(this.id, subscriberId);
        }
      };
    }
  };
  constructor({
    contextId,
    gpu,
    devices,
    reasonNoGPU,
    model,
    androidLib,
    systemInfo
  }) {
    this.id = contextId;
    this.gpu = gpu;
    this.devices = devices;
    this.reasonNoGPU = reasonNoGPU;
    this.model = model;
    this.androidLib = androidLib;
    this.systemInfo = systemInfo;
  }
  async loadSession(filepath) {
    const {
      llamaLoadSession
    } = getJsi();
    let path = filepath;
    if (path.startsWith('file://')) path = path.slice(7);
    return llamaLoadSession(this.id, path);
  }
  async saveSession(filepath, options) {
    const {
      llamaSaveSession
    } = getJsi();
    return llamaSaveSession(this.id, filepath, options?.tokenSize || -1);
  }
  isLlamaChatSupported() {
    return !!this.model.chatTemplates.llamaChat;
  }
  isJinjaSupported() {
    const {
      jinja
    } = this.model.chatTemplates;
    return !!jinja?.toolUse || !!jinja?.default;
  }
  async getFormattedChat(messages, template, params) {
    const mediaPaths = [];
    const chat = messages.map(msg => {
      if (Array.isArray(msg.content)) {
        const content = msg.content.map(part => {
          if (part.type === 'image_url') {
            let path = part.image_url?.url || '';
            if (path?.startsWith('file://')) path = path.slice(7);
            mediaPaths.push(path);
            return {
              type: 'text',
              text: RNLLAMA_MTMD_DEFAULT_MEDIA_MARKER
            };
          } else if (part.type === 'input_audio') {
            const {
              input_audio: audio
            } = part;
            if (!audio) throw new Error('input_audio is required');
            const {
              format
            } = audio;
            if (format != 'wav' && format != 'mp3') {
              throw new Error(`Unsupported audio format: ${format}`);
            }
            if (audio.url) {
              const path = audio.url.replace(/file:\/\//, '');
              mediaPaths.push(path);
            } else if (audio.data) {
              mediaPaths.push(audio.data);
            }
            return {
              type: 'text',
              text: RNLLAMA_MTMD_DEFAULT_MEDIA_MARKER
            };
          }
          return part;
        });
        return {
          ...msg,
          content
        };
      }
      return msg;
    });
    const forcePureContent = params?.force_pure_content ?? false;
    // When force_pure_content is set, accept any model that has a chat_template
    // string in its metadata without requiring template validation to pass.
    const hasChatTemplate = !!this.model.metadata['tokenizer.chat_template'];
    const useJinja = (forcePureContent ? hasChatTemplate : this.isJinjaSupported()) && (params?.jinja ?? true);
    let tmpl;
    if (template) tmpl = template;
    const jsonSchema = getJsonSchema(params?.response_format);
    const {
      llamaGetFormattedChat
    } = getJsi();
    const result = await llamaGetFormattedChat(this.id, JSON.stringify(chat), tmpl, {
      jinja: useJinja,
      json_schema: jsonSchema ? JSON.stringify(jsonSchema) : undefined,
      tools: params?.tools ? JSON.stringify(params.tools) : undefined,
      parallel_tool_calls: params?.parallel_tool_calls ? JSON.stringify(params.parallel_tool_calls) : undefined,
      tool_choice: params?.tool_choice,
      enable_thinking: params?.enable_thinking ?? true,
      reasoning_format: params?.reasoning_format ?? 'none',
      add_generation_prompt: params?.add_generation_prompt,
      now: typeof params?.now === 'number' ? params.now.toString() : params?.now,
      chat_template_kwargs: params?.chat_template_kwargs ? JSON.stringify(Object.entries(params.chat_template_kwargs).reduce((acc, [key, value]) => {
        acc[key] = JSON.stringify(value);
        return acc;
      }, {})) : undefined,
      force_pure_content: forcePureContent
    });
    if (!useJinja) {
      return {
        type: 'llama-chat',
        prompt: result,
        has_media: mediaPaths.length > 0,
        media_paths: mediaPaths
      };
    }
    const jinjaResult = result;
    jinjaResult.type = 'jinja';
    jinjaResult.has_media = mediaPaths.length > 0;
    jinjaResult.media_paths = mediaPaths;
    return jinjaResult;
  }
  async completion(params, callback) {
    const nativeParams = {
      ...params,
      prompt: params.prompt || '',
      emit_partial_completion: !!callback
    };
    if (params.messages) {
      const formattedResult = await this.getFormattedChat(params.messages, params.chat_template || params.chatTemplate, {
        jinja: params.jinja,
        tools: params.tools,
        parallel_tool_calls: params.parallel_tool_calls,
        tool_choice: params.tool_choice,
        response_format: params.response_format,
        enable_thinking: params.enable_thinking,
        reasoning_format: params.reasoning_format,
        add_generation_prompt: params.add_generation_prompt,
        now: params.now,
        chat_template_kwargs: params.chat_template_kwargs,
        force_pure_content: params.force_pure_content
      });
      if (formattedResult.type === 'jinja') {
        const jinjaResult = formattedResult;
        nativeParams.prompt = jinjaResult.prompt || '';
        if (typeof jinjaResult.chat_format === 'number') nativeParams.chat_format = jinjaResult.chat_format;
        if (jinjaResult.grammar) nativeParams.grammar = jinjaResult.grammar;
        if (typeof jinjaResult.grammar_lazy === 'boolean') nativeParams.grammar_lazy = jinjaResult.grammar_lazy;
        if (jinjaResult.grammar_triggers) nativeParams.grammar_triggers = jinjaResult.grammar_triggers;
        if (jinjaResult.preserved_tokens) nativeParams.preserved_tokens = jinjaResult.preserved_tokens;
        if (jinjaResult.additional_stops) {
          if (!nativeParams.stop) nativeParams.stop = [];
          nativeParams.stop.push(...jinjaResult.additional_stops);
        }
        if (jinjaResult.has_media) {
          nativeParams.media_paths = jinjaResult.media_paths;
        }
        if (typeof jinjaResult.generation_prompt === 'string') nativeParams.generation_prompt = jinjaResult.generation_prompt;
        if (typeof jinjaResult.thinking_forced_open === 'boolean') nativeParams.thinking_forced_open = jinjaResult.thinking_forced_open;
        if (typeof jinjaResult.thinking_start_tag === 'string') nativeParams.thinking_start_tag = jinjaResult.thinking_start_tag;
        if (typeof jinjaResult.thinking_end_tag === 'string') nativeParams.thinking_end_tag = jinjaResult.thinking_end_tag;
        if (jinjaResult.chat_parser) nativeParams.chat_parser = jinjaResult.chat_parser;
      } else if (formattedResult.type === 'llama-chat') {
        const llamaChatResult = formattedResult;
        nativeParams.prompt = llamaChatResult.prompt || '';
        if (llamaChatResult.has_media) {
          nativeParams.media_paths = llamaChatResult.media_paths;
        }
      }
    } else {
      nativeParams.prompt = params.prompt || '';
    }
    if (!nativeParams.media_paths && params.media_paths) {
      nativeParams.media_paths = params.media_paths;
    }
    if (params.response_format && !nativeParams.grammar) {
      const jsonSchema = getJsonSchema(params.response_format);
      if (jsonSchema) nativeParams.json_schema = JSON.stringify(jsonSchema);
    }
    if (!nativeParams.prompt) throw new Error('Prompt is required');
    const {
      llamaCompletion
    } = getJsi();
    return llamaCompletion(this.id, nativeParams, callback);
  }
  stopCompletion() {
    const {
      llamaStopCompletion
    } = getJsi();
    return llamaStopCompletion(this.id);
  }
  tokenize(text, {
    media_paths: mediaPaths
  } = {}) {
    const {
      llamaTokenize
    } = getJsi();
    return llamaTokenize(this.id, text, mediaPaths);
  }
  detokenize(tokens) {
    const {
      llamaDetokenize
    } = getJsi();
    return llamaDetokenize(this.id, tokens);
  }
  embedding(text, params) {
    const {
      llamaEmbedding
    } = getJsi();
    return llamaEmbedding(this.id, text, params || {});
  }
  async rerank(query, documents, params) {
    const {
      llamaRerank
    } = getJsi();
    const results = await llamaRerank(this.id, query, documents, params || {});
    return results.map(result => ({
      ...result,
      document: documents[result.index]
    })).sort((a, b) => b.score - a.score);
  }
  async bench(pp, tg, pl, nr) {
    const {
      llamaBench
    } = getJsi();
    const result = await llamaBench(this.id, pp, tg, pl, nr);
    const parsed = JSON.parse(result);
    return {
      nKvMax: parsed.n_kv_max,
      nBatch: parsed.n_batch,
      nUBatch: parsed.n_ubatch,
      flashAttn: parsed.flash_attn,
      isPpShared: parsed.is_pp_shared,
      nGpuLayers: parsed.n_gpu_layers,
      nThreads: parsed.n_threads,
      nThreadsBatch: parsed.n_threads_batch,
      pp: parsed.pp,
      tg: parsed.tg,
      pl: parsed.pl,
      nKv: parsed.n_kv,
      tPp: parsed.t_pp,
      speedPp: parsed.speed_pp,
      tTg: parsed.t_tg,
      speedTg: parsed.speed_tg,
      t: parsed.t,
      speed: parsed.speed
    };
  }
  async applyLoraAdapters(loraList) {
    const {
      llamaApplyLoraAdapters
    } = getJsi();
    const loraAdapters = normalizeLoraAdapters({
      loraList
    });
    return llamaApplyLoraAdapters(this.id, loraAdapters);
  }
  async removeLoraAdapters() {
    const {
      llamaRemoveLoraAdapters
    } = getJsi();
    return llamaRemoveLoraAdapters(this.id);
  }
  async getLoadedLoraAdapters() {
    const {
      llamaGetLoadedLoraAdapters
    } = getJsi();
    return llamaGetLoadedLoraAdapters(this.id);
  }

  /**
   * Initialize multimodal support (vision/audio) with a projector model.
   * @param path - Path to the multimodal projector model file (mmproj)
   * @param use_gpu - Whether to use GPU for multimodal processing (default: true)
   * @param image_min_tokens - Minimum number of tokens for image input (for dynamic resolution models)
   * @param image_max_tokens - Maximum number of tokens for image input (for dynamic resolution models).
   *                           Lower values reduce memory usage and improve speed for high-resolution images.
   *                           Recommended: 256-512 for faster inference, up to 4096 for maximum detail.
   */
  async initMultimodal({
    path,
    use_gpu: useGpu,
    image_min_tokens: imageMinTokens,
    image_max_tokens: imageMaxTokens
  }) {
    const {
      llamaInitMultimodal
    } = getJsi();
    if (path.startsWith('file://')) path = path.slice(7);
    return llamaInitMultimodal(this.id, {
      path,
      use_gpu: useGpu ?? true,
      image_min_tokens: imageMinTokens,
      image_max_tokens: imageMaxTokens
    });
  }
  async isMultimodalEnabled() {
    const {
      llamaIsMultimodalEnabled
    } = getJsi();
    return await llamaIsMultimodalEnabled(this.id);
  }
  async getMultimodalSupport() {
    const {
      llamaGetMultimodalSupport
    } = getJsi();
    return await llamaGetMultimodalSupport(this.id);
  }
  async releaseMultimodal() {
    const {
      llamaReleaseMultimodal
    } = getJsi();
    return await llamaReleaseMultimodal(this.id);
  }
  async initVocoder({
    path,
    n_batch: nBatch
  }) {
    const {
      llamaInitVocoder
    } = getJsi();
    if (path.startsWith('file://')) path = path.slice(7);
    return await llamaInitVocoder(this.id, {
      path,
      n_batch: nBatch
    });
  }
  async isVocoderEnabled() {
    const {
      llamaIsVocoderEnabled
    } = getJsi();
    return await llamaIsVocoderEnabled(this.id);
  }
  async getFormattedAudioCompletion(speaker, textToSpeak) {
    const {
      llamaGetFormattedAudioCompletion
    } = getJsi();
    return await llamaGetFormattedAudioCompletion(this.id, speaker ? JSON.stringify(speaker) : '', textToSpeak);
  }
  async getAudioCompletionGuideTokens(textToSpeak) {
    const {
      llamaGetAudioCompletionGuideTokens
    } = getJsi();
    return await llamaGetAudioCompletionGuideTokens(this.id, textToSpeak);
  }
  async decodeAudioTokens(tokens) {
    const {
      llamaDecodeAudioTokens
    } = getJsi();
    return await llamaDecodeAudioTokens(this.id, tokens);
  }
  async releaseVocoder() {
    const {
      llamaReleaseVocoder
    } = getJsi();
    return await llamaReleaseVocoder(this.id);
  }

  /**
   * Clear the KV cache and reset conversation state
   * @param clearData If true, clears both metadata and tensor data buffers (slower). If false, only clears metadata (faster).
   * @returns Promise that resolves when cache is cleared
   *
   * Call this method between different conversations to prevent cache contamination.
   * Without clearing, the model may use cached context from previous conversations,
   * leading to incorrect or unexpected responses.
   *
   * For hybrid architecture models (e.g., LFM2), this is essential as they
   * use recurrent state that cannot be partially removed - only fully cleared.
   */
  async clearCache(clearData = false) {
    const {
      llamaClearCache
    } = getJsi();
    return llamaClearCache(this.id, clearData);
  }
  async release() {
    const {
      llamaReleaseContext
    } = getJsi();
    return llamaReleaseContext(this.id);
  }
}
export async function toggleNativeLog(enabled) {
  await installJsi();
  const {
    llamaToggleNativeLog
  } = getJsi();
  return llamaToggleNativeLog(enabled, emitNativeLog);
}
export function addNativeLogListener(listener) {
  logListeners.push(listener);
  return {
    remove: () => {
      logListeners.splice(logListeners.indexOf(listener), 1);
    }
  };
}
export async function setContextLimit(limit) {
  await installJsi();
  const {
    llamaSetContextLimit
  } = getJsi();
  return llamaSetContextLimit(limit);
}
let contextIdCounter = 0;
const contextIdRandom = () => /* @ts-ignore */
process.env.NODE_ENV === 'test' ? 0 : Math.floor(Math.random() * 100000);
const modelInfoSkip = ['tokenizer.ggml.tokens', 'tokenizer.ggml.token_type', 'tokenizer.ggml.merges', 'tokenizer.ggml.scores'];
export async function loadLlamaModelInfo(model) {
  await installJsi();
  const {
    llamaModelInfo
  } = getJsi();
  let path = model;
  if (path.startsWith('file://')) path = path.slice(7);
  return llamaModelInfo(path, modelInfoSkip);
}
const poolTypeMap = {
  none: 0,
  mean: 1,
  cls: 2,
  last: 3,
  rank: 4
};
const stripFilePrefix = path => path.replace(/^file:\/\//, '');
function normalizeLoraAdapters({
  lora,
  loraScaled,
  loraList
}) {
  const adapters = new Map();
  if (lora) {
    const normalizedPath = stripFilePrefix(lora);
    if (normalizedPath) {
      adapters.set(normalizedPath, {
        path: normalizedPath,
        scaled: loraScaled
      });
    }
  }
  if (loraList) {
    loraList.forEach(adapter => {
      const normalizedPath = stripFilePrefix(adapter.path);
      if (!normalizedPath) return;
      adapters.set(normalizedPath, {
        path: normalizedPath,
        scaled: adapter.scaled
      });
    });
  }
  return Array.from(adapters.values());
}
export async function getBackendDevicesInfo() {
  await installJsi();
  const {
    llamaGetBackendDevicesInfo
  } = getJsi();
  try {
    const jsonString = await llamaGetBackendDevicesInfo();
    return JSON.parse(jsonString);
  } catch (e) {
    console.warn('[RNLlama] Failed to parse backend devices info, falling back to empty list', e);
    return [];
  }
}
export async function initLlama({
  model,
  is_model_asset: isModelAsset,
  pooling_type: poolingType,
  lora,
  lora_scaled: loraScaled,
  lora_list: loraList,
  devices,
  ...rest
}, onProgress) {
  await installJsi();
  const {
    llamaInitContext
  } = getJsi();
  let path = model;
  if (path.startsWith('file://')) path = path.slice(7);
  const loraAdapters = normalizeLoraAdapters({
    lora,
    loraScaled,
    loraList
  });
  const contextId = contextIdCounter + contextIdRandom();
  contextIdCounter += 1;
  let lastProgress = 0;
  const progressCallback = onProgress ? progress => {
    lastProgress = progress;
    try {
      onProgress(progress);
    } catch (err) {
      console.warn('[RNLlama] onProgress callback failed', err);
    }
  } : undefined;
  if (progressCallback) progressCallback(0);
  const poolType = poolTypeMap[poolingType];
  if (rest.cache_type_k && !validCacheTypes.includes(rest.cache_type_k)) {
    console.warn(`[RNLlama] initLlama: Invalid cache K type: ${rest.cache_type_k}, falling back to f16`);
    delete rest.cache_type_k;
  }
  if (rest.cache_type_v && !validCacheTypes.includes(rest.cache_type_v)) {
    console.warn(`[RNLlama] initLlama: Invalid cache V type: ${rest.cache_type_v}, falling back to f16`);
    delete rest.cache_type_v;
  }
  let filteredDevs = [];
  if (Array.isArray(devices)) {
    filteredDevs = [...devices];
    const backendDevices = await getBackendDevicesInfo();
    if (Platform.OS === 'android' && devices.includes('HTP*')) {
      const htpDevices = backendDevices.filter(d => d.deviceName.startsWith('HTP')).map(d => d.deviceName);
      filteredDevs = filteredDevs.reduce((acc, dev) => {
        if (dev.startsWith('HTP*')) {
          acc.push(...htpDevices);
        } else if (!dev.startsWith('HTP')) {
          acc.push(dev);
        }
        return acc;
      }, []);
    }
  }
  const {
    gpu,
    devices: usedDevices,
    reasonNoGPU,
    model: modelDetails,
    androidLib,
    systemInfo
  } = await llamaInitContext(contextId, {
    model: path,
    is_model_asset: !!isModelAsset,
    use_progress_callback: !!progressCallback,
    pooling_type: poolType,
    ...(loraAdapters.length > 0 ? {
      lora_list: loraAdapters
    } : {}),
    devices: filteredDevs.length > 0 ? filteredDevs : undefined,
    ...rest
  }, progressCallback);
  if (progressCallback && lastProgress < 100) progressCallback(100);
  return new LlamaContext({
    contextId,
    gpu,
    devices: usedDevices,
    reasonNoGPU,
    model: modelDetails,
    androidLib,
    systemInfo
  });
}
export async function releaseAllLlama() {
  if (!isJsiInstalled) return;
  const {
    llamaReleaseAllContexts
  } = getJsi();
  return llamaReleaseAllContexts();
}
export const BuildInfo = {
  number: BUILD_NUMBER,
  commit: BUILD_COMMIT
};
//# sourceMappingURL=index.js.map