import { PIPELINE_PLUGINS, getEnabledPluginSet, isPluginEnabled } from '@/lib/pipeline-plugins';

export type ProjectConfigLike = Record<string, unknown>;

/** Target architecture patterns offered in the wizard. Single source of truth,
 *  also used to forward the choice to the engine as a translation rule. */
export const ARCH_PATTERNS = [
  { value: 'layered', label: 'Layered (N-tier)' },
  { value: 'clean', label: 'Clean Architecture' },
  { value: 'hexagonal', label: 'Hexagonal (Ports & Adapters)' },
  { value: 'onion', label: 'Onion Architecture' },
  { value: 'mvc', label: 'MVC' },
  { value: 'mvvm', label: 'MVVM' },
  { value: 'microservices', label: 'Microservices' },
  { value: 'modular-monolith', label: 'Modular Monolith' },
  { value: 'event-driven', label: 'Event-Driven' },
  { value: 'cqrs', label: 'CQRS' },
  { value: 'cqrs-es', label: 'CQRS + Event Sourcing' },
  { value: 'ddd', label: 'Domain-Driven Design' },
  { value: 'serverless', label: 'Serverless / FaaS' },
  { value: 'soa', label: 'Service-Oriented (SOA)' },
  { value: 'pipeline', label: 'Pipes & Filters' },
  { value: 'actor', label: 'Actor Model' },
  { value: 'microkernel', label: 'Microkernel (Plugin)' },
  { value: 'space-based', label: 'Space-Based' },
] as const;

const ARCH_LABEL: Record<string, string> = Object.fromEntries(ARCH_PATTERNS.map((p) => [p.value, p.label]));

/** Engine directive per target error-handling model. Empty/preserve emits nothing (mirror the source). */
const ERROR_RULE: Record<string, string> = {
  exceptions: 'Error handling: model errors using idiomatic exceptions / try-catch in the target language.',
  'result-type': 'Error handling: model errors using Result/Either-style return types rather than exceptions.',
  'error-codes': 'Error handling: propagate explicit error codes / return values for error conditions.',
};

/** All error-handling options. `preserve` is always offered (mirror the source model). */
export const ERROR_OPTIONS = [
  { value: 'preserve', label: 'Preserve source model' },
  { value: 'exceptions', label: 'Exceptions / try-catch' },
  { value: 'result-type', label: 'Result / Either types' },
  { value: 'error-codes', label: 'Explicit error codes' },
] as const;

/** Idiomatic error-handling options for the target language: always `preserve` plus the model that fits the family. */
export function errorOptions(targetLang: string) {
  const t = targetLang.toLowerCase().replace(/[^a-z0-9_]/g, '');
  const pick = (model: string) => ERROR_OPTIONS.filter((o) => o.value === 'preserve' || o.value === model);
  if (['java', 'kotlin', 'scala', 'groovy', 'java_legacy', 'clojure', 'csharp', 'cs', 'vbnet', 'dotnet_legacy'].includes(t)) return pick('exceptions');
  if (['rust', 'haskell', 'fsharp', 'ocaml', 'elixir', 'erlang', 'gleam'].includes(t)) return pick('result-type');
  if (['c', 'go'].includes(t)) return pick('error-codes');
  return ERROR_OPTIONS;
}

/** Build engine instructions derived from wizard config (architecture, naming, error handling, preservation, testing). */
export function configRules(cfg: ProjectConfigLike): string[] {
  const rules: string[] = [];

  const arch = typeof cfg.architecturePattern === 'string' ? cfg.architecturePattern.trim() : '';
  if (arch) rules.push(`Architecture: structure the generated target code following the ${ARCH_LABEL[arch] ?? arch} architecture pattern.`);

  const naming = typeof cfg.namingConvention === 'string' ? cfg.namingConvention.trim() : '';
  if (naming) {
    rules.push(naming === 'preserve'
      ? 'Naming: preserve the original identifier names from the source wherever the target language allows.'
      : `Naming: use ${naming} for identifiers (classes, methods, variables) in the generated target code.`);
  }

  const err = typeof cfg.errorHandling === 'string' ? cfg.errorHandling.trim() : '';
  if (err && ERROR_RULE[err]) rules.push(ERROR_RULE[err]);

  if (cfg.preserveApiSignatures === true) rules.push('Public API: keep the public function/method signatures unchanged wherever the target language allows.');
  if (cfg.preserveBusinessLogic !== false) rules.push('Business logic: reproduce the core business logic exactly — do not alter calculations, branching, or control flow.');
  if (cfg.preserveFormatting === true) rules.push('Formatting: keep the original code structure and statement ordering where reasonable.');
  if (cfg.addTypeAnnotations === true) rules.push('Types: add explicit type annotations wherever the target language supports them.');

  const testsOn = cfg.addTests === true || cfg.generateTests === true;
  const fw = typeof cfg.testingFramework === 'string' ? cfg.testingFramework.trim() : '';
  if (testsOn && fw && !/^none/i.test(fw)) rules.push(`Testing: generate unit and integration tests using the ${fw} framework.`);

  return rules;
}

/** Merge the config-derived directives with the user's free-text custom rules. */
export function mergeRules(base: string | undefined, cfg: ProjectConfigLike): string | undefined {
  const parts = [...configRules(cfg), base].filter((x): x is string => Boolean(x));
  return parts.length ? parts.join('\n') : undefined;
}

export function needsLegacyConvert(
  cfg: ProjectConfigLike,
  project: { source_path?: string; sourcePath?: string; repo_url?: string; repoUrl?: string },
): boolean {
  const repoUrl = String(project.repo_url ?? project.repoUrl ?? '').trim();
  const srcPath = String(project.source_path ?? cfg.sourcePath ?? '').trim();
  const uploadId = typeof cfg.uploadId === 'string' ? cfg.uploadId.trim() : '';

  // Upload-only projects (browser-uploaded folder, no local path or repo URL) must use the
  // /runs endpoint — the legacy /convert endpoint only accepts filesystem paths or repo URLs.
  // All the feature flags below are now also supported by /runs, so there is no reason to
  // force legacy when the source is an upload bundle.
  if (uploadId && !srcPath && !repoUrl) {
    return false;
  }

  if (Array.isArray(cfg.additionalSourceLanguages) && (cfg.additionalSourceLanguages as unknown[]).filter(Boolean).length > 0) {
    return true;
  }
  if (Array.isArray(cfg.dependencyMapping) && (cfg.dependencyMapping as unknown[]).length > 0) {
    return true;
  }
  if (typeof cfg.useStages === 'boolean' || typeof cfg.useRepair === 'boolean') {
    return true;
  }

  const enabledPluginSet = getEnabledPluginSet(cfg);
  if (PIPELINE_PLUGINS.some((p) => !isPluginEnabled(p.id, enabledPluginSet))) {
    return true;
  }

  if (srcPath && !repoUrl && !uploadId) {
    return true;
  }

  return false;
}

export type RunUsageSnapshot = {
  runId: string;
  status: string;
  accounting: {
    totals?: { calls?: number; promptTokens?: number; completionTokens?: number; totalTokens?: number; costUsd?: number };
    byStage?: Array<{ stage: string; calls?: number; totalTokens?: number; costUsd?: number }>;
  } | null;
  redactions: { count: number; byRule: Record<string, number> } | null;
};

export function buildStartRunOptions(
  cfg: ProjectConfigLike,
  srcLang: string,
  tgtLang: string,
  uploadId: string,
): {
  uploadId: string;
  sourceLanguage: string;
  targetLanguage: string;
  additionalSourceLanguages?: string[];
  framework?: string;
  sourceFramework?: string;
  intent?: 'compat-strict' | 'parity' | 'modernize';
  qualityLevel?: number;
  maxIterations?: number;
  preserveComments?: boolean;
  customRules?: string;
  includePatterns?: string;
  excludePatterns?: string;
  useStages?: boolean;
  useRepair?: boolean;
  disabledPlugins?: string[];
  dependencyMapping?: Array<{ id: string; source: string; target: string; version: string; notes: string; status: 'auto' | 'modified' | 'added' }>;
  maxCostUsd?: number;
  maxTotalTokens?: number;
  profile?: 'auto' | 'batch' | 'online' | 'library' | 'utility' | 'mixed';
} {
  const qualityLevelRaw =
    typeof cfg.qualityLevel === 'number' && Number.isFinite(cfg.qualityLevel) ? cfg.qualityLevel : 0;
  const qualityLevel = Math.min(3, Math.max(0, Math.round(qualityLevelRaw)));
  const maxIterations =
    typeof cfg.maxIterations === 'number' && Number.isFinite(cfg.maxIterations) ? cfg.maxIterations : 3;
  const preserveComments = typeof cfg.preserveComments === 'boolean' ? cfg.preserveComments : undefined;

  const userRules =
    typeof cfg.customRules === 'string' && cfg.customRules.trim().length > 0 ? cfg.customRules.trim() : undefined;
  const customRules = mergeRules(userRules, cfg);
  const includePatterns =
    typeof cfg.includePatterns === 'string' && cfg.includePatterns.trim().length > 0
      ? cfg.includePatterns.trim()
      : undefined;
  const excludePatterns =
    typeof cfg.excludePatterns === 'string' && cfg.excludePatterns.trim().length > 0
      ? cfg.excludePatterns.trim()
      : undefined;
  const sourceFramework =
    typeof cfg.sourceFramework === 'string' && cfg.sourceFramework.trim().length > 0
      ? cfg.sourceFramework.trim()
      : undefined;
  const framework =
    typeof cfg.framework === 'string' && cfg.framework.trim().length > 0 ? cfg.framework.trim() : undefined;
  const intent =
    cfg.intent === 'compat-strict' || cfg.intent === 'parity' || cfg.intent === 'modernize'
      ? cfg.intent
      : undefined;
  const engineP95 =
    cfg.engineEstimate !== null &&
    typeof cfg.engineEstimate === 'object' &&
    typeof (cfg.engineEstimate as Record<string, unknown>).p95CostUsd === 'number'
      ? (cfg.engineEstimate as Record<string, unknown>).p95CostUsd as number
      : undefined;
  const maxCostUsd =
    typeof cfg.maxCostUsd === 'number' && Number.isFinite(cfg.maxCostUsd) && cfg.maxCostUsd > 0
      ? cfg.maxCostUsd
      : engineP95 != null && Number.isFinite(engineP95) && engineP95 > 0
      ? engineP95
      : undefined;
  const maxTotalTokens =
    typeof cfg.maxTotalTokens === 'number' && Number.isFinite(cfg.maxTotalTokens) && cfg.maxTotalTokens > 0
      ? cfg.maxTotalTokens
      : undefined;
  const useStages = typeof cfg.useStages === 'boolean' ? cfg.useStages : undefined;
  const useRepair = typeof cfg.useRepair === 'boolean' ? cfg.useRepair : undefined;
  const additionalSourceLanguages =
    Array.isArray(cfg.additionalSourceLanguages)
      ? (cfg.additionalSourceLanguages as string[]).filter(Boolean)
      : undefined;
  const cfgEnabledPluginSet = getEnabledPluginSet(cfg);
  const disabledPlugins =
    cfgEnabledPluginSet !== null
      ? PIPELINE_PLUGINS.map((p) => p.id).filter((id) => !cfgEnabledPluginSet.has(id))
      : undefined;
  const dependencyMapping =
    Array.isArray(cfg.dependencyMapping) && (cfg.dependencyMapping as unknown[]).length > 0
      ? (cfg.dependencyMapping as Array<{ id: string; source: string; target: string; version: string; notes: string; status: 'auto' | 'modified' | 'added' | 'removed' }>)
          .filter((e) => e.status !== 'removed') as Array<{ id: string; source: string; target: string; version: string; notes: string; status: 'auto' | 'modified' | 'added' }>
      : undefined;
  const VALID_PROFILES = ['auto', 'batch', 'online', 'library', 'utility', 'mixed'] as const;
  const profileRaw = typeof cfg.projectProfile === 'string' ? cfg.projectProfile.trim() : '';
  const profile = (VALID_PROFILES as readonly string[]).includes(profileRaw) && profileRaw !== 'auto'
    ? profileRaw as 'batch' | 'online' | 'library' | 'utility' | 'mixed'
    : undefined;

  return {
    uploadId,
    sourceLanguage: srcLang,
    targetLanguage: tgtLang,
    qualityLevel,
    maxIterations,
    ...(preserveComments !== undefined ? { preserveComments } : {}),
    ...(customRules ? { customRules } : {}),
    ...(includePatterns ? { includePatterns } : {}),
    ...(excludePatterns ? { excludePatterns } : {}),
    ...(sourceFramework ? { sourceFramework } : {}),
    ...(framework ? { framework } : {}),
    ...(intent ? { intent } : {}),
    ...(useStages !== undefined ? { useStages } : {}),
    ...(useRepair !== undefined ? { useRepair } : {}),
    ...(additionalSourceLanguages?.length ? { additionalSourceLanguages } : {}),
    ...(disabledPlugins?.length ? { disabledPlugins } : {}),
    ...(dependencyMapping?.length ? { dependencyMapping } : {}),
    ...(maxCostUsd ? { maxCostUsd } : {}),
    ...(maxTotalTokens ? { maxTotalTokens } : {}),
    ...(profile ? { profile } : {}),
  };
}
