/**
 * execute.ts — Deterministic `gitflow init <url>`.
 *
 * Builds the reference worktree architecture in one shot (no LLM in the loop):
 *   1. bare-clone <url> into <root>/.git
 *   2. import main  → <root>/01-Main   (or main    if !numbered)
 *   3. import develop → <root>/02-Develop (or develop if !numbered) — created from main if absent
 *   4. create <root>/{features,releases,hotfixes} + <root>/.gitflow/cache
 *   5. write the canonical config to <root>/.gitflow/config.json (shared by all worktrees)
 *
 * Imports from ../lib/. No validation here (done in validate.ts).
 */
import { join, dirname, basename } from 'path';
import { existsSync, mkdirSync, rmSync } from 'fs';
import type { InitSpec, InitResult } from './types.js';
import type { GitFlowConfig, NameVariants } from '../lib/types.js';
import { detectPlatform } from '../lib/platform.js';
import { detectProvider } from '../lib/provider.js';
import { cloneBare, addWorktreeCheckout, resolveMainBranch } from '../lib/worktree.js';
import { detectVersion } from '../lib/version.js';
import { createDefaultConfig, writeConfig } from '../lib/config.js';
import { normalizeForStorage } from '../lib/paths.js';

export async function execute(spec: InitSpec, workDir: string = process.cwd()): Promise<InitResult> {
  let cleanupRoot: string | null = null;
  try {
    const platformInfo = detectPlatform();

    // ── A repository URL is mandatory ───────────────────────
    if (!spec.url) {
      return {
        success: true,
        needsInput: true,
        detected: { platform: platformInfo.platform, provider: 'unknown' },
        validation: {
          blockers: [],
          warnings: ['A repository URL is required: gitflow init <url>'],
        },
      };
    }

    const url = spec.url;
    const provider = detectProvider(url);
    const name = spec.name || deriveName(url);
    const source = normalizeForStorage(spec.source || normalizeForStorage(workDir));
    // `root` lets the project folder differ from the project name (e.g. clone
    // FribikeSharing INTO an existing client folder D:/.../TPF). Default: source/name.
    const root = spec.root ? normalizeForStorage(spec.root) : join(source, name).replace(/\\/g, '/');
    const alreadyInitialized = existsSync(join(root, '.git')) || existsSync(join(root, '.bare'));

    // ── Proposal mode — derive name + check target, touch nothing on disk ──
    // Lets the skill confirm name + layout + source before the (network) clone.
    if (spec.propose || !spec.name) {
      return {
        success: true,
        needsInput: true,
        detected: { platform: platformInfo.platform, provider },
        proposal: {
          name,
          source,
          root,
          numbered: spec.numbered,
          exists: alreadyInitialized,
          alternates: proposeAlternates(name, source),
        },
        validation: {
          blockers: [],
          warnings: alreadyInitialized ? [`${root} already contains a git repo`] : [],
        },
      };
    }

    // Guard: never clobber an existing repo unless explicitly forced.
    if (alreadyInitialized && !spec.force) {
      return {
        success: false,
        needsInput: true,
        detected: { platform: platformInfo.platform, provider },
        proposal: {
          name,
          source,
          root,
          numbered: spec.numbered,
          exists: true,
          alternates: proposeAlternates(name, source),
        },
        error: `${root} already contains a git repo. Pass "force": true to reuse it, or choose another name/root.`,
        validation: {
          blockers: [`${root} already contains a git repo (.git/.bare present)`],
          warnings: [],
        },
      };
    }

    // ── 1. Bare clone into <root>/.git ──────────────────────
    const bareDir = join(root, '.git').replace(/\\/g, '/');
    const rootPreexisted = existsSync(root);
    if (alreadyInitialized && spec.force) {
      // Forced re-init: drop the previous (possibly partial) repo first.
      rmSync(join(root, '.git'), { recursive: true, force: true });
      rmSync(join(root, '.bare'), { recursive: true, force: true });
    }
    mkdirSync(root, { recursive: true });
    if (!rootPreexisted) cleanupRoot = root; // roll back our fresh dir if a later step fails
    await cloneBare(url, bareDir);

    // ── 2/3. Import main + develop as worktrees ─────────────
    // mainBranch is the MAINLINE (main/master) — never the repo's HEAD default,
    // since repos that default to develop (e.g. FribikeSharing) must not check
    // develop out in BOTH worktrees.
    const mainBranch = spec.mainBranch || (await resolveMainBranch(bareDir));
    const developBranch = spec.developBranch || 'develop';

    // Folder naming is the user's only layout choice. Numbered (01-Main/02-Develop,
    // the "organized" mode) or plain (main/develop, the "simple" mode). The config
    // `mode` is DERIVED from it so the two can never disagree.
    const mode = spec.mode ?? (spec.numbered ? 'organized' : 'simple');
    const mainDir = join(root, spec.numbered ? '01-Main' : 'main').replace(/\\/g, '/');
    const developDir = join(root, spec.numbered ? '02-Develop' : 'develop').replace(/\\/g, '/');

    await addWorktreeCheckout(bareDir, mainDir, mainBranch);

    // Create the develop worktree only when develop is a DISTINCT branch (it is
    // branched off main when the remote has none). Skip it for the degenerate
    // single-branch repo where develop would collide with main.
    const warnings: string[] = [];
    const developCreated = developBranch !== mainBranch;
    if (developCreated) {
      await addWorktreeCheckout(bareDir, developDir, developBranch, mainBranch);
    } else {
      warnings.push(`No '${developBranch}' branch distinct from the main branch '${mainBranch}' — develop worktree skipped.`);
    }
    // Version detection needs a checked-out worktree (the bare root has no working
    // files): prefer develop, fall back to main. The config itself lives at the root.
    const configHome = developCreated ? developDir : mainDir;

    // ── 4. Ephemeral worktree folders + gitflow cache ───────
    const featuresDir = join(root, 'features').replace(/\\/g, '/');
    const releasesDir = join(root, 'releases').replace(/\\/g, '/');
    const hotfixesDir = join(root, 'hotfixes').replace(/\\/g, '/');
    for (const d of [featuresDir, releasesDir, hotfixesDir]) mkdirSync(d, { recursive: true });
    mkdirSync(join(root, '.gitflow', 'cache'), { recursive: true });

    // ── 5. Canonical config at the shared repo root (.gitflow/) ──
    const versionInfo = await detectVersion(configHome);
    const config: GitFlowConfig = createDefaultConfig({
      platform: {
        detected: platformInfo.platform,
        shell: platformInfo.shell,
        detectedAt: new Date().toISOString(),
      },
      workspace: { path: source, name },
      repository: {
        name,
        rootFolder: root,
        nameVariants: nameVariants(name),
        defaultBranch: mainBranch,
        remoteUrl: url,
      },
      git: {
        provider,
        branches: { main: mainBranch, develop: developBranch },
        prefixes: { feature: 'feature/', release: 'release/', hotfix: 'hotfix/' },
      },
      worktrees: {
        enabled: mode !== 'disabled',
        mode,
        structure: {
          main: mainDir,
          develop: developCreated ? developDir : mainDir,
          features: featuresDir,
          releases: releasesDir,
          hotfixes: hotfixesDir,
        },
      },
      versioning: {
        strategy: 'semver',
        current: versionInfo.current,
        tagPrefix: 'v',
        sources: ['csproj', 'package.json', 'VERSION'],
      },
    });

    const configPath = join(root, '.gitflow', 'config.json').replace(/\\/g, '/');
    await writeConfig(config, configPath);

    return {
      success: true,
      detected: { platform: platformInfo.platform, provider, defaultBranch: mainBranch },
      structure: {
        root,
        bare: bareDir,
        main: mainDir,
        develop: developCreated ? developDir : mainDir,
        features: featuresDir,
        releases: releasesDir,
        hotfixes: hotfixesDir,
        configPath,
      },
      config,
      validation: { blockers: [], warnings },
    };
  } catch (err) {
    const message = (err as Error).message;
    if (cleanupRoot) {
      // Roll back a partially-created project so a retry starts clean.
      try { rmSync(cleanupRoot, { recursive: true, force: true }); } catch { /* best effort */ }
    }
    return {
      success: false,
      error: message,
      validation: { blockers: [message], warnings: [] },
    };
  }
}

// ── Name derivation ───────────────────────────────────────

/** Last path segment of a repo URL, minus a trailing `.git`. */
function deriveName(url: string): string {
  const clean = url.replace(/[/\\]+$/, '');
  const seg = clean.split(/[/\\:]/).filter(Boolean).pop() || 'project';
  return seg.replace(/\.git$/i, '');
}

/** Suggest free alternate names when the first choice is taken. */
function proposeAlternates(name: string, source: string): string[] {
  const candidates = [`${name}-v5`, `${name}-new`, `${name}-2`];
  return candidates.filter((c) => !existsSync(join(source, c)));
}

function tokenize(name: string): string[] {
  return name.split(/[.\-_\s]+/).filter(Boolean);
}

function pascalWord(w: string): string {
  return w.charAt(0).toUpperCase() + w.slice(1);
}

function nameVariants(name: string): NameVariants {
  const tokens = tokenize(name);
  return {
    pascalCase: tokens.map(pascalWord).join(''),
    pascalCaseDot: tokens.map(pascalWord).join('.'),
    kebabCase: tokens.map((t) => t.toLowerCase()).join('-'),
    snakeCase: tokens.map((t) => t.toLowerCase()).join('_'),
    // short all-lowercase tokens (cli, api, ui, sdk…) read better uppercased
    displayName: tokens
      .map((t) => (t === t.toLowerCase() && t.length <= 3 ? t.toUpperCase() : pascalWord(t)))
      .join(' '),
  };
}

// basename is imported for parity with other CLIs / future use.
void basename;
