#!/usr/bin/env bash

:<<'DOC'
Linear API client for issue queries, label management, and workflow operations.
Built on graphql.api.sh for query execution.

Operations are defined in lib/graphql/linear/*.graphql and loaded at
call time via graphql_load.
DOC

import \
  "$SHELLOPS_DIR/lib/graphql.api.sh" \
  "$SHELLOPS_DIR/lib/json.api.sh"

__linear_api__ENDPOINT="https://api.linear.app/graphql"
__linear_api__SERVICE="linear"

:<<'DOC'
Creates default Linear options.

Usage: new_linear_options
DOC
function new_linear_options() {
  jq_set '{}' \
    '.token' ""
}

:<<'DOC'
Creates a default attachment object for linear_add_attachment.

Usage:
  local att
  att="$(jq_set "$(new_attachment)" '.title' "repo" '.url' "https://...")"
DOC
function new_attachment() {
  jq_set '{}'\
    '.title' "" \
    '.url' ""
}

function __linear_api__validate_token() {
  local options="$1"
  local token

  token="$(jq_get "$options" '.token')"
  if [[ -z "$token" ]]; then
    error "No Linear API token set"
    return 1
  fi

  echo "$token"
}

function __linear_api__http_opts() {
  jq_set '{}' '.auth' "$1"
}

function __linear_api__check_success() {
  local result="$1" operation="$2"
  local success

  success="$(jq_get "$result" ".$operation.success")"
  if [[ "$success" != "true" ]]; then
    error "Linear $operation failed"
    return 1
  fi
}

:<<'DOC'
Fetches a Linear issue by its identifier (e.g. "SHSH-48").

Returns a JSON object with: identifier, title, description, priority,
priorityLabel, state, and labels.

Usage: linear_get_issue "$issue_id" "$options"
DOC
function linear_get_issue() {
  local issue_id="$1" options="$2"
  local token query variables result http_opts

  token="$(__linear_api__validate_token "$options")" || return 1
  http_opts="$(__linear_api__http_opts "$token")"
  query="$(graphql_load "$__linear_api__SERVICE" "get-issue")" || return 1
  variables="$(jq_set '{}' '.id' "$issue_id")"

  result="$(
    graphql_request \
      "$__linear_api__ENDPOINT" \
      "$http_opts" \
      "$query" \
      "$variables"
  )" || return 1

  jq_get "$result" '.issue'
}

:<<'DOC'
Lists all teams in the workspace, including their workflow states.

Returns a JSON array of team objects with: id, name, key, states.

Usage: linear_list_teams "$options"
DOC
function linear_list_teams() {
  local options="$1"
  local token query result http_opts

  token="$(__linear_api__validate_token "$options")" || return 1
  http_opts="$(__linear_api__http_opts "$token")"
  query="$(graphql_load "$__linear_api__SERVICE" "list-teams")" || return 1

  result="$(
    graphql_request \
      "$__linear_api__ENDPOINT" \
      "$http_opts" \
      "$query"
  )" || return 1

  jq_get "$result" '.teams.nodes'
}

:<<'DOC'
Lists untriaged issues scoped to the given team IDs. Paginated.

Excludes issues that are completed, cancelled, started, or already
carry a blizz/* label.

Returns a JSON array of issue objects.

Usage: linear_list_untriaged_issues "$options" "$team_ids_json"
DOC
function linear_list_untriaged_issues() {
  local options="$1" team_ids="$2"
  local token query variables http_opts

  token="$(__linear_api__validate_token "$options")" || return 1
  http_opts="$(__linear_api__http_opts "$token")"
  query="$(graphql_load "$__linear_api__SERVICE" "list-untriaged-issues")" || return 1
  variables="$(jq_set '{}' '.teamIds' "$team_ids")"

  graphql_paginate \
    "$__linear_api__ENDPOINT" \
    "$http_opts" \
    "$query" \
    "$variables" \
    '.issues'
}

:<<'DOC'
Returns the current labels attached to an issue.

Returns a JSON array of {id, name} objects.

Usage: linear_get_issue_labels "$issue_id" "$options"
DOC
function linear_get_issue_labels() {
  local issue_id="$1" options="$2"
  local token query variables result http_opts

  token="$(__linear_api__validate_token "$options")" || return 1
  http_opts="$(__linear_api__http_opts "$token")"
  query="$(graphql_load "$__linear_api__SERVICE" "get-issue-labels")" || return 1
  variables="$(jq_set '{}' '.id' "$issue_id")"

  result="$(
    graphql_request \
      "$__linear_api__ENDPOINT" \
      "$http_opts" \
      "$query" \
      "$variables"
  )" || return 1

  jq_get "$result" '.issue.labels.nodes'
}

:<<'DOC'
Lists all workspace-level issue labels. Paginated.

Returns a JSON array of {id, name, color} objects.

Usage: linear_list_labels "$options"
DOC
function linear_list_labels() {
  local options="$1"
  local token query http_opts

  token="$(__linear_api__validate_token "$options")" || return 1
  http_opts="$(__linear_api__http_opts "$token")"
  query="$(graphql_load "$__linear_api__SERVICE" "list-labels")" || return 1

  graphql_paginate \
    "$__linear_api__ENDPOINT" \
    "$http_opts" \
    "$query" \
    '{}' \
    '.issueLabels'
}

:<<'DOC'
Creates a comment on a Linear issue.

Usage: linear_add_comment "$issue_id" "$body" "$options"
DOC
function linear_add_comment() {
  local issue_id="$1" body="$2" options="$3"
  local token query variables result http_opts

  token="$(__linear_api__validate_token "$options")" || return 1
  http_opts="$(__linear_api__http_opts "$token")"
  query="$(graphql_load "$__linear_api__SERVICE" "comment-create")" || return 1
  variables="$(jq_set '{}' '.issueId' "$issue_id" '.body' "$body")"

  result="$(
    graphql_request \
      "$__linear_api__ENDPOINT" \
      "$http_opts" \
      "$query" \
      "$variables"
  )" || return 1

  __linear_api__check_success "$result" "commentCreate"
}

:<<'DOC'
Creates or updates an attachment on a Linear issue.

URL is idempotent per issue — re-creating with the same URL updates the
existing attachment instead of duplicating.

Attachment object fields:
  .title     (required) Display title for the attachment card
  .url       (required) Link URL — idempotent key per issue
  .subtitle  (optional) Subtitle text below the title
  .metadata  (optional) JSON object stored as structured metadata

Usage:
  local attachment
  attachment="$(jq_set '{}' '.title' "repo" '.url' "https://..." '.subtitle' "info" '.metadata' "$meta")"
  linear_add_attachment "$issue_id" "$attachment" "$options"
DOC
function linear_add_attachment() {
  local issue_id="$1" attachment="$2" options="$3"
  local token query variables result http_opts

  token="$(__linear_api__validate_token "$options")" || return 1
  http_opts="$(__linear_api__http_opts "$token")"
  query="$(graphql_load "$__linear_api__SERVICE" "attachment-create")" || return 1
  variables="$(jq_set "$attachment" '.issueId' "$issue_id")"

  result="$(
    graphql_request \
      "$__linear_api__ENDPOINT" \
      "$http_opts" \
      "$query" \
      "$variables"
  )" || return 1

  __linear_api__check_success "$result" "attachmentCreate"
}

:<<'DOC'
Creates a workspace-level issue label.

Returns the created label object with id and name.

Usage: linear_create_label "$name" "$color" "$options"
DOC
function linear_create_label() {
  local name="$1" color="$2" options="$3"
  local token query variables result http_opts

  token="$(__linear_api__validate_token "$options")" || return 1
  http_opts="$(__linear_api__http_opts "$token")"
  query="$(graphql_load "$__linear_api__SERVICE" "label-create")" || return 1
  variables="$(jq_set '{}' '.name' "$name" '.color' "$color")"

  result="$(
    graphql_request \
      "$__linear_api__ENDPOINT" \
      "$http_opts" \
      "$query" \
      "$variables"
  )" || return 1

  __linear_api__check_success "$result" "issueLabelCreate" || return 1
  jq_get "$result" '.issueLabelCreate.issueLabel'
}

:<<'DOC'
Adds labels to an issue, preserving existing ones.

Fetches the issue's current labels, merges with the provided IDs,
and updates the issue with the deduplicated combined set.

Usage: linear_add_labels "$issue_id" "$label_ids_json" "$options"
DOC
function linear_add_labels() {
  local issue_id="$1" new_ids="$2" options="$3"
  local current_labels current_ids combined token query variables result

  current_labels="$(linear_get_issue_labels "$issue_id" "$options")" || return 1
  current_ids="$(jq_pluck "$current_labels" '.id')"
  combined="$(jq -c 'unique' <<< "$(jq_concat "$current_ids" "$new_ids")")"

  token="$(__linear_api__validate_token "$options")" || return 1
  http_opts="$(__linear_api__http_opts "$token")"
  query="$(graphql_load "$__linear_api__SERVICE" "issue-update-labels")" || return 1
  variables="$(jq_set '{}' '.id' "$issue_id" '.labelIds' "$combined")"

  result="$(
    graphql_request \
      "$__linear_api__ENDPOINT" \
      "$http_opts" \
      "$query" \
      "$variables"
  )" || return 1

  __linear_api__check_success "$result" "issueUpdate"
}

:<<'DOC'
Transitions an issue to a new workflow state.

Usage: linear_update_issue_state "$issue_id" "$state_id" "$options"
DOC
function linear_update_issue_state() {
  local issue_id="$1" state_id="$2" options="$3"
  local token query variables result http_opts

  token="$(__linear_api__validate_token "$options")" || return 1
  http_opts="$(__linear_api__http_opts "$token")"
  query="$(graphql_load "$__linear_api__SERVICE" "issue-update-state")" || return 1
  variables="$(jq_set '{}' '.id' "$issue_id" '.stateId' "$state_id")"

  result="$(
    graphql_request \
      "$__linear_api__ENDPOINT" \
      "$http_opts" \
      "$query" \
      "$variables"
  )" || return 1

  __linear_api__check_success "$result" "issueUpdate"
}

:<<'DOC'
Finds the "Done" state ID for a team, given the teams JSON from
linear_list_teams and a team key (e.g. "OPS").

Returns the state ID, or fails if no completed state is found.

Usage: linear_find_done_state "$teams_json" "$team_key"
DOC
function linear_find_done_state() {
  local teams_json="$1" team_key="$2"
  local team states done_state
  local matched_teams matched_states

  matched_teams="$(jq_filter "$teams_json" ".key == \"$team_key\"")"
  team="$(jq_first "$matched_teams")"
  jq_defined "$team" || return 0

  states="$(jq_get "$team" '.states.nodes')"
  matched_states="$(jq_filter "$states" '.type == "completed" and .name == "Done"')"
  done_state="$(jq_first "$matched_states")"
  jq_get "$done_state" '.id'
}

:<<'DOC'
Moves an issue to a different team, setting it to the given workflow state.

The caller is responsible for looking up the target team's backlog state ID.

Usage: linear_move_issue_to_team "$issue_id" "$team_id" "$state_id" "$options"
DOC
function linear_move_issue_to_team() {
  local issue_id="$1" team_id="$2" state_id="$3" options="$4"
  local token query variables result http_opts

  token="$(__linear_api__validate_token "$options")" || return 1
  http_opts="$(__linear_api__http_opts "$token")"
  query="$(graphql_load "$__linear_api__SERVICE" "issue-move-team")" || return 1
  variables="$(jq_set '{}' '.id' "$issue_id" '.teamId' "$team_id" '.stateId' "$state_id")"

  result="$(
    graphql_request \
      "$__linear_api__ENDPOINT" \
      "$http_opts" \
      "$query" \
      "$variables"
  )" || return 1

  __linear_api__check_success "$result" "issueUpdate"
}
