import 'dart:io';
import 'dart:math';
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:path/path.dart' as p;
import 'package:syncfusion_flutter_pdf/pdf.dart';

import '../models/ink_stroke.dart';
import '../models/pen_tool.dart';
import '../models/pressure_curve.dart';
import '../services/undo_manager.dart';
import '../utils/stroke_stabilizer.dart';
import '../widgets/annotation_toolbar.dart';
import '../widgets/ink_canvas.dart';

/// Per-slide annotation state. The [UndoManager] is the single source of
/// truth for a slide's strokes; [strokes] reflects its current contents so
/// the live canvas and the PDF export always render what was actually drawn.
class _SlideAnnotations {
  final UndoManager undoManager = UndoManager();
  List<InkStroke> get strokes => undoManager.currentStrokes;
}

/// Screen that displays PPTX slides with an ink annotation overlay.
///
/// Each slide is shown as an image in a [PageView]. A transparent [InkCanvas]
/// sits on top of each slide so the user can annotate freely. Annotations are
/// stored per-slide and can be exported as a PDF.
class PptAnnotatorScreen extends StatefulWidget {
  final String filePath;
  final List<String> slideImagePaths;
  final String? extractedText;

  const PptAnnotatorScreen({
    super.key,
    required this.filePath,
    required this.slideImagePaths,
    this.extractedText,
  });

  @override
  State<PptAnnotatorScreen> createState() => _PptAnnotatorScreenState();
}

class _PptAnnotatorScreenState extends State<PptAnnotatorScreen> {
  late final PageController _pageController;
  late final Map<int, _SlideAnnotations> _annotations;
  int _currentPage = 0;
  bool _isDrawing = false;
  bool _showTextPanel = false;
  // Set to true once the unsaved-annotations warning SnackBar has been shown.
  bool _hasShownUnsavedWarning = false;

  // Toolbar state
  PenTool _currentTool = PenTool.pen;
  Color _currentColor = Colors.black;
  double _currentStrokeWidth = 2.0;
  bool _filled = false;
  PressureCurveType _pressureCurveType = PressureCurveType.linear;
  StabilizationLevel _stabilizationLevel = StabilizationLevel.none;

  // Derived
  late final String _fileName;
  late final int _slideCount;
  late final String _extractedText;

  PressureCurve get _pressureCurve {
    switch (_pressureCurveType) {
      case PressureCurveType.linear:
        return PressureCurve.linear;
      case PressureCurveType.soft:
        return PressureCurve.soft;
      case PressureCurveType.hard:
        return PressureCurve.hard;
      case PressureCurveType.custom:
        return const PressureCurve(type: PressureCurveType.custom);
    }
  }

  UndoManager get _currentUndoManager =>
      _annotations.putIfAbsent(_currentPage, _SlideAnnotations.new).undoManager;

  @override
  void initState() {
    super.initState();
    _fileName = p.basename(widget.filePath);
    _slideCount = widget.slideImagePaths.length;
    _extractedText = widget.extractedText ?? '';

    _pageController = PageController();
    _annotations = {};
    for (var i = 0; i < _slideCount; i++) {
      _annotations[i] = _SlideAnnotations();
    }
  }

  @override
  void dispose() {
    _pageController.dispose();
    super.dispose();
  }

  // -- Drawing callbacks --

  void _onStrokeComplete(InkStroke stroke) {
    setState(() {
      _currentUndoManager.addStroke(stroke);
    });
    // Warn once per session that PPT annotations are not auto-saved.
    if (!_hasShownUnsavedWarning) {
      _hasShownUnsavedWarning = true;
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (!mounted) return;
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
            content: Text(
              "PPT ink isn't saved automatically — use Export to PDF to keep your annotations.",
            ),
            duration: Duration(seconds: 5),
          ),
        );
      });
    }
  }

  void _onErase(String strokeId, List<InkStroke> replacements) {
    setState(() {
      final original = _currentUndoManager.currentStrokes
          .where((s) => s.id == strokeId)
          .firstOrNull;
      if (original != null) {
        _currentUndoManager.removeStroke(original, replacements: replacements);
      }
    });
  }

  // -- Export --

  Future<void> _exportPdf() async {
    if (!mounted) return;

    ScaffoldMessenger.of(
      context,
    ).showSnackBar(const SnackBar(content: Text('Exporting PDF...')));

    try {
      final bytes = await _buildPdfBytes();
      if (!mounted) return;

      final dir = await _getExportDir();
      final baseName = p.basenameWithoutExtension(_fileName);
      final outPath = p.join(dir.path, '${baseName}_annotated.pdf');
      await File(outPath).writeAsBytes(bytes);

      if (!mounted) return;
      ScaffoldMessenger.of(
        context,
      ).showSnackBar(SnackBar(content: Text('PDF saved: $outPath')));
    } catch (e) {
      if (!mounted) return;
      ScaffoldMessenger.of(
        context,
      ).showSnackBar(SnackBar(content: Text('Export failed: $e')));
    }
  }

  Future<Directory> _getExportDir() async {
    try {
      final home = Platform.environment['HOME'];
      if (home != null) {
        final dir = Directory(p.join(home, 'Documents', 'BadNote'));
        if (!await dir.exists()) {
          await dir.create(recursive: true);
        }
        return dir;
      }
    } catch (_) {}
    return Directory.current;
  }

  Future<Uint8List> _buildPdfBytes() async {
    final doc = PdfDocument();
    doc.pageSettings.margins.all = 0;

    for (var i = 0; i < _slideCount; i++) {
      final page = doc.pages.add();
      final pageSize = page.getClientSize();

      // Draw slide image
      final imgPath = widget.slideImagePaths[i];
      try {
        final imgBytes = await File(imgPath).readAsBytes();
        final bitmap = PdfBitmap(imgBytes);

        final imgW = bitmap.width.toDouble();
        final imgH = bitmap.height.toDouble();
        final scale = min(pageSize.width / imgW, pageSize.height / imgH);
        final drawW = imgW * scale;
        final drawH = imgH * scale;
        final offX = (pageSize.width - drawW) / 2;
        final offY = (pageSize.height - drawH) / 2;
        final imgRect = Rect.fromLTWH(offX, offY, drawW, drawH);

        page.graphics.drawImage(bitmap, imgRect);

        // Draw ink strokes
        final annots = _annotations[i];
        if (annots != null && annots.strokes.isNotEmpty) {
          // KNOWN LIMITATION: strokes are captured in the live viewer's
          // full-fill pixel space (the InkCanvas is Positioned.fill over the
          // whole slide area, while the slide image is BoxFit.contain inside
          // it). The scale below is derived from the PDF page layout, not the
          // live widget size, so exported ink can be misaligned/scaled wrong.
          // A correct fix normalizes strokes to [0,1] of the *rendered image
          // rect* at capture time (mirroring PdfAnnotationLayer) and maps that
          // to the PDF draw rect here. Requires on-device visual verification.
          final imgAspect = imgW / imgH;
          final pageAspect = pageSize.width / pageSize.height;
          double widgetW, widgetH;
          if (imgAspect > pageAspect) {
            widgetW = pageSize.width;
            widgetH = pageSize.width / imgAspect;
          } else {
            widgetH = pageSize.height;
            widgetW = pageSize.height * imgAspect;
          }
          final scaleX = drawW / widgetW;
          final scaleY = drawH / widgetH;

          for (final stroke in annots.strokes) {
            if (stroke.tool == PenTool.eraser) continue;
            if (stroke.points.length < 2) continue;

            final r = (stroke.color >> 16) & 0xFF;
            final g = (stroke.color >> 8) & 0xFF;
            final b = stroke.color & 0xFF;
            final pdfColor = PdfColor(r, g, b);

            final path = PdfPath();
            path.startFigure();
            for (var j = 0; j < stroke.points.length - 1; j++) {
              final pt1 = stroke.points[j];
              final pt2 = stroke.points[j + 1];
              path.addLine(
                Offset(offX + pt1.x * scaleX, offY + pt1.y * scaleY),
                Offset(offX + pt2.x * scaleX, offY + pt2.y * scaleY),
              );
            }

            page.graphics.drawPath(
              path,
              pen: PdfPen(pdfColor, width: stroke.strokeWidth),
            );
          }
        }
      } catch (_) {
        page.graphics.drawRectangle(
          brush: PdfSolidBrush(PdfColor(230, 230, 230)),
          bounds: Rect.fromLTWH(0, 0, pageSize.width, pageSize.height),
        );
      }
    }

    final bytes = await doc.save();
    doc.dispose();
    return Uint8List.fromList(bytes);
  }

  // -- UI --

  @override
  Widget build(BuildContext context) {
    // No slides to annotate: show an empty state and skip the toolbar, which
    // would otherwise dereference a non-existent slide's annotation state.
    if (_slideCount == 0) {
      return Scaffold(
        appBar: AppBar(
          title: Text(_fileName, style: const TextStyle(fontSize: 16)),
        ),
        body: const Center(child: Text('No slides to display')),
      );
    }

    return Scaffold(
      appBar: AppBar(
        title: Text(_fileName, style: const TextStyle(fontSize: 16)),
        actions: [
          if (_extractedText.isNotEmpty)
            IconButton(
              icon: Icon(
                _showTextPanel
                    ? Icons.text_snippet
                    : Icons.text_snippet_outlined,
              ),
              tooltip: 'Toggle extracted text',
              onPressed: () => setState(() => _showTextPanel = !_showTextPanel),
            ),
          IconButton(
            icon: const Icon(Icons.picture_as_pdf),
            tooltip: 'Export as PDF',
            onPressed: _exportPdf,
          ),
        ],
      ),
      body: Column(
        children: [
          AnnotationToolbar(
            currentTool: _currentTool,
            currentColor: _currentColor,
            currentStrokeWidth: _currentStrokeWidth,
            filled: _filled,
            pressureCurveType: _pressureCurveType,
            stabilizationLevel: _stabilizationLevel,
            canUndo: _currentUndoManager.canUndo,
            canRedo: _currentUndoManager.canRedo,
            onToolChanged: (tool) => setState(() => _currentTool = tool),
            onColorChanged: (color) => setState(() => _currentColor = color),
            onStrokeWidthChanged: (w) =>
                setState(() => _currentStrokeWidth = w),
            onFilledChanged: (f) => setState(() => _filled = f),
            onPressureCurveChanged: (v) =>
                setState(() => _pressureCurveType = v),
            onStabilizationChanged: (v) =>
                setState(() => _stabilizationLevel = v),
            onUndo: _undo,
            onRedo: _redo,
          ),
          Expanded(
            child: Row(
              children: [
                Expanded(child: _buildSlideViewer()),
                if (_showTextPanel) _buildTextPanel(),
              ],
            ),
          ),
          _buildPageIndicator(),
        ],
      ),
    );
  }

  Widget _buildSlideViewer() {
    if (_slideCount == 0) {
      return const Center(child: Text('No slides to display'));
    }

    return Listener(
      onPointerDown: (_) => setState(() => _isDrawing = true),
      onPointerUp: (_) => setState(() => _isDrawing = false),
      child: PageView.builder(
        controller: _pageController,
        physics: _isDrawing ? const NeverScrollableScrollPhysics() : null,
        itemCount: _slideCount,
        onPageChanged: (page) => setState(() => _currentPage = page),
        itemBuilder: (context, index) {
          return Padding(
            padding: const EdgeInsets.all(8),
            child: Stack(
              children: [
                // Slide image (background)
                Positioned.fill(
                  child: Image.file(
                    File(widget.slideImagePaths[index]),
                    fit: BoxFit.contain,
                    errorBuilder: (context, error, stackTrace) => Container(
                      color: Colors.grey.shade200,
                      child: Center(
                        child: Text(
                          'Slide ${index + 1}',
                          style: TextStyle(
                            fontSize: 24,
                            color: Colors.grey.shade500,
                          ),
                        ),
                      ),
                    ),
                  ),
                ),
                // Ink annotation overlay (foreground)
                Positioned.fill(
                  child: InkCanvas(
                    strokes: _annotations[index]?.strokes ?? [],
                    onStrokeComplete: _onStrokeComplete,
                    onErase: _onErase,
                    tool: _currentTool,
                    color: _currentColor,
                    strokeWidth: _currentStrokeWidth,
                    pressureCurve: _pressureCurve,
                    stabilizationLevel: _stabilizationLevel,
                    filled: _filled,
                  ),
                ),
              ],
            ),
          );
        },
      ),
    );
  }

  Widget _buildPageIndicator() {
    if (_slideCount == 0) return const SizedBox.shrink();

    return Container(
      padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
      color: Theme.of(context).colorScheme.surface,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          // Previous button — always present for both modes.
          IconButton(
            icon: const Icon(Icons.chevron_left),
            onPressed: _currentPage > 0
                ? () => _pageController.previousPage(
                    duration: const Duration(milliseconds: 300),
                    curve: Curves.easeInOut,
                  )
                : null,
          ),
          // Dot row for small decks; compact text counter for large decks.
          if (_slideCount <= 12)
            ...List.generate(_slideCount, (i) {
              final isActive = i == _currentPage;
              return GestureDetector(
                onTap: () => _pageController.animateToPage(
                  i,
                  duration: const Duration(milliseconds: 300),
                  curve: Curves.easeInOut,
                ),
                child: Container(
                  width: isActive ? 12 : 8,
                  height: isActive ? 12 : 8,
                  margin: const EdgeInsets.symmetric(horizontal: 4),
                  decoration: BoxDecoration(
                    shape: BoxShape.circle,
                    color: isActive
                        ? Theme.of(context).colorScheme.primary
                        : Colors.grey.shade400,
                  ),
                ),
              );
            })
          else
            Text(
              '${_currentPage + 1} / $_slideCount',
              style: TextStyle(fontSize: 13, color: Colors.grey.shade600),
            ),
          // Next button — always present for both modes.
          IconButton(
            icon: const Icon(Icons.chevron_right),
            onPressed: _currentPage < _slideCount - 1
                ? () => _pageController.nextPage(
                    duration: const Duration(milliseconds: 300),
                    curve: Curves.easeInOut,
                  )
                : null,
          ),
          const Spacer(),
          // Slide counter is always shown at the trailing end for dot mode;
          // the compact text above already serves this role for large decks.
          if (_slideCount <= 12)
            Text(
              '${_currentPage + 1} / $_slideCount',
              style: TextStyle(fontSize: 13, color: Colors.grey.shade600),
            ),
        ],
      ),
    );
  }

  Widget _buildTextPanel() {
    return SizedBox(
      width: 280,
      child: Card(
        margin: const EdgeInsets.all(8),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Container(
              padding: const EdgeInsets.all(12),
              decoration: BoxDecoration(
                color: Theme.of(context).colorScheme.surfaceContainerHighest,
                borderRadius: const BorderRadius.vertical(
                  top: Radius.circular(12),
                ),
              ),
              child: Row(
                children: [
                  const Icon(Icons.text_fields, size: 18),
                  const SizedBox(width: 8),
                  Text(
                    'Extracted Text',
                    style: Theme.of(context).textTheme.titleSmall,
                  ),
                  const Spacer(),
                  IconButton(
                    icon: const Icon(Icons.close, size: 18),
                    onPressed: () => setState(() => _showTextPanel = false),
                  ),
                ],
              ),
            ),
            Expanded(
              child: SingleChildScrollView(
                padding: const EdgeInsets.all(12),
                child: SelectableText(
                  _extractedText,
                  style: Theme.of(context).textTheme.bodySmall,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  // -- Dialogs --

  void _undo() {
    setState(() => _currentUndoManager.undo());
  }

  void _redo() {
    setState(() => _currentUndoManager.redo());
  }
}
