/** Shared helpers for management MCP tools. */

/** Wrap a string as an MCP text result. */
export function text(s: string) {
  return { content: [{ type: 'text' as const, text: s }] };
}

/** Wrap an error as an MCP text result flagged is_error. */
export function fail(s: string) {
  return { content: [{ type: 'text' as const, text: s }], isError: true };
}

/** Run a handler, converting thrown errors into a clean is_error result so a
 *  tool bug never crashes the session. */
export async function guard(fn: () => Promise<ReturnType<typeof text>> | ReturnType<typeof text>) {
  try { return await fn(); }
  catch (e) { return fail(`Error: ${(e as Error).message}`); }
}

/** Human label for which session a created task attached to. Reflects what
 *  actually happened — an explicit fresh session, a continued existing one, or a
 *  fresh start because no prior session was found — rather than over-promising
 *  "continuing" when there was nothing to continue. */
export function describeSession(
  newSession: boolean | undefined,
  conversationId: string | null | undefined,
): string {
  if (newSession) return 'new session';
  return conversationId ? 'continuing session' : 'no prior session — started fresh';
}

/** True if a marketplace skill is installed globally or in any project. A
 *  SkillItem exposes `installedGlobal` / `installedProjects` — there is no
 *  `installed` field, so reading one silently never matches. */
export function skillInstalled(e: { installedGlobal?: boolean; installedProjects?: string[] }): boolean {
  return !!(e.installedGlobal || e.installedProjects?.length);
}

/** Display status for one connector settings field. Secret-bearing fields NEVER
 *  echo their (decrypted-at-rest) value — only whether they are set — so listing a
 *  connector can't leak credentials. This mirrors the codebase's canonical secret
 *  detection (lib/connectors/registry.ts isSecretField = `secret || password`, and
 *  the ConnectorsPanel UI), which masks `secret` AND `password` and treats
 *  `instances` arrays as opaque because each row can carry its own sub-secret (e.g.
 *  a per-server PAT). Only plain non-secret scalar values are shown (clipped). */
export function connectorFieldStatus(field: { type: string; required?: boolean }, value: unknown): string {
  const unset = field.required ? 'UNSET (required)' : 'unset';
  // secret / password — report set-ness only, never the value.
  if (field.type === 'secret' || field.type === 'password') return value ? 'set' : unset;
  // instances — opaque: rows may hold sub-secrets, so never serialize the value.
  if (field.type === 'instances') {
    let rows = value;
    if (typeof rows === 'string') { try { rows = JSON.parse(rows); } catch { /* keep as-is */ } }
    const n = Array.isArray(rows) ? rows.length : (value ? 1 : 0);
    return n ? `${n} instance(s) configured` : unset;
  }
  if (value !== undefined && value !== '') return `= ${String(JSON.stringify(value)).slice(0, 40)}`;
  return field.required ? 'MISSING (required)' : 'unset';
}

/** Human description of a schedule/job trigger from its kind + timing fields. */
export function describeTiming(s: {
  schedule_kind?: string | null;
  schedule_interval_minutes?: number;
  schedule_cron?: string | null;
  schedule_at?: string | null;
}): string {
  switch (s.schedule_kind) {
    case 'cron': return `cron "${s.schedule_cron || '?'}"`;
    case 'once': return `once at ${s.schedule_at || '?'}`;
    case 'manual': return 'manual only';
    case 'period': return `every ${s.schedule_interval_minutes ?? '?'}m`;
    default:
      // Unknown/absent kind: prefer a real trigger field over mislabelling as an interval.
      if (s.schedule_cron) return `cron "${s.schedule_cron}"`;
      if (s.schedule_at) return `once at ${s.schedule_at}`;
      return s.schedule_interval_minutes != null ? `every ${s.schedule_interval_minutes}m` : 'unknown schedule';
  }
}

/** Pure pipeline-summary logic for forge_status: count running across ALL runs
 *  (not a truncated recent window, which undercounts an older still-running run)
 *  and surface the 3 newest. */
export function summarizePipelines(
  all: { id: string; status: string; workflowName: string; createdAt: string }[],
): { runningCount: number; recent: string[] } {
  const runningCount = all.filter((p) => p.status === 'running').length;
  const recent = [...all]
    .sort((a, b) => b.createdAt.localeCompare(a.createdAt))
    .slice(0, 3)
    .map((p) => `${p.id} [${p.status}] ${p.workflowName}`);
  return { runningCount, recent };
}

/** Compact token/count formatter: 1234567 → "1.2M", 340000 → "340K", 999 → "999".
 *  Non-finite input collapses to "0" so a bad row never prints "NaN". */
export function fmtTokens(n: number): string {
  if (!Number.isFinite(n)) return '0';
  const abs = Math.abs(n);
  if (abs >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
  if (abs >= 1_000) return `${Math.round(n / 1_000)}K`;
  return String(Math.round(n));
}
