'use strict';

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

/**
 * Adapter Registry — replaces hardcoded require list at agent.js:8-9
 * (CtmAdapter / SlackAdapter) plus the implicit chatgpt/claude-web/
 * plaintext one-shot import path.
 *
 * Each adapter ships:
 *   - <name>.js              — implementation
 *                              * class adapters: `module.exports = ClassName`
 *                              * function adapters: `module.exports = { poll, ... }`
 *   - <name>.plugin.json     — kind=adapter, mode (poll | import),
 *                              configKey, enabledByDefault, classExport,
 *                              capabilities
 *
 * Each registered entry: { id, manifest, module, source }.
 * - module is the loaded require() result (class or { poll } object)
 * - mode='poll' adapters are loaded into the live agent.js loop based
 *   on `enabledByDefault` || `config.adapters[configKey].enabled`
 * - mode='import' adapters surface via the import-data UI (not live-loop)
 */

const _registry = new Registry('adapter');
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(filter = {}) {
  const all = _registry.list();
  if (!filter || Object.keys(filter).length === 0) return all;
  return all.filter((entry) => {
    if (filter.mode && entry.manifest?.mode !== filter.mode) return false;
    if (filter.classExport !== undefined && entry.manifest?.classExport !== filter.classExport) return false;
    return true;
  });
}

function ids(filter = {}) {
  return list(filter).map((e) => e.id);
}

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

/**
 * Instantiate a class-export adapter (mode=poll, classExport=true).
 * Returns null if the id is unknown, the adapter is function-shaped, or
 * the constructor throws.
 */
function instantiate(id, ctorOpts = {}) {
  const entry = _registry.get(id);
  if (!entry) return null;
  if (!entry.manifest?.classExport) {
    addDiagnostic({
      level: 'warn',
      pluginId: id,
      kind: 'adapter',
      message: `instantiate() called on function-shaped adapter; use functions[id].poll() directly`,
      component: 'adapter-registry',
    });
    return null;
  }
  if (typeof entry.module !== 'function') {
    addDiagnostic({
      level: 'error',
      pluginId: id,
      kind: 'adapter',
      message: 'classExport=true but module export is not a constructor',
      component: 'adapter-registry',
    });
    return null;
  }
  try {
    return new entry.module(ctorOpts);
  } catch (err) {
    addDiagnostic({
      level: 'error',
      pluginId: id,
      kind: 'adapter',
      message: `Adapter constructor failed: ${err.message}`,
      component: 'adapter-registry',
    });
    return null;
  }
}

/**
 * Returns true if the live-loop adapter is enabled for the config block.
 * Resolution: explicit `config.adapters[configKey].enabled` wins; if the
 * key is missing, falls back to `manifest.enabledByDefault`.
 */
function isPollEnabledByConfig(manifest, config) {
  if (!manifest || !config) return Boolean(manifest?.enabledByDefault);
  if (manifest.mode !== 'poll') return false;
  const key = manifest.configKey || manifest.id;
  const block = config.adapters && config.adapters[key];
  if (!block) return Boolean(manifest.enabledByDefault);
  if (block.enabled === false) return false;
  if (block.enabled === true) return true;
  // unset → fall back to default
  return Boolean(manifest.enabledByDefault);
}

function _resetForTest() {
  _registry.clear();
  _bootstrapped = false;
}

function bootstrap(opts = {}) {
  const dir = opts.dir || path.join(__dirname);
  const { manifests: bundled, diagnostics } = loadSiblingManifestsFromDir(dir, 'bundled', {
    exclude: ['adapter-base.plugin.json'],
  });
  if (diagnostics?.length) {
    addDiagnostics(diagnostics, { component: 'adapter-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: 'adapter-registry' });
      }
      thirdParty = manifests.filter((m) => m.kind === 'adapter');
    } catch (err) {
      addDiagnostic({
        level: 'warn',
        message: `npm-scanner failed: ${err.message}`,
        component: 'adapter-registry',
      });
    }
  }

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

    if (manifest.classExport && typeof mod !== 'function') {
      addDiagnostic({
        level: 'error',
        pluginId: manifest.id,
        kind: 'adapter',
        message: `classExport=true but module does not export a class`,
        component: 'adapter-registry',
      });
      continue;
    }
    if (!manifest.classExport && (typeof mod !== 'object' || mod === null)) {
      addDiagnostic({
        level: 'error',
        pluginId: manifest.id,
        kind: 'adapter',
        message: `classExport=false but module exports are not an object`,
        component: 'adapter-registry',
      });
      continue;
    }

    try {
      _registry.register(
        {
          id: manifest.id,
          manifest,
          module: mod,
          source: manifest.source || 'bundled',
        },
        { replace: true }
      );
    } catch (err) {
      addDiagnostic({
        level: 'error',
        pluginId: manifest.id,
        kind: 'adapter',
        message: `Adapter registration failed: ${err.message}`,
        component: 'adapter-registry',
      });
    }
  }
  _bootstrapped = true;
}

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

module.exports = {
  register,
  get,
  has,
  list,
  ids,
  size,
  instantiate,
  isPollEnabledByConfig,
  bootstrap,
  ensureBootstrapped,
  _resetForTest,
};
