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

/**
 * WU-2738 (INIT-060, ADR-013 §6 governance): `sidekick:routine:commit_plan`
 * is the only path from a planned routine to a committed one. It is
 * registered as a runtime-callable tool (not a top-level surface) so the
 * kernel's governed dispatch emits `agent-runtime:tool_called` inside a
 * turn, and the commit itself emits `sidekick:routine_committed` carrying
 * a content-hash of the commit envelope. No fast-path bypasses this tool.
 */

import {
  buildRoutineCommitEnvelope,
  hashRoutineCommitEnvelope,
  type RoutineCommitAttestation,
} from '../src/routines/commit.js';
import { buildRoutineCommittedEvent, emitSidekickEvent } from '../sidekick-events.js';
import { getStoragePort } from './storage.js';
import {
  asNonEmptyString,
  buildAuditEvent,
  failure,
  nowIso,
  success,
  toRecord,
  type ToolContextLike,
  type ToolOutput,
} from './shared.js';

const ROUTINE_COMMIT_PLAN_TOOL_NAME = 'sidekick:routine:commit_plan';

function readAttestation(value: unknown): RoutineCommitAttestation | null {
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    return null;
  }
  const record = value as Record<string, unknown>;
  const actor = asNonEmptyString(record.actor);
  const reason = asNonEmptyString(record.reason);
  if (!actor || !reason) {
    return null;
  }
  return {
    ...record,
    actor,
    reason,
  };
}

export default async function routineCommitPlanTool(
  input: unknown,
  context?: ToolContextLike,
): Promise<ToolOutput> {
  const parsed = toRecord(input);
  const planId = asNonEmptyString(parsed.plan_id);
  const attestation = readAttestation(parsed.attestation);

  if (!planId) {
    return failure('INVALID_INPUT', 'plan_id is required.');
  }
  if (!attestation) {
    return failure(
      'INVALID_INPUT',
      'attestation must be an object with non-empty actor and reason fields.',
    );
  }

  const storage = getStoragePort();
  const routines = await storage.readStore('routines');
  const routine = routines.find((entry) => entry.id === planId);
  if (!routine) {
    return failure('NOT_FOUND', `routine ${planId} was not found.`);
  }

  const committedAt = nowIso();
  const envelope = buildRoutineCommitEnvelope({ routine, attestation, committedAt });
  const envelopeContentHash = hashRoutineCommitEnvelope(envelope);

  await storage.appendAudit(
    buildAuditEvent({
      tool: ROUTINE_COMMIT_PLAN_TOOL_NAME,
      op: 'execute',
      context,
      ids: [routine.id],
      details: {
        plan_id: routine.id,
        envelope_content_hash: envelopeContentHash,
      },
    }),
  );

  await emitSidekickEvent(
    buildRoutineCommittedEvent(routine, { envelope_content_hash: envelopeContentHash }),
  );

  return success({
    plan_id: routine.id,
    routine_id: routine.id,
    envelope_content_hash: envelopeContentHash,
    committed_at: committedAt,
  });
}
