/**
 * start CLI execute — Execution logic, no validation
 */

import type { StartSpec, StartResult } from './types.js';
import { readConfig } from '../lib/config.js';
import { buildFullBranchName, getBaseBranch, normalizeNameForType } from '../lib/branch.js';
import { getWorktreePath } from '../lib/worktree.js';
import { branchExists, fetch, checkoutNew, push, execGit } from '../lib/git.js';
import { writeAndCommitVersion } from '../lib/version.js';
import { isCompliantMigrationName } from '../lib/efcore.js';

/**
 * 4.x start-efcore-preflight (non-blocking): for release/hotfix, list tracked
 * migration files whose names don't follow the convention so the caller can
 * suggest `/efcore squash`. Returns the offending basenames.
 */
async function scanNonCompliantMigrations(cwd?: string): Promise<string[]> {
  const r = await execGit(['ls-files'], cwd);
  if (r.exitCode !== 0) return [];
  const files = r.stdout
    .split('\n')
    .filter(
      (f) =>
        /\/Migrations\//i.test(f) &&
        f.endsWith('.cs') &&
        !/\.Designer\.cs$/i.test(f) &&
        !/ModelSnapshot\.cs$/i.test(f),
    );
  return files.filter((f) => !isCompliantMigrationName(f)).map((f) => f.split(/[/\\]/).pop() || f);
}

export async function execute(spec: StartSpec, cwd?: string): Promise<StartResult> {
  try {
    const config = await readConfig();
    // Per-type naming: feature names → kebab; release/hotfix versions keep dots
    // (e.g. `3.53.0` stays `release/3.53.0`, never `release/3530`).
    const normalized = normalizeNameForType(spec.name, spec.type);
    const fullBranch = buildFullBranchName(normalized, spec.type, config);
    const baseBranch = getBaseBranch(spec.type, config);
    const warnings: string[] = [];

    if (await branchExists(fullBranch, false, cwd)) {
      return {
        success: false,
        error: `Branch ${fullBranch} already exists locally`,
        branch: fullBranch,
        branchType: spec.type,
        baseBranch,
        pushed: false,
      };
    }

    if (await branchExists(fullBranch, true, cwd)) {
      return {
        success: false,
        error: `Branch ${fullBranch} already exists on remote`,
        branch: fullBranch,
        branchType: spec.type,
        baseBranch,
        pushed: false,
      };
    }

    // EF Core migration-naming preflight (release/hotfix) — restored from 4.x,
    // surfaced as a non-blocking warning (the skill can offer /efcore squash).
    if (spec.type === 'release' || spec.type === 'hotfix') {
      const bad = await scanNonCompliantMigrations(cwd);
      if (bad.length > 0) {
        const shown = bad.slice(0, 5).join(', ');
        warnings.push(
          `EF Core: ${bad.length} migration(s) with non-compliant names — run /efcore squash before this ${spec.type}: ${shown}${bad.length > 5 ? ', …' : ''}`,
        );
      }
    }

    // A release/hotfix carries the version it will release — materialized below.
    const releaseVersion =
      spec.type === 'release' || spec.type === 'hotfix' ? normalized : undefined;
    let versionSet = false;

    if (!spec.dryRun) {
      await fetch('origin', cwd);

      // Create the branch (worktree when enabled+resolvable, else flat checkout)
      // and resolve the working dir where it is checked out, so the version
      // commit lands ON the new branch.
      let workdir = cwd ?? process.cwd();
      let worktreePath: string | undefined;

      const wtPath = config.worktrees.enabled ? getWorktreePath(fullBranch, config) : '';
      if (wtPath) {
        const { createWorktree } = await import('../lib/worktree.js');
        await createWorktree(fullBranch, wtPath, baseBranch, cwd);
        workdir = wtPath;
        worktreePath = wtPath;
      } else {
        // Create AND check out the branch in one op: `git checkout -b <b> <base>`.
        await checkoutNew(fullBranch, baseBranch, cwd);
      }

      // Model B: materialize the release/hotfix version in the sources (+ tracked
      // config) as the FIRST commit, so the branch carries exactly the version it
      // will tag at finish. Without this, the tag and package.json diverge and the
      // publish pipeline ships the wrong version.
      if (releaseVersion) {
        const set = await writeAndCommitVersion(
          workdir,
          releaseVersion,
          config.versioning.sources,
          `chore: set version to ${releaseVersion}`,
        );
        versionSet = set.committed;
        if (set.error) warnings.push(`Could not set version ${releaseVersion}: ${set.error}`);
        else if (!set.committed) warnings.push(`Sources already at ${releaseVersion} — no version commit needed.`);
      }

      if (!spec.noPush) {
        await push(fullBranch, workdir, true);
      }

      return {
        success: true,
        branch: fullBranch,
        branchType: spec.type,
        baseBranch,
        worktreePath,
        pushed: !spec.noPush,
        versionSet,
        version: releaseVersion,
        warnings: warnings.length > 0 ? warnings : undefined,
      };
    }

    return {
      success: true,
      branch: fullBranch,
      branchType: spec.type,
      baseBranch,
      pushed: false,
      versionSet: false,
      version: releaseVersion,
      warnings: warnings.length > 0 ? warnings : undefined,
    };
  } catch (err: unknown) {
    return {
      success: false,
      error: (err as Error).message,
      branch: '',
      branchType: spec.type,
      baseBranch: '',
      pushed: false,
    };
  }
}
