'use strict';

const crypto = require('node:crypto');

const DEFAULT_RESULT_BUDGET_BYTES = 16 * 1024;
const DEFAULT_PREVIEW_BYTES = 4 * 1024;

function buildRuntimeToolResultEnvelope({
  call = {},
  spec = {},
  result,
  error = null,
  startedAt = 0,
  endedAt = Date.now(),
  now = () => new Date().toISOString(),
  resultBudgetBytes,
  previewBytes = DEFAULT_PREVIEW_BYTES,
} = {}) {
  const rawError = error || result?.error || null;
  const ok = !rawError && result?.invalid !== true && result?.denied !== true;
  const budget = Number.isFinite(Number(resultBudgetBytes))
    ? Number(resultBudgetBytes)
    : Number(spec.resultBudgetBytes || spec.result_budget_bytes || DEFAULT_RESULT_BUDGET_BYTES);
  const serialized = serializeResult(result);
  const bytes = Buffer.byteLength(serialized, 'utf8');
  const outputPreview = truncateBytes(serialized, previewBytes);
  const truncated = bytes > budget;
  const outputForModel = rawError
    ? { error: safeErrorMessage(rawError), ok: false }
    : truncated
      ? {
          ok,
          truncated: true,
          bytes,
          sha256: sha256(serialized),
          preview: truncateBytes(serialized, Math.min(previewBytes, budget)),
        }
      : result;

  return {
    version: 1,
    ok,
    toolCallId: String(call.id || call.toolCallId || call.tool_call_id || ''),
    name: String(call.name || call.tool || spec.name || ''),
    inputPreview: previewValue(call.input || call.args || {}),
    outputPreview,
    outputForModel,
    artifactRefs: extractArtifactRefs(result),
    summary: summarizeResult(result, rawError),
    error: rawError ? safeErrorMessage(rawError) : null,
    telemetry: {
      durationMs: Math.max(0, Number(endedAt || 0) - Number(startedAt || endedAt || 0)),
      errorType: rawError ? classifyToolError(rawError, result) : '',
      outputBytes: bytes,
      truncated,
      timestamp: now(),
    },
  };
}

function serializeResult(result) {
  if (typeof result === 'string') return result;
  try {
    return JSON.stringify(result == null ? null : result);
  } catch {
    return String(result);
  }
}

function previewValue(value, maxBytes = 1024) {
  return truncateBytes(serializeResult(value), maxBytes);
}

function truncateBytes(text, maxBytes) {
  const value = String(text || '');
  const budget = Math.max(0, Number(maxBytes || 0));
  if (!budget || Buffer.byteLength(value, 'utf8') <= budget) return value;
  let out = value.slice(0, budget);
  while (Buffer.byteLength(out, 'utf8') > budget) out = out.slice(0, -1);
  return `${out}\n[truncated]`;
}

function summarizeResult(result, error) {
  if (error) return `Tool failed: ${safeErrorMessage(error)}`;
  if (result == null) return 'No result';
  if (typeof result === 'string') return `${result.length} chars`;
  if (typeof result !== 'object') return String(typeof result);
  if (result.summary) return String(result.summary);
  if (result.ok === true) return 'ok';
  const keys = Object.keys(result).sort().slice(0, 8);
  return keys.length ? `object: ${keys.join(', ')}` : 'object';
}

function extractArtifactRefs(result) {
  const refs = [];
  const push = (artifact) => {
    if (!artifact || typeof artifact !== 'object') return;
    refs.push({
      artifactId: artifact.artifactId || artifact.id || '',
      kind: artifact.kind || artifact.type || '',
      path: artifact.path || artifact.file_path || '',
      sha256: artifact.sha256 || '',
      bytes: artifact.bytes || artifact.size || undefined,
    });
  };
  if (result?.artifact) push(result.artifact);
  if (Array.isArray(result?.artifacts)) result.artifacts.forEach(push);
  return refs.filter((ref) => ref.artifactId || ref.path || ref.sha256);
}

function safeErrorMessage(error) {
  if (!error) return '';
  if (typeof error === 'string') return error;
  return String(error.message || error.error || error.reason || error.name || error);
}

function classifyToolError(error, result) {
  if (result?.denied) return 'permission_denied';
  if (result?.invalid) return 'invalid_tool_call';
  const text = safeErrorMessage(error).toLowerCase();
  if (/permission|denied|forbidden/.test(text)) return 'permission_denied';
  if (/timeout|timed out/.test(text)) return 'timeout';
  if (/abort|cancel/.test(text)) return 'aborted';
  if (/not found|missing|required|schema|argument/.test(text)) return 'validation';
  return error?.name || 'tool_error';
}

function sha256(text) {
  return crypto.createHash('sha256').update(String(text || '')).digest('hex');
}

module.exports = {
  DEFAULT_RESULT_BUDGET_BYTES,
  buildRuntimeToolResultEnvelope,
  classifyToolError,
  previewValue,
  truncateBytes,
};
