// renderer.dart — Substitutes {{PLACEHOLDERS}} and writes output files

import 'dart:io';
import 'package:path/path.dart' as p;
import 'models.dart';

class Renderer {
  static Future<Map<String, String>> render({
    required ProjectBrief brief,
    required List<String> templateFiles,
    required String templateSrc,
  }) async {
    final context = _buildContext(brief);
    final output = <String, String>{};

    for (final key in templateFiles) {
      final tmplPath = _templatePath(templateSrc, key);
      final file = File(tmplPath);
      if (!file.existsSync()) {
        // Gracefully skip missing optional templates with a placeholder
        output[_outputPath(key)] = _missingTemplatePlaceholder(key);
        continue;
      }
      var content = await file.readAsString();
      content = _substituteAll(content, context);
      _checkUnreplacedPlaceholders(
          content, key); // Pillar 3: validate no broken {{VAR}}
      output[_outputPath(key)] = content;
    }
    return output;
  }

  /// Build the full substitution context from a brief
  static Map<String, String> _buildContext(ProjectBrief brief) {
    return {
      'PROJECT_NAME': brief.projectName,
      'PACKAGE_ID': brief.packageId,
      'DESCRIPTION': brief.description,
      'SCALE': brief.scale,
      'STATE_MANAGEMENT': _displayName(brief.stateManagement),
      'STATE_MGMT_RAW': brief.stateManagement,
      'ARCHITECTURE': _displayName(brief.architecture),
      'ARCH_RAW': brief.architecture,
      'ROUTING': _displayName(brief.routing),
      'ROUTING_RAW': brief.routing,
      'BACKEND': brief.backends.map(_displayName).join(' + '),
      'BACKENDS_LIST': brief.backends.join(', '),
      'AUTH': _displayName(brief.auth),
      'AUTH_RAW': brief.auth,
      'PLATFORMS_LIST': brief.platforms.join(', '),
      'CODEGEN_LIST':
          brief.codegenTools.isEmpty ? 'none' : brief.codegenTools.join(', '),
      'FLAVORS_LIST': brief.flavors.join(', '),
      'CICD_TOOL': _displayName(brief.cicd),
      'CICD_RAW': brief.cicd,
      'TESTING_DEPTH': brief.testingDepth,
      'E2E_TOOL': brief.e2eTool,
      'FEATURES_LIST': brief.featureModules.join(', '),
      'SPECIAL_FEATURES': brief.specialFeatures.join(', '),
      'DESIGN_SOURCE': brief.designSource,
      'FIGMA_URL': brief.figmaUrl,
      'API_DOCS_FORMAT': brief.apiDocsFormat,
      'API_DOCS_PATH': brief.apiDocsPath,
      'GIT_REFS_BLOCK': _gitRefsBlock(brief),
      'LOCAL_PATHS_BLOCK': _localPathsBlock(brief),
      'THEME_SUMMARY': _themeSummary(brief),
      'ROLES_SUMMARY': _rolesSummary(brief),
      'HIGH_CONTRAST_NOTE': _highContrastNote(brief),
      'HIGH_CONTRAST_UX_LINE': _highContrastUxLine(brief),
      'ARCH_IMPORT_RULES': _archImportRules(brief.architecture),
      'TEST_PATTERN': _testPattern(brief.stateManagement),
      'LOCALES_LIST': brief.locales.join(', '),
      'TEMPLATE_VERSION': '1.0.1',
    };
  }

  static String _substituteAll(String content, Map<String, String> ctx) {
    for (final entry in ctx.entries) {
      content = content.replaceAll('{{${entry.key}}}', entry.value);
    }
    return content;
  }

  static void _checkUnreplacedPlaceholders(String content, String key) {
    final pattern = RegExp(r'\{\{[A-Z_]+\}\}');
    final matches = pattern.allMatches(content);
    if (matches.isNotEmpty) {
      final vars = matches.map((m) => m.group(0)).toSet();
      // Print warning but don't fail — let golden tests catch it
      stderr
          .writeln('⚠  Unreplaced placeholder(s) in $key: ${vars.join(", ")}');
    }
  }

  static String _templatePath(String templateSrc, String key) {
    if (key.startsWith('skills/')) {
      final skillName = p.basename(key);
      return p.join(templateSrc, 'skills', skillName, 'SKILL.md.tmpl');
    }
    if (key.startsWith('hooks/')) {
      // special: hooks-json → hooks.json.tmpl
      if (key.endsWith('hooks-json')) {
        return p.join(templateSrc, 'hooks', 'hooks.json.tmpl');
      }
      return p.join(templateSrc, 'hooks', '${p.basename(key)}.ts.tmpl');
    }
    return p.join(templateSrc, '$key.mdc.tmpl');
  }

  static String _outputPath(String key) {
    if (key.startsWith('skills/')) {
      final name = p.basename(key);
      return 'skills/$name/SKILL.md';
    }
    if (key.startsWith('hooks/')) {
      if (key.endsWith('hooks-json')) return 'hooks/hooks.json';
      return 'hooks/${p.basename(key)}.ts';
    }
    return '${key.replaceAll('rules/', 'rules/')}.mdc';
  }

  static String _missingTemplatePlaceholder(String key) {
    return '---\n# Template not found: $key\n# This file will be generated once the template is created.\n---\n';
  }

  static String _displayName(String raw) {
    const names = {
      'bloc': 'BLoC / Cubit',
      'riverpod': 'Riverpod',
      'getx': 'GetX',
      'hooks_riverpod': 'Hooks + Riverpod',
      'clean': 'Clean Architecture',
      'feature_first': 'Feature-First',
      'mvvm': 'MVVM',
      'mvc': 'MVC',
      'layered': 'Layered',
      'gorouter': 'GoRouter',
      'getx_nav': 'GetX Navigation',
      'auto_route': 'Auto Route',
      'firebase': 'Firebase',
      'supabase': 'Supabase',
      'rest': 'REST API',
      'firebase_auth': 'Firebase Auth',
      'supabase_auth': 'Supabase Auth',
      'jwt_rest': 'JWT / REST Auth',
      'oauth2': 'OAuth 2.0',
      'none': 'None',
      'codemagic': 'Codemagic',
      'github_actions': 'GitHub Actions',
      'fastlane': 'Fastlane',
    };
    return names[raw] ?? raw;
  }

  static String _gitRefsBlock(ProjectBrief brief) {
    if (brief.referenceRepos.isEmpty) {
      return '_No Git repository URLs listed._ Add entries under `references.repos` in project-brief.yaml when other repos are part of the product context.';
    }
    return brief.referenceRepos.map((u) => '- $u').join('\n');
  }

  static String _localPathsBlock(ProjectBrief brief) {
    if (brief.localPaths.isEmpty) {
      return '_No local paths listed._ Add monorepo packages or sibling folders under `references.local_paths` in project-brief.yaml when relevant.';
    }
    return brief.localPaths.map((path) => '- `$path`').join('\n');
  }

  static String _themeSummary(ProjectBrief brief) {
    if (brief.themeVariants.isEmpty) {
      return '*(none specified — defaults not applied in brief; check YAML)*';
    }
    return brief.themeVariants.map(_themeLabel).join(', ');
  }

  static String _themeLabel(String token) {
    switch (token) {
      case 'high_contrast':
        return 'high contrast';
      default:
        return token;
    }
  }

  static String _rolesSummary(ProjectBrief brief) {
    if (!brief.rolesEnabled) {
      return 'Not enabled (`app_context.roles_enabled: false`).';
    }
    if (brief.roleNames.isEmpty) {
      return 'Enabled but **no role names** — add `app_context.role_names` in project-brief.yaml.';
    }
    return brief.roleNames.join(', ');
  }

  static String _highContrastNote(ProjectBrief brief) {
    if (!brief.themeVariants.contains('high_contrast')) return '';
    return '\n- **High contrast:** validate contrast, borders, and focus in the high-contrast theme alongside light/dark (WCAG).\n';
  }

  static String _highContrastUxLine(ProjectBrief brief) {
    if (!brief.themeVariants.contains('high_contrast')) return '';
    return '\n- **High contrast theme:** validate loading, empty, and error states; never rely on color alone for meaning (use icons/text/semantics).';
  }

  static String _archImportRules(String arch) {
    switch (arch) {
      case 'clean':
        return '''
- presentation/ MUST NOT import from data/
- domain/ MUST NOT import from data/ or presentation/
- data/ CAN import from domain/ (implements interfaces)
- Use dependency injection to invert data → domain dependency''';
      case 'feature_first':
        return '''
- features/auth/ MUST NOT import from features/home/ etc.
- Shared code lives in core/ or shared/ only
- Each feature is self-contained: screen + provider + model + repo''';
      case 'mvvm':
        return '''
- View MUST NOT contain business logic
- ViewModel MUST NOT import Flutter widgets
- Model MUST NOT import ViewModel or View''';
      case 'mvc':
        return '''
- View (Widget) MUST NOT contain business logic
- Controller MUST NOT import Flutter widgets directly
- Model MUST be plain Dart, no framework dependencies''';
      default:
        return '- Follow layer separation appropriate for $arch architecture';
    }
  }

  static String _testPattern(String sm) {
    switch (sm) {
      case 'bloc':
        return 'blocTest<MyCubit, MyState>(description, build: () => MyCubit(), act: (c) => c.method(), expect: () => [MyState()])';
      case 'riverpod':
        return 'final container = ProviderContainer(overrides: [myProvider.overrideWithValue(mockValue)]); addTearDown(container.dispose);';
      case 'getx':
        return 'Get.put(MyController()); final ctrl = Get.find<MyController>(); expect(ctrl.value, expected);';
      default:
        return '// Write tests appropriate for $sm state management';
    }
  }
}
