#!/bin/bash
# Game-side, allowlisted respawn configuration. No paths from panel input.
set -uo pipefail
export LC_ALL=C
SHARE_DIR="${M2_SHARE_DIR:-/opt/metin2/share}"
VAR_DIR="${M2_VAR_DIR:-/opt/metin2/var}"
SPOOL="${M2_RATES_SPOOL:-/opt/m2spool}"
STATE_DIR="$VAR_DIR/map-regens"
WANTED="$STATE_DIR/wanted"
REQUEST="$SPOOL/map-regens.request"
MAP_IDS=(1 3 21 23 41 43 61 63 64 25 104 108 109)
MAP_STONE_IDS=(1 3 21 23 41 43 61 63 64)
kv() { sed -n "s/^$2=//p" "$1" 2>/dev/null | head -1; }
map_dir() {
  case "$1" in
    1) echo metin2_map_a1;; 3) echo metin2_map_a3;;
    21) echo metin2_map_b1;; 23) echo metin2_map_b3;;
    41) echo metin2_map_c1;; 43) echo metin2_map_c3;;
    61) echo map_n_snowm_01;;
    63) echo metin2_map_n_desert_01;; 64) echo map_n_threeway;;
    25) echo metin2_map_monkey_dungeon_12;;
    104) echo metin2_map_spiderdungeon;;
    108) echo metin2_map_monkey_dungeon2;; 109) echo metin2_map_monkey_dungeon3;;
    *) return 1;;
  esac
}
map_file() {
  local id="${1#stone_}" kind=regen dir
  [[ "$1" == stone_* ]] && kind=stone
  dir=$(map_dir "$id") || return 1
  printf '%s/locale/english/map/%s/%s.txt' "$SHARE_DIR" "$dir" "$kind"
}
valid_seconds() { [[ "$1" =~ ^[0-9]{1,4}$ ]] && ((10#$1 >= 1 && 10#$1 <= 3600)); }
keys() { local id; for id in "${MAP_IDS[@]}"; do echo "$id"; done; for id in "${MAP_STONE_IDS[@]}"; do echo "stone_$id"; done; }
publish() {
  local tmp="$SPOOL/map-regens.status.new.$$" key value
  {
    printf 'state=%s\ntime=%s\nmessage=%s\n' "$1" "$(date +%s)" "$2"
    while read -r key; do
      value=$(kv "$WANTED" "$key")
      [[ -z "$value" ]] || printf 'map_%s=%s\n' "$key" "$value"
    done < <(keys)
    true
  } >"$tmp" || return 1
  chmod 0660 "$tmp" && mv "$tmp" "$SPOOL/map-regens.status"
}
prepare() {
  local key file index dir
  mkdir -p "$STATE_DIR" || return 1
  chown metin2:metin2 "$STATE_DIR" || return 1
  while read -r key; do
    file=$(map_file "$key") || return 1
    index=${key#stone_}; dir=$(map_dir "$index")
    # Check against the image's authoritative map/index, including Orc Valley.
    awk -v id="$index" -v dir="$dir" '$1==id && $2==dir {found=1} END {exit !found}' \
      "$SHARE_DIR/locale/english/map/index" || return 1
    [[ -f "$file" ]] || continue
    [[ -f "$file.m2orig" ]] || cp -p "$file" "$file.m2orig" || return 1
    chown metin2:metin2 "$(dirname "$file")" "$file" "$file.m2orig" || return 1
  done < <(keys)
}
poll() {
  local id key value legacy_map action tmp="$WANTED.new.$$" changed=0
  [[ -f "$REQUEST" ]] || return 0
  id=$(kv "$REQUEST" id)
  [[ "$id" =~ ^[a-zA-Z0-9_-]{1,100}$ ]] || return 0
  [[ "$id" != "$(cat "$STATE_DIR/last-id" 2>/dev/null)" ]] || return 0
  mkdir -p "$STATE_DIR" || return 1
  # Invalid requests are not retried every five seconds.
  printf '%s\n' "$id" >"$STATE_DIR/last-id" || return 1
  legacy_map=$(kv "$REQUEST" map); action=$(kv "$REQUEST" action)
  : >"$tmp" || return 1
  while read -r key; do
    value=$(kv "$REQUEST" "map_$key")
    if [[ -n "$legacy_map" && "$legacy_map" == "$key" ]]; then
      if [[ "$action" == reset ]]; then value=reset; else value=$(kv "$REQUEST" seconds); fi
    fi
    if [[ -n "$value" ]]; then
      changed=1
      if [[ "$value" == reset ]]; then continue; fi
      if ! valid_seconds "$value"; then
        rm -f "$tmp"; publish failed "Nieprawidlowy czas respawnu"; return 1
      fi
      value=$((10#$value))
      [[ -f "$(map_file "$key")" ]] || { rm -f "$tmp"; publish failed "Brak pliku respawnu"; return 1; }
    else
      value=$(kv "$WANTED" "$key")
    fi
    [[ -z "$value" ]] || printf '%s=%s\n' "$key" "$value" >>"$tmp"
  done < <(keys)
  [[ "$changed" == 1 ]] || { rm -f "$tmp"; return 1; }
  mv "$tmp" "$WANTED" || return 1
  publish running "Przygotowano ustawienia mobow i Metinow"
  return 10
}
apply() {
  local key file value staged="$STATE_DIR/staging.$$" n=0 i
  local -a targets=()
  mkdir -p "$staged" || return 1
  while read -r key; do
    file=$(map_file "$key"); value=$(kv "$WANTED" "$key")
    awk -v id="${key#stone_}" -v dir="$(map_dir "${key#stone_}")" '$1==id && $2==dir {found=1} END {exit !found}' \
      "$SHARE_DIR/locale/english/map/index" || { rm -rf "$staged"; return 1; }
    [[ -f "$file" ]] || { [[ -z "$value" ]] && continue; rm -rf "$staged"; return 1; }
    [[ -f "$file.m2orig" ]] || cp -p "$file" "$file.m2orig" || { rm -rf "$staged"; return 1; }
    if [[ -n "$value" ]]; then
      valid_seconds "$value" || { rm -rf "$staged"; return 1; }
      # Only the delay token changes; NPC/boss files are outside this tool.
      awk -v seconds="$value" '
        $1 ~ /^(m|g|ga|r|s)$/ && NF>=11 {
          prefix=$0; for(i=1;i<=7;i++) sub(/^[ \t]*[^ \t]+[ \t]*/, "", prefix)
          offset=length($0)-length(prefix); token=prefix; sub(/[ \t].*$/, "", token)
          print substr($0,1,offset) seconds "s" substr(prefix,length(token)+1); next
        } {print}
      ' "$file.m2orig" >"$staged/$n.new" || { rm -rf "$staged"; return 1; }
    else cp "$file.m2orig" "$staged/$n.new" || { rm -rf "$staged"; return 1; }; fi
    cp -p "$file" "$staged/$n.old" || { rm -rf "$staged"; return 1; }
    targets+=("$file"); n=$((n+1))
  done < <(keys)
  for ((i=0;i<n;i++)); do
    file=${targets[$i]}
    if ! cp "$staged/$i.new" "$file.new" || ! mv "$file.new" "$file"; then
      for ((i=0;i<n;i++)); do cp -p "$staged/$i.old" "${targets[$i]}"; done
      rm -rf "$staged"; publish failed "Nie udalo sie zapisac plikow map"; return 1
    fi
  done
  rm -rf "$staged"
}
case "${1:-}" in
  prepare) prepare;; poll) poll;; apply) apply;;
  publish) publish "${2:-ok}" "${3:-Ustawienia respawnu aktywne}";;
  *) exit 1;;
esac
