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

# ── 경로 설정 ────────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
AGENT_OPS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
PROJECT_ROOT="$(cd "$AGENT_OPS_DIR/.." && pwd)"

# ── 색상 ─────────────────────────────────────────────────────────────────────
RED='\033[0;31m'; YELLOW='\033[1;33m'; GREEN='\033[0;32m'; RESET='\033[0m'

# ── 진입 파일 공통 관리 ──────────────────────────────────────────────────────
source "$SCRIPT_DIR/entry-files.sh"
source "$SCRIPT_DIR/ai-ignore.sh"

# ── 대상 경로 해석 (폴더명 / 상대경로 / 절대경로) ────────────────────────────
resolve_target() {
  local input="$1"
  if [[ "$input" == /* ]]; then
    [[ -d "$input" ]] && echo "$input" || echo ""
    return
  fi
  if [[ "$input" == */* ]]; then
    local resolved
    resolved="$(cd "$input" 2>/dev/null && pwd)" || { echo ""; return; }
    echo "$resolved"
    return
  fi
  local sibling="$(dirname "$PROJECT_ROOT")/$input"
  [[ -d "$sibling" ]] && echo "$sibling" || echo ""
}

# ── 버전 비교: v1 > v2 이면 0 반환 ───────────────────────────────────────────
version_gt() {
  local v1="$1" v2="$2"
  [[ "$v1" == "$v2" ]] && return 1
  [[ "$(printf '%s\n%s' "$v1" "$v2" | sort -V | head -1)" == "$v2" ]]
}

# ── 버전 +1 ──────────────────────────────────────────────────────────────────
bump_version() {
  bash "$SCRIPT_DIR/bump-version.sh" "$1"
}

remove_generated_caches() {
  local root="$1"
  [[ -d "$root" ]] || return 0
  find "$root" -type f \( -name '*.pyc' -o -name '*.pyo' \) -delete
  find "$root" -depth -type d \( \
    -name '__pycache__' -o \
    -name '.pytest_cache' -o \
    -name '.mypy_cache' -o \
    -name '.ruff_cache' \
  \) -exec rm -rf -- {} +
}

copy_tree_without_caches() {
  local src="$1" dst="$2"
  mkdir -p "$dst"
  (
    cd "$src"
    tar \
      --exclude='__pycache__' \
      --exclude='*/__pycache__' \
      --exclude='.pytest_cache' \
      --exclude='*/.pytest_cache' \
      --exclude='.mypy_cache' \
      --exclude='*/.mypy_cache' \
      --exclude='.ruff_cache' \
      --exclude='*/.ruff_cache' \
      --exclude='*.pyc' \
      --exclude='*.pyo' \
      -cf - .
  ) | tar -C "$dst" -xf -
}

# ── 폴더 동기화 (삭제된 파일도 반영) ────────────────────────────────────────
sync_folder() {
  local src="$1" dst="$2" exclude="${3:-}"
  mkdir -p "$dst"
  # dst에서 src에 없는 항목 제거 (exclude 파일 보존)
  find "$dst" -mindepth 1 -maxdepth 1 | while IFS= read -r item; do
    local name
    name="$(basename "$item")"
    [[ -n "$exclude" && "$name" == "$exclude" ]] && continue
    if [[ ! -e "$src/$name" ]]; then
      rm -rf "$item"
    fi
  done
  # src에서 dst로 복사 (exclude 파일 제외)
  find "$src" -mindepth 1 -maxdepth 1 | while IFS= read -r item; do
    local name
    name="$(basename "$item")"
    [[ -n "$exclude" && "$name" == "$exclude" ]] && continue
    rm -rf "$dst/$name"
    if [[ -d "$item" ]]; then
      mkdir -p "$dst/$name"
      copy_tree_without_caches "$item" "$dst/$name"
    else
      cp "$item" "$dst/"
    fi
    # 검증 중 생긴 cache는 공통 산출물이 아니므로 전파하지 않는다.
    remove_generated_caches "$dst/$name"
  done
}

sync_common() {
  local src="$1" dst="$2"
  sync_folder "$src/rules/common" "$dst/rules/common"
  sync_folder "$src/skills/common" "$dst/skills/common"
  sync_folder "$src/bin" "$dst/bin"
  create_project_agent_ops_dirs "$dst"
  agent_ops_ensure_private_overrides_gitignore "$(dirname "$dst")/.gitignore"
}

common_differs() {
  local src="$1" dst="$2"
  local path

  for path in "rules/common" "skills/common" "bin"; do
    if [[ ! -d "$src/$path" || ! -d "$dst/$path" ]]; then
      return 0
    fi
    if ! diff -qr \
      --exclude='__pycache__' \
      --exclude='.pytest_cache' \
      --exclude='.mypy_cache' \
      --exclude='.ruff_cache' \
      --exclude='*.pyc' \
      --exclude='*.pyo' \
      "$src/$path" "$dst/$path" >/dev/null; then
      return 0
    fi
  done

  return 1
}

entry_files_differ() {
  local rules_md="$1" target_root="$2"
  local f

  [[ -f "$rules_md" ]] || return 0
  for f in "${AGENT_OPS_ENTRY_FILES[@]}"; do
    if [[ ! -f "$target_root/$f" ]] || ! cmp -s "$rules_md" "$target_root/$f"; then
      return 0
    fi
  done

  return 1
}

create_project_agent_ops_dirs() {
  local agent_ops_dir="$1"
  mkdir -p "$agent_ops_dir/rules/project/domain"
  mkdir -p "$agent_ops_dir/rules/private"
  mkdir -p "$agent_ops_dir/skills/project"
  mkdir -p "$agent_ops_dir/skills/private"
}

copy_common_scaffold() {
  local src="$1" dst="$2"
  mkdir -p "$dst/rules" "$dst/skills"
  cp "$src/.version" "$dst/"
  rm -rf "$dst/bin" "$dst/rules/common" "$dst/skills/common"
  cp -r "$src/bin" "$dst/"
  copy_tree_without_caches "$src/rules/common" "$dst/rules/common"
  copy_tree_without_caches "$src/skills/common" "$dst/skills/common"
  remove_generated_caches "$dst/rules/common"
  remove_generated_caches "$dst/skills/common"
  create_project_agent_ops_dirs "$dst"
}

discover_agent_ops_targets() {
  local parent
  parent="$(dirname "$PROJECT_ROOT")"

  find "$parent" -mindepth 1 -maxdepth 1 -type d | sort | while IFS= read -r candidate; do
    local resolved
    resolved="$(cd "$candidate" && pwd)"
    [[ "$resolved" == "$PROJECT_ROOT" ]] && continue
    [[ -d "$resolved/agent-ops" ]] || continue
    echo "$resolved"
  done
}

agent_ops_git_paths() {
  local include_ai_config="${1:-1}"

  printf '%s\n' "agent-ops/.version"
  printf '%s\n' "agent-ops/bin"
  printf '%s\n' "agent-ops/rules/common"
  printf '%s\n' "agent-ops/skills/common"

  local f
  for f in "${AGENT_OPS_ENTRY_FILES[@]}"; do
    if [[ -e "$f" ]] || git ls-files --error-unmatch "$f" >/dev/null 2>&1; then
      printf '%s\n' "$f"
    fi
  done
  if [[ "$include_ai_config" != "1" || -f ".agent-ops-source" ]]; then
    return
  fi
  for f in "${AGENT_OPS_AI_SYNC_FILES[@]}"; do
    if [[ -e "$f" ]] || git ls-files --error-unmatch "$f" >/dev/null 2>&1; then
      printf '%s\n' "$f"
    fi
  done
}

stage_private_gitignore_only() {
  local file=".gitignore"
  local index_file private_file patch_file

  if ! git ls-files --error-unmatch "$file" >/dev/null 2>&1; then
    echo -e "${RED}Error: $file 이 추적 중이 아니어서 private ignore만 분리 stage할 수 없습니다.${RESET}"
    return 1
  fi
  if ! git diff --cached --quiet -- "$file"; then
    echo -e "${RED}Error: $file 에 기존 staged 변경이 있어 private ignore만 분리 stage할 수 없습니다.${RESET}"
    return 1
  fi

  index_file="$(mktemp)"
  private_file="$(mktemp)"
  patch_file="$(mktemp)"
  git show ":$file" > "$index_file"
  cp "$index_file" "$private_file"
  agent_ops_ensure_private_overrides_gitignore "$private_file"

  if ! cmp -s "$index_file" "$private_file"; then
    diff -u --label "a/$file" --label "b/$file" "$index_file" "$private_file" > "$patch_file" || true
    git apply --cached "$patch_file"
  fi
  rm -f "$index_file" "$private_file" "$patch_file"
}

commit_and_push_agent_ops_scope() {
  local repo="$1" message="$2"
  local include_ai_config="${3:-1}"

  if ! git -C "$repo" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
    echo -e "${RED}Error: git 저장소가 아닙니다: $repo${RESET}"
    return 1
  fi

  (
    cd "$repo"
    mapfile -t paths < <(agent_ops_git_paths "$include_ai_config")
    if [[ "$include_ai_config" == "private-gitignore" ]]; then
      stage_private_gitignore_only
      paths+=(".gitignore")
    fi
    git add -- "${paths[@]}"
    if git ls-files --error-unmatch "$AGENT_ROADMAP_CURRENT_LOCAL_PATTERN" >/dev/null 2>&1 \
      && grep -qxF "$AGENT_ROADMAP_CURRENT_LOCAL_PATTERN" ".gitignore" 2>/dev/null; then
      git rm --cached --quiet --ignore-unmatch "$AGENT_ROADMAP_CURRENT_LOCAL_PATTERN"
      paths+=("$AGENT_ROADMAP_CURRENT_LOCAL_PATTERN")
    fi

    if git diff --cached --quiet -- "${paths[@]}"; then
      echo "  agent-ops 변경 없음: commit 건너뜀"
    else
      git commit -m "$message" -- "${paths[@]}"
    fi

    git push
  )
}

sync_framework_to_target() {
  local target="$1"
  local dst_ao="$target/agent-ops"
  local src_ver
  src_ver="$(cat "$SRC_AO/.version" 2>/dev/null || echo "0.0.0")"

  echo "▶ $(basename "$PROJECT_ROOT") → $(basename "$target")"

  if ! git -C "$target" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
    echo -e "${RED}Error: git 저장소가 아닙니다: $target${RESET}"
    return 1
  fi

  if [[ ! -d "$dst_ao" ]]; then
    echo "  최초 진행: agent-ops 공통 scaffold 복사"
    copy_common_scaffold "$SRC_AO" "$dst_ao"
    apply_agent_ops_entry_files "$SRC_AO/rules/common/rules.md" "$target"
    echo -e "${YELLOW}  init-agent-ops 스킬로 초기화를 진행하세요.${RESET}"
  else
    echo "  이후 진행: common/ 동기화"
    sync_common "$SRC_AO" "$dst_ao"
    cp "$SRC_AO/.version" "$dst_ao/.version"
    apply_agent_ops_entry_files "$SRC_AO/rules/common/rules.md" "$target"
  fi
  ensure_agent_ops_ai_ignore_config "$target"

  commit_and_push_agent_ops_scope "$target" "sync: agent-ops from $(basename "$PROJECT_ROOT") v$src_ver"
  echo -e "${GREEN}✓ 완료 → $(basename "$target")${RESET}"
}

# ── 사용법 ────────────────────────────────────────────────────────────────────
usage() {
  echo "사용법: $0 [--pull] [target]"
  echo ""
  echo "  (기본)   push 동기화"
  echo "  --pull   target(공통 원본 repo, 기본 agentic-framework) → 현재 프로젝트 로 pull"
  echo ""
  echo "  target: 폴더명 (동일 레벨 탐색) | 상대경로 | 절대경로"
  echo "  .agent-ops-source 원본 repo에서 target 생략 시 상위 폴더의 agent-ops 적용 프로젝트 전체 동기화"
}

# ─────────────────────────────────────────────────────────────────────────────
PULL_MODE="0"
if [[ "${1:-}" == "--pull" ]]; then
  PULL_MODE="1"
  shift
fi

TARGET_INPUT="${1:-}"

SRC_AO="$AGENT_OPS_DIR"
IS_FRAMEWORK="$([[ -f "$PROJECT_ROOT/.agent-ops-source" ]] && echo "1" || echo "0")"

# ── pull 모드: .agent-ops-source 원본 repo → 현재 프로젝트 ───────────────────
if [[ "$PULL_MODE" == "1" ]]; then
  if [[ -z "$TARGET_INPUT" ]]; then
    usage; exit 1
  fi
  TARGET="$(resolve_target "$TARGET_INPUT")"
  if [[ -z "$TARGET" ]]; then
    echo -e "${RED}Error: 대상을 찾을 수 없습니다: $TARGET_INPUT${RESET}"
    exit 1
  fi
  DST_AO="$TARGET/agent-ops"

  if [[ "$IS_FRAMEWORK" == "1" ]]; then
    echo -e "${RED}Error: .agent-ops-source가 있는 공통 원본 repo에서는 --pull을 사용할 수 없습니다.${RESET}"
    exit 1
  fi
  if [[ ! -f "$TARGET/.agent-ops-source" ]]; then
    echo -e "${RED}Error: target이 공통 원본 repo가 아닙니다 (.agent-ops-source 없음)${RESET}"
    exit 1
  fi
  SRC_VER="$(cat "$SRC_AO/.version" 2>/dev/null || echo "0.0.0")"
  DST_VER="$(cat "$DST_AO/.version" 2>/dev/null || echo "0.0.0")"
  echo "▶ $(basename "$TARGET") → $(basename "$PROJECT_ROOT") (pull)"
  echo "  현재 버전: $SRC_VER | 원본 버전: $DST_VER"
  sync_common "$DST_AO" "$SRC_AO"
  cp "$DST_AO/.version" "$SRC_AO/.version"
  apply_agent_ops_entry_files "$DST_AO/rules/common/rules.md" "$PROJECT_ROOT"
  ensure_agent_ops_ai_ignore_config "$PROJECT_ROOT"
  commit_and_push_agent_ops_scope "$PROJECT_ROOT" "sync: pull from agentic-framework v$DST_VER"
  echo -e "${GREEN}✓ 완료 (pull v$DST_VER)${RESET}"
  exit 0
fi

# ── .agent-ops-source 원본 repo에서 다른 프로젝트로 ──────────────────────────
if [[ "$IS_FRAMEWORK" == "1" ]]; then
  SRC_VER="$(cat "$SRC_AO/.version" 2>/dev/null || echo "0.0.0")"
  echo "  .agent-ops-source 감지: push 동기화에서는 버전 bump 없이 v$SRC_VER 그대로 전파"
  if entry_files_differ "$SRC_AO/rules/common/rules.md" "$PROJECT_ROOT"; then
    echo "  공통 원본 repo 진입 파일 재적용"
    apply_agent_ops_entry_files "$SRC_AO/rules/common/rules.md" "$PROJECT_ROOT"
    commit_and_push_agent_ops_scope "$PROJECT_ROOT" "sync: reapply agent-ops entry files" "0"
  fi
  if [[ -n "$TARGET_INPUT" ]]; then
    TARGET="$(resolve_target "$TARGET_INPUT")"
    if [[ -z "$TARGET" ]]; then
      echo -e "${RED}Error: 대상을 찾을 수 없습니다: $TARGET_INPUT${RESET}"
      exit 1
    fi
    sync_framework_to_target "$TARGET"
  else
    mapfile -t TARGETS < <(discover_agent_ops_targets)
    if [[ "${#TARGETS[@]}" -eq 0 ]]; then
      echo -e "${YELLOW}agent-ops가 적용된 sibling 프로젝트가 없습니다.${RESET}"
      exit 0
    fi

    STATUS=0
    for TARGET in "${TARGETS[@]}"; do
      if ! sync_framework_to_target "$TARGET"; then
        STATUS=1
      fi
    done
    exit "$STATUS"
  fi
  exit 0
fi

# ── 일반 프로젝트에서 .agent-ops-source 원본 repo로 (push) ───────────────────
if [[ -z "$TARGET_INPUT" ]]; then
  TARGET_INPUT="agentic-framework"
fi

TARGET="$(resolve_target "$TARGET_INPUT")"
if [[ -z "$TARGET" ]]; then
  echo -e "${RED}Error: 대상을 찾을 수 없습니다: $TARGET_INPUT${RESET}"
  exit 1
fi
DST_AO="$TARGET/agent-ops"

echo "▶ $(basename "$PROJECT_ROOT") → $(basename "$TARGET")"

if [[ ! -f "$TARGET/.agent-ops-source" ]]; then
  echo -e "${RED}Error: target이 공통 원본 repo가 아닙니다 (.agent-ops-source 없음)${RESET}"
  exit 1
fi

SRC_VER="$(cat "$SRC_AO/.version" 2>/dev/null || echo "0.0.0")"
DST_VER="$(cat "$DST_AO/.version" 2>/dev/null || echo "0.0.0")"
echo "  현재 버전: $SRC_VER | 원본 버전: $DST_VER"

COMMON_CHANGED="1"
if ! common_differs "$SRC_AO" "$DST_AO"; then
  COMMON_CHANGED="0"
fi

ENTRY_FILES_CHANGED="0"
if entry_files_differ "$SRC_AO/rules/common/rules.md" "$PROJECT_ROOT" \
  || entry_files_differ "$DST_AO/rules/common/rules.md" "$TARGET"; then
  ENTRY_FILES_CHANGED="1"
fi

if [[ "$COMMON_CHANGED" == "1" ]] && version_gt "$DST_VER" "$SRC_VER"; then
  echo -e "${RED}⚠  버전 충돌: 원본 repo($DST_VER) > current($SRC_VER)${RESET}"
  echo -e "${YELLOW}   먼저 sync-pull로 내려받은 뒤 재시도하세요.${RESET}"
  exit 1
fi

if [[ "$COMMON_CHANGED" == "0" && "$ENTRY_FILES_CHANGED" == "0" ]]; then
  echo "  공통 파일/진입 파일 변경 없음: .version 차이는 무시하고 버전 갱신/커밋을 건너뜀"
  echo -e "${GREEN}✓ 완료 (공통 변경 없음)${RESET}"
  exit 0
fi

if [[ "$COMMON_CHANGED" == "0" ]]; then
  echo "  공통 파일 변경 없음: 진입 파일만 재적용"
  apply_agent_ops_entry_files "$SRC_AO/rules/common/rules.md" "$PROJECT_ROOT"
  apply_agent_ops_entry_files "$DST_AO/rules/common/rules.md" "$TARGET"
  commit_and_push_agent_ops_scope "$TARGET" "sync: reapply agent-ops entry files" "0"
  commit_and_push_agent_ops_scope "$PROJECT_ROOT" "sync: reapply agent-ops entry files" "0"
  echo -e "${GREEN}✓ 완료 (진입 파일 재적용)${RESET}"
  exit 0
fi

NEW_VER="$(bump_version "$SRC_VER")"
sync_common "$SRC_AO" "$DST_AO"
echo "$NEW_VER" > "$SRC_AO/.version"
echo "$NEW_VER" > "$DST_AO/.version"
apply_agent_ops_entry_files "$SRC_AO/rules/common/rules.md" "$PROJECT_ROOT"
apply_agent_ops_entry_files "$DST_AO/rules/common/rules.md" "$TARGET"

commit_and_push_agent_ops_scope "$TARGET" "sync: from $(basename "$PROJECT_ROOT") v$NEW_VER" "private-gitignore"
commit_and_push_agent_ops_scope "$PROJECT_ROOT" "sync: to agentic-framework v$NEW_VER" "private-gitignore"

echo -e "${GREEN}✓ 완료 (v$SRC_VER → v$NEW_VER)${RESET}"
