"use strict";

const fs = require("fs");
const path = require("path");

const { GLOBAL_CLAUDE_DIR, SAFETY_HOOK_DEFINITIONS } = require("./constants");
const { log, logSuccess, logSkip, logWarning } = require("./logging");
const { writeSettingsAtomic } = require("./fs-utils");

// ─── Global Env Writer (SPECS §2.c) ─────────────────────────────────────────

/**
 * Merge-safe writer for a single env key in a Claude Code settings.json file.
 *
 * Contract (docs/features/ECK-DEV-1-.../SPECS.md §2.c):
 *   - Missing file  → create { env: { [envKey]: envValue } } and mkdir -p parent.
 *   - No env block  → add env with only the target key; other top-level keys
 *                     untouched.
 *   - Has env block → preserve all other env keys; set/overwrite only envKey.
 *   - Atomic write  → .tmp + rename (no partial-write window).
 *   - Malformed JSON on the existing file → throw (installer aborts).
 *   - Formatting    → 2-space indent, trailing newline, no BOM.
 */
function writeGlobalEnv(settingsPath, envKey, envValue) {
  let settings = {};
  if (fs.existsSync(settingsPath)) {
    const raw = fs.readFileSync(settingsPath, "utf-8");
    try {
      settings = JSON.parse(raw);
    } catch (err) {
      throw new Error(
        `Malformed JSON in ${settingsPath}: ${err.message}. ` +
          `Fix or remove the file, then re-run the installer.`,
      );
    }
    if (
      settings === null ||
      typeof settings !== "object" ||
      Array.isArray(settings)
    ) {
      throw new Error(
        `Malformed JSON in ${settingsPath}: expected an object at the root.`,
      );
    }
  } else {
    fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
  }

  if (
    !settings.env ||
    typeof settings.env !== "object" ||
    Array.isArray(settings.env)
  ) {
    settings.env = {};
  }
  settings.env[envKey] = envValue;

  const content = JSON.stringify(settings, null, 2) + "\n";
  const tmp = settingsPath + ".tmp";
  fs.writeFileSync(tmp, content, "utf-8");
  fs.renameSync(tmp, settingsPath);
}

// ─── Hook Command Builder ───────────────────────────────────────────────────

// Build a hook command using an absolute path with forward slashes, quoted.
// Cross-platform agentic tools (Claude Code, Cortex Code, etc.) do not all
// expand `~` consistently — Cortex Code on Windows treats it as a literal
// path component and resolves it relative to CWD. Writing the resolved
// absolute path avoids the divergence.
function assertSafeForShellQuoting(absPath) {
  // The hook command is stored as a JSON string and later parsed by a shell.
  // The path is interpolated inside double-quotes, so any of these characters
  // would terminate the quoted string early or trigger shell evaluation.
  // Forward slashes are intentional (we normalize Windows backslashes away
  // before this check); the bare backslash check catches macOS/Linux home
  // dirs containing a literal backslash.
  if (/["$`\n]/.test(absPath) || /\\/.test(absPath)) {
    throw new Error(
      `Refusing to build hook command: home directory path contains a ` +
        `character that is unsafe for shell-quoted interpolation ` +
        `(", $, \`, \\, or newline). Path: ${JSON.stringify(absPath)}`,
    );
  }
}

function hookCommand(globalDir, filename) {
  const abs = path.join(globalDir, "hooks", filename).replace(/\\/g, "/");
  assertSafeForShellQuoting(abs);
  return `node "${abs}"`;
}

// ─── Hook File Verification ─────────────────────────────────────────────────

function verifyHookFiles(globalDir = GLOBAL_CLAUDE_DIR) {
  const hooksDir = path.join(globalDir, "hooks");
  const required = [
    "evolv-statusline.js",
    "evolv-context-monitor.js",
    "evolv-check-update.js",
    "evolv-session-guidance.js",
  ];
  const missing = required.filter(
    (f) => !fs.existsSync(path.join(hooksDir, f)),
  );
  if (missing.length > 0) {
    logWarning(`Hook files not found: ${missing.join(", ")}`);
    logWarning("Run: npm run build:hooks then re-install");
  }
  return missing;
}

// ─── Settings Registration ──────────────────────────────────────────────────

function registerSettingsEntries({
  dryRun = false,
  globalDir = GLOBAL_CLAUDE_DIR,
  silent = false,
} = {}) {
  // Verify hook files exist before registering them in settings
  if (!dryRun) {
    verifyHookFiles(globalDir);
  }
  const settingsPath = path.join(globalDir, "settings.json");
  let settings = {};
  if (fs.existsSync(settingsPath)) {
    try {
      settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
    } catch {
      log(
        "  settings.json exists but is not valid JSON — skipping registration",
      );
      return { registered: 0 };
    }
  }

  let registered = 0;

  // 1. statusLine entry
  const hasStatusLine =
    settings.statusLine &&
    settings.statusLine.command &&
    settings.statusLine.command.includes("evolv-statusline");
  if (!hasStatusLine) {
    if (dryRun) {
      if (!silent) logSuccess("Would add statusLine to settings.json");
    } else {
      settings.statusLine = {
        type: "command",
        command: hookCommand(globalDir, "evolv-statusline.js"),
      };
      if (!silent) logSuccess("Registered statusLine in settings.json");
    }
    registered++;
  } else {
    if (!silent) logSkip("statusLine already registered");
  }

  // 2. PostToolUse context-monitor hook
  let hasContextMonitor = false;
  if (settings.hooks && Array.isArray(settings.hooks.PostToolUse)) {
    hasContextMonitor = settings.hooks.PostToolUse.some((entry) => {
      if (Array.isArray(entry.hooks)) {
        return entry.hooks.some(
          (h) => h.command && h.command.includes("evolv-context-monitor.js"),
        );
      }
      if (entry.command && entry.command.includes("evolv-context-monitor.js")) {
        return true;
      }
      return false;
    });
  }

  if (!hasContextMonitor) {
    if (dryRun) {
      if (!silent)
        logSuccess(
          "Would add PostToolUse context-monitor hook to settings.json",
        );
    } else {
      if (!settings.hooks) settings.hooks = {};
      if (!Array.isArray(settings.hooks.PostToolUse))
        settings.hooks.PostToolUse = [];
      settings.hooks.PostToolUse.push({
        matcher: "",
        hooks: [
          {
            type: "command",
            command: hookCommand(globalDir, "evolv-context-monitor.js"),
          },
        ],
      });
      if (!silent)
        logSuccess(
          "Registered PostToolUse context-monitor hook in settings.json",
        );
    }
    registered++;
  } else {
    if (!silent) logSkip("PostToolUse context-monitor hook already registered");
  }

  // 3. spinnerVerbs
  const hasSpinnerVerbs =
    settings.spinnerVerbs &&
    Array.isArray(settings.spinnerVerbs.verbs) &&
    settings.spinnerVerbs.verbs.length > 0;
  if (!hasSpinnerVerbs) {
    if (dryRun) {
      if (!silent) logSuccess("Would add spinnerVerbs to settings.json");
    } else {
      settings.spinnerVerbs = {
        mode: "replace",
        verbs: [
          "Building",
          "Analyzing",
          "Generating",
          "Scaffolding",
          "Migrating",
          "Reviewing",
          "Configuring",
          "Resolving",
          "Synthesizing",
          "Validating",
          "Compiling",
          "Optimizing",
          "Mapping",
          "Evaluating",
          "Orchestrating",
        ],
      };
      if (!silent) logSuccess("Registered spinnerVerbs in settings.json");
    }
    registered++;
  } else {
    if (!silent) logSkip("spinnerVerbs already registered");
  }

  // 4. companyAnnouncements
  const hasAnnouncements =
    Array.isArray(settings.companyAnnouncements) &&
    settings.companyAnnouncements.length > 0;
  if (!hasAnnouncements) {
    if (dryRun) {
      if (!silent)
        logSuccess("Would add companyAnnouncements to settings.json");
    } else {
      settings.companyAnnouncements = [
        "evolv Coder Kit is active. Run /eck:help to see available skills.",
        "Tip: Run /eck:project-status to check your project health.",
        "Tip: Run /eck:audit-current-project to validate your setup.",
        "Run /eck:list-global-catalog to browse available project components.",
        "New here? Run /eck:new-project to scaffold a project with best practices.",
      ];
      if (!silent)
        logSuccess("Registered companyAnnouncements in settings.json");
    }
    registered++;
  } else {
    if (!silent) logSkip("companyAnnouncements already registered");
  }

  // 5. showTurnDuration
  if (settings.showTurnDuration === undefined) {
    if (dryRun) {
      if (!silent) logSuccess("Would add showTurnDuration to settings.json");
    } else {
      settings.showTurnDuration = true;
      if (!silent) logSuccess("Registered showTurnDuration in settings.json");
    }
    registered++;
  } else {
    if (!silent) logSkip("showTurnDuration already registered");
  }

  // 6. terminalProgressBarEnabled
  if (settings.terminalProgressBarEnabled === undefined) {
    if (dryRun) {
      if (!silent)
        logSuccess("Would add terminalProgressBarEnabled to settings.json");
    } else {
      settings.terminalProgressBarEnabled = true;
      if (!silent)
        logSuccess("Registered terminalProgressBarEnabled in settings.json");
    }
    registered++;
  } else {
    if (!silent) logSkip("terminalProgressBarEnabled already registered");
  }

  // 7. SessionStart check-update hook
  let hasCheckUpdate = false;
  if (settings.hooks && Array.isArray(settings.hooks.SessionStart)) {
    hasCheckUpdate = settings.hooks.SessionStart.some((entry) => {
      if (Array.isArray(entry.hooks)) {
        return entry.hooks.some(
          (h) => h.command && h.command.includes("evolv-check-update.js"),
        );
      }
      if (entry.command && entry.command.includes("evolv-check-update.js")) {
        return true;
      }
      return false;
    });
  }

  if (!hasCheckUpdate) {
    if (dryRun) {
      if (!silent)
        logSuccess("Would add SessionStart check-update hook to settings.json");
    } else {
      if (!settings.hooks) settings.hooks = {};
      if (!Array.isArray(settings.hooks.SessionStart))
        settings.hooks.SessionStart = [];
      settings.hooks.SessionStart.push({
        matcher: "",
        hooks: [
          {
            type: "command",
            command: hookCommand(globalDir, "evolv-check-update.js"),
          },
        ],
      });
      if (!silent)
        logSuccess(
          "Registered SessionStart check-update hook in settings.json",
        );
    }
    registered++;
  } else {
    if (!silent) logSkip("SessionStart check-update hook already registered");
  }

  // 8. SessionStart session-guidance hook
  let hasSessionGuidance = false;
  if (settings.hooks && Array.isArray(settings.hooks.SessionStart)) {
    hasSessionGuidance = settings.hooks.SessionStart.some((entry) => {
      if (Array.isArray(entry.hooks)) {
        return entry.hooks.some(
          (h) => h.command && h.command.includes("evolv-session-guidance.js"),
        );
      }
      if (
        entry.command &&
        entry.command.includes("evolv-session-guidance.js")
      ) {
        return true;
      }
      return false;
    });
  }

  if (!hasSessionGuidance) {
    if (dryRun) {
      if (!silent)
        logSuccess(
          "Would add SessionStart session-guidance hook to settings.json",
        );
    } else {
      if (!settings.hooks) settings.hooks = {};
      if (!Array.isArray(settings.hooks.SessionStart))
        settings.hooks.SessionStart = [];
      settings.hooks.SessionStart.push({
        matcher: "",
        hooks: [
          {
            type: "command",
            command: hookCommand(globalDir, "evolv-session-guidance.js"),
          },
        ],
      });
      if (!silent)
        logSuccess(
          "Registered SessionStart session-guidance hook in settings.json",
        );
    }
    registered++;
  } else {
    if (!silent)
      logSkip("SessionStart session-guidance hook already registered");
  }

  // 9. outputStyle
  if (settings.outputStyle === undefined) {
    let outputStyleValue = "standard";
    try {
      const prefsPath = path.join(
        globalDir,
        "evolv-coder-kit",
        "user-preferences.json",
      );
      if (fs.existsSync(prefsPath)) {
        const prefs = JSON.parse(fs.readFileSync(prefsPath, "utf-8"));
        if (prefs.outputMode) {
          outputStyleValue = prefs.outputMode;
        }
      }
    } catch {
      // Ignore — use default
    }
    if (dryRun) {
      if (!silent) logSuccess("Would add outputStyle to settings.json");
    } else {
      settings.outputStyle = outputStyleValue;
      if (!silent) logSuccess("Registered outputStyle in settings.json");
    }
    registered++;
  } else {
    if (!silent) logSkip("outputStyle already registered");
  }

  // 10. permissions (global deny list only — allows are project-level)
  const hasDenyList =
    settings.permissions &&
    Array.isArray(settings.permissions.deny) &&
    settings.permissions.deny.includes("Bash(sudo *)");
  if (!hasDenyList) {
    const baselineDeny = [
      "Bash(rm -rf /)",
      "Bash(rm -rf /*)",
      "Bash(rm -rf ~)",
      "Bash(rm -rf ~/*)",
      "Bash(sudo *)",
      "Bash(chmod 777 *)",
      "Bash(chmod -R 777 *)",
      "Bash(curl * | bash)",
      "Bash(curl * | sh)",
      "Bash(curl * | zsh)",
      "Bash(wget * | bash)",
      "Bash(wget * | sh)",
      "Bash(wget * | zsh)",
      "Bash(*global-state.json*)",
    ];
    if (dryRun) {
      if (!silent)
        logSuccess("Would add permissions deny list to settings.json");
    } else {
      if (!settings.permissions) settings.permissions = {};
      // Merge with existing user entries using Set deduplication
      const existingDeny = Array.isArray(settings.permissions.deny)
        ? settings.permissions.deny
        : [];
      settings.permissions.deny = [
        ...new Set([...existingDeny, ...baselineDeny]),
      ];
      if (!silent)
        logSuccess("Registered permissions deny list in settings.json");
    }
    registered++;
  } else {
    if (!silent) logSkip("permissions deny list already registered");
  }

  // 11. sandbox (disabled by default — user enables via /sandbox or settings)
  const hasSandbox = settings.sandbox && settings.sandbox.enabled !== undefined;
  if (!hasSandbox) {
    if (dryRun) {
      if (!silent) logSuccess("Would add sandbox to settings.json");
    } else {
      settings.sandbox = {
        enabled: false,
        autoAllowBashIfSandboxed: true,
        network: {
          allowedDomains: [
            "github.com",
            "*.github.com",
            "*.githubusercontent.com",
            "registry.npmjs.org",
            "*.npmjs.org",
            "pypi.org",
            "*.pypi.org",
            "api.anthropic.com",
          ],
        },
      };
      if (!silent) logSuccess("Registered sandbox in settings.json");
    }
    registered++;
  } else {
    if (!silent) logSkip("sandbox already registered");
  }

  if (registered > 0 && !dryRun) {
    writeSettingsAtomic(settingsPath, settings);
  }

  return { registered };
}

module.exports = {
  SAFETY_HOOK_DEFINITIONS,
  assertSafeForShellQuoting,
  hookCommand,
  verifyHookFiles,
  registerSettingsEntries,
  writeGlobalEnv,
};
