#!/usr/bin/env bash
set -euo pipefail

API_BASE="${EVOLINK_API_BASE:-https://api.evolink.ai}"
CREATE_ENDPOINT="${API_BASE}/v1/videos/generations"
DOCS_BASE="https://evolink.ai/docs/en/api-manual/video-series/seedance2.5"
SKILL_SLUG="seedance-2-5-video-gen"
PACKAGE_NAME="evolink-seedance-2-5"
CAMPAIGN="seedance-2-5-video-gen"

usage() {
  sed -n '/^# Usage:/,/^# End usage/p' "$0" | sed 's/^# \{0,1\}//'
}

# Usage:
# seedance-2-5-video-gen.sh "PROMPT" [options]
#
# Options:
#   --mode text|image|reference|edit|extend
#                                      Workflow, default: text
#   --image-url URL                   Repeatable image input
#   --video-url URL                   Repeatable reference video input
#   --audio-url URL                   Repeatable reference audio input
#   --duration -1|4..30               Output seconds, default: 5; edit defaults to -1
#   --quality 480p|720p               Output quality, default: 720p
#   --aspect-ratio RATIO              16:9, 9:16, 1:1, 4:3, 3:4, 21:9, adaptive
#   --output-format mp4|mov           Default: mp4
#   --generate-audio true|false       Default: true
#   --content-filter true|false       Default: true
#   --web-search true|false           Text mode only, default: false
#   --callback-url HTTPS_URL          Optional completion webhook
#   --touchpoint first_call|subsequent_call
#   --timeout SECONDS                 Poll timeout, default: 600
#   --dry-run                         Validate and print payload without an API call
#   --validate-key                    Validate EVOLINK_API_KEY without generation
#   --check-availability              Exit 0 when all five official model pages are live
#   --help                            Show this help
# End usage

require_command() {
  command -v "$1" >/dev/null 2>&1 || {
    printf 'ERROR: required command not found: %s\n' "$1" >&2
    exit 127
  }
}

is_bool() {
  [ "$1" = "true" ] || [ "$1" = "false" ]
}

is_https_url() {
  case "$1" in
    https://*) return 0 ;;
    *) return 1 ;;
  esac
}

check_availability() {
  local closed=0 slug body
  for slug in \
    seedance-2.5-text-to-video \
    seedance-2.5-image-to-video \
    seedance-2.5-reference-to-video \
    seedance-2.5-video-edit \
    seedance-2.5-video-extend; do
    body="$(curl -fsSL "${DOCS_BASE}/${slug}.md")"
    if printf '%s' "$body" | grep -Fq 'This model is not yet available'; then
      printf '%s_status=closed\n' "$slug"
      closed=1
    else
      printf '%s_status=live\n' "$slug"
    fi
  done
  if [ "$closed" -eq 1 ]; then
    printf 'launch_status=closed\n'
    return 3
  fi
  printf 'launch_status=live\n'
}

MODE="text"
PROMPT=""
DURATION=5
DURATION_SET=false
QUALITY="720p"
ASPECT_RATIO="adaptive"
OUTPUT_FORMAT="mp4"
GENERATE_AUDIO="true"
CONTENT_FILTER="true"
WEB_SEARCH="false"
CALLBACK_URL=""
TOUCHPOINT="first_call"
TIMEOUT_SECONDS=600
DRY_RUN=false
VALIDATE_KEY=false
CHECK_AVAILABILITY=false
IMAGE_URLS=()
VIDEO_URLS=()
AUDIO_URLS=()

while [ "$#" -gt 0 ]; do
  case "$1" in
    --mode) MODE="${2:?--mode requires a value}"; shift 2 ;;
    --image-url) IMAGE_URLS+=("${2:?--image-url requires a URL}"); shift 2 ;;
    --video-url) VIDEO_URLS+=("${2:?--video-url requires a URL}"); shift 2 ;;
    --audio-url) AUDIO_URLS+=("${2:?--audio-url requires a URL}"); shift 2 ;;
    --duration) DURATION="${2:?--duration requires seconds}"; DURATION_SET=true; shift 2 ;;
    --quality) QUALITY="${2:?--quality requires 480p or 720p}"; shift 2 ;;
    --aspect-ratio) ASPECT_RATIO="${2:?--aspect-ratio requires a value}"; shift 2 ;;
    --output-format) OUTPUT_FORMAT="${2:?--output-format requires mp4 or mov}"; shift 2 ;;
    --generate-audio) GENERATE_AUDIO="${2:?--generate-audio requires true or false}"; shift 2 ;;
    --content-filter) CONTENT_FILTER="${2:?--content-filter requires true or false}"; shift 2 ;;
    --web-search) WEB_SEARCH="${2:?--web-search requires true or false}"; shift 2 ;;
    --callback-url) CALLBACK_URL="${2:?--callback-url requires an HTTPS URL}"; shift 2 ;;
    --touchpoint) TOUCHPOINT="${2:?--touchpoint requires a value}"; shift 2 ;;
    --timeout) TIMEOUT_SECONDS="${2:?--timeout requires seconds}"; shift 2 ;;
    --dry-run) DRY_RUN=true; shift ;;
    --validate-key) VALIDATE_KEY=true; shift ;;
    --check-availability) CHECK_AVAILABILITY=true; shift ;;
    --help|-h) usage; exit 0 ;;
    --*) printf 'ERROR: unknown option: %s\n' "$1" >&2; usage >&2; exit 2 ;;
    *)
      if [ -n "$PROMPT" ]; then
        printf 'ERROR: prompt must be passed as one quoted argument\n' >&2
        exit 2
      fi
      PROMPT="$1"
      shift
      ;;
  esac
done

require_command curl
require_command jq

if [ "$CHECK_AVAILABILITY" = true ]; then
  check_availability
  exit $?
fi

if [ "$VALIDATE_KEY" = true ]; then
  : "${EVOLINK_API_KEY:?Set EVOLINK_API_KEY first}"
  HTTP_CODE="$(curl -sS -o /dev/null -w '%{http_code}' \
    --request GET "${API_BASE}/v1/credits" \
    --header "Authorization: Bearer ${EVOLINK_API_KEY}")"
  if [ "$HTTP_CODE" != "200" ]; then
    printf 'ERROR: key validation returned HTTP %s\n' "$HTTP_CODE" >&2
    exit 1
  fi
  printf 'KEY_VALID=true\n'
  exit 0
fi

if [ -z "$PROMPT" ]; then
  printf 'ERROR: a quoted prompt is required\n' >&2
  usage >&2
  exit 2
fi

case "$MODE" in
  text) MODEL="seedance-2.5-text-to-video" ;;
  image) MODEL="seedance-2.5-image-to-video" ;;
  reference) MODEL="seedance-2.5-reference-to-video" ;;
  edit) MODEL="seedance-2.5-video-edit" ;;
  extend) MODEL="seedance-2.5-video-extend" ;;
  *) printf 'ERROR: --mode must be text, image, reference, edit, or extend\n' >&2; exit 2 ;;
esac

if [ "$MODE" = edit ] && [ "$DURATION_SET" = false ]; then
  DURATION=-1
fi
case "$DURATION" in
  -1) ;;
  ''|*[!0-9]*) printf 'ERROR: duration must be -1 or an integer from 4 to 30\n' >&2; exit 2 ;;
  *)
    if [ "$DURATION" -lt 4 ] || [ "$DURATION" -gt 30 ]; then
      printf 'ERROR: duration must be -1 or an integer from 4 to 30\n' >&2
      exit 2
    fi
    ;;
esac
if [ "$MODE" = edit ] && [ "$DURATION" != -1 ]; then
  printf 'ERROR: edit mode only accepts --duration -1 because output follows the first input video\n' >&2
  exit 2
fi
case "$QUALITY" in 480p|720p) ;; *) printf 'ERROR: quality must be 480p or 720p\n' >&2; exit 2 ;; esac
case "$ASPECT_RATIO" in 16:9|9:16|1:1|4:3|3:4|21:9|adaptive) ;; *) printf 'ERROR: unsupported aspect ratio\n' >&2; exit 2 ;; esac
if { [ "$MODE" = image ] || [ "$MODE" = edit ] || [ "$MODE" = extend ]; } && [ "$ASPECT_RATIO" != adaptive ]; then
  printf 'ERROR: %s mode only accepts --aspect-ratio adaptive\n' "$MODE" >&2
  exit 2
fi
case "$OUTPUT_FORMAT" in mp4|mov) ;; *) printf 'ERROR: output format must be mp4 or mov\n' >&2; exit 2 ;; esac
is_bool "$GENERATE_AUDIO" || { printf 'ERROR: --generate-audio must be true or false\n' >&2; exit 2; }
is_bool "$CONTENT_FILTER" || { printf 'ERROR: --content-filter must be true or false\n' >&2; exit 2; }
is_bool "$WEB_SEARCH" || { printf 'ERROR: --web-search must be true or false\n' >&2; exit 2; }
case "$TOUCHPOINT" in first_call|subsequent_call) ;; *) printf 'ERROR: unsupported touchpoint\n' >&2; exit 2 ;; esac

if [ "$MODE" = text ] && { [ "${#IMAGE_URLS[@]}" -gt 0 ] || [ "${#VIDEO_URLS[@]}" -gt 0 ] || [ "${#AUDIO_URLS[@]}" -gt 0 ]; }; then
  printf 'ERROR: text mode does not accept image, video, or audio URLs\n' >&2
  exit 2
fi
if [ "$MODE" = image ]; then
  if [ "${#IMAGE_URLS[@]}" -lt 1 ] || [ "${#IMAGE_URLS[@]}" -gt 2 ]; then
    printf 'ERROR: image mode requires one or two --image-url values\n' >&2
    exit 2
  fi
  if [ "${#VIDEO_URLS[@]}" -gt 0 ] || [ "${#AUDIO_URLS[@]}" -gt 0 ]; then
    printf 'ERROR: image mode does not accept video or audio URLs\n' >&2
    exit 2
  fi
fi
if [ "$MODE" = reference ]; then
  if [ "${#IMAGE_URLS[@]}" -eq 0 ] && [ "${#VIDEO_URLS[@]}" -eq 0 ] && [ "${#AUDIO_URLS[@]}" -eq 0 ]; then
    printf 'ERROR: reference mode requires at least one media URL\n' >&2
    exit 2
  fi
  [ "${#IMAGE_URLS[@]}" -le 30 ] || { printf 'ERROR: reference mode supports at most 30 images\n' >&2; exit 2; }
  [ "${#VIDEO_URLS[@]}" -le 10 ] || { printf 'ERROR: reference mode supports at most 10 videos\n' >&2; exit 2; }
  [ "${#AUDIO_URLS[@]}" -le 10 ] || { printf 'ERROR: reference mode supports at most 10 audio clips\n' >&2; exit 2; }
  prompt_lower="$(printf '%s' "$PROMPT" | tr '[:upper:]' '[:lower:]')"
  case "$prompt_lower" in
    *edit\ @video*|*edit\ the\ video*)
      printf 'ERROR: use --mode edit for video-edit intent; reference mode may fail after submission\n' >&2
      exit 2
      ;;
    *extend\ @video*|*extend\ the\ video*)
      printf 'ERROR: use --mode extend for video-extension intent; reference mode may fail after submission\n' >&2
      exit 2
      ;;
  esac
fi
if [ "$MODE" = edit ] || [ "$MODE" = extend ]; then
  if [ "${#VIDEO_URLS[@]}" -lt 1 ] || [ "${#VIDEO_URLS[@]}" -gt 10 ]; then
    printf 'ERROR: %s mode requires one to ten --video-url values; the first is the target video\n' "$MODE" >&2
    exit 2
  fi
  [ "${#IMAGE_URLS[@]}" -le 30 ] || { printf 'ERROR: %s mode supports at most 30 images\n' "$MODE" >&2; exit 2; }
  [ "${#AUDIO_URLS[@]}" -le 10 ] || { printf 'ERROR: %s mode supports at most 10 audio clips\n' "$MODE" >&2; exit 2; }
fi
if [ "$MODE" != text ] && [ "$WEB_SEARCH" = true ]; then
  printf 'ERROR: web search is supported only in text mode\n' >&2
  exit 2
fi
if [ -n "$CALLBACK_URL" ] && ! is_https_url "$CALLBACK_URL"; then
  printf 'ERROR: callback URL must use HTTPS\n' >&2
  exit 2
fi

if [ "${#IMAGE_URLS[@]}" -eq 0 ]; then
  images_json='[]'
else
  images_json="$(printf '%s\n' "${IMAGE_URLS[@]}" | jq -R . | jq -s .)"
fi
if [ "${#VIDEO_URLS[@]}" -eq 0 ]; then
  videos_json='[]'
else
  videos_json="$(printf '%s\n' "${VIDEO_URLS[@]}" | jq -R . | jq -s .)"
fi
if [ "${#AUDIO_URLS[@]}" -eq 0 ]; then
  audio_json='[]'
else
  audio_json="$(printf '%s\n' "${AUDIO_URLS[@]}" | jq -R . | jq -s .)"
fi

PAYLOAD="$(jq -n \
  --arg model "$MODEL" \
  --arg prompt "$PROMPT" \
  --argjson duration "$DURATION" \
  --arg quality "$QUALITY" \
  --arg aspect_ratio "$ASPECT_RATIO" \
  --arg output_format "$OUTPUT_FORMAT" \
  --argjson generate_audio "$GENERATE_AUDIO" \
  --argjson content_filter "$CONTENT_FILTER" \
  --argjson web_search "$WEB_SEARCH" \
  --arg callback_url "$CALLBACK_URL" \
  --argjson image_urls "$images_json" \
  --argjson video_urls "$videos_json" \
  --argjson audio_urls "$audio_json" \
  '{model:$model,prompt:$prompt,duration:$duration,quality:$quality,aspect_ratio:$aspect_ratio,output_format:$output_format,generate_audio:$generate_audio,content_filter:$content_filter}
   + (if $web_search then {model_params:{web_search:true}} else {} end)
   + (if $callback_url != "" then {callback_url:$callback_url} else {} end)
   + (if ($image_urls|length)>0 then {image_urls:$image_urls} else {} end)
   + (if ($video_urls|length)>0 then {video_urls:$video_urls} else {} end)
   + (if ($audio_urls|length)>0 then {audio_urls:$audio_urls} else {} end)')"

if [ "$DRY_RUN" = true ]; then
  printf 'DRY_RUN=true\n'
  printf 'CREATE_ENDPOINT=%s\n' "$CREATE_ENDPOINT"
  printf 'MODEL=%s\n' "$MODEL"
  printf '%s\n' "$PAYLOAD" | jq .
  exit 0
fi

: "${EVOLINK_API_KEY:?Set EVOLINK_API_KEY first}"
STARTED_AT="$(date +%s)"
WORK_DIR="$(mktemp -d)"
trap 'rm -rf "$WORK_DIR"' EXIT

create_body="${WORK_DIR}/create.json"
http_code="$(curl -sS -o "$create_body" -w '%{http_code}' \
  --request POST "$CREATE_ENDPOINT" \
  --header "Authorization: Bearer ${EVOLINK_API_KEY}" \
  --header 'Content-Type: application/json' \
  --header 'X-EvoLink-Source: skill' \
  --header "X-EvoLink-Skill: ${SKILL_SLUG}" \
  --header "X-EvoLink-Package: ${PACKAGE_NAME}" \
  --header "X-EvoLink-Campaign: ${CAMPAIGN}" \
  --header "X-EvoLink-Touchpoint: ${TOUCHPOINT}" \
  --data "$PAYLOAD")"
if [ "$http_code" -lt 200 ] || [ "$http_code" -ge 300 ]; then
  printf 'ERROR: create request returned HTTP %s\n' "$http_code" >&2
  jq . "$create_body" >&2 2>/dev/null || sed -n '1,120p' "$create_body" >&2
  exit 1
fi

TASK_ID="$(jq -r '.id // empty' "$create_body")"
if [ -z "$TASK_ID" ]; then
  printf 'ERROR: create response did not include id\n' >&2
  jq . "$create_body" >&2
  exit 1
fi
ESTIMATED="$(jq -r '.task_info.estimated_time // 0' "$create_body")"
printf 'TASK_SUBMITTED: task_id=%s estimated=%ss\n' "$TASK_ID" "$ESTIMATED"

if [ -n "$CALLBACK_URL" ]; then
  printf 'CALLBACK_PENDING: task_id=%s callback_url=%s\n' "$TASK_ID" "$CALLBACK_URL"
  exit 0
fi

deadline=$((STARTED_AT + TIMEOUT_SECONDS))
while [ "$(date +%s)" -lt "$deadline" ]; do
  poll_body="${WORK_DIR}/poll.json"
  poll_code="$(curl -sS -o "$poll_body" -w '%{http_code}' \
    --request GET "${API_BASE}/v1/tasks/${TASK_ID}" \
    --header "Authorization: Bearer ${EVOLINK_API_KEY}")"
  if [ "$poll_code" -lt 200 ] || [ "$poll_code" -ge 300 ]; then
    printf 'ERROR: poll request returned HTTP %s\n' "$poll_code" >&2
    jq . "$poll_body" >&2 2>/dev/null || sed -n '1,120p' "$poll_body" >&2
    exit 1
  fi
  STATUS="$(jq -r '.status // empty' "$poll_body")"
  PROGRESS="$(jq -r '.progress // 0' "$poll_body")"
  printf 'STATUS_UPDATE: status=%s progress=%s%%\n' "$STATUS" "$PROGRESS"
  case "$STATUS" in
    completed)
      jq . "$poll_body"
      jq -r '.results[]? | "RESULT_URL=" + .' "$poll_body"
      printf 'ELAPSED=%ss\n' "$(( $(date +%s) - STARTED_AT ))"
      exit 0
      ;;
    failed)
      printf 'ERROR: task failed\n' >&2
      jq . "$poll_body" >&2
      exit 1
      ;;
  esac
  sleep 5
done

printf 'POLL_TIMEOUT: task_id=%s dashboard=https://evolink.ai/dashboard\n' "$TASK_ID" >&2
exit 2
