import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:nhancepolicy/customAppBar/toastHelper.dart'; import 'package:nhancepolicy/logger.dart'; import 'package:nhancepolicy/presentation/email_template/email_html_preview.dart'; import 'package:nhancepolicy/presentation/email_template/email_template_models.dart'; import 'package:nhancepolicy/presentation/email_template/grapes_email_editor.dart'; import 'package:nhancepolicy/service/api_service.dart'; import 'package:nhancepolicy/service/token_storage_service.dart'; class ReminderEmailTemplateDialog extends StatefulWidget { final String clientId; final String clientPolicyId; final String clientBranchId; final String token; final ApiService apiService; final String defaultSubject; final String defaultHtmlBody; final ReminderEmailTemplateMode mode; final Future Function(String subject, String htmlBody)? onSend; final bool embedded; const ReminderEmailTemplateDialog({ super.key, required this.clientId, required this.clientPolicyId, required this.clientBranchId, required this.token, required this.apiService, required this.defaultSubject, required this.defaultHtmlBody, this.mode = ReminderEmailTemplateMode.config, this.onSend, this.embedded = false, }); @override State createState() => _ReminderEmailTemplateDialogState(); } class _ReminderEmailTemplateDialogState extends State { static const _teal = Color(0xFF009195); final _templateNameController = TextEditingController(); final _subjectController = TextEditingController(); final _htmlEditController = TextEditingController(); final _grapesController = GrapesEmailEditorController(); bool _isLoading = true; bool _isSubmitting = false; bool _isHtmlEditMode = false; int _editorGeneration = 0; int? _templateId; String _templateName = 'Enrollment Reminder Mail'; String _htmlBody = ''; List _placeholders = const []; @override void initState() { super.initState(); _loadTemplate(); } @override void dispose() { _templateNameController.dispose(); _subjectController.dispose(); _htmlEditController.dispose(); super.dispose(); } void _syncHtmlEditor() { _htmlEditController.text = _htmlBody; } Future _captureHtml() async { if (_isHtmlEditMode) { _htmlBody = _htmlEditController.text; return; } if (_grapesController.isReady) { _htmlBody = await _grapesController.exportHtml(); } else { final latest = _grapesController.currentHtml; if (latest.trim().isNotEmpty) { _htmlBody = latest; } } } Future _toggleHtmlEditMode() async { if (_isHtmlEditMode) { _htmlBody = _htmlEditController.text; setState(() { _isHtmlEditMode = false; _editorGeneration++; }); return; } await _captureHtml(); _syncHtmlEditor(); setState(() => _isHtmlEditMode = true); } void _insertPlaceholder(String token) { if (_isHtmlEditMode) { _insertInHtmlTextField('$token'); return; } _grapesController.insertToken(token); } void _insertInHtmlTextField(String snippet) { final controller = _htmlEditController; final text = controller.text; final selection = controller.selection; final start = selection.start >= 0 ? selection.start : text.length; final end = selection.end >= 0 ? selection.end : text.length; final updated = text.replaceRange(start, end, snippet); controller.value = TextEditingValue( text: updated, selection: TextSelection.collapsed(offset: start + snippet.length), ); setState(() => _htmlBody = updated); } Future _loadTemplate() async { setState(() => _isLoading = true); try { final response = await widget.apiService.getReminderMailConfigApi( widget.clientPolicyId, widget.token, isEmailTemplate: true, ); if (!mounted) return; final ok = response['status'] == true || response['status'] == 'success'; final template = ok ? EmailTemplateData.fromApiResponse( response, defaultSubject: widget.defaultSubject, defaultHtmlBody: widget.defaultHtmlBody, ) : EmailTemplateData( subject: widget.defaultSubject, htmlBody: widget.defaultHtmlBody, ); _templateId = template.templateId; _templateName = template.templateName; _templateNameController.text = template.templateName; _subjectController.text = template.subject; _htmlBody = template.htmlBody; _placeholders = template.placeholders; _syncHtmlEditor(); } catch (e) { logDebug('Template load failed: $e'); _templateNameController.text = 'Enrollment Reminder Mail'; _subjectController.text = widget.defaultSubject; _htmlBody = widget.defaultHtmlBody; _placeholders = EmailTemplateData.defaultPlaceholders(); _syncHtmlEditor(); if (mounted) { ToastHelper.showWarningToast( context, 'Using default template. Submit to save changes.', ); } } finally { if (mounted) { setState(() { _isLoading = false; _editorGeneration++; }); } } } Future _resolveHrId() async { final tokenService = TokenStorageService(); final hrId = await tokenService.readValue('enrollmentHrId') ?? await tokenService.readValue('empHrId'); return int.tryParse(hrId?.toString() ?? ''); } Map _buildSavePayload() { final policyId = int.tryParse(widget.clientPolicyId); final payload = { 'client_id': widget.clientId, 'client_policy_id': policyId ?? widget.clientPolicyId, 'client_branch_id': widget.clientBranchId, 'email_subject': _subjectController.text.trim(), 'email_body': _htmlBody.trim(), 'template_name': _templateNameController.text.trim().isNotEmpty ? _templateNameController.text.trim() : _templateName, }; if (_templateId != null) { payload['id'] = _templateId; } return payload; } Future _submit() async { await _captureHtml(); final subject = _subjectController.text.trim(); final htmlBody = _htmlBody.trim(); if (subject.isEmpty || htmlBody.isEmpty) { if (mounted) { ToastHelper.showWarningToast( context, 'Subject and email body are required', ); } return; } if (widget.mode == ReminderEmailTemplateMode.send && widget.onSend != null) { setState(() => _isSubmitting = true); try { await widget.onSend!(subject, htmlBody); if (mounted) Navigator.pop(context); } finally { if (mounted) setState(() => _isSubmitting = false); } return; } setState(() => _isSubmitting = true); try { final payload = _buildSavePayload(); final hrId = await _resolveHrId(); if (hrId != null) { payload['hr_id'] = hrId; } final response = await widget.apiService.saveEnrollmentReminderEmailTemplateApi( widget.token, payload, ); if (!mounted) return; final ok = response['status'] == true || response['status'] == 'success'; if (ok) { final data = response['data']; if (data is Map) { final idRaw = data['id'] ?? data['template_id']; _templateId = int.tryParse(idRaw?.toString() ?? ''); } ToastHelper.showSuccessToast( context, response['message']?.toString() ?? 'Template saved successfully', ); } else { ToastHelper.showErrorToast( context, response['message']?.toString() ?? 'Failed to save template', ); } } catch (e) { logDebug('Template save failed: $e'); if (mounted) { ToastHelper.showErrorToast( context, 'Failed to save template. Please try again.', ); } } finally { if (mounted) { setState(() => _isSubmitting = false); } } } Widget _tealButton({ required String label, required VoidCallback? onPressed, bool loading = false, double? width, }) { return SizedBox( width: width, height: 36, child: ElevatedButton( onPressed: loading ? null : onPressed, style: ElevatedButton.styleFrom( backgroundColor: _teal, elevation: 0, padding: const EdgeInsets.symmetric(horizontal: 16), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), ), child: loading ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : Text( label, style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w600, color: Colors.white, ), ), ), ); } Future _showPreviewMail() async { await _captureHtml(); final previewHtml = _htmlBody; if (previewHtml.trim().isEmpty) { if (mounted) { ToastHelper.showWarningToast(context, 'Email body is empty'); } return; } if (!mounted) return; showDialog( context: context, useRootNavigator: true, barrierDismissible: true, builder: (ctx) => Dialog( insetPadding: const EdgeInsets.all(24), child: SizedBox( width: MediaQuery.sizeOf(ctx).width * 0.7, height: MediaQuery.sizeOf(ctx).height * 0.8, child: Column( children: [ Padding( padding: const EdgeInsets.all(12), child: Row( children: [ Text( 'Preview Mail', style: GoogleFonts.poppins( fontWeight: FontWeight.w600, fontSize: 16, ), ), const Spacer(), IconButton( onPressed: () => Navigator.pop(ctx), icon: const Icon(Icons.close), ), ], ), ), const Divider(height: 1), Expanded(child: EmailHtmlPreview(html: previewHtml)), ], ), ), ), ); } Widget _webPointerGuard({required Widget child}) { if (kIsWeb) { return PointerInterceptor(child: child); } return child; } Widget _placeholdersMenu() { final menuChild = Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( border: Border.all( color: _placeholders.isEmpty ? const Color(0xFFE0E0E0) : const Color(0xFFCCCCCC), ), borderRadius: BorderRadius.circular(4), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( 'PlaceHolders', style: GoogleFonts.poppins( fontSize: 12, color: _placeholders.isEmpty ? Colors.black38 : Colors.black87, ), ), Icon( Icons.arrow_drop_down, size: 18, color: _placeholders.isEmpty ? Colors.black26 : Colors.black87, ), ], ), ); if (_placeholders.isEmpty) { return _webPointerGuard(child: menuChild); } return _webPointerGuard( child: PopupMenuButton( position: PopupMenuPosition.over, itemBuilder: (context) => _placeholders .map( (p) => _InterceptorPopupMenuItem( value: p, child: Text( '${p.label} (${p.token})', style: GoogleFonts.poppins(fontSize: 12), ), ), ) .toList(), onSelected: (p) => _insertPlaceholder(p.token), child: menuChild, ), ); } Widget _buildEditorArea() { if (_isHtmlEditMode) { return Container( margin: const EdgeInsets.fromLTRB(20, 8, 20, 0), decoration: BoxDecoration( color: const Color(0xFF1E1E1E), borderRadius: BorderRadius.circular(4), border: Border.all(color: const Color(0xFFE5E7EB)), ), padding: const EdgeInsets.all(8), child: TextField( controller: _htmlEditController, maxLines: null, expands: true, style: GoogleFonts.robotoMono(fontSize: 12, color: Colors.white), decoration: const InputDecoration( border: InputBorder.none, hintText: 'Edit HTML source...', hintStyle: TextStyle(color: Colors.white38), ), onChanged: (v) => _htmlBody = v, ), ); } return Padding( padding: const EdgeInsets.fromLTRB(20, 8, 20, 0), child: GrapesEmailEditor( key: ValueKey('grapes-editor-$_editorGeneration'), html: _htmlBody, controller: _grapesController, onHtmlChanged: (html) => _htmlBody = html, ), ); } Widget _buildTemplateContent() { final primaryLabel = widget.mode == ReminderEmailTemplateMode.send ? 'Send' : 'Submit'; if (_isLoading) { return const Center(child: CircularProgressIndicator(color: _teal)); } return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: EdgeInsets.fromLTRB(20, widget.embedded ? 12 : 16, 12, 8), child: Row( children: [ Text( 'Template Name', style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w600, ), ), const SizedBox(width: 12), Expanded( child: TextField( controller: _templateNameController, style: GoogleFonts.poppins(fontSize: 14), decoration: InputDecoration( isDense: true, filled: true, fillColor: const Color(0xFFF5F5F5), contentPadding: const EdgeInsets.symmetric( horizontal: 12, vertical: 10, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(4), borderSide: BorderSide.none, ), ), ), ), if (!widget.embedded) IconButton( onPressed: () => Navigator.pop(context), icon: const Icon(Icons.close, size: 20), ), ], ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( 'Subject', style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w600, ), ), const SizedBox(width: 12), Expanded( child: TextField( controller: _subjectController, style: GoogleFonts.poppins(fontSize: 14), decoration: InputDecoration( isDense: true, filled: true, fillColor: const Color(0xFFF5F5F5), contentPadding: const EdgeInsets.symmetric( horizontal: 12, vertical: 10, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(4), borderSide: BorderSide.none, ), ), ), ), const SizedBox(width: 8), _placeholdersMenu(), const SizedBox(width: 8), _webPointerGuard( child: _tealButton( label: 'Preview Mail', onPressed: () => _showPreviewMail(), width: 120, ), ), ], ), ), Expanded(child: _buildEditorArea()), const Divider(height: 1), Padding( padding: const EdgeInsets.fromLTRB(20, 12, 20, 16), child: Row( children: [ _webPointerGuard( child: TextButton.icon( onPressed: () => _toggleHtmlEditMode(), icon: Icon( Icons.code, size: 16, color: _isHtmlEditMode ? _teal : Colors.black54, ), label: Text( _isHtmlEditMode ? 'Visual Editor' : 'Dev HTML', style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: _isHtmlEditMode ? _teal : Colors.black54, ), ), ), ), const Spacer(), _webPointerGuard( child: _tealButton( label: primaryLabel, onPressed: _submit, loading: _isSubmitting, width: 120, ), ), ], ), ), ], ); } @override Widget build(BuildContext context) { if (widget.embedded) { return _buildTemplateContent(); } final size = MediaQuery.sizeOf(context); return Dialog( insetPadding: const EdgeInsets.all(16), backgroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), child: SizedBox( width: size.width * 0.95, height: size.height * 0.92, child: _buildTemplateContent(), ), ); } } /// Popup menu items over [HtmlElementView] (GrapesJS iframe) need a /// [PointerInterceptor] on web or the iframe steals pointer events. class _InterceptorPopupMenuItem extends PopupMenuItem { const _InterceptorPopupMenuItem({ required super.child, super.value, }); @override PopupMenuItemState> createState() => _InterceptorPopupMenuItemState(); } class _InterceptorPopupMenuItemState extends PopupMenuItemState> { @override Widget build(BuildContext context) { final item = super.build(context); if (kIsWeb) { return PointerInterceptor(child: item); } return item; } }