// validator.dart — Validates project-brief.yaml schema (Pillar 3)

import 'dart:io';
import 'package:yaml/yaml.dart';
import 'models.dart';

class Validator {
  static const _validStateManagement = {
    'bloc',
    'riverpod',
    'getx',
    'hooks_riverpod'
  };
  static const _validArchitectures = {
    'clean',
    'feature_first',
    'mvvm',
    'mvc',
    'layered'
  };
  static const _validRouting = {'gorouter', 'getx_nav', 'auto_route'};
  static const _validBackends = {'firebase', 'supabase', 'rest'};
  static const _validAuth = {
    'firebase_auth',
    'supabase_auth',
    'jwt_rest',
    'oauth2',
    'none'
  };
  static const _validPlatforms = {'ios', 'android', 'web', 'desktop'};
  static const _validCodegen = {
    'freezed',
    'json_serializable',
    'injectable',
    'retrofit'
  };
  static const _validScale = {'small', 'medium', 'large'};
  static const _validTestingDepth = {
    'unit_widget',
    'integration',
    'e2e',
    'full'
  };
  static const _validCicd = {'codemagic', 'github_actions', 'fastlane', 'none'};
  static const _validE2eTools = {'patrol', 'maestro'};
  static const _validDesignSource = {
    'figma_mcp',
    'figma_manual',
    'native_ref',
    'html_ref',
    'none'
  };
  static const _validApiFormats = {'openapi', 'postman', 'markdown', 'none'};
  static const _validThemeVariants = {'light', 'dark', 'high_contrast'};

  static Future<ValidationResult> validateFile(String path) async {
    final file = File(path);
    if (!file.existsSync()) {
      return ValidationResult(
          isValid: false, errors: ['File not found: $path']);
    }
    final content = await file.readAsString();
    final yaml = loadYaml(content) as YamlMap;
    return _validateYaml(yaml);
  }

  static Future<ValidationResult> validate(ProjectBrief brief) async {
    return _validateBrief(brief);
  }

  static ValidationResult _validateYaml(YamlMap yaml) {
    final errors = <String>[];
    final warnings = <String>[];

    final project = yaml['project'] as YamlMap?;
    final stack = yaml['stack'] as YamlMap?;
    final envs = yaml['environments'] as YamlMap?;
    final testing = yaml['testing'] as YamlMap?;
    final design = yaml['design'] as YamlMap?;
    final apiDocs = yaml['api_docs'] as YamlMap?;
    final appCtx = yaml['app_context'] as YamlMap?;

    if (project == null) {
      errors.add('Missing required section: project');
    } else {
      if (project['name'] == null) errors.add('project.name is required');
      if (project['package'] == null) errors.add('project.package is required');
      if (project['scale'] != null && !_validScale.contains(project['scale'])) {
        errors.add('project.scale must be one of: ${_validScale.join(", ")}');
      }
    }

    if (stack == null) {
      errors.add('Missing required section: stack');
    } else {
      _validateField(stack, 'state_management', _validStateManagement, errors);
      _validateField(stack, 'architecture', _validArchitectures, errors);
      _validateField(stack, 'routing', _validRouting, errors);
      _validateField(stack, 'auth', _validAuth, warnings); // warning only

      // Validate platforms list
      final platforms = stack['platforms'];
      if (platforms != null && platforms is YamlList) {
        for (final p in platforms) {
          if (!_validPlatforms.contains(p.toString())) {
            errors.add(
                'stack.platforms contains unknown value: $p. Valid: ${_validPlatforms.join(", ")}');
          }
        }
      }

      // Validate codegen list
      final codegen = stack['codegen'];
      if (codegen != null && codegen is YamlList) {
        for (final c in codegen) {
          if (!_validCodegen.contains(c.toString())) {
            warnings.add(
                'stack.codegen contains unknown value: $c. Known: ${_validCodegen.join(", ")}');
          }
        }
      }

      // Cross-validation: GetX nav requires GetX SM
      if (stack['routing']?.toString() == 'getx_nav' &&
          stack['state_management']?.toString() != 'getx') {
        warnings.add(
            'stack.routing is getx_nav but state_management is not getx. This is unusual.');
      }

      // Cross-validation: web platform warnings
      final platformsList = (stack['platforms'] as YamlList?)
              ?.map((e) => e.toString())
              .toList() ??
          [];
      if (platformsList.contains('web')) {
        warnings.add(
            'Web platform detected: ensure you avoid dart:io imports (use dart:html or flutter_web_plugins)');
      }
    }

    if (envs != null) {
      _validateField(envs, 'cicd', _validCicd, warnings,
          prefix: 'environments');
    }
    if (testing != null) {
      _validateField(testing, 'depth', _validTestingDepth, errors,
          prefix: 'testing');
      _validateField(testing, 'e2e_tool', _validE2eTools, warnings,
          prefix: 'testing');
    }
    if (design != null) {
      _validateField(design, 'source', _validDesignSource, warnings,
          prefix: 'design');
    }
    if (apiDocs != null) {
      _validateField(apiDocs, 'format', _validApiFormats, warnings,
          prefix: 'api_docs');
    }
    if (appCtx != null) {
      final themes = appCtx['theme_variants'];
      for (final t in _yamlStrings(themes)) {
        if (!_validThemeVariants.contains(t)) {
          warnings.add(
              'app_context.theme_variants contains unknown value: $t (expected: ${_validThemeVariants.join(", ")})');
        }
      }
      if (appCtx['roles_enabled'] == true) {
        final names = appCtx['role_names'];
        final list = names is YamlList
            ? names
            : names is List
                ? names
                : const [];
        if (list.isEmpty) {
          warnings.add(
              'app_context.roles_enabled is true but role_names is empty');
        }
      }
    }

    return ValidationResult(
      isValid: errors.isEmpty,
      errors: errors,
      warnings: warnings,
    );
  }

  static ValidationResult _validateBrief(ProjectBrief brief) {
    final errors = <String>[];
    final warnings = <String>[];

    if (brief.projectName.isEmpty) errors.add('project.name is required');
    if (brief.packageId.isEmpty) errors.add('project.package is required');
    if (!_validStateManagement.contains(brief.stateManagement)) {
      errors.add(
          'stack.state_management "${brief.stateManagement}" is not valid. Use: ${_validStateManagement.join(", ")}');
    }
    if (!_validArchitectures.contains(brief.architecture)) {
      errors.add(
          'stack.architecture "${brief.architecture}" is not valid. Use: ${_validArchitectures.join(", ")}');
    }
    for (final b in brief.backends) {
      if (!_validBackends.contains(b)) {
        errors.add(
            'stack.backend contains "$b". Valid values: ${_validBackends.join(", ")}');
      }
    }
    for (final t in brief.themeVariants) {
      if (!_validThemeVariants.contains(t)) {
        warnings.add(
            'app_context.theme_variants contains unknown value: $t (expected: ${_validThemeVariants.join(", ")})');
      }
    }
    if (brief.rolesEnabled && brief.roleNames.isEmpty) {
      warnings.add('roles_enabled is true but role_names is empty');
    }

    return ValidationResult(
        isValid: errors.isEmpty, errors: errors, warnings: warnings);
  }

  static Iterable<String> _yamlStrings(dynamic value) sync* {
    if (value == null) return;
    if (value is YamlList) {
      for (final e in value) yield e.toString();
    } else if (value is List) {
      for (final e in value) yield e.toString();
    } else {
      yield value.toString();
    }
  }

  static void _validateField(
      YamlMap map, String field, Set<String> valid, List<String> output,
      {String prefix = 'stack'}) {
    final val = map[field]?.toString();
    if (val != null && !valid.contains(val)) {
      output
          .add('$prefix.$field "$val" is not valid. Use: ${valid.join(", ")}');
    }
  }
}
