async function runGuardHook(
  workspace: string,
  payload: Record<string, unknown>,
  deadlineMs: number,
) {
  const guardArgv = [
    ...(GUARD_FROZEN ? [] : ["guard"]),
    "hook",
    "--guard-home",
    GUARD_HOME,
    "--harness",
    "opencode",
    "--workspace",
    workspace,
    "--json",
  ];
  return spawnGuardProcess({
    args: GUARD_FROZEN ? guardArgv : ["-I", "-S", "-s", "-c", GUARD_HOOK_LAUNCHER],
    nativeArgs: GUARD_FROZEN ? undefined : (pythonTarget: string) => nativeHookArgs(pythonTarget, guardArgv),
    cwd: GUARD_HOME,
    deadlineMs,
    env: hookProcessEnv(guardArgv),
    stdin: JSON.stringify(payload),
  });
}

function parseGuardPayload(stdout: string): Record<string, unknown> | null {
  const trimmed = stdout.trim();
  if (!trimmed) {
    return null;
  }
  const parseCandidate = (candidate: string): Record<string, unknown> | null => {
    try {
      const parsed = JSON.parse(candidate);
      if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
        return parsed as Record<string, unknown>;
      }
    } catch {}
    return null;
  };
  const direct = parseCandidate(trimmed);
  if (direct !== null) {
    return direct;
  }
  const lines = trimmed.split(/\r?\n/);
  for (let index = lines.length - 1; index >= 0; index -= 1) {
    const candidate = lines[index]?.trim();
    if (!candidate) {
      continue;
    }
    const parsed = parseCandidate(candidate);
    if (parsed !== null) {
      return parsed;
    }
  }
  return null;
}

function guardReviewUrl(payload: Record<string, unknown>): string | null {
  const primary = typeof payload.primary_approval_url === "string" ? payload.primary_approval_url.trim() : "";
  if (primary) {
    return primary;
  }
  const reviewUrl = typeof payload.review_url === "string" ? payload.review_url.trim() : "";
  if (reviewUrl) {
    return reviewUrl;
  }
  const queued = payload.approval_requests;
  if (Array.isArray(queued)) {
    for (const item of queued) {
      if (!item || typeof item !== "object") {
        continue;
      }
      const approvalUrl = (item as { approval_url?: unknown }).approval_url;
      if (typeof approvalUrl === "string" && approvalUrl.trim()) {
        return approvalUrl.trim().replace(/\/approvals\//g, "/requests/");
      }
    }
  }
  const center = typeof payload.approval_center_url === "string" ? payload.approval_center_url.trim() : "";
  return center || null;
}

export function guardBlockMessage(stdout: string, stderr: string): string {
  const payload = parseGuardPayload(stdout);
  if (payload === null) {
    return stderr.trim() || "HOL Guard blocked this OpenCode action.";
  }
  const decision = payload.decision_v2_json;
  const decisionPayload =
    decision && typeof decision === "object"
      ? (decision as { harness_message?: unknown; retry_instruction?: unknown })
      : null;
  const reviewHint = typeof payload.review_hint === "string" ? payload.review_hint.trim() : "";
  const retryInstruction =
    typeof decisionPayload?.retry_instruction === "string" ? decisionPayload.retry_instruction.trim() : "";
  const harnessMessage =
    typeof decisionPayload?.harness_message === "string" ? decisionPayload.harness_message.trim() : "";
  const baseMessage =
    reviewHint ||
    retryInstruction ||
    harnessMessage ||
    stderr.trim() ||
    "HOL Guard blocked this OpenCode action.";
  const reviewUrl = guardReviewUrl(payload);
  if (!reviewUrl || baseMessage.includes(reviewUrl)) {
    return baseMessage;
  }
  return (
    `${baseMessage} Open HOL Guard to approve or keep this blocked: ${reviewUrl}. `
    + "After you choose, retry the same OpenCode action."
  );
}

/**
 * The tool input Guard reviews. OpenCode's own `bash` tool carries the
 * model's one-line `description` of the command, which the review card shows
 * as the agent's words. Only that tool sends it: Guard binds a `bash`
 * approval to the command text alone, while another intercepted tool's whole
 * input binds its approval, so a reworded description would ask again.
 */
export function hookToolInput(
  tool: string,
  args: Record<string, unknown> | undefined,
  command: string,
  workspace: string,
): Record<string, string> {
  const toolInput: Record<string, string> = { command, workdir: workspace };
  if (tool === "bash" && typeof args?.description === "string") {
    toolInput.description = args.description;
  }
  return toolInput;
}

export const HolGuardPretoolPlugin = async ({
  directory,
}: {
  directory: string;
}) => {
  return {
    "tool.execute.before": async (
      input: { tool: string },
      output: { args: Record<string, unknown> },
    ) => {
      if (!INTERCEPT_TOOLS.has(input.tool)) {
        return;
      }
      const command = normalizeCommand(output.args?.command);
      if (command === null) {
        return;
      }
      const workspace = effectiveWorkingDirectory(directory, output.args?.workdir);
      const deadlineMs = Date.now() + GUARD_HOOK_TIMEOUT_MS;
      let result;
      try {
        result = await runGuardHook(
          directory || process.cwd(),
          {
            hook_event_name: "PreToolUse",
            event: "PreToolUse",
            tool_name: input.tool,
            tool_input: hookToolInput(input.tool, output.args, command, workspace),
            cwd: workspace,
            workspace_root: directory || process.cwd(),
            source_scope: directory?.trim() ? "project" : "global",
          },
          deadlineMs,
        );
      } catch (error) {
        const detail = error instanceof Error ? error.message : String(error);
        if (detail.includes(GUARD_RUNTIME_MISSING) && isGuardSelfRepairCommand(command)) {
          return;
        }
        throw new Error(
          `HOL Guard could not review this ${input.tool} command (${detail}). ` +
            "Run `hol-guard install opencode` in a host terminal outside OpenCode, then restart OpenCode.",
        );
      }
      if (result.exitCode === 0) {
        return;
      }
      if (result.exitCode === 1) {
        throw new Error(guardBlockMessage(result.stdout, result.stderr));
      }
      throw new Error(
        result.stderr.trim() ||
          `HOL Guard hook failed while reviewing this ${input.tool} command.`,
      );
    },
  };
};


type OpenCodeV2ToolEvent = {
  tool: string;
  input: Record<string, unknown>;
};

// Keep the V1 server entrypoint while registering the V2 hook explicitly.
export default {
  id: "hol-guard-pretool",
  server: HolGuardPretoolPlugin,
  async setup(ctx: {
    location: { directory: string };
    tool: {
      hook: (
        name: "execute.before",
        handler: (event: OpenCodeV2ToolEvent) => Promise<void>,
      ) => Promise<unknown>;
    };
  }) {
    const hooks = await HolGuardPretoolPlugin({ directory: ctx.location.directory });
    await ctx.tool.hook("execute.before", async (event) => {
      await hooks["tool.execute.before"]({ tool: event.tool }, { args: event.input });
    });
  },
};
