import type { PumukiCriticalNotificationEvent } from './framework-menu-system-notifications-types';
import {
  normalizeNotificationText,
  truncateNotificationText,
} from './framework-menu-system-notifications-text';
import {
  buildNotificationTrackingRemediation,
  extractNotificationTrackingContext,
  TRACKING_BLOCKED_REMEDIATION,
} from './framework-menu-system-notifications-tracking';
import { buildLegacySkillCauseRemediation } from './framework-menu-system-notifications-legacy-skill-cause';

type BlockedRemediationVariant = 'banner' | 'dialog';

const BLOCKED_REMEDIATION_BY_CODE: Readonly<Record<string, string>> = {
  EVIDENCE_GATE_BLOCKED:
    'Revisa status/doctor para ver la causa exacta del gate, corrígela y revalida.',
  EVIDENCE_MISSING: 'Genera la evidencia del slice actual y vuelve a validar esta fase.',
  EVIDENCE_INVALID: 'Regenera la evidencia de esta iteración y repite la validación.',
  EVIDENCE_CHAIN_INVALID: 'Regenera la evidencia para restaurar la cadena de integridad y vuelve a validar.',
  EVIDENCE_STALE: 'Refresca la evidencia y vuelve a validar PRE_WRITE/PRE_PUSH.',
  EVIDENCE_BRANCH_MISMATCH: 'La evidencia no corresponde a esta rama. Regenera el receipt/evidencia y vuelve a validar.',
  EVIDENCE_REPO_ROOT_MISMATCH: 'Regenera la evidencia desde este repositorio y vuelve a validar.',
  PRE_PUSH_UPSTREAM_MISSING: 'Configura upstream con `git push --set-upstream origin <branch>` y repite PRE_PUSH.',
  SDD_SESSION_MISSING:
    'Abre `openspec/changes/` y copia la carpeta de tu tarea, por ejemplo `rgo-1900-35`. Ejecuta `pumuki sdd session --open --change=<id>` usando ese nombre. Después vuelve a intentar el commit; PRE_WRITE automáticamente.',
  SDD_SESSION_INVALID:
    'Refresca la sesión SDD activa: `pumuki sdd session --refresh --ttl-minutes=90`. Después vuelve a intentar el commit; Pumuki ejecutará PRE_WRITE automáticamente.',
  OPENSPEC_MISSING: 'Instala OpenSpec en este repositorio y vuelve a validar el gate.',
  MCP_ENTERPRISE_RECEIPT_MISSING: 'Genera el receipt enterprise de MCP y vuelve a validar.',
  BACKEND_AVOID_EXPLICIT_ANY: 'Sustituye `any` por tipos concretos en backend y relanza el gate.',
  GIT_ATOMICITY_TOO_MANY_SCOPES: 'Divide el cambio por scope o en commits más pequeños y vuelve a ejecutar el gate.',
  SOLID_HEURISTIC: 'Corrige la violación detectada y vuelve a ejecutar el gate.',
  TRACKING_CANONICAL_IN_PROGRESS_INVALID: TRACKING_BLOCKED_REMEDIATION,
  TRACKING_CANONICAL_SOURCE_CONFLICT: TRACKING_BLOCKED_REMEDIATION,
  ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES_HIGH:
    'Ejecuta `pumuki policy reconcile --strict --json` y revalida antes de continuar.',
  EVIDENCE_PREWRITE_WORKTREE_WARN:
    'Reduce el worktree pendiente a un slice atómico antes del commit: stagea solo la tarea activa o guarda el resto en stash nombrado, y vuelve a intentar el commit.',
  EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT:
    'El worktree supera el límite permitido: divide cambios por scope en commits atómicos o stashes nombrados antes de continuar.',
};

const BLOCKED_REMEDIATION_MAX_LENGTH_BY_VARIANT: Readonly<Record<BlockedRemediationVariant, number>> = {
  banner: 120,
  dialog: 220,
};

const GENERIC_BLOCKED_REMEDIATION =
  'Corrige el bloqueo indicado y vuelve a ejecutar el comando.';

const buildMultipleCausesRemediation = (count: number): string =>
  `Corrige las ${count} causas bloqueantes listadas arriba, empezando por violaciones AST/skills de codigo cuando existan, y vuelve a ejecutar el gate.`;

const normalizeBlockedRemediation = (value: string): string =>
  normalizeNotificationText(value)
    .replace(/^cómo solucionarlo:\s*/i, '')
    .replace(/^remediation:\s*/i, '')
    .replace(/^solution:\s*/i, '')
    .replace(/^fix:\s*/i, '');

const resolveFallbackRemediation = (causeCode: string): string =>
  BLOCKED_REMEDIATION_BY_CODE[causeCode] ?? GENERIC_BLOCKED_REMEDIATION;

const resolveSddSessionCauseCode = (
  event: Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>,
  causeCode: string
): 'SDD_SESSION_MISSING' | 'SDD_SESSION_INVALID' | null => {
  if (causeCode === 'SDD_SESSION_MISSING' || causeCode === 'SDD_SESSION_INVALID') {
    return causeCode;
  }
  const searchable = [
    event.causeMessage,
    event.remediation,
    ...(event.blockingCauses ?? []).flatMap((cause) => [
      cause.code,
      cause.ruleId,
      cause.message,
      cause.remediation,
    ]),
  ]
    .filter((value): value is string => typeof value === 'string')
    .join(' ')
    .toLowerCase();
  if (
    searchable.includes('sdd_session_missing') ||
    searchable.includes('no hay sesión sdd activa') ||
    searchable.includes('no hay sesion sdd activa') ||
    searchable.includes('no active sdd session')
  ) {
    return 'SDD_SESSION_MISSING';
  }
  if (
    searchable.includes('sdd_session_invalid') ||
    searchable.includes('sesión sdd actual no es válida') ||
    searchable.includes('sesion sdd actual no es valida') ||
    searchable.includes('invalid sdd session')
  ) {
    return 'SDD_SESSION_INVALID';
  }
  return null;
};

const resolveWorktreeHygieneCauseCode = (
  event: Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>,
  causeCode: string
): 'EVIDENCE_PREWRITE_WORKTREE_WARN' | 'EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT' | null => {
  if (
    causeCode === 'EVIDENCE_PREWRITE_WORKTREE_WARN' ||
    causeCode === 'EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT'
  ) {
    return causeCode;
  }
  const searchable = [
    event.causeMessage,
    event.remediation,
    ...(event.blockingCauses ?? []).flatMap((cause) => [
      cause.code,
      cause.ruleId,
      cause.message,
      cause.remediation,
    ]),
  ]
    .filter((value): value is string => typeof value === 'string')
    .join(' ')
    .toLowerCase();
  if (searchable.includes('evidence_prewrite_worktree_over_limit')) {
    return 'EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT';
  }
  if (
    searchable.includes('evidence_prewrite_worktree_warn') ||
    searchable.includes('worktree hygiene warning') ||
    searchable.includes('warn_threshold=')
  ) {
    return 'EVIDENCE_PREWRITE_WORKTREE_WARN';
  }
  return null;
};

const isGenericPolicyReconcileRemediation = (message: string): boolean => {
  const normalized = message.toLowerCase();
  return normalized.includes('policy reconcile') && normalized.includes('sdd validate');
};

const hasEnglishHints = (message: string): boolean => {
  const normalized = message.toLowerCase();
  return [
    'avoid explicit any',
    'set-upstream',
    'refresh evidence',
    'fix and retry',
    'split the change',
    'smaller commits',
    're-run',
    'rerun',
    'retry',
    'to continue',
    'run ',
  ].some((hint) => normalized.includes(hint));
};

const toKnownSpanishRemediationFromMessage = (message: string, causeCode: string): string | null => {
  const normalized = message.toLowerCase();
  if (normalized.includes('avoid explicit any')) {
    return BLOCKED_REMEDIATION_BY_CODE.BACKEND_AVOID_EXPLICIT_ANY;
  }
  if (normalized.includes('set-upstream') || normalized.includes('no upstream tracking reference')) {
    return BLOCKED_REMEDIATION_BY_CODE.PRE_PUSH_UPSTREAM_MISSING;
  }
  if (normalized.includes('refresh evidence') || normalized.includes('evidence is stale')) {
    return BLOCKED_REMEDIATION_BY_CODE.EVIDENCE_STALE;
  }
  if (normalized.includes('split the change')) {
    return BLOCKED_REMEDIATION_BY_CODE.GIT_ATOMICITY_TOO_MANY_SCOPES;
  }
  if (normalized.includes('fix and retry') || normalized.includes('retry')) {
    return resolveFallbackRemediation(causeCode);
  }
  return null;
};

export const resolveBlockedRemediation = (
  event: Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>,
  causeCode: string,
  options?: {
    variant?: BlockedRemediationVariant;
  }
): string => {
  const variant = options?.variant ?? 'dialog';
  const maxLength = BLOCKED_REMEDIATION_MAX_LENGTH_BY_VARIANT[variant];
  const sddSessionCauseCode = resolveSddSessionCauseCode(event, causeCode);
  if (sddSessionCauseCode) {
    return truncateNotificationText(
      resolveFallbackRemediation(sddSessionCauseCode),
      maxLength
    );
  }
  const worktreeHygieneCauseCode = resolveWorktreeHygieneCauseCode(event, causeCode);
  if (worktreeHygieneCauseCode) {
    return truncateNotificationText(
      resolveFallbackRemediation(worktreeHygieneCauseCode),
      maxLength
    );
  }
  const blockingCausesCount = event.blockingCauses?.length ?? 0;
  if (blockingCausesCount > 1) {
    return truncateNotificationText(
      buildMultipleCausesRemediation(blockingCausesCount),
      maxLength
    );
  }
  const firstCauseWithRemediation = event.blockingCauses?.find(
    (cause) => cause.remediation && cause.remediation.trim().length > 0
  );
  if (firstCauseWithRemediation?.remediation) {
    return truncateNotificationText(
      normalizeBlockedRemediation(firstCauseWithRemediation.remediation),
      maxLength
    );
  }
  const fromEvent = event.remediation
    ? normalizeBlockedRemediation(event.remediation)
    : '';
  const trackingContext = extractNotificationTrackingContext(event.causeMessage);
  if (trackingContext) {
    return truncateNotificationText(buildNotificationTrackingRemediation(trackingContext), maxLength);
  }
  const legacySkillCauseRemediation = buildLegacySkillCauseRemediation(event.causeMessage);
  if (legacySkillCauseRemediation) {
    return truncateNotificationText(legacySkillCauseRemediation, maxLength);
  }
  if (fromEvent.length > 0) {
    if (
      causeCode === 'EVIDENCE_GATE_BLOCKED' &&
      isGenericPolicyReconcileRemediation(fromEvent)
    ) {
      return truncateNotificationText(resolveFallbackRemediation(causeCode), maxLength);
    }
    const translated = toKnownSpanishRemediationFromMessage(fromEvent, causeCode);
    if (translated) {
      return truncateNotificationText(translated, maxLength);
    }
    if (hasEnglishHints(fromEvent)) {
      return truncateNotificationText(resolveFallbackRemediation(causeCode), maxLength);
    }
    return truncateNotificationText(fromEvent, maxLength);
  }
  return truncateNotificationText(resolveFallbackRemediation(causeCode), maxLength);
};
