// Copyright (c) 2026 Hellmai Ltd
// SPDX-License-Identifier: AGPL-3.0-only

/**
 * WU-2738 (INIT-060, ADR-013 §6 governance): plan-to-commit envelope +
 * content-hash helpers for the governed sidekick:routine:commit_plan
 * tool. The commit envelope is what an agent attestation carries; the
 * content-hash is what lands on the audit trail so cloud can correlate
 * a `sidekick:routine_committed` event with the originating envelope
 * without the PII-bearing body ever leaving the local workspace.
 */

import { createHash } from 'node:crypto';
import type { RoutineRecord } from '../../tool-impl/storage.js';

const CONTENT_HASH_ALGORITHM = 'sha256';
const CONTENT_HASH_ENCODING = 'hex';

export interface RoutineCommitAttestation {
  actor: string;
  reason: string;
  // Intentionally untyped extension slot: attestation may carry arbitrary
  // operator-supplied metadata which can contain PII. The envelope hash
  // covers the entire attestation; the body never appears on the audit
  // event surface.
  [key: string]: unknown;
}

export interface RoutineCommitEnvelope {
  plan_id: string;
  routine_name: string;
  step_count: number;
  committed_at: string;
  attestation: RoutineCommitAttestation;
}

export interface BuildRoutineCommitEnvelopeInput {
  routine: RoutineRecord;
  attestation: RoutineCommitAttestation;
  committedAt: string;
}

export function buildRoutineCommitEnvelope(
  input: BuildRoutineCommitEnvelopeInput,
): RoutineCommitEnvelope {
  return {
    plan_id: input.routine.id,
    routine_name: input.routine.name,
    step_count: input.routine.steps.length,
    committed_at: input.committedAt,
    attestation: { ...input.attestation },
  };
}

function canonicalize(value: unknown): unknown {
  if (Array.isArray(value)) {
    return value.map((entry) => canonicalize(entry));
  }
  if (value && typeof value === 'object') {
    const record = value as Record<string, unknown>;
    const sortedKeys = Object.keys(record).sort();
    const result: Record<string, unknown> = {};
    for (const key of sortedKeys) {
      result[key] = canonicalize(record[key]);
    }
    return result;
  }
  return value;
}

export function hashRoutineCommitEnvelope(envelope: RoutineCommitEnvelope): string {
  const canonical = JSON.stringify(canonicalize(envelope));
  return createHash(CONTENT_HASH_ALGORITHM).update(canonical).digest(CONTENT_HASH_ENCODING);
}
