import { resolve, sep } from 'node:path';

export class WriteGuardError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'WriteGuardError';
  }
}

// Deny list: files that should never be written regardless of path-guard.
// Subset of BooChat's secret_guard.ts — covers the most dangerous patterns.
// Full parity with BooChat's deny list is not needed for write-guard because
// the write tools are intentional (model chose to create/edit); we block only
// files that are unambiguously secrets.
const SECRET_PATTERNS: readonly string[] = [
  '.env',
  '.env.local',
  '.env.production',
  '.env.development',
  '.env.staging',
  'id_rsa',
  'id_dsa',
  'id_ecdsa',
  'id_ed25519',
  '*.pem',
  '*.key',
  '*.p12',
  '*.pfx',
  '*.crt',
  'credentials.json',
  '*.kdbx',
  '.netrc',
];

export function isSecretPath(filePath: string): boolean {
  const normalized = filePath.replace(/\\/g, '/');
  const segments = normalized.split('/').filter((s) => s.length > 0);
  if (segments.length === 0) return false;
  const basename = segments[segments.length - 1]!;

  return SECRET_PATTERNS.some((pattern) => {
    if (pattern.startsWith('*')) {
      return basename.endsWith(pattern.slice(1));
    }
    return basename === pattern;
  });
}

/**
 * Resolve and validate a write target path.
 *
 * Key difference from BooChat's pathGuard: no realpath() — the file may not
 * exist yet (creates). Uses resolve() to normalize ../ segments and then
 * checks the result stays within projectRoot.
 */
export function resolveWritePath(projectRoot: string, filePath: string): string {
  if (!filePath || filePath.trim().length === 0) {
    throw new WriteGuardError('file path is required');
  }

  if (filePath.includes('\x00')) {
    throw new WriteGuardError('file path contains null byte');
  }

  const candidate = filePath.startsWith('/') ? filePath : resolve(projectRoot, filePath);
  const normalized = resolve(candidate); // normalizes ../ segments

  if (!normalized.startsWith(projectRoot + sep) && normalized !== projectRoot) {
    throw new WriteGuardError(`path escapes project root: ${filePath}`);
  }

  if (isSecretPath(normalized)) {
    throw new WriteGuardError(`cannot write to secret file: ${filePath}`);
  }

  return normalized;
}
