// lib/tools/composite/registry.js — sole writer + loader for composite tools.
//
// A composite tool is a DECLARATIVE recipe (data, not code) chaining existing
// primitive registry tools + LLM-synthesis steps. This module is the only writer
// of the composite_tools table. It computes and stores the inherited risk tier
// (MAX over steps) at author time so the hot list path never recomputes it.
//
// Surfacing: listCompositeTools() returns dynamic tool descriptors in the SAME
// shape as a static MARKETPLACE_TOOLS row, tagged with source.kind='composite'.
// lib/tools/registry.js concatenates these into listMarketplaceTools() so every
// gate in executeRegistryTool runs on the composite descriptor, and the generic
// composite_tool_run adapter executes the recipe by RE-ENTERING executeRegistryTool
// per step (so no gate is skippable via composition).

import { getDb, nowIso } from '../../db.js';
import { validateCompositeSpec, RISK_ORDER } from './spec-validator.js';

function approvalForRisk(risk) {
  const required = RISK_ORDER.indexOf(risk) >= RISK_ORDER.indexOf('medium');
  return { approval_required: required, approval_mode: required ? 'approval_required' : 'auto' };
}

/**
 * Validate + write a composite tool. Throws an Error with code 'spec_invalid'
 * (and .errors) when the recipe is malformed.
 *
 * @param {object} spec   the recipe
 * @param {object} ctx    { created_by_user_id, created_by_agent_id }
 * @param {object} opts   { knownToolNames?: Set<string>, toolRiskByName?: Map<string,string> }
 *                        passed in by the caller so this module never imports the
 *                        primitive registry (avoids an import cycle).
 */
export function upsertCompositeTool(spec, ctx = {}, opts = {}) {
  const result = validateCompositeSpec(spec, {
    knownToolNames: opts.knownToolNames,
    toolRiskByName: opts.toolRiskByName,
  });
  if (!result.ok) {
    const err = new Error('composite spec invalid');
    err.code = 'spec_invalid';
    err.errors = result.errors;
    throw err;
  }
  const n = result.normalized;
  const db = getDb();
  const now = nowIso();
  const existing = db.prepare('SELECT slug FROM composite_tools WHERE slug = ?').get(n.slug);
  const specJson = JSON.stringify(n);
  const inputSchemaJson = JSON.stringify(n.input_schema || { type: 'object', properties: {} });
  if (existing) {
    db.prepare(`UPDATE composite_tools
        SET title=?, description=?, spec_json=?, risk_level=?, input_schema_json=?, status='active', updated_at=?
      WHERE slug=?`)
      .run(n.title, n.description, specJson, n.risk_level, inputSchemaJson, now, n.slug);
  } else {
    db.prepare(`INSERT INTO composite_tools
        (slug, title, description, spec_json, risk_level, input_schema_json, status,
         created_by_user_id, created_by_agent_id, created_at, updated_at)
      VALUES (?, ?, ?, ?, ?, ?, 'active', ?, ?, ?, ?)`)
      .run(n.slug, n.title, n.description, specJson, n.risk_level, inputSchemaJson,
        ctx.created_by_user_id || null, ctx.created_by_agent_id || null, now, now);
  }
  return { slug: n.slug, risk_level: n.risk_level, warnings: result.warnings };
}

export function archiveCompositeTool(slug) {
  const db = getDb();
  const row = db.prepare('SELECT slug FROM composite_tools WHERE slug=?').get(slug);
  if (!row) return { ok: false, error: 'not_found', slug };
  db.prepare("UPDATE composite_tools SET status='archived', updated_at=? WHERE slug=?").run(nowIso(), slug);
  return { ok: true, slug };
}

export function getCompositeTool(slug) {
  return getDb().prepare("SELECT * FROM composite_tools WHERE slug=? AND status='active'").get(slug) || null;
}

/** Parsed normalized recipe for the interpreter. null if missing/archived. */
export function getCompositeSpec(slug) {
  const row = getCompositeTool(slug);
  if (!row) return null;
  try {
    return JSON.parse(row.spec_json);
  } catch {
    return null;
  }
}

/**
 * Dynamic tool descriptors for every ACTIVE composite — same shape as a static
 * MARKETPLACE_TOOLS row, so executeRegistryTool's gates apply unchanged.
 */
export function listCompositeTools() {
  let rows;
  try {
    rows = getDb().prepare("SELECT * FROM composite_tools WHERE status='active' ORDER BY slug ASC").all();
  } catch {
    return [];
  }
  return rows.map((row) => {
    const { approval_required, approval_mode } = approvalForRisk(row.risk_level);
    let input_schema;
    try {
      input_schema = JSON.parse(row.input_schema_json || '') || { type: 'object', properties: {} };
    } catch {
      input_schema = { type: 'object', properties: {} };
    }
    return {
      name: row.slug,
      title: row.title || row.slug,
      description: row.description || `Composite tool: ${row.slug}`,
      category: 'composite',
      risk_level: row.risk_level,
      approval_required,
      approval_mode,
      required_env: [],
      enabled: true,
      available: true,
      missing_env: [],
      allowlist_required: false,
      allowlist_scope_key: null,
      allowlist_default_allow: false,
      input_schema,
      source: { kind: 'composite', composite_slug: row.slug },
    };
  });
}
