import 'package:flutter/material.dart'; import '../data/models.dart'; import '../data/repository.dart'; import '../theme/reclaim_tokens.dart'; import '../widgets/mode_banner.dart'; import '../widgets/evidence_sidebar.dart'; import '../widgets/norm_text_card.dart'; import '../widgets/reclaim_card.dart'; import '../widgets/tier_badge.dart'; class NormDetailPage extends StatelessWidget { const NormDetailPage({ super.key, required this.repository, required this.evaluation, }); final EvaluationRepository repository; final Evaluation evaluation; @override Widget build(BuildContext context) { final t = Theme.of(context).textTheme; final e = evaluation; return Scaffold( appBar: AppBar( title: Text('${e.norm.jurabk} ${e.norm.paragraph}'), ), body: Column( children: [ ModeBanner(repository: repository), Expanded( child: SingleChildScrollView( padding: const EdgeInsets.all(ReclaimSpace.xl), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 3, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(e.norm.title, style: t.displaySmall), const SizedBox(height: 4), SelectableText( e.norm.eli, style: t.bodyLarge?.copyWith( fontFamily: ReclaimTypography.mono, color: ReclaimColors.mute, ), ), ], ), ), TierBadge(tier: e.tierLowest), ], ), const SizedBox(height: ReclaimSpace.lg), _MetricsRow(e: e), const SizedBox(height: ReclaimSpace.lg), NormTextCard(norm: e.norm), const SizedBox(height: ReclaimSpace.lg), ReclaimCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Pflichten', style: t.headlineSmall), const SizedBox( height: ReclaimSpace.sm), Text( 'Atomare deontische Aussagen, extrahiert ' 'aus dem Norm-Text (Phase 1: regex + LLM-' 'Vorschlag, Juristen-Review obligatorisch).', style: t.labelSmall?.copyWith( color: ReclaimColors.mute), ), const SizedBox( height: ReclaimSpace.md), for (final d in e.duties) _DutyTile(duty: d), ], ), ), if (e.notes != null) ...[ const SizedBox(height: ReclaimSpace.lg), ReclaimCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Methodik-Notiz', style: t.headlineSmall), const SizedBox( height: ReclaimSpace.sm), Text(e.notes!, style: t.bodyLarge), ], ), ), ], ], ), ), const SizedBox(width: ReclaimSpace.xl), SizedBox( width: 340, child: EvidenceSidebar(evaluation: e), ), ], ), ), ), ], ), ); } } class _MetricsRow extends StatelessWidget { const _MetricsRow({required this.e}); final Evaluation e; @override Widget build(BuildContext context) { return Row( children: [ _MetricBox( label: 'Schaden', value: _eur(e.skmEurPerYear), sub: 'SKM P×F×T×h', ), const SizedBox(width: ReclaimSpace.md), _MetricBox( label: 'Nutzen', value: '${e.benefitScore.toStringAsFixed(1)} / 5', sub: '7-dim. Score', ), const SizedBox(width: ReclaimSpace.md), _MetricBox( label: 'Betroffene', value: _kmu(e.affectedCount), sub: 'DESTATIS', ), const SizedBox(width: ReclaimSpace.md), _MetricBox( label: 'Frust', value: '${e.frustScore.toStringAsFixed(1)} / 10', sub: 'Lesbarkeit + Tiefe', ), ], ); } static String _eur(double v) { if (v >= 1e9) return '${(v / 1e9).toStringAsFixed(2)} Mrd €'; if (v >= 1e6) return '${(v / 1e6).toStringAsFixed(1)} Mio €'; if (v >= 1e3) return '${(v / 1e3).toStringAsFixed(0)} k €'; return '${v.toStringAsFixed(0)} €'; } static String _kmu(int n) { if (n >= 1_000_000) return '${(n / 1e6).toStringAsFixed(1)} Mio'; if (n >= 1_000) return '${(n / 1e3).toStringAsFixed(0)} k'; return n.toString(); } } class _MetricBox extends StatelessWidget { const _MetricBox({ required this.label, required this.value, required this.sub, }); final String label; final String value; final String sub; @override Widget build(BuildContext context) { final t = Theme.of(context).textTheme; return Expanded( child: ReclaimCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: t.labelSmall ?.copyWith(color: ReclaimColors.mute)), const SizedBox(height: 4), Text(value, style: t.displaySmall), const SizedBox(height: 4), Text(sub, style: t.labelSmall ?.copyWith(color: ReclaimColors.mute)), ], ), ), ); } } class _DutyTile extends StatelessWidget { const _DutyTile({required this.duty}); final Duty duty; @override Widget build(BuildContext context) { final t = Theme.of(context).textTheme; return Padding( padding: const EdgeInsets.only(bottom: ReclaimSpace.md), child: Container( padding: const EdgeInsets.all(ReclaimSpace.md), decoration: BoxDecoration( border: Border.all( color: Theme.of(context) .colorScheme .onSurface .withValues(alpha: 0.12)), borderRadius: const BorderRadius.all(ReclaimRadius.sm), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2), decoration: BoxDecoration( color: ReclaimColors.signal.withValues(alpha: 0.18), borderRadius: const BorderRadius.all(ReclaimRadius.sm), ), child: Text( duty.modality, style: t.labelSmall?.copyWith( color: ReclaimColors.signal, fontWeight: FontWeight.w600, ), ), ), const SizedBox(width: ReclaimSpace.sm), Text(duty.sourceNorm, style: t.labelSmall?.copyWith( fontFamily: ReclaimTypography.mono, color: ReclaimColors.mute, )), ]), const SizedBox(height: ReclaimSpace.sm), Text(duty.action, style: t.bodyLarge), const SizedBox(height: 4), Text( 'Adressat: ${duty.addressee} · ' 'Frequenz: ${duty.frequency}' '${duty.authority != null ? " · Behörde: ${duty.authority}" : ""}' '${duty.consequence != null ? " · Sanktion: ${duty.consequence}" : ""}', style: t.labelSmall?.copyWith(color: ReclaimColors.mute), ), ], ), ), ); } }