#!/usr/bin/env node
"use strict";

const crypto = require("node:crypto");
const { spawnSync } = require("node:child_process");
const fs = require("node:fs");
const path = require("node:path");
const readline = require("node:readline");

const SERVER_NAME = "registro-imprese-sari-review";
const PLUGIN_ROOT = path.resolve(__dirname, "..");
const PLUGIN_MANIFEST = JSON.parse(
  fs.readFileSync(path.join(PLUGIN_ROOT, ".codex-plugin", "plugin.json"), "utf8"),
);
const SERVER_VERSION = PLUGIN_MANIFEST.version || "0.1.0";
const PLUGIN_NAME = "registro-imprese-sari";
const WIDGET_URI = "ui://widget/registro-imprese-sari-review.html";
const WIDGET_MIME_TYPE = "text/html;profile=mcp-app";
const MAX_ITEMS = 500;
const MAX_PAYLOAD_BYTES = 2_000_000;
const MAX_TEXT_LENGTH = 10_000;
const MAX_PERSISTENCE_CONTEXTS = 128;
const PERSISTENCE_CONTEXT_TTL_MS = 4 * 60 * 60 * 1000;
const PERSISTENCE_TOKEN_RE = /^[A-Za-z0-9_-]{43}$/;
const PERSISTENCE_CONTEXTS = new Map();
const TOOL_NAMES = {
  validate: "validate_registro_imprese_sari_review",
  render: "render_registro_imprese_sari_review",
  save: "save_registro_imprese_sari_decisions",
  apply: "apply_registro_imprese_sari_decisions",
};
const ALLOWED_ACTIONS = new Set([
  "accept",
  "reject",
  "edit",
  "mark_unclear",
  "request_more_documents",
  "skip",
]);
const ITEM_TYPES = new Set([
  "case_fact",
  "practice_step",
  "official_source",
  "missing_information",
  "audit_check",
  "artifact",
]);
const ACTION_STATUS = {
  accept: "accepted",
  reject: "rejected",
  edit: "edited_revision_required",
  mark_unclear: "needs_clarification",
  request_more_documents: "needs_documents",
  skip: "skipped",
};
const PROHIBITED_SECRET_KEYS = new Set([
  "access_token",
  "api_key",
  "cie",
  "cns",
  "cookie",
  "cookies",
  "credential",
  "credentials",
  "one_time_code",
  "otp",
  "passcode",
  "password",
  "refresh_token",
  "session",
  "session_cookie",
  "session_id",
  "signature",
  "spid",
  "token",
]);
function isObject(value) {
  return value !== null && typeof value === "object" && !Array.isArray(value);
}

function pythonExecutable() {
  const virtualEnvironmentPython = process.env.VIRTUAL_ENV
    ? path.join(
        process.env.VIRTUAL_ENV,
        process.platform === "win32" ? "Scripts/python.exe" : "bin/python",
      )
    : "";
  const repositoryPython = path.resolve(
    PLUGIN_ROOT,
    "..",
    "..",
    ".venv",
    process.platform === "win32" ? "Scripts/python.exe" : "bin/python",
  );
  const candidates = [
    process.env.VERA_CLIENT_WORKFLOW_PYTHON,
    process.env.PYTHON,
    virtualEnvironmentPython,
    repositoryPython,
    "python3",
    "python",
  ].filter(Boolean);
  for (const candidate of candidates) {
    if (path.isAbsolute(candidate) && !fs.existsSync(candidate)) continue;
    return candidate;
  }
  return "python3";
}

const CLIENT_WORKFLOW_PREFLIGHT = String.raw`
import json
import sys
from pathlib import Path

plugin_root = Path(sys.argv[1]).resolve()
output_dir = Path(sys.argv[2])
workflow_id = sys.argv[3]
candidates = (
    plugin_root.parent / "_shared" / "vendor" / "modules",
    plugin_root / "vendor" / "modules",
    plugin_root.parent.parent / "vendor" / "modules",
)
for candidate in candidates:
    if (candidate / "vera_assurance").is_dir():
        sys.path.insert(0, str(candidate))
        break
else:
    raise RuntimeError("The required vera_assurance module is not available.")

from vera_assurance import load_client_workflow_context_for_output

context = load_client_workflow_context_for_output(
    output_dir,
    expected_workflow_id=workflow_id,
)
print(json.dumps({
    "ok": True,
    "schema_version": context["schema_version"],
    "workflow_id": context["workflow_id"],
    "run_id": context["run_id"],
}, separators=(",", ":")))
`;

function preflightClientWorkflowRun(outputDir, expectedRunId) {
  if (!outputDir) return null;
  // Run ownership and lifecycle state are exact audit properties, so every
  // persistence boundary reuses the shared v2 validator immediately before write.
  const completed = spawnSync(
    pythonExecutable(),
    ["-I", "-B", "-c", CLIENT_WORKFLOW_PREFLIGHT, PLUGIN_ROOT, outputDir, PLUGIN_NAME],
    { cwd: PLUGIN_ROOT, encoding: "utf8", maxBuffer: 64 * 1024 },
  );
  if (completed.error || completed.status !== 0) {
    throw new Error(
      "Registro Imprese SARI persistence requires a running v2 customer-folder workflow run",
    );
  }
  let result;
  try {
    result = JSON.parse(completed.stdout.trim());
  } catch {
    throw new Error("Registro Imprese SARI customer-run preflight returned an invalid result");
  }
  if (
    !isObject(result)
    || result.ok !== true
    || result.schema_version !== "vera.client_workflow_context.v2"
    || result.workflow_id !== PLUGIN_NAME
    || result.run_id !== expectedRunId
  ) {
    throw new Error("Registro Imprese SARI customer-run preflight returned an invalid result");
  }
  return result;
}

function objectSchema(properties, required = [], additionalProperties = true) {
  return { type: "object", properties, required, additionalProperties };
}

function toolUiMeta() {
  return {
    ui: { resourceUri: WIDGET_URI, visibility: ["model"] },
    "ui/resourceUri": WIDGET_URI,
    "openai/outputTemplate": WIDGET_URI,
    "openai/widgetAccessible": true,
    "openai/toolInvocation/invoking": "Apertura revisione Registro Imprese",
    "openai/toolInvocation/invoked": "Revisione Registro Imprese aperta",
  };
}

function widgetMeta() {
  return {
    ui: { resourceUri: WIDGET_URI },
    "openai/widgetDescription":
      "Revisione professionale di fonti ufficiali, passaggi DIRE, informazioni mancanti e controlli della pratica Registro Imprese.",
    "openai/widgetPrefersBorder": false,
    "openai/widgetCSP": { connect_domains: [], resource_domains: [] },
    "openai/widgetDomain": "https://chatgpt.com",
  };
}

function toolDefinitions() {
  const reviewPayload = objectSchema(
    {
      schema_version: { type: "string" },
      plugin: { type: "string" },
      workflow: { type: "string" },
      run_id: { type: "string" },
      language: { type: "string" },
      review_type: { type: "string" },
      status: { type: "string" },
      item_count: { type: "number" },
      items: { type: "array", items: { type: "object" } },
      filing_status: { type: "string" },
      filing_authorized: { type: "boolean" },
    },
    ["schema_version", "plugin", "workflow", "run_id", "item_count", "items"],
  );
  const reviewInput = objectSchema(
    {
      client_engagement: { type: "string" },
      run_intake: { type: "object" },
      review_payload: reviewPayload,
      ui_decisions: { type: "object" },
      final_artifacts: { type: "object" },
    },
    ["review_payload"],
  );
  const decision = objectSchema(
    {
      item_id: { type: "string" },
      action: { type: "string", enum: Array.from(ALLOWED_ACTIONS) },
      reviewer_note: { type: "string" },
      edit_value: { type: "string" },
      requested_documents: { type: "array", items: { type: "string" } },
    },
    ["item_id", "action"],
  );
  const decisionInput = objectSchema(
    {
      client_engagement: { type: "string" },
      run_intake: { type: "object" },
      persistence_token: { type: "string" },
      review_payload: reviewPayload,
      decisions: { type: "array", items: decision },
      decision_source: { type: "string" },
      reviewer: { type: "string" },
      ui_decisions: { type: "object" },
      final_artifacts: { type: "object" },
    },
    ["review_payload", "decisions"],
  );
  return [
    {
      name: TOOL_NAMES.validate,
      title: "Valida revisione Registro Imprese e SARI",
      description:
        `Valida il payload di revisione prima della visualizzazione. Chiamare prima di ${TOOL_NAMES.render}.`,
      inputSchema: reviewInput,
      annotations: {
        readOnlyHint: true,
        destructiveHint: false,
        idempotentHint: true,
        openWorldHint: false,
      },
    },
    {
      name: TOOL_NAMES.render,
      title: "Apri revisione Registro Imprese e SARI",
      description:
        "Mostra fonti ufficiali, passaggi DIRE, informazioni mancanti e controlli della pratica.",
      inputSchema: reviewInput,
      _meta: toolUiMeta(),
      annotations: {
        readOnlyHint: true,
        destructiveHint: false,
        idempotentHint: false,
        openWorldHint: false,
      },
    },
    {
      name: TOOL_NAMES.save,
      title: "Salva decisioni Registro Imprese e SARI",
      description:
        "Convalida e salva ui_decisions.json nella sola cartella del run; non modifica la pratica e non accede a portali.",
      inputSchema: decisionInput,
      annotations: {
        readOnlyHint: false,
        destructiveHint: true,
        idempotentHint: false,
        openWorldHint: false,
      },
    },
    {
      name: TOOL_NAMES.apply,
      title: "Applica decisioni alla carta di lavoro",
      description:
        "Scrive applied_decisions.json e aggiorna lo stato del pacchetto. Le modifiche restano richieste di revisione: nessun accesso, firma o invio.",
      inputSchema: decisionInput,
      annotations: {
        readOnlyHint: false,
        destructiveHint: true,
        idempotentHint: false,
        openWorldHint: false,
      },
    },
  ];
}

function resources() {
  return [
    {
      uri: WIDGET_URI,
      name: "registro_imprese_sari_review_widget",
      title: "Revisione Registro Imprese e SARI",
      description: "Superficie interattiva per la revisione professionale della pratica.",
      mimeType: WIDGET_MIME_TYPE,
      _meta: widgetMeta(),
    },
  ];
}

function resourceText(uri) {
  if (uri !== WIDGET_URI) throw new Error(`unknown widget resource: ${uri}`);
  return fs.readFileSync(
    path.join(PLUGIN_ROOT, "assets", "registro-imprese-sari-review-widget.html"),
    "utf8",
  );
}

function requireText(value, field, maxLength = MAX_TEXT_LENGTH) {
  if (typeof value !== "string" || !value.trim()) {
    throw new Error(`${field} must be a non-empty string`);
  }
  if (value.length > maxLength) throw new Error(`${field} exceeds ${maxLength} characters`);
  return value.trim();
}

function optionalText(value, field, maxLength = MAX_TEXT_LENGTH) {
  if (value == null) return "";
  if (typeof value !== "string") throw new Error(`${field} must be text when provided`);
  if (value.length > maxLength) throw new Error(`${field} exceeds ${maxLength} characters`);
  return value.trim();
}

function auditSecrets(value, field = "review_payload") {
  if (Array.isArray(value)) {
    value.forEach((entry, index) => auditSecrets(entry, `${field}[${index}]`));
    return;
  }
  if (isObject(value)) {
    for (const [key, entry] of Object.entries(value)) {
      const normalizedKey = key.trim().toLowerCase().replaceAll("-", "_").replaceAll(" ", "_");
      if (PROHIBITED_SECRET_KEYS.has(normalizedKey)) {
        throw new Error(`${field}.${key} contains prohibited credential or session material`);
      }
      auditSecrets(entry, `${field}.${key}`);
    }
  }
}

function reviewRunIntake(value, review) {
  if (!isObject(value)) return null;
  const projected = {
    schema_version: value.schema_version || null,
    plugin: value.plugin || null,
    workflow: value.workflow || null,
    run_id: review.run_id,
    reference_date: value.reference_date || null,
    language: value.language || null,
    status: value.status || null,
  };
  for (const field of [
    "client_reference",
    "client_name",
    "client_identity",
    "professional_question",
    "case_summary",
    "activity",
    "requested_operation",
    "inferred_task",
    "assumptions",
    "unresolved_questions",
    "data_posture",
  ]) {
    if (value[field] != null) projected[field] = value[field];
  }
  auditSecrets(projected, "run_intake");
  return projected;
}

function validateItem(item, index) {
  if (!isObject(item)) throw new Error(`review_payload.items[${index}] must be an object`);
  requireText(item.id, `review_payload.items[${index}].id`, 160);
  requireText(item.title, `review_payload.items[${index}].title`, 400);
  const itemType = requireText(item.item_type, `review_payload.items[${index}].item_type`, 80);
  if (!ITEM_TYPES.has(itemType)) throw new Error(`unsupported item type: ${itemType}`);
  if (!Array.isArray(item.allowed_actions) || !item.allowed_actions.length) {
    throw new Error(`review_payload.items[${index}].allowed_actions must be non-empty`);
  }
  for (const action of item.allowed_actions) {
    if (!ALLOWED_ACTIONS.has(action)) throw new Error(`unsupported allowed action: ${action}`);
  }
  if (item.recommended_action != null && !ALLOWED_ACTIONS.has(item.recommended_action)) {
    throw new Error(`unsupported recommended action for ${item.id}`);
  }
}

function canonicalValue(value) {
  if (Array.isArray(value)) return value.map(canonicalValue);
  if (!isObject(value)) return value;
  return Object.fromEntries(
    Object.keys(value)
      .sort()
      .map((key) => [key, canonicalValue(value[key])]),
  );
}

function canonicalJson(value) {
  return JSON.stringify(canonicalValue(value));
}

function sha256Bytes(raw) {
  return crypto.createHash("sha256").update(raw).digest("hex");
}

function validateReview(args) {
  if (!isObject(args)) throw new Error("tool arguments must be an object");
  const review = args.review_payload;
  if (!isObject(review)) throw new Error("review_payload must be an object");
  requireText(review.schema_version, "review_payload.schema_version", 20);
  if (review.plugin !== PLUGIN_NAME || review.workflow !== PLUGIN_NAME) {
    throw new Error(`review_payload plugin and workflow must be ${PLUGIN_NAME}`);
  }
  requireText(review.run_id, "review_payload.run_id", 80);
  if (!Array.isArray(review.items)) throw new Error("review_payload.items must be an array");
  if (review.items.length > MAX_ITEMS) throw new Error(`review payload exceeds ${MAX_ITEMS} items`);
  if (review.item_count !== review.items.length) {
    throw new Error("review_payload.item_count must equal review_payload.items.length");
  }
  review.items.forEach(validateItem);
  auditSecrets(review);
  if (review.filing_authorized !== false || review.filing_status !== "not_filed") {
    throw new Error("review payload must remain not_filed with filing_authorized=false");
  }
  const runIntake = isObject(args.run_intake) ? args.run_intake : null;
  if (runIntake?.run_id != null && runIntake.run_id !== review.run_id) {
    throw new Error("run_intake.run_id must match review_payload.run_id");
  }
  if (runIntake?.plugin != null && runIntake.plugin !== PLUGIN_NAME) {
    throw new Error(`run_intake.plugin must be ${PLUGIN_NAME}`);
  }
  const finalArtifacts = isObject(args.final_artifacts) ? args.final_artifacts : null;
  if (
    finalArtifacts
    && (
      finalArtifacts.plugin !== PLUGIN_NAME
      || finalArtifacts.run_id !== review.run_id
      || finalArtifacts.ready_to_file !== false
      || finalArtifacts.filing_status !== "not_filed"
    )
  ) {
    throw new Error("final_artifacts must match the run and preserve the no-filing boundary");
  }
  const payload = {
    widget_type: "registro_imprese_sari_review",
    run_intake: reviewRunIntake(runIntake, review),
    review_payload: review,
    ui_decisions: isObject(args.ui_decisions) ? args.ui_decisions : null,
    final_artifacts: reviewFinalArtifacts(finalArtifacts, review),
    decision_policy: {
      save_tool: TOOL_NAMES.save,
      apply_tool: TOOL_NAMES.apply,
      edits_modify_practice_directly: false,
      portal_actions_enabled: false,
      fallback: "copy_json",
    },
  };
  if (Buffer.byteLength(JSON.stringify(payload), "utf8") > MAX_PAYLOAD_BYTES) {
    throw new Error(`widget payload exceeds ${MAX_PAYLOAD_BYTES} bytes`);
  }
  return payload;
}

function reviewFinalArtifacts(value, review) {
  const finalArtifacts = isObject(value) ? value : null;
  if (!finalArtifacts) return null;
  return {
    schema_version: finalArtifacts.schema_version || null,
    plugin: finalArtifacts.plugin || null,
    workflow: finalArtifacts.workflow || null,
    run_id: finalArtifacts.run_id || review.run_id,
    language: review.language || finalArtifacts.language || null,
    status: finalArtifacts.status || null,
    professional_review_required: finalArtifacts.professional_review_required !== false,
    ready_to_file: false,
    filing_status: "not_filed",
    portal_access_performed: false,
    signature_performed: false,
    submission_performed: false,
    blockers: Array.isArray(finalArtifacts.blockers) ? finalArtifacts.blockers : [],
    outputs: Array.isArray(finalArtifacts.outputs) ? finalArtifacts.outputs : [],
    caveats: Array.isArray(finalArtifacts.caveats) ? finalArtifacts.caveats : [],
    next_actions: Array.isArray(finalArtifacts.next_actions) ? finalArtifacts.next_actions : [],
  };
}

function findGitRoot(start) {
  let current = path.resolve(start);
  while (true) {
    if (fs.existsSync(path.join(current, ".git"))) return current;
    const parent = path.dirname(current);
    if (parent === current) return null;
    current = parent;
  }
}

function isWithin(parent, child) {
  const relative = path.relative(parent, child);
  return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
}

function resolveRunOutputReference(args, runIntake) {
  const rawReference =
    typeof runIntake?.output_dir === "string" ? runIntake.output_dir.trim() : "";
  if (!rawReference) return null;
  if (path.isAbsolute(rawReference)) return path.resolve(rawReference);
  if (runIntake?.path_reference !== "run_root_relative") {
    throw new Error("relative run output requires path_reference=run_root_relative");
  }
  const contextValue =
    typeof args.client_engagement === "string"
      ? args.client_engagement.trim()
      : "";
  if (!contextValue || !path.isAbsolute(contextValue)) {
    throw new Error(
      "client_engagement is required to resolve the portable run output",
    );
  }
  const contextPath = path.resolve(contextValue);
  const contextStat = fs.lstatSync(contextPath);
  if (!contextStat.isFile() || contextStat.isSymbolicLink()) {
    throw new Error("client_engagement must identify a regular context file");
  }
  if (fs.realpathSync(contextPath) !== contextPath) {
    throw new Error("client_engagement may not traverse symlinks");
  }
  const runRoot = path.dirname(contextPath);
  const outputDir = path.resolve(runRoot, rawReference);
  if (outputDir === runRoot || !isWithin(runRoot, outputDir)) {
    throw new Error("run output reference leaves the customer run");
  }
  return outputDir;
}

function persistenceContextForOutputDir(rawOutputDir, review, runIntakeArg = null) {
  const outputDir = path.resolve(rawOutputDir);
  const gitRoot = findGitRoot(PLUGIN_ROOT);
  if (gitRoot && isWithin(gitRoot, outputDir)) {
    throw new Error("run output directory must remain outside the Git workspace");
  }
  const stat = fs.lstatSync(outputDir);
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
    throw new Error("run output directory must be a regular directory");
  }
  if (process.platform !== "win32" && (stat.mode & 0o077) !== 0) {
    throw new Error("run output directory must be owner-only (0700)");
  }
  const realOutput = fs.realpathSync(outputDir);
  if (realOutput !== outputDir) throw new Error("run output directory may not traverse symlinks");
  const storedRunPath = path.join(outputDir, "run_intake.json");
  const storedRun = readJson(storedRunPath);
  if (storedRun.plugin !== PLUGIN_NAME || storedRun.run_id !== review.run_id) {
    throw new Error("stored run_intake.json does not match the review payload");
  }
  if (runIntakeArg?.plugin != null && runIntakeArg.plugin !== PLUGIN_NAME) {
    throw new Error("run_intake.plugin must match the plugin");
  }
  return { outputDir, storedRun, storedRunPath };
}

function prunePersistenceContexts(now = Date.now()) {
  for (const [token, context] of PERSISTENCE_CONTEXTS.entries()) {
    if (context.expiresAt <= now) PERSISTENCE_CONTEXTS.delete(token);
  }
  while (PERSISTENCE_CONTEXTS.size >= MAX_PERSISTENCE_CONTEXTS) {
    const oldest = PERSISTENCE_CONTEXTS.keys().next().value;
    if (oldest == null) break;
    PERSISTENCE_CONTEXTS.delete(oldest);
  }
}

function issuePersistenceToken(args, review) {
  const context = resolvePersistenceContext(args, review);
  if (!context) return null;
  const verified = verifyStoredPackage(context, review);
  prunePersistenceContexts();
  const token = crypto.randomBytes(32).toString("base64url");
  PERSISTENCE_CONTEXTS.set(token, {
    outputReference: context.storedRun.output_dir,
    pathReference: context.storedRun.path_reference,
    runId: review.run_id,
    reviewHash: verified.reviewHash,
    expiresAt: Date.now() + PERSISTENCE_CONTEXT_TTL_MS,
  });
  return token;
}

function resolvePersistenceContext(args, review) {
  const token = optionalText(args.persistence_token, "persistence_token", 128);
  const runIntakeArg = isObject(args.run_intake) ? args.run_intake : null;
  if (token) {
    if (!PERSISTENCE_TOKEN_RE.test(token)) {
      throw new Error("persistence_token has an invalid format");
    }
    prunePersistenceContexts();
    const storedContext = PERSISTENCE_CONTEXTS.get(token);
    if (!storedContext || storedContext.expiresAt <= Date.now()) {
      throw new Error("persistence_token is unknown or expired; render the review again");
    }
    if (storedContext.runId !== review.run_id) {
      throw new Error("persistence_token belongs to another run");
    }
    const tokenRunIntake = {
      output_dir: storedContext.outputReference,
      path_reference: storedContext.pathReference,
    };
    const currentOutputDir = resolveRunOutputReference(
      args,
      tokenRunIntake,
    );
    const context = persistenceContextForOutputDir(
      currentOutputDir,
      review,
      runIntakeArg,
    );
    const storedReviewHash = sha256Bytes(
      fs.readFileSync(path.join(context.outputDir, "review_payload.json")),
    );
    if (storedReviewHash !== storedContext.reviewHash) {
      throw new Error("persistence_token review binding no longer matches the stored package");
    }
    return context;
  }
  const rawOutputDir = typeof runIntakeArg?.output_dir === "string" ? runIntakeArg.output_dir.trim() : "";
  if (!rawOutputDir) return null;
  return persistenceContextForOutputDir(
    resolveRunOutputReference(args, runIntakeArg),
    review,
    runIntakeArg,
  );
}

function readJson(filePath) {
  const stat = fs.lstatSync(filePath);
  if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`expected regular JSON file: ${filePath}`);
  return JSON.parse(fs.readFileSync(filePath, "utf8"));
}

function writeJsonAtomic(filePath, value) {
  if (fs.existsSync(filePath) && fs.lstatSync(filePath).isSymbolicLink()) {
    throw new Error(`refusing to replace symbolic link: ${filePath}`);
  }
  const temporary = `${filePath}.tmp-${process.pid}`;
  fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
  fs.chmodSync(temporary, 0o600);
  fs.renameSync(temporary, filePath);
  fs.chmodSync(filePath, 0o600);
}

function verifyStoredPackage(context, review) {
  const reviewPath = path.join(context.outputDir, "review_payload.json");
  const reviewBytes = fs.readFileSync(reviewPath);
  const storedReview = JSON.parse(reviewBytes.toString("utf8"));
  if (canonicalJson(storedReview) !== canonicalJson(review)) {
    throw new Error("tool review_payload does not match stored review_payload.json");
  }
  const finalPath = path.join(context.outputDir, "final_artifacts.json");
  const finalArtifacts = readJson(finalPath);
  if (finalArtifacts.plugin !== PLUGIN_NAME || finalArtifacts.run_id !== review.run_id) {
    throw new Error("stored final_artifacts.json does not match the review payload");
  }
  if (finalArtifacts.ready_to_file !== false || finalArtifacts.filing_status !== "not_filed") {
    throw new Error("stored package violates the no-filing boundary");
  }
  const reviewHash = sha256Bytes(reviewBytes);
  if (finalArtifacts.review_payload_sha256 !== reviewHash) {
    throw new Error("stored review payload hash does not match final_artifacts.json");
  }
  const auditPath = path.join(context.outputDir, "practice_validation_audit.json");
  const audit = readJson(auditPath);
  if (audit.plugin !== PLUGIN_NAME || audit.run_id !== review.run_id || audit.status === "schema_error") {
    throw new Error("practice validation audit is missing, mismatched, or invalid");
  }
  if (finalArtifacts.validation_audit_sha256 !== sha256Bytes(fs.readFileSync(auditPath))) {
    throw new Error("practice validation audit hash does not match final_artifacts.json");
  }
  return { reviewHash, finalArtifacts, finalPath, audit };
}

function normalizeRequestedDocuments(value, field) {
  if (value == null) return [];
  if (!Array.isArray(value)) throw new Error(`${field} must be an array`);
  return value.map((entry, index) => requireText(entry, `${field}[${index}]`, 300));
}

function normalizeDecision(decision, index, itemById, seen, decidedAt) {
  if (!isObject(decision)) throw new Error(`decisions[${index}] must be an object`);
  const itemId = requireText(decision.item_id, `decisions[${index}].item_id`, 160);
  if (seen.has(itemId)) throw new Error(`duplicate decision item_id: ${itemId}`);
  seen.add(itemId);
  const item = itemById.get(itemId);
  if (!item) throw new Error(`unknown decision item_id: ${itemId}`);
  const action = requireText(decision.action, `decisions[${index}].action`, 80);
  if (!ALLOWED_ACTIONS.has(action) || !item.allowed_actions.includes(action)) {
    throw new Error(`action ${action} is not allowed for ${itemId}`);
  }
  const reviewerNote = optionalText(decision.reviewer_note, `decisions[${index}].reviewer_note`);
  const editValue = optionalText(decision.edit_value, `decisions[${index}].edit_value`);
  if (action === "edit" && !editValue) {
    throw new Error(`decisions[${index}].edit_value is required for edit`);
  }
  const requestedDocuments = normalizeRequestedDocuments(
    decision.requested_documents,
    `decisions[${index}].requested_documents`,
  );
  if (action === "request_more_documents" && !requestedDocuments.length) {
    throw new Error(`decisions[${index}].requested_documents is required for document requests`);
  }
  const normalized = {
    item_id: itemId,
    item_type: item.item_type,
    title: item.title,
    action,
    status: ACTION_STATUS[action],
    decided_at: decidedAt,
  };
  if (reviewerNote) normalized.reviewer_note = reviewerNote;
  if (editValue) normalized.edit_value = editValue;
  if (requestedDocuments.length) normalized.requested_documents = requestedDocuments;
  return normalized;
}

function buildUiDecisions(args, review) {
  if (!Array.isArray(args.decisions)) throw new Error("decisions must be an array");
  if (args.decisions.length > review.items.length) throw new Error("too many decisions");
  auditSecrets(args.decisions, "decisions");
  const itemById = new Map(review.items.map((item) => [item.id, item]));
  const seen = new Set();
  const decidedAt = new Date().toISOString();
  const decisions = args.decisions.map((decision, index) =>
    normalizeDecision(decision, index, itemById, seen, decidedAt),
  );
  const reviewer = optionalText(args.reviewer, "reviewer", 160);
  const decisionSource = optionalText(args.decision_source, "decision_source", 120) || "mcp_widget";
  if (reviewer) auditSecrets(reviewer, "reviewer");
  auditSecrets(decisionSource, "decision_source");
  const status = decisions.length === 0
    ? "pending_review"
    : decisions.length === review.items.length
      ? "reviewed"
      : "partial_review";
  const payload = {
    schema_version: review.schema_version,
    plugin: PLUGIN_NAME,
    workflow: PLUGIN_NAME,
    run_id: review.run_id,
    ...(review.language ? { language: review.language } : {}),
    decided_at: decisions.length ? decidedAt : null,
    decision_source: decisionSource,
    review_payload_path: "review_payload.json",
    review_payload_sha256: null,
    decisions,
    decision_count: decisions.length,
    item_count: review.items.length,
    status,
  };
  if (reviewer) payload.reviewer = reviewer;
  return payload;
}

function saveDecisions(args) {
  const widget = validateReview(args);
  const review = widget.review_payload;
  const uiDecisions = buildUiDecisions(args, review);
  const context = resolvePersistenceContext(args, review);
  const spanish = review.language === "es";
  let storedPath = null;
  if (context) {
    const verified = verifyStoredPackage(context, review);
    uiDecisions.review_payload_sha256 = verified.reviewHash;
    storedPath = path.join(context.outputDir, "ui_decisions.json");
    preflightClientWorkflowRun(context.outputDir, review.run_id);
    writeJsonAtomic(storedPath, uiDecisions);
  }
  return {
    ok: true,
    validation_type: "registro_imprese_sari_decisions",
    run_id: review.run_id,
    decision_count: uiDecisions.decision_count,
    item_count: uiDecisions.item_count,
    status: uiDecisions.status,
    persisted: Boolean(storedPath),
    ui_decisions_path: storedPath ? "ui_decisions.json" : null,
    message: storedPath
      ? spanish
        ? `Se han guardado ${uiDecisions.decision_count} decisiones de revisión.`
        : `Salvate ${uiDecisions.decision_count} decisioni di revisione.`
      : spanish
        ? "Las decisiones son válidas; no se ha proporcionado ninguna carpeta de ejecución para guardarlas."
        : "Decisioni valide; nessuna cartella di run è stata fornita per il salvataggio.",
    ui_decisions: uiDecisions,
  };
}

function applyDecisions(args) {
  const widget = validateReview(args);
  const review = widget.review_payload;
  const uiDecisions = buildUiDecisions(args, review);
  const context = resolvePersistenceContext(args, review);
  const spanish = review.language === "es";
  if (!context) {
    return {
      ok: true,
      validation_type: "registro_imprese_sari_application_preview",
      run_id: review.run_id,
      persisted: false,
      message: spanish
        ? "La aplicación es válida; no se ha proporcionado ninguna carpeta de ejecución."
        : "Applicazione validata; nessuna cartella di run è stata fornita.",
    };
  }
  const verified = verifyStoredPackage(context, review);
  uiDecisions.review_payload_sha256 = verified.reviewHash;
  const decidedIds = new Set(uiDecisions.decisions.map((decision) => decision.item_id));
  const undecided = review.items
    .filter((item) => !decidedIds.has(item.id))
    .map((item) => ({ item_id: item.id, reason: "decision_missing" }));
  const blockers = [
    ...undecided,
    ...uiDecisions.decisions
      .filter((decision) => decision.action !== "accept")
      .map((decision) => ({
        item_id: decision.item_id,
        action: decision.action,
        reason: decision.status,
      })),
  ];
  const revisions = uiDecisions.decisions
    .filter((decision) => decision.action === "edit")
    .map((decision) => ({
      item_id: decision.item_id,
      status: "revision_required",
      edit_value: decision.edit_value,
      direct_artifact_modification_performed: false,
    }));
  const appliedAt = new Date().toISOString();
  const applicationStatus = blockers.length
    ? "review_incomplete_or_revision_required"
    : "reviewed_no_portal_action";
  const applied = {
    schema_version: review.schema_version,
    plugin: PLUGIN_NAME,
    workflow: PLUGIN_NAME,
    run_id: review.run_id,
    ...(review.language ? { language: review.language } : {}),
    applied_at: appliedAt,
    review_payload_sha256: verified.reviewHash,
    decision_count: uiDecisions.decision_count,
    item_count: uiDecisions.item_count,
    blocker_count: blockers.length,
    revision_required_count: revisions.length,
    application_status: applicationStatus,
    blockers,
    revisions,
    portal_access_performed: false,
    signature_performed: false,
    submission_performed: false,
    ready_to_file: false,
  };
  const updatedFinal = {
    ...verified.finalArtifacts,
    status: verified.finalArtifacts.status,
    ready_to_file: false,
    filing_status: "not_filed",
    portal_access_performed: false,
    signature_performed: false,
    submission_performed: false,
    review_application: {
      applied_at: appliedAt,
      application_status: applicationStatus,
      decision_count: uiDecisions.decision_count,
      blocker_count: blockers.length,
      revision_required_count: revisions.length,
      applied_decisions_path: "applied_decisions.json",
      direct_artifact_modification_performed: false,
    },
  };
  const uiPath = path.join(context.outputDir, "ui_decisions.json");
  const appliedPath = path.join(context.outputDir, "applied_decisions.json");
  preflightClientWorkflowRun(context.outputDir, review.run_id);
  writeJsonAtomic(uiPath, uiDecisions);
  writeJsonAtomic(appliedPath, applied);
  writeJsonAtomic(verified.finalPath, updatedFinal);
  const executionTrace = Array.isArray(context.storedRun.execution_trace)
    ? [...context.storedRun.execution_trace]
    : [];
  executionTrace.push({
    step_id: `apply_registro_imprese_sari_decisions_${executionTrace.length + 1}`,
    kind: "deterministic_review_apply",
    command: [TOOL_NAMES.apply],
    execution_location: "local_mcp",
    status: "passed",
    inputs: ["review_payload.json", "ui_decisions.json", "final_artifacts.json"],
    outputs: ["ui_decisions.json", "applied_decisions.json", "final_artifacts.json"],
  });
  writeJsonAtomic(context.storedRunPath, {
    ...context.storedRun,
    status: updatedFinal.status,
    execution_trace: executionTrace,
  });
  return {
    ok: true,
    validation_type: "registro_imprese_sari_application",
    run_id: review.run_id,
    decision_count: uiDecisions.decision_count,
    item_count: uiDecisions.item_count,
    blocker_count: blockers.length,
    revision_required_count: revisions.length,
    application_status: applicationStatus,
    persisted: true,
    ui_decisions_path: path.basename(uiPath),
    applied_decisions_path: path.basename(appliedPath),
    final_artifacts_path: path.basename(verified.finalPath),
    ready_to_file: false,
    portal_actions_performed: false,
    message: spanish
      ? "Las decisiones se han aplicado únicamente al documento de trabajo; no se ha realizado ningún acceso ni presentación."
      : "Decisioni applicate alla sola carta di lavoro; nessun accesso o invio è stato eseguito.",
    applied_decisions: applied,
    final_artifacts: reviewFinalArtifacts(updatedFinal, review),
  };
}

function callTool(name, args) {
  if (name === TOOL_NAMES.validate) {
    const payload = validateReview(args);
    return {
      ok: true,
      validation_type: "registro_imprese_sari_review",
      run_id: payload.review_payload.run_id,
      item_count: payload.review_payload.item_count,
      message: payload.review_payload.language === "es"
        ? `Los datos son válidos. Puede llamar a ${TOOL_NAMES.render}.`
        : `Payload valido. È possibile chiamare ${TOOL_NAMES.render}.`,
      review_payload: payload.review_payload,
    };
  }
  if (name === TOOL_NAMES.render) {
    const payload = validateReview(args);
    const persistenceToken = issuePersistenceToken(args, payload.review_payload);
    return {
      ...payload,
      persistence_available: Boolean(persistenceToken),
      persistence_token: persistenceToken,
    };
  }
  if (name === TOOL_NAMES.save) return saveDecisions(args);
  if (name === TOOL_NAMES.apply) return applyDecisions(args);
  throw new Error(`unknown tool: ${name}`);
}

function toolResult(payload, name) {
  const result = {
    content: [{ type: "text", text: JSON.stringify(payload) }],
    structuredContent: payload,
    isError: false,
  };
  if (name === TOOL_NAMES.render) result._meta = toolUiMeta();
  return result;
}

function toolError(error) {
  const payload = { ok: false, error: error instanceof Error ? error.message : String(error) };
  return {
    content: [{ type: "text", text: JSON.stringify(payload) }],
    structuredContent: payload,
    isError: true,
  };
}

function rpcResult(id, result) {
  return { jsonrpc: "2.0", id, result };
}

function rpcError(id, code, message) {
  return { jsonrpc: "2.0", id, error: { code, message } };
}

function handleRpc(message) {
  const id = message.id ?? null;
  const params = isObject(message.params) ? message.params : {};
  try {
    if (message.method === "initialize") {
      return rpcResult(id, {
        protocolVersion: params.protocolVersion || "2024-11-05",
        serverInfo: { name: SERVER_NAME, version: SERVER_VERSION },
        capabilities: { tools: {}, resources: {}, prompts: {} },
        instructions:
          `Call ${TOOL_NAMES.validate} before ${TOOL_NAMES.render}; save/apply use a run-scoped opaque persistence token and never perform portal actions.`,
      });
    }
    if (message.method === "notifications/initialized") return null;
    if (message.method === "tools/list") return rpcResult(id, { tools: toolDefinitions() });
    if (message.method === "tools/call") {
      if (typeof params.name !== "string") return rpcError(id, -32602, "tools/call requires a tool name");
      if (!isObject(params.arguments)) return rpcError(id, -32602, "tools/call arguments must be an object");
      try {
        return rpcResult(id, toolResult(callTool(params.name, params.arguments), params.name));
      } catch (error) {
        return rpcResult(id, toolError(error));
      }
    }
    if (message.method === "resources/list") return rpcResult(id, { resources: resources() });
    if (message.method === "resources/read") {
      if (typeof params.uri !== "string") return rpcError(id, -32602, "resources/read requires a URI");
      return rpcResult(id, {
        contents: [
          {
            uri: params.uri,
            mimeType: WIDGET_MIME_TYPE,
            text: resourceText(params.uri),
            _meta: widgetMeta(),
          },
        ],
      });
    }
    if (message.method === "resources/templates/list") return rpcResult(id, { resourceTemplates: [] });
    if (message.method === "prompts/list") return rpcResult(id, { prompts: [] });
    return rpcError(id, -32601, "method not found");
  } catch (error) {
    return rpcError(id, -32000, error instanceof Error ? error.message : String(error));
  }
}

function send(payload) {
  process.stdout.write(`${JSON.stringify(payload)}\n`);
}

function main() {
  const lines = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
  lines.on("line", (line) => {
    if (!line.trim()) return;
    let message;
    try {
      message = JSON.parse(line);
    } catch {
      send(rpcError(null, -32700, "parse error"));
      return;
    }
    const response = handleRpc(message);
    if (response !== null && message.id != null) send(response);
  });
}

main();
