'use strict';

const path = require('path');
const { Registry } = require('../plugins/registry-base');
const { loadSiblingManifestsFromDir } = require('../plugins/manifest-loader');
const { addDiagnostics, addDiagnostic } = require('../plugins/diagnostics');

/**
 * LLM Provider Registry — replaces hardcoded `FACTORIES` map at
 * llm/client.js:14.
 *
 * Each provider ships:
 *   - <name>.js                 — factory export `createXProvider(config)`
 *   - <name>.plugin.json        — manifest with id, kind=provider,
 *                                 factoryExport, defaultModel,
 *                                 modelPrefixes (for model→provider
 *                                 disambiguation), capabilities,
 *                                 requires.envOneOf
 *
 * Adding a new provider (xAI, Together, Fireworks, etc.) = drop two
 * files in wall-e/llm/. Zero edits to client.js, zero edits anywhere
 * else.
 *
 * Each registered entry: { id, manifest, factory, envFactory?, source }.
 */

const _registry = new Registry('provider');
let _bootstrapped = false;

function register(entry, opts = {}) {
  return _registry.register(entry, opts);
}

function get(id) {
  return _registry.get(id);
}

function has(id) {
  return _registry.has(id);
}

function list() {
  return _registry.list();
}

function ids() {
  return _registry.ids();
}

function size() {
  return _registry.size();
}

/**
 * Create a provider instance via the registered factory.
 * Returns null if id unknown or factory throws.
 */
function createProvider(id, config) {
  const entry = _registry.get(id);
  if (!entry || typeof entry.factory !== 'function') return null;
  try {
    return entry.factory(config || {});
  } catch (err) {
    addDiagnostic({
      level: 'error',
      pluginId: id,
      kind: 'provider',
      message: `Provider factory failed: ${err.message}`,
      component: 'provider-registry',
    });
    return null;
  }
}

/**
 * Use the provider's env-shortcut factory if it has one
 * (e.g. createAnthropicFromEnv). Returns null if absent or throws.
 */
function createProviderFromEnv(id) {
  const entry = _registry.get(id);
  if (!entry || typeof entry.envFactory !== 'function') return null;
  try {
    return entry.envFactory();
  } catch (err) {
    addDiagnostic({
      level: 'error',
      pluginId: id,
      kind: 'provider',
      message: `Provider env factory failed: ${err.message}`,
      component: 'provider-registry',
    });
    return null;
  }
}

/**
 * Get the default model declared by the provider's manifest.
 * Returns null if id unknown or manifest lacks defaultModel.
 */
function getDefaultModel(id) {
  const entry = _registry.get(id);
  return entry?.manifest?.defaultModel || null;
}

/**
 * Map a model string to a provider id by checking manifest.modelPrefixes.
 * Returns the matching provider id, or null if no manifest claims it.
 *
 * Used by chat router + _modelMatchesProvider check.
 */
function detectProviderForModel(model) {
  if (!model) return null;
  const m = String(model).toLowerCase();
  for (const entry of _registry.list()) {
    const prefixes = entry.manifest?.modelPrefixes;
    if (!Array.isArray(prefixes)) continue;
    for (const px of prefixes) {
      if (typeof px !== 'string' || !px) continue;
      if (m.startsWith(px.toLowerCase())) return entry.id;
    }
  }
  return null;
}

/**
 * Manifest-driven _modelMatchesProvider replacement.
 * If the model string matches a different provider's prefix list,
 * it doesn't match this one. Models not claimed by any prefix list
 * are assumed compatible (e.g. ollama tag-style names).
 */
function modelMatchesProvider(model, providerId) {
  if (!model || !providerId) return true;
  const detected = detectProviderForModel(model);
  if (!detected) return true; // No claim — accept.
  const provider = String(providerId || '').toLowerCase();
  if (provider === 'openai-compatible' || provider.startsWith('openai-compatible-')) {
    return detected === 'openai';
  }
  return detected === provider;
}

/** Test helper. */
function _resetForTest() {
  _registry.clear();
  _bootstrapped = false;
}

/**
 * Default seeding — bundled providers from wall-e/llm/, plus 3rd-party
 * providers from `node_modules/` (Item H). Idempotent.
 */
function bootstrap(opts = {}) {
  const dir = opts.dir || path.join(__dirname);
  const { manifests: bundled, diagnostics } = loadSiblingManifestsFromDir(dir, 'bundled');
  if (diagnostics?.length) {
    addDiagnostics(diagnostics, { component: 'provider-registry' });
  }

  let thirdParty = [];
  if (opts.scanNpm !== false) {
    try {
      const { scanWorkspace } = require('../plugins/npm-scanner');
      const ws = opts.workspaceDir || path.resolve(__dirname, '..', '..');
      const { manifests, diagnostics: npmDiag } = scanWorkspace(ws);
      if (npmDiag?.length) {
        addDiagnostics(npmDiag, { component: 'provider-registry' });
      }
      thirdParty = manifests.filter((m) => m.kind === 'provider');
    } catch (err) {
      addDiagnostic({
        level: 'warn',
        message: `npm-scanner failed: ${err.message}`,
        component: 'provider-registry',
      });
    }
  }

  for (const manifest of [...bundled, ...thirdParty]) {
    if (manifest.kind !== 'provider') continue;
    let mod;
    try {
      mod = require(manifest.entryFile);
    } catch (err) {
      addDiagnostic({
        level: 'error',
        pluginId: manifest.id,
        kind: 'provider',
        message: `Failed to load provider module ${manifest.entryFile}: ${err.message}`,
        component: 'provider-registry',
      });
      continue;
    }

    const factory = mod[manifest.factoryExport];
    if (typeof factory !== 'function') {
      addDiagnostic({
        level: 'error',
        pluginId: manifest.id,
        kind: 'provider',
        message: `Provider module ${manifest.entryFile} does not export ${manifest.factoryExport}`,
        component: 'provider-registry',
      });
      continue;
    }
    const envFactory = manifest.envFactoryExport
      ? mod[manifest.envFactoryExport]
      : undefined;

    try {
      _registry.register(
        {
          id: manifest.id,
          manifest,
          factory,
          envFactory: typeof envFactory === 'function' ? envFactory : undefined,
          source: manifest.source || 'bundled',
        },
        { replace: true }
      );
    } catch (err) {
      addDiagnostic({
        level: 'error',
        pluginId: manifest.id,
        kind: 'provider',
        message: `Provider registration failed: ${err.message}`,
        component: 'provider-registry',
      });
    }
  }
  _bootstrapped = true;
}

function ensureBootstrapped() {
  if (!_bootstrapped) bootstrap();
}

module.exports = {
  register,
  get,
  has,
  list,
  ids,
  size,
  createProvider,
  createProviderFromEnv,
  getDefaultModel,
  detectProviderForModel,
  modelMatchesProvider,
  bootstrap,
  ensureBootstrapped,
  _resetForTest,
};
