/**
 * Shared types for the Datto RMM Overshell script library.
 *
 * Each script is a TypeScript module exporting a typed `RmmScript`. The
 * registry in `index.ts` aggregates them. All script bodies are
 * version-controlled in this repo; nothing in the DB is treated as
 * executable PowerShell.
 */

export type RmmTargetType = 'site_anchor' | 'asset_self';

/**
 * How the script returns its payload to Pulse.
 *  - `overshell_stdout` (default) — payload arrives in Datto getJobResults
 *    stdout. Suitable for small (<50KB) JSON payloads.
 *  - `b2_upload` — collector uploads gzipped JSON to Backblaze B2 and
 *    POSTs a webhook to /api/rmm/loglift/upload. Pulse downloads + parses
 *    asynchronously. Used by the LogLift event-log pipeline.
 */
export type RmmTransport = 'overshell_stdout' | 'b2_upload';

export interface RmmScript {
  /** Stable id used in URLs + persistence. kebab-case. */
  id: string;
  /** Human-friendly display name. */
  name: string;
  /** One-line description for the picker UI. */
  description: string;
  /** Where this script runs:
   *  - `site_anchor`: pick the WNP endpoint for the client.
   *  - `asset_self`: caller supplies the device_uid (typically the audited asset). */
  target_type: RmmTargetType;
  /** How the payload comes back. Defaults to overshell_stdout. */
  transport?: RmmTransport;
  /**
   * The PowerShell body. Best practice: end with `ConvertTo-Json` so
   * `parseOutput` can `JSON.parse` cleanly. Use `-Depth 4` or higher when
   * the structure is nested.
   *
   * For b2_upload scripts, the body lives outside Pulse (deployed as a
   * Datto component) and this field is empty.
   */
  body: string;
  /**
   * Best-effort parse of stdout into a structured object. May throw — the
   * worker catches and stores the error in rmm_executions.parse_error.
   *
   * For b2_upload scripts, the payload comes from B2 (parsed in the
   * webhook handler) so this is a no-op.
   */
  parseOutput: (stdout: string) => unknown;
  /** Hint for the picker UI + sanity check against the 5-minute hard cap. */
  expected_runtime_seconds: number;
  /** Schema version. Bump when we change the body or output shape so
   *  audit-context consumers can decide whether to use stored evidence. */
  version: number;
}

/**
 * Default JSON parser — strips any leading non-JSON noise (Datto sometimes
 * prefixes Overshell stdout with a banner) and parses the first balanced
 * JSON object/array it finds.
 */
export function parseJsonOutput(stdout: string): unknown {
  const trimmed = stdout.trim();
  if (!trimmed) throw new Error('Empty stdout');
  // Find the first { or [ and parse from there.
  const objStart = trimmed.indexOf('{');
  const arrStart = trimmed.indexOf('[');
  let start = -1;
  if (objStart === -1 && arrStart === -1) {
    throw new Error(`No JSON found in stdout: ${trimmed.slice(0, 120)}`);
  } else if (objStart === -1) start = arrStart;
  else if (arrStart === -1) start = objStart;
  else start = Math.min(objStart, arrStart);
  const candidate = trimmed.slice(start);
  return JSON.parse(candidate);
}
