// lib/tools/adapters/tool-forge.js — Option B: `propose_tool`.
//
// For genuinely new integrations that don't fit a declarative connector (non-REST,
// SDK-only, binary, complex OAuth), Tool-Smith GENERATES code but NEVER self-loads
// it. propose_tool writes a proposal package (adapter scaffold + a MARKETPLACE_TOOLS
// snippet + a test stub + a README) into a staging directory and records a
// tool_proposals row + a review task. The proposed tool is INERT — it is not in
// MARKETPLACE_TOOLS / ADAPTERS and is never callable until a human (or the
// CC-builder via the /build → /review loop) merges it and restarts.
//
// Risk: 'medium' + approval_required (it writes files to a bounded staging dir but
// executes nothing). NOT irreversible_external — there is no external side effect.

import fs from 'node:fs';
import path from 'node:path';
import { getDb, nowIso, newId } from '../../db.js';
import { WORKSPACE } from '../../paths.js';
import { isInsideAllowedRoots } from '../safety.js';

const SLUG_REGEX = /^[a-z][a-z0-9_]{2,63}$/;

function asActingUserId(options) {
  return options?.acting_on_behalf_of_user_id || options?.user_id || null;
}

function adapterScaffold(slug, summary) {
  const fn = slug.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
  return `// lib/tools/adapters/${slug}.js — PROPOSED, not yet wired.
//
// ${summary || 'Proposed tool adapter.'}
//
// This file was generated by propose_tool and is INERT. To make it live a human
// must: review it, register '${slug}' in MARKETPLACE_TOOLS + ADAPTERS in
// lib/tools/registry.js, add tests, run \`npm run check\`, and restart.

import { wrapFetch } from '../../egress.js';

export async function ${fn}(input = {}, options = {}) {
  // TODO: implement. Use wrapFetch('${slug}', options.agent_id) for any external
  // call so egress is logged. Respect allowed-roots/allowlists/secrets-by-vault.
  throw new Error('${slug} is a proposal and is not implemented yet');
}
`;
}

function registrySnippet(slug, summary, risk) {
  return `// Add to MARKETPLACE_TOOLS in lib/tools/registry.js:
{
  name: '${slug}',
  title: '${slug}',
  description: ${JSON.stringify(summary || `Proposed tool ${slug}`)},
  category: 'proposed',
  risk_level: '${risk || 'medium'}',
  approval_mode: 'approval_required',
  approval_required: true,
  required_env: [],
  enabled_default: false,
  allowlist_required: false,
  input_schema: { type: 'object', properties: {} },
},

// Add to ADAPTERS in lib/tools/registry.js:
//   ${slug}: ${slug.replace(/_([a-z])/g, (_, c) => c.toUpperCase())},
// and import it from './adapters/${slug}.js'.
`;
}

function testStub(slug) {
  return `import test from 'node:test';
import assert from 'node:assert/strict';

test('${slug}: throws until implemented', async () => {
  const { ${slug.replace(/_([a-z])/g, (_, c) => c.toUpperCase())} } = await import('../lib/tools/adapters/${slug}.js');
  await assert.rejects(() => ${slug.replace(/_([a-z])/g, (_, c) => c.toUpperCase())}({}, {}));
});
`;
}

export async function proposeTool(input = {}, options = {}) {
  const slug = typeof input?.slug === 'string' ? input.slug.trim() : '';
  if (!slug || !SLUG_REGEX.test(slug)) {
    return { ok: false, status: 'error', error: 'slug_required', message: 'slug required (snake_case, 3-64 chars)' };
  }
  const summary = typeof input?.summary === 'string' ? input.summary.slice(0, 2000) : '';
  const risk = ['low', 'medium', 'high', 'irreversible_external'].includes(input?.risk) ? input.risk : 'medium';

  const stagingRel = path.join('tool-proposals', slug);
  const stagingDir = path.join(WORKSPACE, stagingRel);

  const files = {
    [`${slug}.adapter.js`]: adapterScaffold(slug, summary),
    'registry-snippet.txt': registrySnippet(slug, summary, risk),
    [`${slug}.test.js`]: testStub(slug),
    'README.md': `# Proposed tool: ${slug}\n\n${summary || ''}\n\nThis is an INERT proposal. It is NOT callable. To ship it, review the\nadapter, apply registry-snippet.txt to lib/tools/registry.js, add tests,\nrun \`npm run check\`, and restart.\n`,
  };

  const written = [];
  try {
    // Staging lives under WORKSPACE (an allowed root). Path is constructed from a
    // validated slug; assert containment with isInsideAllowedRoots.
    if (!isInsideAllowedRoots(stagingDir)) {
      return { ok: false, status: 'error', error: 'staging_write_failed', message: 'staging dir is outside the workspace' };
    }
    fs.mkdirSync(stagingDir, { recursive: true });
    for (const [name, content] of Object.entries(files)) {
      const target = path.join(stagingDir, name);
      if (!isInsideAllowedRoots(target)) continue;
      fs.writeFileSync(target, content, 'utf8');
      written.push(path.join(stagingRel, name));
    }
  } catch (err) {
    return { ok: false, status: 'error', error: 'staging_write_failed', message: String(err?.message || err) };
  }

  const db = getDb();
  const now = nowIso();
  const id = newId('proposal');
  let taskId = null;
  try {
    const { createTask } = await import('../../delegation.js');
    const task = createTask({
      title: `Review tool proposal: ${slug}`,
      description: `propose_tool generated a scaffold for '${slug}' at ${stagingRel}.\n\n${summary}\n\nReview, wire into lib/tools/registry.js (see registry-snippet.txt), add tests, run npm run check, restart. The tool is INERT until merged.`,
      priority: 'p2',
      created_by_agent_id: options?.agent_id || null,
    });
    taskId = task?.id || null;
  } catch {
    // Task creation is best-effort — the proposal row is the durable record.
  }

  db.prepare(`INSERT INTO tool_proposals
      (id, slug, title, summary, staging_dir, files_json, status, task_id, created_by_user_id, created_by_agent_id, created_at, updated_at)
    VALUES (?, ?, ?, ?, ?, ?, 'proposed', ?, ?, ?, ?, ?)`)
    .run(id, slug, slug, summary, stagingRel, JSON.stringify(written), taskId,
      asActingUserId(options), options?.agent_id || null, now, now);

  return {
    ok: true,
    status: 'complete',
    proposal_id: id,
    slug,
    staging_dir: stagingRel,
    files: written,
    task_id: taskId,
    callable: false,
    message: `Proposal staged. '${slug}' is INERT and will not be callable until reviewed, merged, and the server restarts.`,
  };
}
