#!/usr/bin/env bash
# slack-cli - Slack CLI for coding agents
# Uses Slack Web API with xoxc token extracted from authenticated browser session.
# Credentials stored at ~/.slack-cli/credentials.json
#
# Usage:
#   slack-cli auth [--domain <workspace>.slack.com]  - Extract tokens from browser
#   slack-cli search <query>       - Search messages (Slack query syntax)
#   slack-cli read <channel> [n]   - Read messages from a channel
#   slack-cli draft <channel> <msg> [--thread <ts>] [--file <path>] - Save a draft
#   slack-cli draft-delete <draft_id> - Delete a server-side draft
#   slack-cli send <draft_id>      - Send a draft (with confirmation)
#   slack-cli post <channel> <message> [--thread <ts>] [--file <path>] - Send with mrkdwn
#   slack-cli status [:emoji:] [text] - Get or set your Slack status
#   slack-cli help                 - Show full help

set -euo pipefail

CONFIG_DIR="${SLACK_CLI_HOME:-$HOME/.slack-cli}"
CREDS_FILE="$CONFIG_DIR/credentials.json"
SLACK_API="https://slack.com/api"

# ── Helpers ──────────────────────────────────────────────────────────────────

_check_deps() {
  for cmd in curl jq; do
    command -v "$cmd" &>/dev/null || { echo "ERROR: '$cmd' is required. Install it first." >&2; exit 1; }
  done
}

_check_creds() {
  if [[ ! -f "$CREDS_FILE" ]]; then
    echo '{"ok":false,"error":"No credentials found. Run: slack-cli auth --domain <workspace>.slack.com"}' | jq .
    exit 1
  fi
}

_token() { jq -r .token "$CREDS_FILE"; }
_cookie() { jq -r .d_cookie "$CREDS_FILE"; }
_domain() { jq -r '.domain // empty' "$CREDS_FILE"; }
_workspace_api() {
  # Enterprise Grid uses workspace-specific URL for drafts API
  local domain
  domain=$(_domain)
  local workspace="${domain%%.*}"
  echo "https://grid-${workspace}.enterprise.slack.com/api"
}

_api() {
  local method="$1"; shift
  local response
  response=$(curl -s "$SLACK_API/$method" \
    -H 'Content-Type: application/x-www-form-urlencoded' \
    -H "Cookie: d=$(_cookie)" \
    -d "token=$(_token)&$*")

  local ok err
  ok=$(echo "$response" | jq -r '.ok')
  if [[ "$ok" != "true" ]]; then
    err=$(echo "$response" | jq -r '.error // empty')
    if [[ "$err" == "token_revoked" || "$err" == "invalid_auth" || "$err" == "not_authed" ]]; then
      echo '{"ok":false,"error":"Token expired. Run: slack-cli auth --domain '"$(_domain)"'"}' | jq .
      exit 1
    fi
  fi
  echo "$response"
}

_api_workspace() {
  # Multipart form API call to workspace-specific URL (matches Slack client behavior)
  local method="$1"; shift
  # Remaining args are -F field pairs
  local response
  response=$(curl -s "$(_workspace_api)/$method" \
    -H "Cookie: d=$(_cookie)" \
    -F "token=$(_token)" \
    "$@")

  local ok err
  ok=$(echo "$response" | jq -r '.ok')
  if [[ "$ok" != "true" ]]; then
    err=$(echo "$response" | jq -r '.error // empty')
    if [[ "$err" == "token_revoked" || "$err" == "invalid_auth" || "$err" == "not_authed" ]]; then
      echo '{"ok":false,"error":"Token expired. Run: slack-cli auth --domain '"$(_domain)"'"}' | jq .
      exit 1
    fi
  fi
  echo "$response"
}

_urlencode() {
  python3 -c "import sys,urllib.parse; print(urllib.parse.quote(sys.stdin.read().strip()))" 2>/dev/null
}

_resolve_channel() {
  local name="${1#\#}"
  name="${name#@}"

  # User ID → open a DM conversation to get channel ID
  if [[ "$name" =~ ^U[A-Z0-9]+$ ]]; then
    local response channel_id
    response=$(_api conversations.open "users=$name")
    channel_id=$(echo "$response" | jq -r '.channel.id // empty')
    echo "$channel_id"
    return
  fi

  # Otherwise search for channel by name
  local response channel_id
  response=$(_api search.messages "query=in:%23${name}&count=1")
  channel_id=$(echo "$response" | jq -r '.messages.matches[0]?.channel.id // empty')
  echo "$channel_id"
}

_resolve_users() {
  local ids="$1"
  local map="{}"
  IFS=',' read -ra id_arr <<< "$ids"
  for uid in "${id_arr[@]}"; do
    [[ -z "$uid" || "$uid" == "null" ]] && continue
    local resp name
    resp=$(_api users.info "user=$uid" 2>/dev/null)
    name=$(echo "$resp" | jq -r '.user.profile.display_name // .user.name // empty' 2>/dev/null)
    if [[ -n "$name" ]]; then
      map=$(echo "$map" | jq --arg k "$uid" --arg v "$name" '. + {($k): $v}')
    fi
  done
  echo "$map"
}

_resolve_user_id_from_search() {
  local actor="$1"
  actor="${actor#@}"
  local response user_id

  # Primary: use search.modules people search (returns actual user IDs)
  local encoded_query
  encoded_query=$(echo -n "$actor" | _urlencode)
  response=$(_api search.modules "query=$encoded_query&module=people&count=1")
  user_id=$(echo "$response" | jq -r '.items[0]?.id // empty')
  if [[ -n "$user_id" && "$user_id" != "null" && "$user_id" =~ ^U ]]; then
    echo "$user_id"
    return
  fi

  # Fallback: search messages from the user
  encoded_query=$(echo -n "from:@$actor" | _urlencode)
  response=$(_api search.messages "query=$encoded_query&count=1")
  user_id=$(echo "$response" | jq -r '.messages.matches[0]?.user // empty')
  echo "$user_id"
}

_build_rich_text_blocks() {
  # Parse message text, resolve @mentions to user IDs, and build rich_text blocks
  # Input: message text with @username mentions
  # Output: JSON blocks array with proper user elements for mentions
  local message="$1"

  # Extract unique @mentions, resolve each to user ID, build JSON map
  local mention_map="{}"
  local mentions
  mentions=$(echo "$message" | grep -oE '@[a-zA-Z0-9_.-]+' | sort -u || true)
  for mention in $mentions; do
    local username="${mention#@}"
    local user_id
    user_id=$(_resolve_user_id_from_search "$username")
    if [[ -n "$user_id" && "$user_id" != "null" && "$user_id" =~ ^U ]]; then
      mention_map=$(echo "$mention_map" | jq --arg k "$username" --arg v "$user_id" '. + {($k): $v}')
    fi
  done

  # Build blocks with Python using the resolved mention map
  python3 -c "
import json, re, sys

message = sys.argv[1]
mention_map = json.loads(sys.argv[2])

parts = re.split(r'(@[a-zA-Z0-9_.-]+)', message)
elements = []
for part in parts:
    if not part:
        continue
    if part.startswith('@'):
        username = part[1:]
        uid = mention_map.get(username)
        if uid:
            elements.append({'type': 'user', 'user_id': uid})
        else:
            elements.append({'type': 'text', 'text': part})
    else:
        elements.append({'type': 'text', 'text': part})

blocks = [{'type': 'rich_text', 'elements': [{'type': 'rich_text_section', 'elements': elements}]}]
print(json.dumps(blocks))
" "$message" "$mention_map"
}

# ── Commands ─────────────────────────────────────────────────────────────────

cmd_auth() {
  local domain=""
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --domain|-d) domain="$2"; shift 2 ;;
      *) domain="$1"; shift ;;
    esac
  done

  if [[ -z "$domain" ]]; then
    # Check if we have a stored domain
    if [[ -f "$CREDS_FILE" ]]; then
      domain=$(_domain)
    fi
    if [[ -z "$domain" ]]; then
      echo "Usage: slack-cli auth --domain <workspace>.slack.com" >&2
      echo "Example: slack-cli auth --domain mycompany.slack.com" >&2
      exit 1
    fi
  fi

  # Normalize domain: strip protocol, ensure .slack.com suffix
  domain="${domain#https://}"
  domain="${domain#http://}"
  domain="${domain%%/*}"
  if [[ "$domain" != *".slack.com" ]]; then
    domain="${domain}.slack.com"
  fi

  echo "Extracting Slack tokens from browser for $domain ..." >&2
  mkdir -p "$CONFIG_DIR"

  local profile_dir="${SLACK_CLI_CHROME_PROFILE:-$HOME/.slack-cli/chrome-profile}"

  local tmp_script
  tmp_script=$(mktemp /tmp/slack-cli-auth-XXXXXX.mjs)
  cat > "$tmp_script" << NODESCRIPT
import { chromium } from 'playwright';
import { writeFileSync, mkdirSync } from 'fs';

const DOMAIN = '${domain}';
const PROFILE_DIR = '${profile_dir}';
const CREDS_FILE = '${CREDS_FILE}';
const CONFIG_DIR = '${CONFIG_DIR}';

async function extractTokens() {
  mkdirSync(CONFIG_DIR, { recursive: true });
  console.error('Launching browser with profile at ' + PROFILE_DIR + ' ...');
  const context = await chromium.launchPersistentContext(PROFILE_DIR, {
    headless: false,
    channel: 'chrome',
    args: ['--disable-blink-features=AutomationControlled'],
    viewport: { width: 1280, height: 800 }
  });

  const page = context.pages()[0] || await context.newPage();
  console.error('Navigating to https://' + DOMAIN + ' ...');
  await page.goto('https://' + DOMAIN, { waitUntil: 'networkidle', timeout: 60000 });

  const url = page.url();
  if (url.includes('signin') || url.includes('/sso/')) {
    console.error('ERROR: Not authenticated. Please log in to Slack in the browser window, then run auth again.');
    await page.waitForTimeout(3000);
    await context.close();
    process.exit(1);
  }

  // Extract d cookie
  const cookies = await context.cookies();
  const dCookie = cookies.find(c => c.name === 'd' && c.domain.includes('slack.com'));
  if (!dCookie) {
    console.error('ERROR: Could not find session cookie.');
    await context.close();
    process.exit(1);
  }

  // Intercept API requests to capture xoxc token
  let token = null;
  const handler = (req) => {
    const postData = req.postData();
    if (postData) {
      const match = postData.match(/token=(xoxc-[^&\\s]+)/);
      if (match) token = decodeURIComponent(match[1]);
    }
  };
  page.on('request', handler);

  // Trigger API activity
  await page.keyboard.press('Meta+g').catch(() => page.keyboard.press('Control+g').catch(() => {}));
  await page.waitForTimeout(500);
  await page.keyboard.press('Escape');
  await page.waitForTimeout(1500);

  if (!token) {
    try {
      const tab = await page.\$('button:has-text("DMs"), button:has-text("Activity")');
      if (tab) { await tab.click(); await page.waitForTimeout(1500); }
    } catch(e) {}
  }

  page.off('request', handler);

  if (!token) {
    console.error('ERROR: Could not capture API token. Try refreshing Slack in the browser and re-running auth.');
    await context.close();
    process.exit(1);
  }

  // Verify with auth.test
  const authResp = await page.evaluate(async (t) => {
    const resp = await fetch('/api/auth.test', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: 'token=' + t
    });
    return resp.json();
  }, token);

  const creds = {
    token,
    d_cookie: dCookie.value,
    domain: DOMAIN,
    team_id: authResp.team_id || '',
    team: authResp.team || '',
    user_id: authResp.user_id || '',
    user: authResp.user || '',
    url: authResp.url || '',
    extracted_at: new Date().toISOString()
  };

  writeFileSync(CREDS_FILE, JSON.stringify(creds, null, 2) + '\\n', { mode: 0o600 });
  console.error('Credentials saved for ' + creds.user + ' @ ' + creds.team + ' (' + creds.team_id + ')');
  console.log(JSON.stringify({ ok: true, user: creds.user, team: creds.team, domain: DOMAIN }));
  await context.close();
}

extractTokens().catch(err => { console.error('ERROR:', err.message); process.exit(1); });
NODESCRIPT

  node "$tmp_script"
  local exit_code=$?
  rm -f "$tmp_script"
  return $exit_code
}

cmd_search() {
  _check_creds
  local query="$*"
  if [[ -z "$query" ]]; then
    echo '{"ok":false,"error":"Usage: slack-cli search <query>"}' | jq .
    echo >&2
    echo "Search modifiers:" >&2
    echo "  from:@name / from:me    Messages from a person" >&2
    echo "  in:#channel / in:@user  Messages in a channel or DM" >&2
    echo "  before:YYYY-MM-DD       Before a date" >&2
    echo "  after:YYYY-MM-DD        After a date" >&2
    echo "  on:YYYY-MM-DD           On exact date" >&2
    echo "  during:month            During a month or year" >&2
    echo "  has::emoji: / has:pin   Has reaction or pin" >&2
    echo "  is:saved / is:thread    Saved or thread messages" >&2
    echo '  "exact phrase"          Exact match' >&2
    echo "  -word                   Exclude word" >&2
    echo "  wild*                   Wildcard (min 3 chars)" >&2
    return 1
  fi

  local count="${SLACK_CLI_COUNT:-20}"
  local sort="${SLACK_CLI_SORT:-timestamp}"
  local sort_dir="${SLACK_CLI_SORT_DIR:-desc}"
  local encoded_query
  encoded_query=$(echo -n "$query" | _urlencode)

  local response
  response=$(_api search.messages "query=$encoded_query&count=$count&sort=$sort&sort_dir=$sort_dir")

  echo "$response" | jq '{
    ok: .ok,
    query: .query,
    total: .messages.total,
    messages: [.messages.matches[]? | {
      text,
      user: .username,
      channel: .channel.name,
      channel_id: .channel.id,
      ts,
      permalink,
      date: (.ts | split(".")[0] | tonumber | strftime("%Y-%m-%d %H:%M:%S"))
    }]
  }'
}

cmd_read() {
  _check_creds
  local channel="${1:-}"
  local count="${2:-20}"

  if [[ -z "$channel" ]]; then
    echo '{"ok":false,"error":"Usage: slack-cli read <channel_name_or_id> [count]"}' | jq .
    return 1
  fi

  local channel_id="$channel"
  if [[ ! "$channel" =~ ^[CDG][A-Z0-9]+$ ]]; then
    channel_id=$(_resolve_channel "$channel")
    if [[ -z "$channel_id" || "$channel_id" == "null" ]]; then
      echo "{\"ok\":false,\"error\":\"Could not resolve channel: $channel. Try the channel ID.\"}" | jq .
      return 1
    fi
  fi

  local response
  response=$(_api conversations.history "channel=$channel_id&limit=$count")

  local ok
  ok=$(echo "$response" | jq -r '.ok')
  if [[ "$ok" != "true" ]]; then
    echo "$response" | jq '{ok, error}'
    return 1
  fi

  local user_ids
  user_ids=$(echo "$response" | jq -r '[.messages[]?.user] | unique | join(",")')
  local user_map="{}"
  if [[ -n "$user_ids" ]]; then
    user_map=$(_resolve_users "$user_ids")
  fi

  echo "$response" | jq --argjson users "$user_map" --arg cid "$channel_id" '{
    ok: .ok,
    channel_id: $cid,
    messages: [.messages[]? | {
      text,
      user_id: .user,
      user: ($users[.user] // .user),
      ts,
      date: (.ts | split(".")[0] | tonumber | strftime("%Y-%m-%d %H:%M:%S")),
      thread_ts: .thread_ts,
      reply_count: .reply_count,
      reactions: [.reactions[]? | {name, count}]
    }] | reverse
  }'
}

cmd_unreads() {
  _check_creds
  local response
  response=$(_api search.messages "query=*&count=30&sort=timestamp&sort_dir=desc")
  echo "$response" | jq '{
    ok: .ok,
    total_recent: .messages.total,
    recent_messages: [.messages.matches[:30]? | .[]? | {
      text: .text[0:200],
      user: .username,
      channel: .channel.name,
      date: (.ts | split(".")[0] | tonumber | strftime("%Y-%m-%d %H:%M:%S")),
      permalink
    }]
  }'
}

cmd_draft() {
  _check_creds
  local channel="${1:-}"
  shift 2>/dev/null || true

  # Parse --thread and --file flags from arguments
  local thread_ts=""
  local file_path=""
  local args=()
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --thread)
        thread_ts="${2:-}"
        shift 2
        ;;
      --file)
        file_path="${2:-}"
        shift 2
        ;;
      *)
        args+=("$1")
        shift
        ;;
    esac
  done
  local message="${args[*]}"

  # If --file provided, read message from file
  if [[ -n "$file_path" ]]; then
    if [[ ! -f "$file_path" ]]; then
      echo "{\"ok\":false,\"error\":\"File not found: $file_path\"}" | jq .
      return 1
    fi
    message=$(cat "$file_path")
  fi

  if [[ -z "$channel" || -z "$message" ]]; then
    echo '{"ok":false,"error":"Usage: slack-cli draft <channel_name_or_id> <message> [--thread <thread_ts>] [--file <path>]"}' | jq .
    return 1
  fi

  local channel_id="$channel"
  if [[ "$channel" =~ ^[CDG][A-Z0-9]+$ ]]; then
    : # Already a channel ID
  elif [[ "$channel" =~ ^U[A-Z0-9]+$ ]]; then
    channel_id=$(_resolve_channel "$channel")
    if [[ -z "$channel_id" || "$channel_id" == "null" ]]; then
      echo "{\"ok\":false,\"error\":\"Could not open DM with user: $channel\"}" | jq .
      return 1
    fi
  else
    channel_id=$(_resolve_channel "$channel")
    if [[ -z "$channel_id" || "$channel_id" == "null" ]]; then
      echo "{\"ok\":false,\"error\":\"Could not resolve channel: $channel\"}" | jq .
      return 1
    fi
  fi

  # Create server-side draft via Slack API (syncs across devices)
  local client_msg_id
  client_msg_id=$(python3 -c "import uuid; print(str(uuid.uuid4()))")

  local blocks
  blocks=$(_build_rich_text_blocks "$message")
  local destinations
  if [[ -n "$thread_ts" ]]; then
    destinations=$(jq -n --arg ch "$channel_id" --arg ts "$thread_ts" '[{channel_id:$ch, thread_ts:$ts}]')
  else
    destinations=$(jq -n --arg ch "$channel_id" '[{channel_id:$ch}]')
  fi

  local response
  response=$(_api_workspace drafts.create \
    -F "blocks=$blocks" \
    -F "client_msg_id=$client_msg_id" \
    -F "attachments=" \
    -F "destinations=$destinations" \
    -F "file_ids=[]" \
    -F "is_from_composer=false" \
    -F "_x_reason=MessageInput:updateDraft" \
    -F "_x_mode=online" \
    -F "_x_sonic=true" \
    -F "_x_app_name=client")

  local ok
  ok=$(echo "$response" | jq -r '.ok')
  if [[ "$ok" == "true" ]]; then
    echo "$response" | jq '{ok, action:"draft_saved", info:"Draft saved to Slack (syncs across devices). Use slack-cli drafts to list, slack-cli send <id> to send.", draft: {id:.draft.id, channel_id:.draft.destinations[0].channel_id, thread_ts:.draft.destinations[0].thread_ts, text:([.draft.blocks[]?.elements[]?.elements[]? | select(.type == "text") | .text] | join("")), created:.draft.date_created}}'
  else
    local draft_err
    draft_err=$(echo "$response" | jq -r '.error // empty')
    case "$draft_err" in
      attached_draft_exists)
        echo '{"ok":false,"error":"attached_draft_exists","hint":"A draft already exists on this channel. Use '\''slack-cli draft-delete <id>'\'' to remove it first, or '\''slack-cli drafts'\'' to list drafts."}' | jq .
        ;;
      invalid_message)
        echo '{"ok":false,"error":"invalid_message","hint":"Message rejected by Slack. This often happens with complex formatting in rich_text blocks. Try '\''slack-cli post'\'' instead which uses mrkdwn formatting."}' | jq .
        ;;
      *)
        echo "$response" | jq '{ok, error}'
        ;;
    esac
    return 1
  fi
}

cmd_drafts() {
  _check_creds

  # List server-side drafts from Slack API
  local response
  response=$(_api_workspace drafts.list \
    -F "is_active=true" \
    -F "limit=25" \
    -F "_x_reason=client-drafts-list" \
    -F "_x_mode=online" \
    -F "_x_sonic=true" \
    -F "_x_app_name=client")

  local ok
  ok=$(echo "$response" | jq -r '.ok')
  if [[ "$ok" == "true" ]]; then
    echo "$response" | jq '{ok, drafts: [.drafts[]? | select(.is_deleted == false and .is_sent == false) | {
      id: .id,
      channel_id: .destinations[0].channel_id,
      text: ([.blocks[]?.elements[]?.elements[]? | select(.type == "text") | .text] | join("")),
      created: .date_created,
      last_updated_ts: .last_updated_ts
    }]}'
  else
    echo "$response" | jq '{ok, error}'
    return 1
  fi
}

cmd_draft_delete() {
  _check_creds
  local draft_id="${1:-}"
  if [[ -z "$draft_id" ]]; then
    echo '{"ok":false,"error":"Usage: slack-cli draft-delete <draft_id>"}' | jq .
    return 1
  fi

  local now_ts
  now_ts=$(python3 -c "import time; print(f'{time.time():.6f}')")

  local response
  response=$(_api_workspace drafts.delete \
    -F "draft_id=$draft_id" \
    -F "client_last_updated_ts=$now_ts" \
    -F "_x_reason=MessageInput:deleteDraft" \
    -F "_x_mode=online" \
    -F "_x_sonic=true" \
    -F "_x_app_name=client")

  local ok
  ok=$(echo "$response" | jq -r '.ok')
  if [[ "$ok" == "true" ]]; then
    echo "{\"ok\":true,\"action\":\"draft_deleted\",\"draft_id\":\"$draft_id\"}" | jq .
  else
    echo "$response" | jq '{ok, error}'
    return 1
  fi
}

cmd_post() {
  _check_creds
  local channel="${1:-}"
  shift 2>/dev/null || true

  # Parse --thread and --file flags from arguments
  local thread_ts=""
  local file_path=""
  local args=()
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --thread)
        thread_ts="${2:-}"
        shift 2
        ;;
      --file)
        file_path="${2:-}"
        shift 2
        ;;
      *)
        args+=("$1")
        shift
        ;;
    esac
  done
  local message="${args[*]}"

  # If --file provided, read message from file
  if [[ -n "$file_path" ]]; then
    if [[ ! -f "$file_path" ]]; then
      echo "{\"ok\":false,\"error\":\"File not found: $file_path\"}" | jq .
      return 1
    fi
    message=$(cat "$file_path")
  fi

  if [[ -z "$channel" || -z "$message" ]]; then
    echo '{"ok":false,"error":"Usage: slack-cli post <channel_name_or_id> <message> [--thread <ts>] [--file <path>]"}' | jq .
    return 1
  fi

  local channel_id="$channel"
  if [[ "$channel" =~ ^[CDG][A-Z0-9]+$ ]]; then
    : # Already a channel ID
  elif [[ "$channel" =~ ^U[A-Z0-9]+$ ]]; then
    channel_id=$(_resolve_channel "$channel")
    if [[ -z "$channel_id" || "$channel_id" == "null" ]]; then
      echo "{\"ok\":false,\"error\":\"Could not open DM with user: $channel\"}" | jq .
      return 1
    fi
  else
    channel_id=$(_resolve_channel "$channel")
    if [[ -z "$channel_id" || "$channel_id" == "null" ]]; then
      echo "{\"ok\":false,\"error\":\"Could not resolve channel: $channel\"}" | jq .
      return 1
    fi
  fi

  # Resolve @mentions to <@UID> format for mrkdwn
  local mentions
  mentions=$(echo "$message" | grep -oE '@[a-zA-Z0-9_.-]+' | sort -u || true)
  for mention in $mentions; do
    local username="${mention#@}"
    local user_id
    user_id=$(_resolve_user_id_from_search "$username")
    if [[ -n "$user_id" && "$user_id" != "null" && "$user_id" =~ ^U ]]; then
      message="${message//@${username}/<@${user_id}>}"
    fi
  done

  local thread_info=""
  if [[ -n "$thread_ts" ]]; then
    thread_info=" (thread reply)"
  fi
  echo "About to send to channel $channel_id$thread_info:" >&2
  echo "$message" >&2
  echo >&2
  read -p "Confirm send? [y/N] " -r confirm
  if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
    echo '{"ok":false,"action":"cancelled"}' | jq .
    return 0
  fi

  local encoded_message
  encoded_message=$(echo -n "$message" | _urlencode)

  local api_params="channel=$channel_id&text=$encoded_message"
  if [[ -n "$thread_ts" ]]; then
    api_params="$api_params&thread_ts=$thread_ts"
  fi

  local response
  response=$(_api chat.postMessage "$api_params")

  local ok
  ok=$(echo "$response" | jq -r '.ok')
  if [[ "$ok" == "true" ]]; then
    echo "$response" | jq '{ok, action:"sent", channel:.channel, ts:.ts}'
  else
    echo "$response" | jq '{ok, error}'
    return 1
  fi
}

cmd_send() {
  _check_creds
  local draft_id="${1:-}"
  if [[ -z "$draft_id" ]]; then
    echo '{"ok":false,"error":"Usage: slack-cli send <draft_id>"}' | jq .
    return 1
  fi

  # Fetch drafts to find the one to send
  local drafts_response
  drafts_response=$(_api_workspace drafts.list \
    -F "is_active=true" \
    -F "limit=25" \
    -F "_x_reason=client-drafts-list" \
    -F "_x_mode=online" \
    -F "_x_sonic=true" \
    -F "_x_app_name=client")

  local draft
  draft=$(echo "$drafts_response" | jq -r --arg id "$draft_id" '.drafts[]? | select(.id == $id and .is_deleted == false and .is_sent == false)')
  if [[ -z "$draft" ]]; then
    echo '{"ok":false,"error":"Draft not found or already sent. Use slack-cli drafts to list available drafts."}' | jq .
    return 1
  fi

  local channel_id message thread_ts
  channel_id=$(echo "$draft" | jq -r '.destinations[0].channel_id')
  thread_ts=$(echo "$draft" | jq -r '.destinations[0].thread_ts // empty')
  # Reconstruct message text with <@USER_ID> for user mentions (Slack mrkdwn format)
  message=$(echo "$draft" | jq -r '[.blocks[]?.elements[]?.elements[]? | if .type == "user" then "<@" + .user_id + ">" elif .type == "text" then .text else empty end] | join("")')

  local thread_info=""
  if [[ -n "$thread_ts" ]]; then
    thread_info=" (thread reply)"
  fi
  echo "About to send to channel $channel_id$thread_info:" >&2
  echo "$message" >&2
  echo >&2
  read -p "Confirm send? [y/N] " -r confirm
  if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
    echo '{"ok":false,"action":"cancelled"}' | jq .
    return 0
  fi

  local encoded_message
  encoded_message=$(echo -n "$message" | _urlencode)

  local api_params="channel=$channel_id&text=$encoded_message"
  if [[ -n "$thread_ts" ]]; then
    api_params="$api_params&thread_ts=$thread_ts"
  fi

  local response
  response=$(_api chat.postMessage "$api_params")

  local ok
  ok=$(echo "$response" | jq -r '.ok')
  if [[ "$ok" == "true" ]]; then
    # Delete the server-side draft after successful send
    local now_ts
    now_ts=$(python3 -c "import time; print(f'{time.time():.6f}')")
    _api_workspace drafts.delete \
      -F "draft_id=$draft_id" \
      -F "client_last_updated_ts=$now_ts" \
      -F "_x_reason=MessageInput:deleteDraft" \
      -F "_x_mode=online" \
      -F "_x_sonic=true" \
      -F "_x_app_name=client" > /dev/null 2>&1

    echo "$response" | jq '{ok, action:"sent", channel:.channel, ts:.ts}'
  else
    echo "$response" | jq '{ok, error}'
  fi
}

cmd_channels() {
  _check_creds
  local response
  response=$(_api search.messages "query=from:me&count=100&sort=timestamp&sort_dir=desc")
  echo "$response" | jq '{
    ok: .ok,
    channels: [.messages.matches[]? | {name:.channel.name, id:.channel.id}] | unique_by(.id) | sort_by(.name)
  }'
}

cmd_dms() {
  _check_creds
  local response
  response=$(_api search.messages "query=from:me&count=50&sort=timestamp&sort_dir=desc")
  echo "$response" | jq '{
    ok: .ok,
    recent_dms: [.messages.matches[]? | select(.channel.is_im == true or (.channel.name | test("^D|^mpdm"))) | {
      channel:.channel.name, channel_id:.channel.id,
      last_message:.text[0:100],
      date: (.ts | split(".")[0] | tonumber | strftime("%Y-%m-%d %H:%M:%S"))
    }] | unique_by(.channel_id)
  }'
}

cmd_userinfo() {
  _check_creds
  local user="${1:-}"
  if [[ -z "$user" ]]; then
    echo '{"ok":false,"error":"Usage: slack-cli userinfo <user_id_or_email>"}' | jq .
    return 1
  fi

  local response
  if [[ "$user" == *"@"* ]]; then
    response=$(_api users.lookupByEmail "email=$user")
  elif [[ "$user" =~ ^U[A-Z0-9]+$ ]]; then
    response=$(_api users.info "user=$user")
  else
    # Resolve username to user ID via people search, then get full info
    local user_id
    user_id=$(_resolve_user_id_from_search "$user")
    if [[ -n "$user_id" && "$user_id" != "null" && "$user_id" =~ ^U ]]; then
      response=$(_api users.info "user=$user_id")
    else
      response=$(_api search.messages "query=from:@$user&count=1")
      echo "$response" | jq '{ok, note:"Partial match via search. Use user ID or email for exact lookup.", match:(.messages.matches[0]? | {username, text:.text[0:100], channel:.channel.name})}'
      return 0
    fi
  fi

  echo "$response" | jq '{ok, user:{id:.user.id, name:.user.name, real_name:.user.real_name, display_name:.user.profile.display_name, email:.user.profile.email, title:.user.profile.title, status:.user.profile.status_text, tz:.user.tz}}'
}

cmd_status() {
  _check_creds
  local emoji="${1:-}"
  shift 2>/dev/null || true
  local text="$*"

  # No args = get current status
  if [[ -z "$emoji" ]]; then
    local response
    response=$(_api users.profile.get "")
    echo "$response" | jq '{ok, status: {emoji: .profile.status_emoji, text: .profile.status_text, expiration: .profile.status_expiration}}'
    return
  fi

  # "clear" = clear status
  if [[ "$emoji" == "clear" ]]; then
    local profile
    profile=$(jq -n '{status_text:"",status_emoji:"",status_expiration:0}')
    local encoded_profile
    encoded_profile=$(echo -n "$profile" | _urlencode)
    local response
    response=$(_api users.profile.set "profile=$encoded_profile")
    echo "$response" | jq '{ok, action:"status_cleared"}'
    return
  fi

  # Normalize emoji: wrap in colons if not already
  if [[ "$emoji" != :*: ]]; then
    emoji=":${emoji}:"
  fi

  local profile
  profile=$(jq -n --arg e "$emoji" --arg t "$text" '{status_text:$t,status_emoji:$e,status_expiration:0}')
  local encoded_profile
  encoded_profile=$(echo -n "$profile" | _urlencode)

  local response
  response=$(_api users.profile.set "profile=$encoded_profile")

  local ok
  ok=$(echo "$response" | jq -r '.ok')
  if [[ "$ok" == "true" ]]; then
    echo "$response" | jq '{ok, action:"status_set", status: {emoji: .profile.status_emoji, text: .profile.status_text}}'
  else
    echo "$response" | jq '{ok, error}'
    return 1
  fi
}

cmd_presence() {
  _check_creds
  local input="${1:-}"
  local response

  if [[ -n "$input" ]]; then
    local user_id="$input"

    if [[ "$input" =~ ^U[A-Z0-9]+$ ]]; then
      user_id="$input"
    else
      user_id=$(_resolve_user_id_from_search "$input")
      if [[ -z "$user_id" || "$user_id" == "null" ]]; then
        echo "{\"ok\":false,\"error\":\"Could not resolve user from input: $input. Try a user ID.\"}" | jq .
        return 1
      fi
    fi

    response=$(_api users.getPresence "user=$user_id")
  else
    response=$(_api users.getPresence "")
  fi

  echo "$response" | jq '{ok, presence} + (if .online != null then {online, auto_away, manual_away, connection_count, last_activity} else {} end)'
}

cmd_whoami() {
  _check_creds
  _api auth.test | jq '{ok, user, user_id, team, team_id, url}'
}

cmd_config() {
  if [[ -f "$CREDS_FILE" ]]; then
    jq '{domain, team, user, user_id, team_id, extracted_at}' "$CREDS_FILE"
  else
    echo '{"error":"Not configured. Run: slack-cli auth --domain <workspace>.slack.com"}' | jq .
  fi
}

cmd_help() {
  cat << 'EOF'
slack-cli - Slack CLI for coding agents

SETUP:
  slack-cli auth --domain <workspace>.slack.com
    Opens a browser to extract API tokens from your authenticated Slack session.
    Requires: playwright (npm i -g playwright), Chrome browser with active Slack login.

COMMANDS:
  auth [--domain <dom>]      Extract tokens from browser session
  whoami                     Show current authenticated user
  config                     Show current configuration
  search <query>             Search messages (full Slack query syntax)
  read <channel> [count]     Read messages from a channel (name or ID)
  unreads                    Show recent activity
  draft <channel> <message> [--thread <ts>] [--file <path>]  Save a draft (optionally as thread reply)
  draft-delete <draft_id>    Delete a server-side draft
  drafts                     List saved drafts
  send <draft_id>            Send a draft (interactive confirmation)
  post <channel> <message> [--thread <ts>] [--file <path>]  Send a message with mrkdwn formatting (with confirmation)
  channels                   List your channels
  dms                        List recent DMs
  userinfo <user>            Look up user by ID, email, or name
  status [emoji] [text]      Get or set your Slack status (use 'clear' to remove)
  presence [user_id|username|email]  Show a user's presence status (active/away). Defaults to self.
  help                       Show this help

SEARCH MODIFIERS:
  from:@name / from:me       Messages from a person
  in:#channel / in:@user     Messages in a channel or DM
  before:YYYY-MM-DD          Before a date
  after:YYYY-MM-DD           After a date
  on:YYYY-MM-DD              On exact date
  during:month               During a month or year
  has::emoji: / has:pin      Has reaction or pin
  is:saved / is:thread       Saved or thread messages
  "exact phrase"             Exact match
  -word                      Exclude word
  wild*                      Wildcard (min 3 chars)

ENVIRONMENT VARIABLES:
  SLACK_CLI_HOME             Config directory (default: ~/.slack-cli)
  SLACK_CLI_CHROME_PROFILE   Chrome profile path for auth (default: ~/.slack-cli/chrome-profile)
  SLACK_CLI_COUNT            Results per page (default: 20)
  SLACK_CLI_SORT             Sort by: timestamp or score (default: timestamp)
  SLACK_CLI_SORT_DIR         Sort direction: asc or desc (default: desc)

EXAMPLES:
  slack-cli auth --domain mycompany.slack.com
  slack-cli search "from:me in:#engineering after:2025-01-01"
  slack-cli search "deployment error has::eyes:"
  slack-cli read engineering 10
  slack-cli draft general "Hey team, the fix is ready for review"
  slack-cli draft general --file /tmp/message.txt
  slack-cli draft-delete Fd1234567890abcdef
  slack-cli post general "Hey team, check out *this feature*!"
  slack-cli post general --file /tmp/message.md --thread 1234567890.123456
  slack-cli status :bus: Commuting
  slack-cli status clear
  slack-cli userinfo user@company.com

All output is structured JSON for easy parsing by scripts and AI agents.
EOF
}

# ── Main ─────────────────────────────────────────────────────────────────────

_check_deps

case "${1:-help}" in
  auth)      shift; cmd_auth "$@" ;;
  search)    shift; cmd_search "$@" ;;
  read)      shift; cmd_read "$@" ;;
  history)   shift; cmd_read "$@" ;;
  unreads)   cmd_unreads ;;
  draft)        shift; cmd_draft "$@" ;;
  draft-delete) shift; cmd_draft_delete "$@" ;;
  drafts)       cmd_drafts ;;
  send)         shift; cmd_send "$@" ;;
  post)         shift; cmd_post "$@" ;;
  channels)  cmd_channels ;;
  dms)       cmd_dms ;;
  userinfo)  shift; cmd_userinfo "$@" ;;
  status)    shift; cmd_status "$@" ;;
  presence)  shift; cmd_presence "$@" ;;
  whoami)    cmd_whoami ;;
  config)    cmd_config ;;
  help|--help|-h) cmd_help ;;
  *)         echo "Unknown command: $1. Run 'slack-cli help'." >&2; exit 1 ;;
esac
