#!/usr/bin/env bash
# Sync canonical caller workflows from jr200-labs/github-action-templates
# into a consuming repo. Reads .github/.shared-config.yaml, resolves
# the declared groups to a flat set of workflow files, and writes
# them verbatim to .github/workflows/.
#
# Usage:
#   sync-shared              # write/update workflow files
#   sync-shared --check      # drift-check mode: diff against on-disk, exit non-zero on diff
#   sync-shared --bootstrap  # first-run init: also fetches this script and the drift-check
#
# `.github/.shared-config.yaml` may pin `ref: shared-vX.Y.Z`; SYNC_REF
# overrides that pin for diagnostics.
#
# Network failures: warn + exit 0 (don't block local commits).
# CI sets STRICT=1 to make warnings fatal.

set -uo pipefail

REPO="jr200-labs/github-action-templates"
DEFAULT_REF="master"
CONFIG=".github/.shared-config.yaml"
WORKFLOWS_DIR=".github/workflows"
STRICT="${STRICT:-0}"
COMMON_FILES=(
    ".githooks/commit-msg"
    ".githooks/lint-message-text.sh"
    "cog.toml"
)

MODE="write"
case "${1:-}" in
    --check)     MODE="check" ;;
    --bootstrap) MODE="bootstrap" ;;
    "")          ;;
    *)           echo "usage: $0 [--check|--bootstrap]" >&2; exit 2 ;;
esac

warn() { echo "sync-shared: $*" >&2; }
die()  { echo "sync-shared: $*" >&2; exit 1; }

for cmd in curl yq diff; do
    command -v "$cmd" >/dev/null 2>&1 || {
        warn "$cmd not found"
        [ "$STRICT" = 1 ] && die "$cmd required in strict mode"
        exit 0
    }
done

[ -f "$CONFIG" ] || die "$CONFIG not found — see github-action-templates/AGENTS.md"

CONFIG_REF=$(yq -r '.ref // ""' "$CONFIG")
if [ -n "${SYNC_REF:-}" ]; then
    REF="$SYNC_REF"
elif [ -n "$CONFIG_REF" ]; then
    REF="$CONFIG_REF"
else
    REF="$DEFAULT_REF"
    warn "$CONFIG has no ref; defaulting to ${DEFAULT_REF}"
fi
ROOT_URL="https://raw.githubusercontent.com/${REPO}/${REF}"
BASE_URL="${SYNC_BASE_URL:-${ROOT_URL}/consumers}"
if [ -n "${SYNC_SHARED_BASE_URL:-}" ]; then
    SHARED_BASE_URL="$SYNC_SHARED_BASE_URL"
elif [[ "${BASE_URL}" == */consumers ]]; then
    SHARED_BASE_URL="${BASE_URL%/consumers}/shared"
else
    SHARED_BASE_URL="${ROOT_URL}/shared"
fi

# Fetch a remote file; return non-zero on network failure.
fetch_from() {
    local base="$1" remote="$2" out="$3"
    mkdir -p "$(dirname "$out")"
    curl -sfL --max-time 10 "${base}/${remote}" -o "${out}.tmp" || return 1
    mv "${out}.tmp" "$out"
}

fetch() {
    local remote="$1" out="$2"
    fetch_from "$BASE_URL" "$remote" "$out"
}

fetch_shared() {
    local remote="$1" out="$2"
    fetch_from "$SHARED_BASE_URL" "$remote" "$out"
}

ensure_git_hooks_path() {
    command -v git >/dev/null 2>&1 || {
        warn "git not found; skipped core.hooksPath=.githooks"
        return 0
    }
    git rev-parse --is-inside-work-tree >/dev/null 2>&1 || {
        warn "not inside a git work tree; skipped core.hooksPath=.githooks"
        return 0
    }
    [ "$(git config --get core.hooksPath || true)" = ".githooks" ] && return 0

    git config core.hooksPath .githooks
    echo "updated: git core.hooksPath -> .githooks"
}

cleanup_legacy_commitlint() {
    local changed=0

    for f in commitlint.config.js commitlint.config.cjs commitlint.config.mjs .shared/commitlint.config.mjs; do
        if [ -f "$f" ]; then
            rm -f "$f"
            echo "removed: $f"
        fi
    done

    if [ -f ".husky/commit-msg" ] && grep -qi 'commitlint' ".husky/commit-msg" 2>/dev/null; then
        rm -f ".husky/commit-msg"
        echo "removed: .husky/commit-msg"
    fi

    [ -f "package.json" ] || return 0

    if ! command -v jq >/dev/null 2>&1; then
        warn "jq not found; skipped legacy commitlint package cleanup"
        return 0
    fi

    if jq '
        del(.scripts.prepare | select(. == "husky" or . == "husky install"))
        | del(.scripts | select(. == {}))
        | del(.dependencies.husky, .devDependencies.husky, .optionalDependencies.husky)
        | del(.dependencies["@commitlint/cli"], .devDependencies["@commitlint/cli"], .optionalDependencies["@commitlint/cli"])
        | del(.dependencies["@commitlint/config-conventional"], .devDependencies["@commitlint/config-conventional"], .optionalDependencies["@commitlint/config-conventional"])
        | del(.dependencies | select(. == {}))
        | del(.devDependencies | select(. == {}))
        | del(.optionalDependencies | select(. == {}))
    ' package.json > package.json.tmp; then
        if ! cmp -s package.json package.json.tmp; then
            mv package.json.tmp package.json
            changed=1
        else
            rm -f package.json.tmp
        fi
    else
        rm -f package.json.tmp
        warn "failed to clean legacy commitlint entries from package.json"
        return 0
    fi

    if [ "$changed" -eq 1 ]; then
        echo "updated: package.json legacy commitlint cleanup"
    fi
}

has_resolved_workflow() {
    local workflow="$1"
    echo "$WORKFLOWS" | grep -qx "$workflow"
}

is_gat_commitlint_job() {
    local workflow_file="$1"
    [ -f "$workflow_file" ] || return 1

    yq -e '.jobs.commitlint.uses == "jr200-labs/github-action-templates/.github/workflows/lint_commits.yaml@master"' "$workflow_file" >/dev/null 2>&1
}

is_gat_lint_pr_metadata_job() {
    local workflow_file="$1"
    [ -f "$workflow_file" ] || return 1

    yq -e '.jobs."lint-pr-metadata".uses == "jr200-labs/github-action-templates/.github/workflows/lint_pr_metadata.yaml@master"' "$workflow_file" >/dev/null 2>&1
}

cleanup_duplicate_workflow_hygiene_jobs() {
    local ci_file="${WORKFLOWS_DIR}/ci.yaml"

    if has_resolved_workflow commitlint && is_gat_commitlint_job "$ci_file"; then
        yq -i 'del(.jobs.commitlint)' "$ci_file"
        echo "updated: ${ci_file} removed duplicate commitlint job"
    fi

    if has_resolved_workflow lint-pr-metadata && is_gat_lint_pr_metadata_job "$ci_file"; then
        yq -i 'del(.jobs."lint-pr-metadata")' "$ci_file"
        echo "updated: ${ci_file} removed duplicate lint-pr-metadata job"
    fi
}

# Resolve groups to a deduped, sorted list of workflow names.
resolve_workflows() {
    local groups
    groups=$(yq -r '.workflows[]' "$CONFIG")
    [ -z "$groups" ] && die "$CONFIG declares no workflows: list"

    local resolved=""
    while IFS= read -r group; do
        [ -z "$group" ] && continue
        local group_file
        group_file=$(mktemp)
        if ! fetch "groups/${group}.yaml" "$group_file"; then
            warn "fetch failed: groups/${group}.yaml"
            rm -f "$group_file"
            [ "$STRICT" = 1 ] && exit 1
            continue
        fi
        local includes
        includes=$(yq -r '.includes[]' "$group_file")
        rm -f "$group_file"
        resolved="${resolved}${includes}"$'\n'
    done <<<"$groups"

    echo "$resolved" | sort -u | grep -v '^$'
}

WORKFLOWS=$(resolve_workflows)
[ -z "$WORKFLOWS" ] && die "no workflows resolved"

case "$MODE" in
    write|bootstrap)
        mkdir -p "$WORKFLOWS_DIR" scripts
        while IFS= read -r wf; do
            if fetch "workflows/${wf}.yaml" "${WORKFLOWS_DIR}/${wf}.yaml"; then
                echo "synced: ${wf}.yaml"
            else
                warn "fetch failed: workflows/${wf}.yaml"
                [ "$STRICT" = 1 ] && exit 1
            fi
        done <<<"$WORKFLOWS"
        for common_file in "${COMMON_FILES[@]}"; do
            if fetch_shared "$common_file" "$common_file"; then
                echo "synced: ${common_file}"
            else
                warn "fetch failed: shared/${common_file}"
                [ "$STRICT" = 1 ] && exit 1
            fi
        done
        [ -f ".githooks/commit-msg" ] && chmod +x ".githooks/commit-msg"
        [ -f ".githooks/lint-message-text.sh" ] && chmod +x ".githooks/lint-message-text.sh"
        cleanup_legacy_commitlint
        cleanup_duplicate_workflow_hygiene_jobs
        ensure_git_hooks_path
        if [ "$MODE" = "bootstrap" ]; then
            fetch "scripts/sync-shared" "scripts/sync-shared" || warn "fetch self failed"
            fetch "scripts/sync-shared-drift-check" "scripts/sync-shared-drift-check" || true
            chmod +x scripts/sync-shared scripts/sync-shared-drift-check 2>/dev/null || true
            echo "bootstrap complete. commit .github/.shared-config.yaml + .github/workflows/* + .githooks/commit-msg + cog.toml + scripts/sync-shared*"
        fi
        ;;
    check)
        rc=0
        tmpdir=$(mktemp -d)
        trap "rm -rf $tmpdir" EXIT
        while IFS= read -r wf; do
            local_file="${WORKFLOWS_DIR}/${wf}.yaml"
            remote_file="${tmpdir}/${wf}.yaml"
            if ! fetch "workflows/${wf}.yaml" "$remote_file"; then
                warn "fetch failed: workflows/${wf}.yaml"
                rc=1
                continue
            fi
            if [ ! -f "$local_file" ]; then
                echo "drift: ${local_file} missing (canonical exists)"
                rc=1
                continue
            fi
            if ! diff -u "$local_file" "$remote_file" >/dev/null; then
                echo "drift: ${local_file} differs from canonical"
                diff -u "$local_file" "$remote_file" | head -40
                rc=1
            fi
        done <<<"$WORKFLOWS"
        for common_file in "${COMMON_FILES[@]}"; do
            local_file="$common_file"
            remote_file="${tmpdir}/shared/${common_file}"
            if ! fetch_shared "$common_file" "$remote_file"; then
                warn "fetch failed: shared/${common_file}"
                rc=1
                continue
            fi
            if [ ! -f "$local_file" ]; then
                echo "drift: ${local_file} missing (canonical exists)"
                rc=1
                continue
            fi
            if ! diff -u "$local_file" "$remote_file" >/dev/null; then
                echo "drift: ${local_file} differs from canonical"
                diff -u "$local_file" "$remote_file" | head -40
                rc=1
            fi
        done
        if has_resolved_workflow commitlint && is_gat_commitlint_job "${WORKFLOWS_DIR}/ci.yaml"; then
            echo "drift: ${WORKFLOWS_DIR}/ci.yaml has duplicate commitlint job; run ./scripts/sync-shared"
            rc=1
        fi
        if has_resolved_workflow lint-pr-metadata && is_gat_lint_pr_metadata_job "${WORKFLOWS_DIR}/ci.yaml"; then
            echo "drift: ${WORKFLOWS_DIR}/ci.yaml has duplicate lint-pr-metadata job; run ./scripts/sync-shared"
            rc=1
        fi
        # Surface unexpected on-disk workflows that aren't part of the resolved set.
        for f in "$WORKFLOWS_DIR"/*.yaml; do
            [ -f "$f" ] || continue
            name=$(basename "$f" .yaml)
            if [[ "$name" == bespoke_* ]]; then
                continue
            fi
            echo "$WORKFLOWS" | grep -qx "$name" || echo "stale-or-bespoke: $f (not in resolved set)"
        done
        exit $rc
        ;;
esac
