// PRE Web GUI — GGUF model resolver for llama.cpp
// Resolves model paths from Ollama's blob store or direct GGUF paths.

const fs = require('fs');
const path = require('path');
const os = require('os');
const { IS_WIN } = require('./platform');

// Ollama model store paths by platform
function getOllamaModelsDir() {
  if (IS_WIN) return path.join(process.env.USERPROFILE || os.homedir(), '.ollama', 'models');
  // Linux: check service path first, then user path
  const servicePath = '/usr/share/ollama/.ollama/models';
  const userPath = path.join(os.homedir(), '.ollama', 'models');
  try {
    if (fs.existsSync(servicePath)) return servicePath;
  } catch {}
  return userPath;
}

/**
 * Resolve an Ollama model tag to its GGUF blob path.
 * Ollama stores manifests at: manifests/registry.ollama.ai/library/<model>/<tag>
 * Each manifest has layers with digest hashes pointing to blobs/ files.
 *
 * @param {string} tag - Ollama model tag (e.g. 'pre-gemma4', 'gemma4:26b-a4b-it-q8_0')
 * @returns {string|null} Absolute path to the GGUF blob, or null
 */
function resolveOllamaModel(tag) {
  const modelsDir = getOllamaModelsDir();
  // Parse tag: "model:tag" or just "model" (defaults to "latest")
  let [modelName, modelTag] = tag.includes(':') ? tag.split(':') : [tag, 'latest'];

  // Try manifests directory
  const manifestPath = path.join(modelsDir, 'manifests', 'registry.ollama.ai', 'library', modelName, modelTag);
  try {
    const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
    // Find the model layer (mediaType contains 'model' or is the largest layer)
    const layers = manifest.layers || [];
    const modelLayer = layers.find(l =>
      l.mediaType?.includes('model') ||
      l.mediaType?.includes('gguf')
    ) || layers.reduce((a, b) => (a.size || 0) > (b.size || 0) ? a : b, {});

    if (modelLayer?.digest) {
      // Digest format: "sha256:abc123..." → blob at blobs/sha256-abc123...
      const blobName = modelLayer.digest.replace(':', '-');
      const blobPath = path.join(modelsDir, 'blobs', blobName);
      if (fs.existsSync(blobPath)) {
        return blobPath;
      }
    }
  } catch {}

  return null;
}

/**
 * Resolve a model name or path to an absolute GGUF file path.
 * Accepts: absolute path, Ollama tag, or model name.
 *
 * @param {string} nameOrPath
 * @returns {string|null}
 */
function resolveModelPath(nameOrPath) {
  if (!nameOrPath) return null;

  // If it looks like an absolute path, verify it exists
  if (path.isAbsolute(nameOrPath)) {
    return fs.existsSync(nameOrPath) ? nameOrPath : null;
  }

  // If it has a file extension, try as relative path
  if (nameOrPath.endsWith('.gguf')) {
    const abs = path.resolve(nameOrPath);
    return fs.existsSync(abs) ? abs : null;
  }

  // Try as Ollama model tag
  return resolveOllamaModel(nameOrPath);
}

/**
 * List available GGUF models from Ollama's store.
 * Returns array of { name, tag, path, size } objects.
 */
function listAvailableModels() {
  const models = [];
  const modelsDir = getOllamaModelsDir();
  const manifestsDir = path.join(modelsDir, 'manifests', 'registry.ollama.ai', 'library');

  try {
    const modelDirs = fs.readdirSync(manifestsDir);
    for (const modelName of modelDirs) {
      const tagDir = path.join(manifestsDir, modelName);
      try {
        const tags = fs.readdirSync(tagDir);
        for (const tag of tags) {
          const fullTag = `${modelName}:${tag}`;
          const ggufPath = resolveOllamaModel(fullTag);
          if (ggufPath) {
            let size = 0;
            try { size = fs.statSync(ggufPath).size; } catch {}
            models.push({
              name: modelName,
              tag,
              fullTag,
              path: ggufPath,
              sizeGB: (size / (1024 * 1024 * 1024)).toFixed(1),
            });
          }
        }
      } catch {}
    }
  } catch {}

  return models;
}

module.exports = { resolveOllamaModel, resolveModelPath, listAvailableModels, getOllamaModelsDir };
