/**
 * GitHub Actions workflow content for exported / pushed migrations.
 * Uses project source + target languages and wizard `config` (targetVersion, packageManager) — no fixed stack.
 */

export function normalizeProjectLang(raw: string | null | undefined): string {
  return (raw ?? '').trim().toLowerCase().replace(/\s.*$/, '').replace(/-\d+(\.\d+)*$/u, '');
}

function slugLang(s: string | null | undefined): string {
  return (s ?? 'unspecified').trim().toLowerCase().replace(/[^a-z0-9._-]+/gu, '-').slice(0, 80) || 'unspecified';
}

function escapeYamlDouble(s: string): string {
  return s.replace(/\\/gu, '\\\\').replace(/"/gu, '\\"');
}

function strConfig(config: Record<string, unknown> | null | undefined, key: string): string {
  const v = config?.[key];
  return typeof v === 'string' ? v : '';
}

/** Major JDK version string for setup-java (digits only). */
export function resolveJdkVersionForWorkflow(
  targetLangNorm: string,
  targetVersion: string,
): string {
  const v = targetVersion.trim();
  const mTwo = v.match(/java[^\d]*(\d{2,3})\b/i);
  if (mTwo) {
    const n = parseInt(mTwo[1], 10);
    if (n >= 8 && n <= 99) return String(n);
  }
  const mOne = v.match(/java[^\d]*(\d)\b/i);
  if (mOne) {
    const n = parseInt(mOne[1], 10);
    if (n >= 8) return String(n);
    return '8';
  }
  if (targetLangNorm === 'kotlin' || targetLangNorm === 'scala') {
    const tail = v.match(/(\d{1,2})\s*$/);
    if (tail) {
      const n = parseInt(tail[1], 10);
      if (n >= 11 && n <= 99) return String(n);
    }
  }
  return '21';
}

function resolvePythonVersion(targetVersion: string): string {
  const m = targetVersion.match(/python\s*([\d.]+)/i) ?? targetVersion.match(/^([\d.]+)$/);
  if (m?.[1]) {
    const parts = m[1].split('.');
    return parts.length >= 2 ? `${parts[0]}.${parts[1]}` : `${parts[0]}.12`;
  }
  return '3.12';
}

function resolveNodeVersion(targetVersion: string): string {
  const v = targetVersion.trim();
  const m = v.match(/node[^\d]*(\d+)/i) ?? v.match(/(\d{2})\s*lts/i);
  if (m?.[1]) return m[1];
  // "TypeScript 5.x" / "JavaScript ES2022" must not yield a bogus two-digit "Node" version
  if (/typescript|javascript|ecma|es20/i.test(v)) return '20';
  const tail = v.match(/\b(\d{2})\b/);
  return tail ? tail[1] : '20';
}

function resolveDotnetVersion(targetVersion: string): string {
  const m = targetVersion.match(/\.net\s*(?:framework\s*)?(\d+(?:\.\d+)?)/i) ?? targetVersion.match(/(\d+)\s*$/);
  if (m?.[1]) {
    const major = parseInt(m[1].split('.')[0] ?? '8', 10);
    if (major <= 4) return '8.0.x';
    return `${major}.0.x`;
  }
  return '8.0.x';
}

function resolvePhpVersion(targetVersion: string): string {
  const m = targetVersion.match(/php\s*([\d.]+)/i);
  return m?.[1] ?? '8.3';
}

function resolveGoVersion(targetVersion: string): string {
  const m = targetVersion.match(/go\s*([\d.]+)/i);
  return m?.[1] ?? '1.22';
}

function resolveRubyVersion(targetVersion: string): string {
  const m = targetVersion.match(/ruby\s*([\d.]+(?:\.[\d]+)?)/i);
  return m?.[1] ?? '3.2';
}

export type ScribaCicdInput = {
  projectName: string;
  sourceLanguage?: string | null;
  targetLanguage?: string | null;
  config?: Record<string, unknown> | null;
  /** ZIP export adds an optional Docker step when a Dockerfile exists */
  includeOptionalDocker?: boolean;
};

function canonicalFamily(norm: string):
  | 'jvm'
  | 'node'
  | 'python'
  | 'php'
  | 'dotnet'
  | 'go'
  | 'rust'
  | 'ruby'
  | 'swift'
  | 'other' {
  if (['java', 'kotlin', 'scala', 'groovy', 'java_legacy', 'clojure'].includes(norm)) return 'jvm';
  if (['typescript', 'javascript', 'node'].includes(norm)) return 'node';
  if (norm === 'python') return 'python';
  if (norm === 'php' || norm === 'php_legacy') return 'php';
  if (['csharp', 'cs', 'fsharp', 'dotnet_legacy', 'vbnet'].includes(norm)) return 'dotnet';
  if (norm === 'go') return 'go';
  if (norm === 'rust') return 'rust';
  if (norm === 'ruby') return 'ruby';
  if (norm === 'swift') return 'swift';
  return 'other';
}

function resolveSwiftVersion(targetVersion: string): string {
  const m = targetVersion.match(/swift\s*([\d.]+)/i);
  if (m?.[1]) return m[1];
  const tail = targetVersion.match(/(\d+\.\d+)/);
  return tail?.[1] ?? '5.10';
}

function normalizePackageManager(raw: string): string {
  return raw.trim().toLowerCase();
}

/** Prefer explicit package manager; fall back to target build label (e.g. "Gradle", "Maven"). */
function inferJvmBuildHint(config: Record<string, unknown> | undefined): 'maven' | 'gradle' | 'sbt' | 'auto' {
  const pm = normalizePackageManager(strConfig(config, 'packageManager'));
  if (pm === 'maven') return 'maven';
  if (pm === 'gradle') return 'gradle';
  const tb = strConfig(config, 'targetBuild').toLowerCase();
  if (tb.includes('maven')) return 'maven';
  if (tb.includes('gradle')) return 'gradle';
  if (tb.includes('sbt')) return 'sbt';
  return 'auto';
}

function jvmBuildScript(hint: 'maven' | 'gradle' | 'sbt' | 'auto'): string {
  const auto = `if [ -f pom.xml ]; then
  mvn -B test --no-transfer-progress || mvn -B package --no-transfer-progress
elif [ -f gradlew ]; then
  chmod +x gradlew 2>/dev/null || true
  ./gradlew test || ./gradlew build
elif [ -f build.gradle ] || [ -f build.gradle.kts ]; then
  echo "::warning::Gradle wrapper missing — add gradlew or run Gradle locally, then commit."
  exit 1
elif [ -f build.sbt ]; then
  echo "SBT project detected — install sbt in CI or use a container action."
  (command -v sbt >/dev/null 2>&1 && sbt -batch test) || echo "::notice::sbt not installed on runner; add setup or use Docker."
elif [ -f project.clj ]; then
  (command -v lein >/dev/null 2>&1 && lein test) || echo "::notice::Leiningen not installed; add technote-space/setup-lein or equivalent."
elif [ -f deps.edn ]; then
  (command -v clojure >/dev/null 2>&1 && clojure -M:test) || echo "::notice::Clojure CLI not installed; add DeLaGuardo/setup-clojure or equivalent."
else
  echo "::notice::No standard JVM build file (pom.xml, Gradle wrapper, build.sbt, project.clj, deps.edn) found at repo root — adjust paths or add build files."
fi`;
  if (hint === 'maven') {
    return `if [ -f pom.xml ]; then
  mvn -B test --no-transfer-progress || mvn -B package --no-transfer-progress
else
  echo "::warning::Expected Maven (pom.xml) per project config — falling back to auto-detect."
${auto}
fi`;
  }
  if (hint === 'gradle') {
    return `if [ -f gradlew ]; then
  chmod +x gradlew 2>/dev/null || true
  ./gradlew test || ./gradlew build
elif [ -f build.gradle ] || [ -f build.gradle.kts ]; then
  echo "::warning::Expected Gradle wrapper per project config."
  exit 1
else
  echo "::warning::Expected Gradle per project config — falling back to auto-detect."
${auto}
fi`;
  }
  if (hint === 'sbt') {
    return `if [ -f build.sbt ]; then
  (command -v sbt >/dev/null 2>&1 && sbt -batch test) || echo "::notice::Install sbt for CI (e.g. olafurpg/setup-scala)."
else
${auto}
fi`;
  }
  return auto;
}

function nodeInstallAndTest(pm: string): string {
  const p = normalizePackageManager(pm);
  if (p === 'yarn') {
    return `corepack enable
yarn install --immutable
yarn test || true
yarn run build || true`;
  }
  if (p === 'pnpm') {
    return `corepack enable
pnpm install --frozen-lockfile
pnpm test || true
pnpm run build || true`;
  }
  return `npm ci
npm test || true
npm run build || true`;
}

function pythonInstallAndTest(pm: string): string {
  const p = normalizePackageManager(pm);
  if (p === 'poetry') {
    return `pipx install poetry || pip install poetry
poetry install
poetry run pytest || pytest`;
  }
  return `pip install -r requirements.txt || pip install .
pytest || true`;
}

export function buildScribaDeployWorkflow(input: ScribaCicdInput): string {
  const projectName = input.projectName || 'project';
  const safeName = escapeYamlDouble(projectName);
  const config = (input.config ?? undefined) as Record<string, unknown> | undefined;
  const targetVersion = strConfig(config, 'targetVersion');
  const packageManager = strConfig(config, 'packageManager');

  const rawTarget = input.targetLanguage ?? '';
  const rawSource = input.sourceLanguage ?? '';
  const tgtNorm = normalizeProjectLang(rawTarget);
  const srcSlug = slugLang(rawSource);
  const tgtSlug = slugLang(rawTarget);
  const family = canonicalFamily(tgtNorm);
  const dockerName = slugLang(projectName).replace(/\./gu, '-');
  const dockerStep =
    input.includeOptionalDocker === true
      ? `      - name: Build Docker image
        if: hashFiles('Dockerfile') != ''
        run: docker build -t ${dockerName}:staging .
`
      : '';

  const header = `name: "Scriba — ${srcSlug} → ${tgtSlug}: ${safeName}"
on:
  push:
    branches: ['scriba/migrate-*', 'main']
env:
  SCRIBA_SOURCE_LANG: ${srcSlug}
  SCRIBA_TARGET_LANG: ${tgtSlug}
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
`;

  const footerDeploy = `      - name: Deploy to staging
        run: echo "Configure deploy for target ${tgtSlug} (source ${srcSlug})"
`;

  if (family === 'jvm') {
    const jdk = resolveJdkVersionForWorkflow(tgtNorm, targetVersion);
    const hint = inferJvmBuildHint(config);
    const script = jvmBuildScript(hint);
    return `${header}      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: '${jdk}'
      - name: Build and test (JVM — ${hint === 'auto' ? 'auto-detect' : hint})
        run: |
${script.split('\n').map((l) => `          ${l}`).join('\n')}
${dockerStep}${footerDeploy}`;
  }

  if (family === 'node') {
    const nodeV = resolveNodeVersion(targetVersion);
    const body = nodeInstallAndTest(packageManager || 'npm');
    return `${header}      - uses: actions/setup-node@v4
        with:
          node-version: '${nodeV}'
      - name: Install, test, and build
        run: |
${body.split('\n').map((l) => `          ${l}`).join('\n')}
${dockerStep}${footerDeploy}`;
  }

  if (family === 'python') {
    const py = resolvePythonVersion(targetVersion);
    const body = pythonInstallAndTest(packageManager || 'pip');
    return `${header}      - uses: actions/setup-python@v5
        with:
          python-version: '${py}'
      - name: Install and test
        run: |
${body.split('\n').map((l) => `          ${l}`).join('\n')}
${dockerStep}${footerDeploy}`;
  }

  if (family === 'php') {
    const php = resolvePhpVersion(targetVersion);
    return `${header}      - uses: shivammathur/setup-php@v2
        with:
          php-version: '${php}'
      - name: Install and test
        run: |
          composer install --no-interaction || true
          ./vendor/bin/phpunit || true
${dockerStep}${footerDeploy}`;
  }

  if (family === 'dotnet') {
    const dn = resolveDotnetVersion(targetVersion);
    return `${header}      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '${dn}'
      - name: Build and test
        run: dotnet test || dotnet build
${dockerStep}${footerDeploy}`;
  }

  if (family === 'go') {
    const gv = resolveGoVersion(targetVersion);
    return `${header}      - uses: actions/setup-go@v5
        with:
          go-version: '${gv}'
      - name: Test
        run: go test ./... || true
${dockerStep}${footerDeploy}`;
  }

  if (family === 'rust') {
    return `${header}      - uses: dtolnay/rust-toolchain@stable
      - name: Test
        run: cargo test || true
${dockerStep}${footerDeploy}`;
  }

  if (family === 'ruby') {
    const rv = resolveRubyVersion(targetVersion);
    return `${header}      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: '${rv}'
          bundler-cache: true
      - name: Bundle and test
        run: |
          bundle install || true
          bundle exec rake test || bundle exec rspec || true
${dockerStep}${footerDeploy}`;
  }

  if (family === 'swift') {
    const sv = resolveSwiftVersion(targetVersion);
    return `${header}      - uses: swift-actions/setup-swift@v2
        with:
          swift-version: '${sv}'
      - name: Build and test
        run: swift build && swift test || true
${dockerStep}${footerDeploy}`;
  }

  return `${header}      - name: Configure CI for your stack
        run: |
          echo "Scriba migration: ${srcSlug} → ${tgtSlug}"
          echo "Target id (normalized): ${tgtNorm || 'unknown'}"
          echo "Add setup-* actions and build/test steps for your language here."
${dockerStep}${footerDeploy}`;
}

// ─── GitLab CI (.gitlab-ci.yml) ─────────────────────────────────────────────

function glDockerImage(family: ReturnType<typeof canonicalFamily>, targetVersion: string, tgtNorm: string): string {
  if (family === 'jvm') {
    const jdk = resolveJdkVersionForWorkflow(tgtNorm, targetVersion);
    return `eclipse-temurin:${jdk}`;
  }
  if (family === 'node') return `node:${resolveNodeVersion(targetVersion)}`;
  if (family === 'python') return `python:${resolvePythonVersion(targetVersion)}`;
  if (family === 'dotnet') {
    const dn = resolveDotnetVersion(targetVersion).replace('.0.x', '');
    return `mcr.microsoft.com/dotnet/sdk:${dn}`;
  }
  if (family === 'php') return `php:${resolvePhpVersion(targetVersion)}-cli`;
  if (family === 'go') return `golang:${resolveGoVersion(targetVersion)}`;
  if (family === 'rust') return 'rust:latest';
  if (family === 'ruby') return `ruby:${resolveRubyVersion(targetVersion)}`;
  if (family === 'swift') return `swift:${resolveSwiftVersion(targetVersion)}`;
  return 'ubuntu:latest';
}

function glBuildScript(family: ReturnType<typeof canonicalFamily>, packageManager: string, tgtNorm: string): string {
  if (family === 'jvm') {
    const hint = tgtNorm === 'scala' ? 'sbt' : packageManager.includes('gradle') ? 'gradle' : packageManager.includes('maven') ? 'maven' : 'auto';
    if (hint === 'maven') return '  - mvn -B test --no-transfer-progress || mvn -B package --no-transfer-progress';
    if (hint === 'gradle') return '  - chmod +x gradlew 2>/dev/null || true\n  - ./gradlew test || ./gradlew build';
    if (hint === 'sbt') return '  - sbt -batch test || true';
    return `  - |
    if [ -f pom.xml ]; then mvn -B test --no-transfer-progress
    elif [ -f gradlew ]; then chmod +x gradlew && ./gradlew test
    else echo "No build file found; add pom.xml or gradlew"; fi`;
  }
  if (family === 'node') {
    const pm = packageManager.includes('yarn') ? 'yarn' : packageManager.includes('pnpm') ? 'pnpm' : 'npm';
    if (pm === 'yarn') return '  - corepack enable\n  - yarn install --immutable\n  - yarn test || true';
    if (pm === 'pnpm') return '  - corepack enable\n  - pnpm install --frozen-lockfile\n  - pnpm test || true';
    return '  - npm ci\n  - npm test || true';
  }
  if (family === 'python') {
    return '  - pip install -r requirements.txt || pip install . || true\n  - pytest || true';
  }
  if (family === 'dotnet') return '  - dotnet test || dotnet build';
  if (family === 'php') return '  - composer install --no-interaction || true\n  - ./vendor/bin/phpunit || true';
  if (family === 'go') return '  - go test ./... || true';
  if (family === 'rust') return '  - cargo test || true';
  if (family === 'ruby') return '  - bundle install || true\n  - bundle exec rake test || bundle exec rspec || true';
  if (family === 'swift') return '  - swift build && swift test || true';
  return '  - echo "Configure build steps for your stack"';
}

export function buildGitLabCiWorkflow(input: ScribaCicdInput): string {
  const config = (input.config ?? undefined) as Record<string, unknown> | undefined;
  const targetVersion = strConfig(config, 'targetVersion');
  const packageManager = strConfig(config, 'packageManager');
  const tgtNorm = normalizeProjectLang(input.targetLanguage ?? '');
  const srcSlug = slugLang(input.sourceLanguage ?? '');
  const tgtSlug = slugLang(input.targetLanguage ?? '');
  const family = canonicalFamily(tgtNorm);
  const image = glDockerImage(family, targetVersion, tgtNorm);
  const buildScript = glBuildScript(family, packageManager, tgtNorm);

  return `image: ${image}

variables:
  SCRIBA_SOURCE_LANG: "${srcSlug}"
  SCRIBA_TARGET_LANG: "${tgtSlug}"

stages:
  - build
  - test
  - deploy

build:
  stage: build
  script:
${buildScript}
  only:
    - scriba/migrate-*
    - main

deploy:
  stage: deploy
  script:
    - echo "Configure deploy for target ${tgtSlug} (source ${srcSlug})"
  only:
    - main
    - scriba/migrate-*
`;
}

// ─── Bitbucket Pipelines (bitbucket-pipelines.yml) ──────────────────────────

export function buildBitbucketPipelinesWorkflow(input: ScribaCicdInput): string {
  const config = (input.config ?? undefined) as Record<string, unknown> | undefined;
  const targetVersion = strConfig(config, 'targetVersion');
  const packageManager = strConfig(config, 'packageManager');
  const tgtNorm = normalizeProjectLang(input.targetLanguage ?? '');
  const srcSlug = slugLang(input.sourceLanguage ?? '');
  const tgtSlug = slugLang(input.targetLanguage ?? '');
  const family = canonicalFamily(tgtNorm);
  const image = glDockerImage(family, targetVersion, tgtNorm);
  const buildScript = glBuildScript(family, packageManager, tgtNorm)
    .split('\n')
    .map(l => l.startsWith('  - ') ? `            - ${l.slice(4)}` : l)
    .join('\n');

  return `image: ${image}

pipelines:
  branches:
    'scriba/migrate-*':
      - step:
          name: Build & Test (${srcSlug} → ${tgtSlug})
          script:
${buildScript}
    main:
      - step:
          name: Build, Test & Deploy
          script:
${buildScript}
            - echo "Configure deploy for target ${tgtSlug}"
`;
}

// ─── Azure Pipelines (azure-pipelines.yml) ──────────────────────────────────

export function buildAzurePipelinesWorkflow(input: ScribaCicdInput): string {
  const config = (input.config ?? undefined) as Record<string, unknown> | undefined;
  const targetVersion = strConfig(config, 'targetVersion');
  const packageManager = strConfig(config, 'packageManager');
  const tgtNorm = normalizeProjectLang(input.targetLanguage ?? '');
  const srcSlug = slugLang(input.sourceLanguage ?? '');
  const tgtSlug = slugLang(input.targetLanguage ?? '');
  const family = canonicalFamily(tgtNorm);

  const trigger = `trigger:
  branches:
    include:
      - 'scriba/migrate-*'
      - main

pool:
  vmImage: 'ubuntu-latest'

variables:
  SCRIBA_SOURCE_LANG: '${srcSlug}'
  SCRIBA_TARGET_LANG: '${tgtSlug}'

steps:
`;

  if (family === 'jvm') {
    const jdk = resolveJdkVersionForWorkflow(tgtNorm, targetVersion);
    const hint = tgtNorm === 'scala' ? 'sbt' : packageManager.includes('gradle') ? 'gradle' : packageManager.includes('maven') ? 'maven' : 'auto';
    const script = hint === 'gradle'
      ? 'chmod +x gradlew 2>/dev/null || true && ./gradlew test || ./gradlew build'
      : hint === 'sbt'
      ? 'sbt -batch test || true'
      : 'if [ -f pom.xml ]; then mvn -B test --no-transfer-progress; elif [ -f gradlew ]; then chmod +x gradlew && ./gradlew test; else echo "No build file"; fi';
    return `${trigger}- task: JavaToolInstaller@0
  inputs:
    versionSpec: '${jdk}'
    jdkArchitectureOption: 'x64'
    jdkSourceOption: 'PreInstalled'
- script: ${script}
  displayName: 'Build and test (${tgtNorm})'
- script: echo "Configure deploy for ${tgtSlug}"
  displayName: 'Deploy'
`;
  }

  if (family === 'node') {
    const nodeV = resolveNodeVersion(targetVersion);
    const pm = packageManager.includes('yarn') ? 'yarn' : packageManager.includes('pnpm') ? 'pnpm' : 'npm';
    const installScript = pm === 'yarn' ? 'corepack enable && yarn install --immutable && yarn test || true'
      : pm === 'pnpm' ? 'corepack enable && pnpm install --frozen-lockfile && pnpm test || true'
      : 'npm ci && npm test || true';
    return `${trigger}- task: NodeTool@0
  inputs:
    versionSpec: '${nodeV}.x'
- script: ${installScript}
  displayName: 'Install and test'
- script: echo "Configure deploy for ${tgtSlug}"
  displayName: 'Deploy'
`;
  }

  if (family === 'python') {
    const py = resolvePythonVersion(targetVersion);
    return `${trigger}- task: UsePythonVersion@0
  inputs:
    versionSpec: '${py}'
- script: pip install -r requirements.txt || pip install . || true && pytest || true
  displayName: 'Install and test'
- script: echo "Configure deploy for ${tgtSlug}"
  displayName: 'Deploy'
`;
  }

  if (family === 'dotnet') {
    const dn = resolveDotnetVersion(targetVersion);
    return `${trigger}- task: UseDotNet@2
  inputs:
    version: '${dn}'
- script: dotnet test || dotnet build
  displayName: 'Build and test'
- script: echo "Configure deploy for ${tgtSlug}"
  displayName: 'Deploy'
`;
  }

  if (family === 'go') {
    const gv = resolveGoVersion(targetVersion);
    return `${trigger}- script: |
    wget -q https://go.dev/dl/go${gv}.linux-amd64.tar.gz -O go.tar.gz
    sudo tar -C /usr/local -xzf go.tar.gz
    export PATH=$PATH:/usr/local/go/bin
    go test ./... || true
  displayName: 'Setup Go ${gv} and test'
- script: echo "Configure deploy for ${tgtSlug}"
  displayName: 'Deploy'
`;
  }

  if (family === 'rust') {
    return `${trigger}- script: cargo test || true
  displayName: 'Test (Rust)'
- script: echo "Configure deploy for ${tgtSlug}"
  displayName: 'Deploy'
`;
  }

  return `${trigger}- script: |
    echo "Scriba migration: ${srcSlug} → ${tgtSlug}"
    echo "Add setup and build steps for your stack here."
  displayName: 'Build and test'
- script: echo "Configure deploy for ${tgtSlug}"
  displayName: 'Deploy'
`;
}
