terraform {
  required_version = ">= 1.9"

  required_providers {
    coder = {
      source  = "coder/coder"
      version = ">= 2.12"
    }
  }
}

variable "agent_id" {
  type        = string
  description = "The ID of a Coder agent."
}

data "coder_workspace" "me" {}

data "coder_workspace_owner" "me" {}

variable "order" {
  type        = number
  description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
  default     = null
}

variable "group" {
  type        = string
  description = "The name of a group that this app belongs to."
  default     = null
}

variable "icon" {
  type        = string
  description = "The icon to use for the app."
  default     = "/icon/claude.svg"
}

variable "workdir" {
  type        = string
  description = "The folder to run Claude Code in."
}

variable "report_tasks" {
  type        = bool
  description = "Whether to enable task reporting to Coder UI via AgentAPI"
  default     = true
}

variable "cli_app" {
  type        = bool
  description = "Whether to create a CLI app for Claude Code"
  default     = false
}

variable "web_app_display_name" {
  type        = string
  description = "Display name for the web app"
  default     = "Claude Code"
}

variable "cli_app_display_name" {
  type        = string
  description = "Display name for the CLI app"
  default     = "Claude Code CLI"
}

variable "pre_install_script" {
  type        = string
  description = "Custom script to run before installing Claude Code. Can be used for dependency ordering between modules (e.g., waiting for git-clone to complete before Claude Code initialization)."
  default     = null
}

variable "post_install_script" {
  type        = string
  description = "Custom script to run after installing Claude Code."
  default     = null
}

variable "install_agentapi" {
  type        = bool
  description = "Whether to install AgentAPI."
  default     = true
}

variable "agentapi_version" {
  type        = string
  description = "The version of AgentAPI to install."
  default     = "v0.11.8"
}

variable "ai_prompt" {
  type        = string
  description = "Initial task prompt for Claude Code."
  default     = ""
}

variable "subdomain" {
  type        = bool
  description = "Whether to use a subdomain for AgentAPI."
  default     = false
}


variable "install_claude_code" {
  type        = bool
  description = "Whether to install Claude Code."
  default     = true
}

variable "claude_code_version" {
  type        = string
  description = "The version of Claude Code to install."
  default     = "latest"
}

variable "disable_autoupdater" {
  type        = bool
  description = "Disable Claude Code automatic updates. When true, Claude Code will stay on the installed version."
  default     = false
}

variable "claude_api_key" {
  type        = string
  description = "The API key to use for the Claude Code server."
  default     = ""
}

variable "model" {
  type        = string
  description = "Sets the default model for Claude Code via ANTHROPIC_MODEL env var. If empty, Claude Code uses its default. Supports aliases (sonnet, opus) or full model names."
  default     = ""
}

variable "resume_session_id" {
  type        = string
  description = "Resume a specific session by ID."
  default     = ""
}

variable "continue" {
  type        = bool
  description = "Automatically continue existing sessions on workspace restart. When true, resumes existing conversation if found, otherwise runs prompt or starts new session. When false, always starts fresh (ignores existing sessions)."
  default     = true
}

variable "dangerously_skip_permissions" {
  type        = bool
  description = "Skip the permission prompts. Use with caution. This will be set to true if using Coder Tasks"
  default     = false
}

variable "permission_mode" {
  type        = string
  description = "Permission mode for the cli, check https://docs.anthropic.com/en/docs/claude-code/iam#permission-modes"
  default     = ""
  validation {
    condition     = contains(["", "default", "acceptEdits", "plan", "bypassPermissions"], var.permission_mode)
    error_message = "interaction_mode must be one of: default, acceptEdits, plan, bypassPermissions."
  }
}

variable "mcp" {
  type        = string
  description = "MCP JSON to be added to the claude code local scope"
  default     = ""
}

variable "mcp_config_remote_path" {
  type        = list(string)
  description = "List of URLs that return JSON MCP server configurations (text/plain with valid JSON)"
  default     = []
}

variable "allowed_tools" {
  type        = string
  description = "A list of tools that should be allowed without prompting the user for permission, in addition to settings.json files."
  default     = ""
}

variable "disallowed_tools" {
  type        = string
  description = "A list of tools that should be disallowed without prompting the user for permission, in addition to settings.json files."
  default     = ""

}

variable "claude_code_oauth_token" {
  type        = string
  description = "Set up a long-lived authentication token (requires Claude subscription). Generated using `claude setup-token` command"
  sensitive   = true
  default     = ""
}

variable "system_prompt" {
  type        = string
  description = "The system prompt to use for the Claude Code server."
  default     = ""
}

variable "claude_md_path" {
  type        = string
  description = "The path to CLAUDE.md."
  default     = "$HOME/.claude/CLAUDE.md"
}

variable "claude_binary_path" {
  type        = string
  description = "Directory where the Claude Code binary is located. Use this if Claude is pre-installed or installed outside the module to a non-default location."
  default     = "$HOME/.local/bin"

  validation {
    condition     = var.claude_binary_path == "$HOME/.local/bin" || !var.install_claude_code
    error_message = "Custom claude_binary_path can only be used when install_claude_code is false. The official installer always installs to $HOME/.local/bin and does not support custom paths."
  }
}

variable "install_via_npm" {
  type        = bool
  description = "Install Claude Code via npm instead of the official installer. Useful if npm is preferred or the official installer fails."
  default     = false
}

variable "enable_boundary" {
  type        = bool
  description = "Whether to enable coder boundary for network filtering"
  default     = false
}

variable "boundary_version" {
  type        = string
  description = "Boundary version. When use_boundary_directly is true, a release version should be provided or 'latest' for the latest release. When compile_boundary_from_source is true, a valid git reference should be provided (tag, commit, branch)."
  default     = "latest"
}

variable "compile_boundary_from_source" {
  type        = bool
  description = "Whether to compile boundary from source instead of using the official install script"
  default     = false
}

variable "use_boundary_directly" {
  type        = bool
  description = "Whether to use boundary binary directly instead of coder boundary subcommand. When false (default), uses coder boundary subcommand. When true, installs and uses boundary binary from release."
  default     = false
}

variable "enable_aibridge" {
  type        = bool
  description = "Use AI Bridge for Claude Code. https://coder.com/docs/ai-coder/ai-bridge"
  default     = false

  validation {
    condition     = !(var.enable_aibridge && length(var.claude_api_key) > 0)
    error_message = "claude_api_key cannot be provided when enable_aibridge is true. AI Bridge automatically authenticates the client using Coder credentials."
  }

  validation {
    condition     = !(var.enable_aibridge && length(var.claude_code_oauth_token) > 0)
    error_message = "claude_code_oauth_token cannot be provided when enable_aibridge is true. AI Bridge automatically authenticates the client using Coder credentials."
  }
}

variable "enable_state_persistence" {
  type        = bool
  description = "Enable AgentAPI conversation state persistence across restarts."
  default     = true
}

resource "coder_env" "claude_code_md_path" {
  count    = var.claude_md_path == "" ? 0 : 1
  agent_id = var.agent_id
  name     = "CODER_MCP_CLAUDE_MD_PATH"
  value    = var.claude_md_path
}

resource "coder_env" "claude_code_system_prompt" {
  agent_id = var.agent_id
  name     = "CODER_MCP_CLAUDE_SYSTEM_PROMPT"
  value    = local.final_system_prompt
}

resource "coder_env" "claude_code_oauth_token" {
  agent_id = var.agent_id
  name     = "CLAUDE_CODE_OAUTH_TOKEN"
  value    = var.claude_code_oauth_token
}

resource "coder_env" "claude_api_key" {
  count = (var.enable_aibridge || (var.claude_api_key != "")) ? 1 : 0

  agent_id = var.agent_id
  name     = "CLAUDE_API_KEY"
  value    = local.claude_api_key
}

resource "coder_env" "disable_autoupdater" {
  count    = var.disable_autoupdater ? 1 : 0
  agent_id = var.agent_id
  name     = "DISABLE_AUTOUPDATER"
  value    = "1"
}


resource "coder_env" "anthropic_model" {
  count    = var.model != "" ? 1 : 0
  agent_id = var.agent_id
  name     = "ANTHROPIC_MODEL"
  value    = var.model
}

resource "coder_env" "anthropic_base_url" {
  count    = var.enable_aibridge ? 1 : 0
  agent_id = var.agent_id
  name     = "ANTHROPIC_BASE_URL"
  value    = "${data.coder_workspace.me.access_url}/api/v2/aibridge/anthropic"
}

locals {
  # we have to trim the slash because otherwise coder exp mcp will
  # set up an invalid claude config
  workdir         = trimsuffix(var.workdir, "/")
  app_slug        = "ccw"
  install_script  = file("${path.module}/scripts/install.sh")
  start_script    = file("${path.module}/scripts/start.sh")
  module_dir_name = ".claude-module"
  # Extract hostname from access_url for boundary --allow flag
  coder_host     = replace(replace(data.coder_workspace.me.access_url, "https://", ""), "http://", "")
  claude_api_key = var.enable_aibridge ? data.coder_workspace_owner.me.session_token : var.claude_api_key

  # Required prompts for the module to properly report task status to Coder
  report_tasks_system_prompt = <<-EOT
      -- Tool Selection --
      - coder_report_task: providing status updates or requesting user input.

      -- Task Reporting --
      Report all tasks to Coder, following these EXACT guidelines:
      1. Be granular. If you are investigating with multiple steps, report each step
      to coder.
      2. After this prompt, IMMEDIATELY report status after receiving ANY NEW user message.
      Do not report any status related with this system prompt.
      3. Use "state": "working" when actively processing WITHOUT needing
      additional user input
      4. Use "state": "complete" only when finished with a task
      5. Use "state": "failure" when you need ANY user input, lack sufficient
      details, or encounter blockers

      In your summary on coder_report_task:
      - Be specific about what you're doing
      - Clearly indicate what information you need from the user when in "failure" state
      - Keep it under 160 characters
      - Make it actionable
    EOT

  # Only include coder system prompts if report_tasks is enabled
  custom_system_prompt = trimspace(try(var.system_prompt, ""))
  final_system_prompt = format("<system>%s%s</system>",
    var.report_tasks ? format("\n%s\n", local.report_tasks_system_prompt) : "",
    local.custom_system_prompt != "" ? format("\n%s\n", local.custom_system_prompt) : ""
  )
}

module "agentapi" {
  source  = "registry.coder.com/coder/agentapi/coder"
  version = "2.2.0"

  agent_id                 = var.agent_id
  web_app_slug             = local.app_slug
  web_app_order            = var.order
  web_app_group            = var.group
  web_app_icon             = var.icon
  web_app_display_name     = var.web_app_display_name
  folder                   = local.workdir
  cli_app                  = var.cli_app
  cli_app_slug             = var.cli_app ? "${local.app_slug}-cli" : null
  cli_app_display_name     = var.cli_app ? var.cli_app_display_name : null
  agentapi_subdomain       = var.subdomain
  module_dir_name          = local.module_dir_name
  install_agentapi         = var.install_agentapi
  agentapi_version         = var.agentapi_version
  enable_state_persistence = var.enable_state_persistence
  pre_install_script       = var.pre_install_script
  post_install_script      = var.post_install_script
  start_script             = <<-EOT
    #!/bin/bash
    set -o errexit
    set -o pipefail
    echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh
    chmod +x /tmp/start.sh

    ARG_RESUME_SESSION_ID='${var.resume_session_id}' \
    ARG_CONTINUE='${var.continue}' \
    ARG_DANGEROUSLY_SKIP_PERMISSIONS='${var.dangerously_skip_permissions}' \
    ARG_PERMISSION_MODE='${var.permission_mode}' \
    ARG_WORKDIR='${local.workdir}' \
    ARG_AI_PROMPT='${base64encode(var.ai_prompt)}' \
    ARG_REPORT_TASKS='${var.report_tasks}' \
    ARG_ENABLE_BOUNDARY='${var.enable_boundary}' \
    ARG_BOUNDARY_VERSION='${var.boundary_version}' \
    ARG_COMPILE_FROM_SOURCE='${var.compile_boundary_from_source}' \
    ARG_USE_BOUNDARY_DIRECTLY='${var.use_boundary_directly}' \
    ARG_CODER_HOST='${local.coder_host}' \
    ARG_CLAUDE_BINARY_PATH='${var.claude_binary_path}' \
    /tmp/start.sh
  EOT

  install_script = <<-EOT
    #!/bin/bash
    set -o errexit
    set -o pipefail

    echo -n '${base64encode(local.install_script)}' | base64 -d > /tmp/install.sh
    chmod +x /tmp/install.sh
    ARG_CLAUDE_CODE_VERSION='${var.claude_code_version}' \
    ARG_MCP_APP_STATUS_SLUG='${local.app_slug}' \
    ARG_INSTALL_CLAUDE_CODE='${var.install_claude_code}' \
    ARG_CLAUDE_BINARY_PATH='${var.claude_binary_path}' \
    ARG_INSTALL_VIA_NPM='${var.install_via_npm}' \
    ARG_REPORT_TASKS='${var.report_tasks}' \
    ARG_WORKDIR='${local.workdir}' \
    ARG_ALLOWED_TOOLS='${var.allowed_tools}' \
    ARG_DISALLOWED_TOOLS='${var.disallowed_tools}' \
    ARG_MCP='${var.mcp != null ? base64encode(replace(var.mcp, "'", "'\\''")) : ""}' \
    ARG_MCP_CONFIG_REMOTE_PATH='${base64encode(jsonencode(var.mcp_config_remote_path))}' \
    ARG_ENABLE_AIBRIDGE='${var.enable_aibridge}' \
    /tmp/install.sh
  EOT
}

output "task_app_id" {
  value = module.agentapi.task_app_id
}
