import { execFileSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
export const GIT_HOOKS_DIR_RELATIVE = ".workspace-kit/git-hooks";
export const GIT_POLICY_APPROVAL_RELATIVE = ".workspace-kit/policy/git-destructive-approval.json";
/** Re-exported for hook installers; see task-store-git-commit-policy.ts */
export { TASK_STORE_COMMIT_APPROVAL_RELATIVE } from "./task-store-git-commit-policy.js";
const PROTECTED_BRANCH_PATTERNS = [
    /^refs\/heads\/main$/,
    /^refs\/heads\/master$/,
    /^refs\/heads\/release\/phase-\d+$/
];
export function isProtectedRef(ref) {
    return PROTECTED_BRANCH_PATTERNS.some((pattern) => pattern.test(ref));
}
export function hasGitDestructiveApproval(workspacePath) {
    if (process.env.WORKSPACE_KIT_POLICY_APPROVAL?.trim()) {
        try {
            const parsed = JSON.parse(process.env.WORKSPACE_KIT_POLICY_APPROVAL);
            if (parsed.confirmed === true) {
                return true;
            }
        }
        catch {
            /* fall through */
        }
    }
    const approvalPath = path.join(workspacePath, GIT_POLICY_APPROVAL_RELATIVE);
    if (!fs.existsSync(approvalPath)) {
        return false;
    }
    try {
        const parsed = JSON.parse(fs.readFileSync(approvalPath, "utf8"));
        if (parsed.confirmed !== true) {
            return false;
        }
        if (parsed.expiresAt && Date.parse(parsed.expiresAt) < Date.now()) {
            return false;
        }
        return true;
    }
    catch {
        return false;
    }
}
function hookScriptHeader() {
    return `#!/usr/bin/env bash
# Installed by workspace-kit (install-git-hooks). Blocks destructive git on protected branches
# unless WORKSPACE_KIT_POLICY_APPROVAL or ${GIT_POLICY_APPROVAL_RELATIVE} grants approval.
set -euo pipefail
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
APPROVAL_FILE="$ROOT/${GIT_POLICY_APPROVAL_RELATIVE}"
has_approval() {
  if [[ -n "\${WORKSPACE_KIT_POLICY_APPROVAL:-}" ]]; then
    node -e "const j=JSON.parse(process.env.WORKSPACE_KIT_POLICY_APPROVAL||'{}'); process.exit(j.confirmed===true?0:1)" 2>/dev/null && return 0
  fi
  if [[ -f "$APPROVAL_FILE" ]]; then
    node -e "const fs=require('fs'); const j=JSON.parse(fs.readFileSync(process.argv[1],'utf8')); const ok=j.confirmed===true&&(!j.expiresAt||Date.parse(j.expiresAt)>Date.now()); process.exit(ok?0:1)" "$APPROVAL_FILE" 2>/dev/null && return 0
  fi
  return 1
}
protected_ref() {
  case "$1" in
    refs/heads/main|refs/heads/master|refs/heads/release/phase-*) return 0 ;;
    *) return 1 ;;
  esac
}
`;
}
function prePushHookBody() {
    return `${hookScriptHeader()}
while read -r local_ref local_sha remote_ref remote_sha; do
  [[ -z "$remote_ref" ]] && continue
  protected_ref "$remote_ref" || continue
  # force push: local_sha is all-zero (delete) or not ancestor
  zero="0000000000000000000000000000000000000000"
  if [[ "$local_sha" == "$zero" ]] || ! git merge-base --is-ancestor "$remote_sha" "$local_sha" 2>/dev/null; then
    if has_approval; then
      exit 0
    fi
    echo "workspace-kit git-policy: blocked force/rewritten push to protected branch $remote_ref" >&2
    echo "Set WORKSPACE_KIT_POLICY_APPROVAL='{\"confirmed\":true,\"rationale\":\"…\"}' or write $APPROVAL_FILE" >&2
    exit 1
  fi
done
exit 0
`;
}
function taskStoreCommitApprovalBody() {
    return `
TASK_STORE_APPROVAL_FILE="$ROOT/.workspace-kit/policy/task-store-sqlite-commit-approval.json"
has_task_store_commit_approval() {
  if [[ -n "\${WORKSPACE_KIT_TASK_STORE_COMMIT_APPROVAL:-}" ]]; then
    node -e "const j=JSON.parse(process.env.WORKSPACE_KIT_TASK_STORE_COMMIT_APPROVAL||'{}'); process.exit(j.confirmed===true?0:1)" 2>/dev/null && return 0
  fi
  if [[ -f "$TASK_STORE_APPROVAL_FILE" ]]; then
    node -e "const fs=require('fs'); const j=JSON.parse(fs.readFileSync(process.argv[1],'utf8')); const ok=j.confirmed===true&&(!j.expiresAt||Date.parse(j.expiresAt)>Date.now()); process.exit(ok?0:1)" "$TASK_STORE_APPROVAL_FILE" 2>/dev/null && return 0
  fi
  return 1
}
staged_task_store_sqlite_paths() {
  git diff --cached --name-only --diff-filter=ACM 2>/dev/null | while IFS= read -r f; do
    case "$f" in
      .workspace-kit/tasks/*.db|.workspace-kit/tasks/*.db-wal|.workspace-kit/tasks/*.db-shm) echo "$f" ;;
    esac
  done
}
block_staged_task_store_without_approval() {
  if has_task_store_commit_approval; then
    return 0
  fi
  local hits
  hits="$(staged_task_store_sqlite_paths)"
  if [[ -n "$hits" ]]; then
    echo "workspace-kit git-policy: blocked commit of live planning SQLite (staged):" >&2
    echo "$hits" | sed 's/^/  - /' >&2
    echo "Unstage, use backup/export commands, or write $TASK_STORE_APPROVAL_FILE with {\\"confirmed\\":true,\\"rationale\\":\\"...\\"}" >&2
    echo "One-shot: WORKSPACE_KIT_TASK_STORE_COMMIT_APPROVAL='{\\"confirmed\\":true,\\"rationale\\":\\"...\\"}'" >&2
    echo "Check: pnpm exec wk run check-task-store-commit '{}'" >&2
    return 1
  fi
  return 0
}
`;
}
function preCommitHookBody() {
    return `${hookScriptHeader()}${taskStoreCommitApprovalBody()}
block_staged_task_store_without_approval || exit 1
branch="$(git symbolic-ref -q HEAD 2>/dev/null || true)"
if [[ -n "$branch" ]] && protected_ref "$branch"; then
  if has_approval; then
    exit 0
  fi
  echo "workspace-kit git-policy: direct commits on protected branch $branch require approval" >&2
  echo "Use a task branch, or set WORKSPACE_KIT_POLICY_APPROVAL / $APPROVAL_FILE" >&2
  exit 1
fi
# Block reset --hard while on a protected branch (detect via reflog action when set)
if [[ "\${GIT_REFLOG_ACTION:-}" == "reset: moving to HEAD" ]] || [[ "\${GIT_REFLOG_ACTION:-}" == reset* ]]; then
  if [[ -n "$branch" ]] && protected_ref "$branch" && ! has_approval; then
    echo "workspace-kit git-policy: reset on protected branch requires approval" >&2
    exit 1
  fi
fi
exit 0
`;
}
function postMergeHookBody() {
    return `#!/usr/bin/env bash
# workspace-kit post-merge: reconcile task-state after git pull/merge (git-event-log authority).
set -euo pipefail
${taskStateGitSyncHookHelpers()}
run_task_state_hydrate_after_git_sync
exit 0
`;
}
function postRewriteHookBody() {
    return `#!/usr/bin/env bash
# workspace-kit post-rewrite: reconcile task-state after pull --rebase, amend, etc.
set -euo pipefail
${taskStateGitSyncHookHelpers()}
run_task_state_hydrate_after_git_sync
exit 0
`;
}
/** Shared shell helpers for post-merge / post-rewrite task-state hydrate (exported for tests). */
export function taskStateGitSyncHookHelpers() {
    return `
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
task_state_git_event_log_enabled() {
  [[ -f "$ROOT/.workspace-kit/config.json" ]] || return 1
  node -e "const fs=require('fs');const p=require('path').join(process.argv[1],'.workspace-kit/config.json');try{const j=JSON.parse(fs.readFileSync(p,'utf8'));const t=j.tasks||{};const a=t.canonicalAuthority||t.taskStateCanonicalAuthority;process.exit(a==='git-event-log'?0:1)}catch{process.exit(1)}" "$ROOT" 2>/dev/null
}
run_task_state_hydrate_after_git_sync() {
  task_state_git_event_log_enabled || return 0
  git -C "$ROOT" checkout -- .workspace-kit/tasks/workspace-kit.db .workspace-kit/tasks/task-state-events.jsonl 2>/dev/null || true
  local hydrate_args='{"fetch":true,"policyApproval":{"confirmed":true,"rationale":"git hook post-sync task-state reconcile"}}'
  if command -v pnpm >/dev/null 2>&1; then
    (cd "$ROOT" && pnpm exec wk run task-state-hydrate "$hydrate_args") || echo "workspace-kit: task-state-hydrate failed after git sync (non-fatal)" >&2
  elif [[ -x "$ROOT/.workspace-kit/bin/wk" ]]; then
    (cd "$ROOT" && "$ROOT/.workspace-kit/bin/wk" run task-state-hydrate "$hydrate_args") || echo "workspace-kit: task-state-hydrate failed after git sync (non-fatal)" >&2
  else
    echo "workspace-kit: skipped post-sync task-state hydrate (pnpm or .workspace-kit/bin/wk not found)" >&2
  fi
}
`;
}
export function installGitPolicyHooks(workspacePath) {
    const hooksDir = path.join(workspacePath, GIT_HOOKS_DIR_RELATIVE);
    fs.mkdirSync(hooksDir, { recursive: true });
    const hooks = {
        "pre-push": prePushHookBody(),
        "pre-commit": preCommitHookBody(),
        "post-merge": postMergeHookBody(),
        "post-rewrite": postRewriteHookBody()
    };
    const installed = [];
    for (const [name, body] of Object.entries(hooks)) {
        const hookPath = path.join(hooksDir, name);
        fs.writeFileSync(hookPath, body, { encoding: "utf8", mode: 0o755 });
        installed.push(path.relative(workspacePath, hookPath));
    }
    const hooksPathConfig = GIT_HOOKS_DIR_RELATIVE;
    return { hooksDir, hooksPathConfig, installed };
}
export function uninstallGitPolicyHooks(workspacePath) {
    const hooksDir = path.join(workspacePath, GIT_HOOKS_DIR_RELATIVE);
    const removed = [];
    if (fs.existsSync(hooksDir)) {
        for (const name of fs.readdirSync(hooksDir)) {
            const hookPath = path.join(hooksDir, name);
            if (fs.statSync(hookPath).isFile()) {
                fs.unlinkSync(hookPath);
                removed.push(path.relative(workspacePath, hookPath));
            }
        }
        try {
            fs.rmdirSync(hooksDir);
        }
        catch {
            /* non-empty */
        }
    }
    return { removed };
}
export function readGitHooksPathFromRepo(workspacePath) {
    const gitDir = path.join(workspacePath, ".git");
    if (!fs.existsSync(gitDir)) {
        return null;
    }
    try {
        const out = execFileSync("git", ["config", "--get", "core.hooksPath"], {
            cwd: workspacePath,
            encoding: "utf8"
        }).trim();
        return out || null;
    }
    catch {
        return null;
    }
}
export function setGitHooksPath(workspacePath, hooksPath) {
    if (hooksPath === null) {
        try {
            execFileSync("git", ["config", "--unset", "core.hooksPath"], { cwd: workspacePath, stdio: "pipe" });
        }
        catch {
            /* already unset */
        }
        return;
    }
    execFileSync("git", ["config", "core.hooksPath", hooksPath], { cwd: workspacePath, stdio: "pipe" });
}
