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

script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
src_dir="$script_dir/config"
dest_dir="${HOME}/.config"


dry_run=false
target_path=""

usage() {
  cat <<'EOF'
Usage: sync-config.sh [--dry-run] [path]

path can be:
  config             (full config)
  config/tmux        (specific folder)
  config/tmux/config (specific file)
  .config            (full config)
  .config/tmux       (specific folder)
  .config/tmux/config (specific file)
  tmux               (folder under .config)
  tmux/config        (file under .config)
EOF
}

for arg in "$@"; do
  case "$arg" in
    --dry-run)
      dry_run=true
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      if [[ -z "$target_path" ]]; then
        target_path="$arg"
      else
        echo "Error: too many arguments." >&2
        usage >&2
        exit 1
      fi
      ;;
  esac
done

hash_cmd="sha256sum"
if ! command -v "$hash_cmd" >/dev/null 2>&1; then
  echo "Error: sha256sum not found. Install coreutils or adjust hash_cmd." >&2
  exit 1
fi

file_hash() {
  "$hash_cmd" "$1" | awk '{print $1}'
}

copy_file() {
  local src="$1"
  local dst="$2"
  if $dry_run; then
    echo "copy: $src -> $dst"
  else
    mkdir -p "$(dirname "$dst")"
    cp -a "$src" "$dst"
    echo "copied: $dst"
  fi
}

delete_file() {
  local dst="$1"
  if $dry_run; then
    echo "delete: $dst"
  else
    rm -f "$dst"
    echo "deleted: $dst"
  fi
}

delete_dir() {
  local dst="$1"
  if $dry_run; then
    echo "delete dir: $dst"
  else
    rm -rf "$dst"
    echo "deleted dir: $dst"
  fi
}

if [[ ! -d "$src_dir" ]]; then
  echo "Error: source config directory not found: $src_dir" >&2
  exit 1
fi

mkdir -p "$dest_dir"

target_rel="$target_path"
if [[ -n "$target_rel" ]]; then
  if [[ "$target_rel" == "config" || "$target_rel" == ".config" ]]; then
    target_rel=""
  elif [[ "$target_rel" == config/* ]]; then
    target_rel=${target_rel#config/}
  elif [[ "$target_rel" == .config/* ]]; then
    target_rel=${target_rel#.config/}
  fi
  target_rel=${target_rel#./}
  if [[ "$target_rel" == "." ]]; then
    target_rel=""
  fi
  if [[ "$target_rel" == /* ]]; then
    echo "Error: absolute paths are not supported: $target_path" >&2
    exit 1
  fi
fi

base_path="$src_dir"
if [[ -n "$target_rel" ]]; then
  base_path="$src_dir/$target_rel"
fi

if [[ ! -e "$base_path" ]]; then
  echo "Error: path not found in source: $base_path" >&2
  exit 1
fi

check_delete_marker() {
  local rel_path="$1"

  # Check centralized .delete folder: config/.delete/path/to/dir_or_file
  if [[ -f "$src_dir/.delete/$rel_path" ]] || [[ -d "$src_dir/.delete/$rel_path" ]]; then
    return 0
  fi

  # Check inline .delete marker: config/path/to/.delete/dir_or_file
  local parent_path="$src_dir/$rel_path"
  local parent_dir=$(dirname "$parent_path")
  local base_name=$(basename "$parent_path")
  if [[ -f "$parent_dir/.delete/$base_name" ]] || [[ -d "$parent_dir/.delete/$base_name" ]]; then
    return 0
  fi

  return 1
}

# Check if a path is a .delete directory or inside one (should be skipped during traversal)
is_in_delete_dir() {
  local rel_path="$1"
  # Check if the path itself is a .delete directory or any parent is .delete
  local current="$rel_path"
  while [[ "$current" != "." && "$current" != "/" && -n "$current" ]]; do
    local base
    base=$(basename "$current")
    if [[ "$base" == ".delete" ]]; then
      return 0
    fi
    current=$(dirname "$current")
  done
  return 1
}

sync_file() {
  local src_path="$1"
  local rel_path=${src_path#"$src_dir"/}
  local dst_path="$dest_dir/$rel_path"

  # Check if there's a corresponding .delete marker for this file
  if check_delete_marker "$rel_path"; then
    if [[ -f "$dst_path" ]]; then
      delete_file "$dst_path"
      deleted_files+=("$dst_path")
    fi
    return
  fi

  if [[ ! -f "$dst_path" ]]; then
    copy_file "$src_path" "$dst_path"
    return
  fi

  local src_hash
  local dst_hash
  src_hash=$(file_hash "$src_path")
  dst_hash=$(file_hash "$dst_path")

  if [[ "$src_hash" != "$dst_hash" ]]; then
    copy_file "$src_path" "$dst_path"
  fi
}

shopt -s globstar nullglob dotglob
if [[ -f "$base_path" ]]; then
  sync_file "$base_path"
elif [[ -d "$base_path" ]]; then
  for src_path in "$base_path"/**; do
    rel_path=${src_path#"$src_dir"/}

    if [[ "$rel_path" == ".git" || "$rel_path" == .git/* ]]; then
      continue
    fi

    # Check if this is a .delete directory - process its contents to delete in destination
    if [[ -d "$src_path" && "$(basename "$rel_path")" == ".delete" ]]; then
      # Get the parent directory of the .delete folder
      parent_rel_path=$(dirname "$rel_path")
      # Process all files in this .delete directory to delete corresponding files in destination
      for delete_marker in "$src_path"/**; do
        if [[ -e "$delete_marker" ]]; then
          # Get the basename of the marker (file/dir to delete)
          marker_basename=$(basename "$delete_marker")
          # Construct the target path in the parent directory
          target_rel_path="$parent_rel_path/$marker_basename"
          target_dst_path="$dest_dir/$target_rel_path"

          if [[ -d "$delete_marker" ]]; then
            if [[ -d "$target_dst_path" ]]; then
              delete_dir "$target_dst_path"
            fi
          elif [[ -f "$delete_marker" ]]; then
            if [[ -f "$target_dst_path" ]]; then
              delete_file "$target_dst_path"
            fi
          fi
        fi
      done
      # Delete the .delete folder in destination if it exists
      delete_dst_path="$dest_dir/$rel_path"
      if [[ -d "$delete_dst_path" ]]; then
        delete_dir "$delete_dst_path"
      fi
      continue
    fi

    # Skip anything inside a .delete directory - don't process or copy
    if is_in_delete_dir "$rel_path"; then
      continue
    fi

    dst_path="$dest_dir/$rel_path"

    if [[ -d "$src_path" ]]; then
      # Check if there's a .delete marker for this directory
      if check_delete_marker "$rel_path"; then
        if [[ -d "$dst_path" ]]; then
          delete_dir "$dst_path"
          deleted_dirs+=("$dst_path")
        fi
        continue
      fi

      if ! $dry_run; then
        mkdir -p "$dst_path"
      else
        echo "mkdir: $dst_path"
      fi
      continue
    fi

    if [[ ! -f "$src_path" ]]; then
      continue
    fi

    sync_file "$src_path"
  done
fi

if $dry_run; then
  echo "Dry run complete. Re-run without --dry-run to apply changes."
else
  echo "Sync complete. Updated files where content differed."
fi
