#!/usr/bin/env bash

set -euo pipefail
IFS=$'\n\t'

if ! command -v jq >/dev/null 2>&1; then
  echo "Error: 'jq' is required to merge VS Code settings." >&2
  exit 1
fi

case "$(uname -s)" in
  Linux)
    SETTINGS_DIR="${VSCODE_SETTINGS_DIR:-$HOME/.config/Code/User}"
    ;;
  Darwin)
    SETTINGS_DIR="${VSCODE_SETTINGS_DIR:-$HOME/Library/Application Support/Code/User}"
    ;;
  *)
    echo "Error: unsupported OS '$(uname -s)'. Use settings.ps1 on Windows." >&2
    exit 1
    ;;
esac

mkdir -p "$SETTINGS_DIR"
SETTINGS_FILE="$SETTINGS_DIR/settings.json"

# Update the target of a dotfiles-managed symlink instead of replacing the link.
while [[ -L "$SETTINGS_FILE" ]]; do
  LINK_TARGET="$(readlink "$SETTINGS_FILE")"
  if [[ "$LINK_TARGET" == /* ]]; then
    SETTINGS_FILE="$LINK_TARGET"
  else
    SETTINGS_FILE="$(dirname "$SETTINGS_FILE")/$LINK_TARGET"
  fi
done
SETTINGS_DIR="$(dirname "$SETTINGS_FILE")"
mkdir -p "$SETTINGS_DIR"

if [[ -f "$SETTINGS_FILE" ]]; then
  BACKUP_FILE="$SETTINGS_FILE.backup.$(date +%Y%m%d%H%M%S).$$"
  cp "$SETTINGS_FILE" "$BACKUP_FILE"
  echo "Backup created: $BACKUP_FILE"
else
  BACKUP_FILE=""
fi

INPUT_FILE="$SETTINGS_FILE"
if [[ ! -f "$INPUT_FILE" ]]; then
  INPUT_FILE="/dev/null"
fi

TEMP_FILE="$(mktemp "$SETTINGS_DIR/settings.json.tmp.XXXXXX")"
trap 'rm -f "$TEMP_FILE"' EXIT

if ! jq -Rs '
  def strip_comments:
    reduce (explode[]) as $c (
      {out: [], state: "normal", escaped: false};
      if .state == "normal" then
        if $c == 34 then .out += [$c] | .state = "string"
        elif $c == 47 then .state = "slash"
        else .out += [$c]
        end
      elif .state == "slash" then
        if $c == 47 then .state = "line_comment"
        elif $c == 42 then .state = "block_comment"
        else
          .out += [47, $c]
          | .state = (if $c == 34 then "string" else "normal" end)
        end
      elif .state == "line_comment" then
        if $c == 10 or $c == 13 then .out += [$c] | .state = "normal" else . end
      elif .state == "block_comment" then
        if $c == 42 then .state = "block_star" else . end
      elif .state == "block_star" then
        if $c == 47 then .state = "normal"
        elif $c != 42 then .state = "block_comment"
        else .
        end
      else
        .out += [$c]
        | if .escaped then .escaped = false
          elif $c == 92 then .escaped = true
          elif $c == 34 then .state = "normal"
          else .
          end
      end
    )
    | if .state == "block_comment" or .state == "block_star" then
        error("unterminated block comment")
      else
        (if .state == "slash" then .out + [47] else .out end)
      end
    | implode;

  def strip_trailing_commas:
    reduce (explode[]) as $c (
      {out: [], state: "normal", escaped: false, comma: false, ws: []};
      if .state == "string" then
        .out += [$c]
        | if .escaped then .escaped = false
          elif $c == 92 then .escaped = true
          elif $c == 34 then .state = "normal"
          else .
          end
      elif .comma and ($c == 9 or $c == 10 or $c == 13 or $c == 32) then
        .ws += [$c]
      elif .comma and ($c == 93 or $c == 125) then
        .out += [$c] | .comma = false | .ws = []
      else
        (if .comma then .out += [44] + .ws | .comma = false | .ws = [] else . end)
        | if $c == 44 then .comma = true
          else
            .out += [$c]
            | if $c == 34 then .state = "string" else . end
          end
      end
    )
    | (if .comma then .out + [44] + .ws else .out end)
    | implode;

  (if test("^[[:space:]]*$") then "{}" else . end)
  | strip_comments
  | strip_trailing_commas
  | fromjson
  | if type != "object" then error("settings.json must contain a JSON object") else . end
  | . + {
      "editor.fontFamily": "\u0027Ubuntu Mono\u0027, \u0027IBM Plex Mono\u0027, \u0027JetBrains Mono\u0027, Hack, MonoLisa, monospace",
      "editor.formatOnSave": true,
      "editor.lineHeight": 24,
      "editor.renderWhitespace": "all",
      "editor.rulers": [80, 100, 120],
      "editor.wordWrap": "wordWrapColumn",
      "editor.wordWrapColumn": 80,
      "workbench.colorTheme": "GitHub Dark Default",
      "workbench.iconTheme": "material-icon-theme"
    }
' "$INPUT_FILE" >"$TEMP_FILE"; then
  echo "Error: could not parse and merge $SETTINGS_FILE." >&2
  [[ -n "$BACKUP_FILE" ]] && echo "Original preserved at: $BACKUP_FILE" >&2
  exit 1
fi

mv "$TEMP_FILE" "$SETTINGS_FILE"
trap - EXIT
echo "VS Code settings updated: $SETTINGS_FILE"
