// lib/tools/adapters/skill-definitions.js
//
// Adapters for `create_skill` and `update_skill` agent tools.
// Both call upsertSkill() directly — the same single write path used by all
// skill authoring surfaces. The strict spec-validator (lib/skills/
// skill-validator.js) is the authority on shape; approval/allowlist/rate-limit
// gates fire in executeRegistryTool BEFORE these adapters run.
//
// Provenance: options.agent_id and options.acting_on_behalf_of_user_id are
// embedded inside manifest_json (marketplace_items has no dedicated provenance
// columns — see lib/skills/definitions.js upsertSkill for the stash pattern).
//
// Why direct call (not HTTP fetch via inproc token):
//   - executeRegistryTool already runs all gates. Going through HTTP adds no
//     security — it just couples this adapter to the dev-server lifecycle,
//     port discovery, and inproc-token plumbing. upsertSkill is the SOLE
//     writer of skill marketplace rows and SKILL.md files; both authoring
//     surfaces (API + this adapter) call it directly.

import { upsertSkill, getSkillDefinition } from '../../skills/definitions.js';
import { getDb } from '../../db.js';

// ---------------------------------------------------------------------------
// Role-gate helpers (verbatim precedent from workflow-definitions.js)
// ---------------------------------------------------------------------------

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

function hasAuthoringAuthority(options = {}) {
  if (options.owner_trusted) return true;
  if (options.actor_role === 'owner' || options.actor_role === 'admin') return true;
  const userId = asActingUserId(options);
  if (!userId || userId === 'system') return false;
  try {
    const row = getDb()
      .prepare("SELECT role FROM org_memberships WHERE user_id=? AND org_id=COALESCE(?, 'personal-org')")
      .get(userId, options.org_id || 'personal-org');
    return row?.role === 'owner' || row?.role === 'admin';
  } catch { return false; }
}

// ---------------------------------------------------------------------------

/**
 * Create a new skill from a declarative spec.
 *
 * Refuses to overwrite an existing slug — that path is `update_skill`.
 * Returns { ok: true, status, skill_id, slug, invocation, file_path } on
 * success, or { ok: false, error, … } on role failure, missing input, slug
 * conflict, or spec validation failure.
 *
 * @param {{ spec: object }} input
 * @param {object} options  - options.agent_id, options.acting_on_behalf_of_user_id,
 *                            options.actor_role, options.owner_trusted, options.org_id
 */
export async function createSkill(input, options = {}) {
  if (!hasAuthoringAuthority(options)) {
    return {
      ok: false,
      status: 'error',
      error: 'insufficient_role',
      message: 'Skill authoring requires owner/admin authority.',
    };
  }

  const spec = input?.spec;
  if (!spec || typeof spec !== 'object') {
    return { ok: false, status: 'error', error: 'spec_required', message: 'spec required (object)' };
  }
  if (!spec.slug || typeof spec.slug !== 'string') {
    return { ok: false, error: 'slug_required', message: 'spec.slug required (string)' };
  }

  // Refuse to silently overwrite — caller must use update_skill instead.
  if (getSkillDefinition(spec.slug)) {
    return { ok: false, status: 'error', error: 'slug_in_use', slug: spec.slug };
  }

  try {
    const r = upsertSkill(spec, {
      created_by_user_id: asActingUserId(options),
      created_by_agent_id: options?.agent_id || null,
    });
    return {
      ok: true,
      status: 'complete',
      skill_id: r.id,
      slug: r.slug,
      invocation: r.invocation,
      file_path: r.file_path,
    };
  } catch (err) {
    if (err.code === 'spec_invalid') {
      return { ok: false, status: 'error', error: 'spec_invalid', errors: err.errors || [err.message] };
    }
    throw err;
  }
}

/**
 * Update an existing skill. The slug is taken from input.slug (path-style)
 * and forced onto spec.slug so an agent cannot rename out from under itself.
 *
 * Returns { ok: true, status, skill_id, slug, invocation, file_path } on
 * success, or { ok: false, error, … } on role failure, missing input,
 * not-found, or spec validation failure.
 *
 * @param {{ slug: string, spec: object }} input
 * @param {object} options
 */
export async function updateSkill(input, options = {}) {
  if (!hasAuthoringAuthority(options)) {
    return {
      ok: false,
      status: 'error',
      error: 'insufficient_role',
      message: 'Skill authoring requires owner/admin authority.',
    };
  }

  const slug = typeof input?.slug === 'string' ? input.slug.trim() : '';
  const spec = input?.spec;

  if (!slug) {
    return { ok: false, status: 'error', error: 'slug_required', message: 'slug required (string)' };
  }
  if (!spec || typeof spec !== 'object') {
    return { ok: false, status: 'error', error: 'spec_required', message: 'spec required (object)' };
  }

  if (!getSkillDefinition(slug)) {
    return { ok: false, status: 'error', error: 'not_found', slug };
  }

  // Defensive copy — force slug so the validator and writer always agree.
  const cloned = { ...spec, slug };

  try {
    const r = upsertSkill(cloned, {
      created_by_user_id: asActingUserId(options),
      created_by_agent_id: options?.agent_id || null,
    });
    return {
      ok: true,
      status: 'complete',
      skill_id: r.id,
      slug: r.slug,
      invocation: r.invocation,
      file_path: r.file_path,
    };
  } catch (err) {
    if (err.code === 'spec_invalid') {
      return { ok: false, status: 'error', error: 'spec_invalid', errors: err.errors || [err.message] };
    }
    throw err;
  }
}
