#!/usr/bin/env dart
// cursor_gen — Flutter Cursor AI config generator
// Usage: cursor_gen [options]

import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'package:args/args.dart';
import 'package:path/path.dart' as p;
import '../src/models.dart';
import '../src/brief_loader.dart';
import '../src/resolver.dart';
import '../src/renderer.dart';
import '../src/version_manager.dart';
import '../src/override_manager.dart';
import '../src/validator.dart';
import '../src/wizard.dart';
import '../src/telemetry.dart';
import '../src/logger.dart';

Future<void> main(List<String> arguments) async {
  final parser = ArgParser()
    ..addFlag('help', abbr: 'h', negatable: false, help: 'Show usage')
    ..addFlag('validate',
        abbr: 'v',
        negatable: false,
        help: 'Validate project-brief.yaml without writing files')
    ..addFlag('refresh',
        abbr: 'r',
        negatable: false,
        help:
            'Re-generate .cursor/ preserving custom/ and CURSOR:CUSTOM markers')
    ..addFlag('check-updates',
        negatable: false, help: 'Check if newer template version is available')
    ..addFlag('diff',
        negatable: false, help: 'Preview what would change before refreshing')
    ..addFlag('wizard',
        abbr: 'w',
        negatable: false,
        help: 'Interactive wizard to create project-brief.yaml')
    ..addFlag('telemetry',
        negatable: false, help: 'Show telemetry / rule-trigger report')
    ..addOption('brief',
        abbr: 'b',
        defaultsTo: 'project-brief.yaml',
        help: 'Path to project-brief.yaml')
    ..addOption('output',
        abbr: 'o', defaultsTo: '.cursor', help: 'Output directory')
    ..addOption('templates',
        defaultsTo: '', help: 'Override template library path');

  final ArgResults args;
  try {
    args = parser.parse(arguments);
  } catch (e) {
    Logger.error('$e');
    print(parser.usage);
    exit(1);
  }

  if (args['help'] as bool) {
    _printBanner();
    print(parser.usage);
    exit(0);
  }

  if (args['wizard'] as bool) {
    await Wizard.run(outputPath: args['brief'] as String);
    exit(0);
  }

  final briefPath = args['brief'] as String;
  final outputDir = args['output'] as String;
  final templateSrc = args['templates'] as String;

  if (args['validate'] as bool) {
    final result = await Validator.validateFile(briefPath);
    if (result.isValid) {
      Logger.success('✔ project-brief.yaml is valid');
    } else {
      Logger.error('✘ Validation failed:');
      for (final e in result.errors) Logger.error('  • $e');
      exit(1);
    }
    exit(0);
  }

  if (args['check-updates'] as bool) {
    await VersionManager.checkUpdates(briefPath: briefPath);
    exit(0);
  }

  if (args['telemetry'] as bool) {
    await Telemetry.printReport(outputDir: outputDir);
    exit(0);
  }

  Logger.info('Loading project brief from $briefPath...');
  final brief = await BriefLoader.load(briefPath);
  final validation = await Validator.validate(brief);
  if (!validation.isValid) {
    Logger.error('✘ Brief validation failed:');
    for (final e in validation.errors) Logger.error('  • $e');
    exit(1);
  }

  final versionStatus = await VersionManager.check(brief: brief);
  if (versionStatus.hasUpdate) {
    Logger.warn(
        '\n⚠  Template update available: ${versionStatus.currentVersion} → ${versionStatus.latestVersion}');
    Logger.warn('   Run: cursor_gen --check-updates  for full diff\n');
  }

  if (args['diff'] as bool) {
    await _runDiff(brief, outputDir, templateSrc);
    exit(0);
  }

  _printBanner();
  Logger.info('Generating .cursor/ for ${brief.projectName}');
  Logger.dim(
      '  Stack: ${brief.stateManagement} + ${brief.architecture} + ${brief.backends.join("+")}');
  Logger.dim('  Platform: ${brief.platforms.join(", ")}');
  Logger.dim(
      '  Codegen: ${brief.codegenTools.isEmpty ? "none" : brief.codegenTools.join(", ")}\n');

  final isRefresh = args['refresh'] as bool;
  final overrideSnapshot = await OverrideManager.snapshot(outputDir);

  final templateFiles = Resolver.resolve(brief);
  Logger.info('Resolved ${templateFiles.length} template files');

  final templateDir =
      templateSrc.isEmpty ? await _defaultTemplateDir() : templateSrc;
  final rendered = await Renderer.render(
    brief: brief,
    templateFiles: templateFiles,
    templateSrc: templateDir,
  );

  await _writeOutput(outputDir, rendered, overrideSnapshot, isRefresh);
  await _writeMetadataJson(outputDir, brief);
  await VersionManager.writeLock(
      outputDir: outputDir, briefPath: briefPath, brief: brief);
  await Telemetry.record(
      projectName: brief.projectName,
      outputDir: outputDir,
      templateFiles: templateFiles);

  Logger.success('\n✔  Done! ${rendered.length} files written to $outputDir/');
  if (overrideSnapshot.customFiles.isNotEmpty) {
    Logger.success(
        '  ↳ ${overrideSnapshot.customFiles.length} custom override(s) preserved untouched');
  }
  Logger.dim(
      '\n  Next: commit .cursor/ to git so all teammates get the same config.');
}

Future<void> _runDiff(
    dynamic brief, String outputDir, String templateSrc) async {
  final templateFiles = Resolver.resolve(brief);
  final rendered = await Renderer.render(
    brief: brief,
    templateFiles: templateFiles,
    templateSrc:
        templateSrc.isEmpty ? await _defaultTemplateDir() : templateSrc,
  );
  Logger.info('Diff preview (no files written):\n');
  for (final entry in rendered.entries) {
    final existing = File('$outputDir/${entry.key}');
    if (!existing.existsSync()) {
      Logger.success('  + ${entry.key}');
    } else {
      final current = await existing.readAsString();
      if (current != entry.value)
        Logger.warn('  ~ ${entry.key}');
      else
        Logger.dim('  = ${entry.key}');
    }
  }
}

Future<void> _writeOutput(
  String outputDir,
  Map<String, String> rendered,
  OverrideSnapshot snapshot,
  bool isRefresh,
) async {
  for (final entry in rendered.entries) {
    final file = File('$outputDir/${entry.key}');
    await file.parent.create(recursive: true);
    if (isRefresh && file.existsSync()) {
      final merged = OverrideManager.mergeCustomSections(
        newContent: entry.value,
        existingContent: await file.readAsString(),
      );
      await file.writeAsString(merged);
    } else {
      await file.writeAsString(entry.value);
    }
  }
  await OverrideManager.restoreCustomFolder(snapshot, outputDir);
}

Future<void> _writeMetadataJson(String outputDir, ProjectBrief brief) async {
  final path = p.join(outputDir, 'cursor-gen-metadata.json');
  final file = File(path);
  await file.parent.create(recursive: true);
  final encoder = JsonEncoder.withIndent('  ');
  await file.writeAsString(encoder.convert(brief.toMetadataMap()));
}

Future<String> _defaultTemplateDir() async {
  final binDir = p.dirname(Platform.script.toFilePath());
  final candidates = <String>[
    // Monorepo layout: flutter-cursor-templates/generator/bin -> ../templates.
    p.normalize(p.join(binDir, '..', '..', 'templates')),
    // Published package layout: cursor_gen/templates.
    p.normalize(p.join(binDir, '..', 'templates')),
  ];

  final packageUri = await Isolate.resolvePackageUri(
    Uri.parse('package:cursor_gen/src/renderer.dart'),
  );
  if (packageUri != null && packageUri.isScheme('file')) {
    final packageRoot = p.dirname(p.dirname(packageUri.toFilePath()));
    candidates.add(p.normalize(p.join(packageRoot, 'templates')));
  }

  return candidates.firstWhere(
    (path) => Directory(path).existsSync(),
    orElse: () => candidates.last,
  );
}

void _printBanner() {
  print('\n╔══════════════════════════════════╗');
  print('║   cursor_gen — Flutter AI Config  ║');
  print('╚══════════════════════════════════╝\n');
}
