/**
 * GitFlow Branch — Name normalization, type detection, validation.
 * Self-contained: imports only from local lib.
 */

import type { BranchType, GitFlowConfig } from './types.js';
import { execGit } from './git.js';

export function normalizeBranchName(input: string, maxLength = 50): string {
  return input
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, '')
    .toLowerCase()
    .replace(/[ _']/g, '-')
    .replace(/[^a-z0-9-]/g, '')
    .replace(/-{2,}/g, '-')
    .replace(/^-/, '')
    .replace(/-$/, '')
    .slice(0, maxLength)
    .replace(/-$/, '');
}

/**
 * Normalize a release/hotfix name while PRESERVING dots — versions like
 * `3.53.0` must stay `3.53.0` (never `3530`). Unlike normalizeBranchName,
 * keeps `.` so the segment maps 1:1 to the semantic version / tag.
 */
export function normalizeReleaseName(input: string, maxLength = 50): string {
  return input
    .normalize('NFD')
    .replace(/[̀-ͯ]/g, '')
    .toLowerCase()
    .replace(/[ _']/g, '-')
    .replace(/[^a-z0-9.\-]/g, '')   // keep dots (vs normalizeBranchName which strips them)
    .replace(/-{2,}/g, '-')
    .replace(/\.{2,}/g, '.')
    .replace(/^[-.]+/, '')
    .replace(/[-.]+$/, '')
    .slice(0, maxLength)
    .replace(/[-.]+$/, '');
}

/**
 * Per-type name normalization: feature names become kebab-case (dots stripped),
 * but release/hotfix names keep their version dots intact.
 */
export function normalizeNameForType(name: string, branchType: BranchType): string {
  return branchType === 'release' || branchType === 'hotfix'
    ? normalizeReleaseName(name)
    : normalizeBranchName(name);
}

export function detectBranchType(branchName: string): BranchType {
  if (branchName === 'main' || branchName === 'master') return 'main';
  if (branchName === 'develop' || branchName === 'development') return 'develop';
  if (branchName.startsWith('feature/')) return 'feature';
  if (branchName.startsWith('release/')) return 'release';
  if (branchName.startsWith('hotfix/')) return 'hotfix';
  return 'other';
}

export function getBaseBranch(branchType: BranchType, config: GitFlowConfig): string {
  switch (branchType) {
    case 'feature': return config.git.branches.develop;
    // GitFlow 4.x rule: a release is cut FROM develop (the integrated branch),
    // then PR'd to main. Only hotfixes branch off main.
    case 'release': return config.git.branches.develop;
    case 'hotfix': return config.git.branches.main;
    default: return config.git.branches.develop;
  }
}

export function getTargetBranch(branchType: BranchType, config: GitFlowConfig): string {
  switch (branchType) {
    case 'feature': return config.git.branches.develop;
    case 'release':
    case 'hotfix': return config.git.branches.main;
    default: return config.git.branches.develop;
  }
}

/**
 * Hard branch-target invariant (defense-in-depth against the feature→main
 * incident). Throws BEFORE any PR/merge so a misconfigured or empty `develop`
 * can never let a feature target main.
 *
 * - config must define both main and develop, and they must differ
 * - the resolved target must be non-empty
 * - a feature must NEVER target main/master
 */
export function assertBranchTarget(branchType: BranchType, target: string, config: GitFlowConfig): void {
  const main = config.git.branches.main;
  const develop = config.git.branches.develop;

  if (!main || !develop) {
    throw new Error(
      `GitFlow config invalid: branches.main ('${main}') and branches.develop ('${develop}') must both be set. ` +
      `Run gitflow init to reconfigure.`,
    );
  }
  if (main === develop) {
    throw new Error(`GitFlow config invalid: develop ('${develop}') must differ from main ('${main}').`);
  }
  if (!target || !target.trim()) {
    throw new Error(`Refusing to proceed: empty target branch for ${branchType} branch.`);
  }
  if (branchType === 'feature' && (target === main || /^(main|master)$/i.test(target))) {
    throw new Error(
      `Refusing: a feature branch must target '${develop}', never '${target}'. ` +
      `(A release/hotfix targets main; a feature never does.)`,
    );
  }
}

export function buildFullBranchName(name: string, branchType: BranchType, config: GitFlowConfig): string {
  const prefix = config.git.prefixes[branchType as keyof typeof config.git.prefixes];
  if (!prefix) return name;
  return `${prefix}${name}`;
}

export function stripPrefix(fullBranch: string): string {
  return fullBranch.replace(/^(feature|release|hotfix)\//, '');
}

export async function branchExists(branch: string, remote = false, cwd?: string): Promise<boolean> {
  if (remote) {
    const result = await execGit(['ls-remote', '--heads', 'origin', branch], cwd);
    return result.stdout.length > 0;
  }
  const result = await execGit(['rev-parse', '--verify', branch], cwd);
  return result.exitCode === 0;
}

export function validateBranchName(name: string): { valid: boolean; error?: string } {
  if (!name || name.trim().length === 0) {
    return { valid: false, error: 'Branch name cannot be empty' };
  }
  if (name.length > 100) {
    return { valid: false, error: 'Branch name too long (max 100 characters)' };
  }
  if (/[~^:?*\[\\]/.test(name)) {
    return { valid: false, error: `Branch name contains invalid characters: ${name}` };
  }
  if (name.startsWith('-') || name.endsWith('.') || name.endsWith('/') || name.includes('..')) {
    return { valid: false, error: `Branch name has invalid format: ${name}` };
  }
  return { valid: true };
}
