// lib/editor/canvas/pen_slide_screen.dart
//
// Pen-first slide (PPT) annotator. Reuses the single performant inking engine
// (PenCanvas) over each slide image, with per-slide normalized strokes and
// prev/next navigation. Export to PDF maps the normalized strokes into each
// slide's draw rect (slide_export.dart) — which also fixes the old exporter's
// known ink-misalignment bug. In-memory only (PPT ink is not auto-saved; Export
// to PDF is how annotations are kept), matching the previous behavior.

import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui;

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

import '../engine/brush.dart';
import '../engine/shape_geometry.dart';
import '../input/pen_config.dart';
import '../input/pen_input_service.dart';
import '../input/pressure_curve.dart' show kNaturalPressureGamma;
import '../layout/viewport_fit.dart';
import '../pdf/slide_export.dart';
import '../ui/page_nav_shortcuts.dart';
import '../ui/pen_settings_page.dart';
import 'editor_tool.dart';
import 'pen_canvas.dart';
import 'pen_palette_widgets.dart';
import 'pen_stroke.dart';

class PenSlideScreen extends StatefulWidget {
  const PenSlideScreen({
    super.key,
    required this.filePath,
    required this.slideImagePaths,
    this.extractedText,
  });

  final String filePath;
  final List<String> slideImagePaths;
  final String? extractedText;

  @override
  State<PenSlideScreen> createState() => _PenSlideScreenState();
}

class _PenSlideScreenState extends State<PenSlideScreen> {
  int _slideIndex = 0;
  final Map<int, List<PenStroke>> _strokesBySlide = {};
  final Map<int, List<List<PenStroke>>> _undo = {};
  final Map<int, List<List<PenStroke>>> _redo = {};

  /// Intrinsic pixel size of each slide image, loaded async so the page rect
  /// keeps the slide's aspect (no distortion). Null until loaded.
  Map<int, Size>? _slideSizes;

  /// The single active-tool state (shared model across the 3 editors).
  EditorToolKind _tool = EditorToolKind.brush;

  /// Selected brush for the BRUSH tool. Highlighter tool uses the highlighter
  /// brush; local state only (not persisted — TODO(brush-persist-selection)).
  BrushKind _penBrush = BrushKind.fountainPen;

  /// Selected shape for the SHAPE tool.
  ShapeKind _shapeKind = ShapeKind.line;

  /// Index of the currently selected committed stroke (SELECT tool), or null.
  int? _selectedStroke;

  /// rnote-style per-brush color memory (see PenNoteScreen). In-memory only.
  final Map<BrushKind, Color> _brushColors = {
    BrushKind.fountainPen: Colors.black,
    BrushKind.ballpoint: Colors.blue,
    BrushKind.pencil: Colors.green,
    BrushKind.highlighter: Colors.orange,
  };

  BrushKind get _activeColorBrush => _tool == EditorToolKind.highlighter
      ? BrushKind.highlighter
      : _penBrush;

  Color get _color => _brushColors[_activeColorBrush] ?? Colors.black;

  bool _allowFingerDrawing = false;
  bool _needsCenter = true;
  bool _showSlider = false;
  double? _scrub;

  PenConfigController? _penConfig;
  final TransformationController _transform = TransformationController();

  static const double _penWidthFraction = 0.006;
  static const double _highlighterWidthFraction = 0.02;
  static const Size _fallbackSlide = Size(1600, 900);

  static const List<Color> _palette = kInkPalette;

  int get _slideCount => widget.slideImagePaths.length;
  List<PenStroke> get _currentStrokes => _strokesBySlide[_slideIndex] ?? const [];

  @override
  void initState() {
    super.initState();
    PenInputService.instance.start();
    _loadSlideSizes();
    _initPenConfig();
  }

  Future<void> _loadSlideSizes() async {
    final sizes = <int, Size>{};
    for (var i = 0; i < _slideCount; i++) {
      try {
        final bytes = await File(widget.slideImagePaths[i]).readAsBytes();
        final codec = await ui.instantiateImageCodec(bytes);
        final frame = await codec.getNextFrame();
        sizes[i] = Size(
            frame.image.width.toDouble(), frame.image.height.toDouble());
        frame.image.dispose();
      } catch (_) {
        sizes[i] = _fallbackSlide;
      }
    }
    if (mounted) setState(() => _slideSizes = sizes);
  }

  Future<void> _initPenConfig() async {
    final controller = await PenConfigController.load();
    if (!mounted) {
      controller.dispose();
      return;
    }
    controller.addListener(_onPenConfigChanged);
    setState(() {
      _penConfig = controller;
      _allowFingerDrawing = controller.value.fingerDrawing;
    });
  }

  void _onPenConfigChanged() {
    if (mounted) setState(() {});
  }

  @override
  void dispose() {
    _penConfig?.removeListener(_onPenConfigChanged);
    _penConfig?.dispose();
    _transform.dispose();
    super.dispose();
  }

  // ── Mutations ──────────────────────────────────────────────────────────────

  void _pushUndo() {
    (_undo[_slideIndex] ??= []).add(List<PenStroke>.from(_currentStrokes));
    _redo[_slideIndex]?.clear();
  }

  void _commitStroke(PenStroke stroke) {
    setState(() {
      _pushUndo();
      _strokesBySlide[_slideIndex] = [..._currentStrokes, stroke];
    });
  }

  void _eraseStroke(int index, List<PenStroke> replacements) {
    final strokes = _currentStrokes;
    if (index < 0 || index >= strokes.length) return;
    setState(() {
      _pushUndo();
      _strokesBySlide[_slideIndex] = [
        ...strokes.sublist(0, index),
        ...replacements,
        ...strokes.sublist(index + 1),
      ];
    });
  }

  void _performUndo() {
    final stack = _undo[_slideIndex];
    if (stack == null || stack.isEmpty) return;
    setState(() {
      (_redo[_slideIndex] ??= []).add(List<PenStroke>.from(_currentStrokes));
      _strokesBySlide[_slideIndex] = stack.removeLast();
    });
  }

  void _performRedo() {
    final stack = _redo[_slideIndex];
    if (stack == null || stack.isEmpty) return;
    setState(() {
      (_undo[_slideIndex] ??= []).add(List<PenStroke>.from(_currentStrokes));
      _strokesBySlide[_slideIndex] = stack.removeLast();
    });
  }

  void _toggleFingerDrawing() {
    final next = !_allowFingerDrawing;
    setState(() => _allowFingerDrawing = next);
    _penConfig?.setFingerDrawing(next);
  }

  void _goToSlide(int i) {
    final clamped = i.clamp(0, _slideCount - 1);
    if (clamped == _slideIndex) return;
    setState(() {
      _slideIndex = clamped;
      _needsCenter = true;
      _selectedStroke = null; // selection is per-slide
    });
  }

  // ── Export ──────────────────────────────────────────────────────────────────

  Future<void> _exportPdf() async {
    final messenger = ScaffoldMessenger.of(context);
    messenger.showSnackBar(
        const SnackBar(content: Text('Exporting PDF...')));
    try {
      final bytes = await _buildPdfBytes();
      final dir = await _exportDir();
      final base = p.basenameWithoutExtension(widget.filePath);
      final outPath = p.join(dir.path, '${base}_annotated.pdf');
      await File(outPath).writeAsBytes(bytes);
      if (!mounted) return;
      messenger.showSnackBar(SnackBar(content: Text('PDF saved: $outPath')));
    } catch (e) {
      if (!mounted) return;
      messenger.showSnackBar(SnackBar(content: Text('Export failed: $e')));
    }
  }

  Future<Directory> _exportDir() 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;
    final sizes = _slideSizes ?? const {};
    for (var i = 0; i < _slideCount; i++) {
      final page = doc.pages.add();
      final pageSize = page.getClientSize();
      try {
        final imgBytes = await File(widget.slideImagePaths[i]).readAsBytes();
        final bitmap = PdfBitmap(imgBytes);
        final imageSize = sizes[i] ??
            Size(bitmap.width.toDouble(), bitmap.height.toDouble());
        final draw = slideDrawRect(
            Size(pageSize.width, pageSize.height), imageSize);
        page.graphics.drawImage(bitmap, draw);

        for (final stroke in _strokesBySlide[i] ?? const <PenStroke>[]) {
          if (stroke.points.length < 2) continue;
          final r = (stroke.color >> 16) & 0xFF;
          final g = (stroke.color >> 8) & 0xFF;
          final b = stroke.color & 0xFF;
          final path = PdfPath();
          path.startFigure();
          for (var j = 0; j < stroke.points.length - 1; j++) {
            final p1 = stroke.points[j];
            final p2 = stroke.points[j + 1];
            path.addLine(
              normToSlide(p1.x, p1.y, draw),
              normToSlide(p2.x, p2.y, draw),
            );
          }
          page.graphics.drawPath(
            path,
            pen: PdfPen(PdfColor(r, g, b),
                width: slideStrokeWidth(stroke.width, draw)),
          );
        }
      } 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);
  }

  // ── Layout ────────────────────────────────────────────────────────────────

  void _centerPage(Size viewport, Size pageSize) {
    final o = centerOffset(pageSize, viewport, 1.0);
    _transform.value = Matrix4.identity()..translateByDouble(o.dx, o.dy, 0, 1);
  }

  double get _strokeWidth => _tool == EditorToolKind.highlighter
      ? (_penConfig?.value.highlighterWidth ?? _highlighterWidthFraction)
      : (_penConfig?.value.penWidth ?? _penWidthFraction);

  // ── SELECT tool: select / move / delete (per-slide, reuses the undo stacks) ──

  void _selectStroke(int? index) {
    setState(() => _selectedStroke = index);
  }

  void _moveStroke(int index, double dx, double dy, bool isDragStart) {
    final strokes = _currentStrokes;
    if (index < 0 || index >= strokes.length) return;
    setState(() {
      if (isDragStart) _pushUndo();
      final next = List<PenStroke>.from(strokes);
      next[index] = translateStroke(next[index], dx, dy);
      _strokesBySlide[_slideIndex] = next;
    });
  }

  void _deleteSelected() {
    final idx = _selectedStroke;
    final strokes = _currentStrokes;
    if (idx == null || idx < 0 || idx >= strokes.length) return;
    setState(() {
      _pushUndo();
      _strokesBySlide[_slideIndex] = [
        ...strokes.sublist(0, idx),
        ...strokes.sublist(idx + 1),
      ];
      _selectedStroke = null;
    });
  }

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    return pageNavShortcuts(
      onPrevious:
          _slideIndex > 0 ? () => _goToSlide(_slideIndex - 1) : null,
      onNext: _slideIndex < _slideCount - 1
          ? () => _goToSlide(_slideIndex + 1)
          : null,
      onFirst: _slideCount > 0 ? () => _goToSlide(0) : null,
      onLast: _slideCount > 0 ? () => _goToSlide(_slideCount - 1) : null,
      child: Scaffold(
      body: Stack(
        children: [
          Positioned.fill(child: _buildCanvas()),
          SafeArea(
            child: Align(
              alignment: Alignment.topCenter,
              child: Padding(
                padding: const EdgeInsets.only(top: 8),
                child: _buildToolPalette(cs),
              ),
            ),
          ),
          SafeArea(
            child: Padding(
              padding: const EdgeInsets.all(8),
              child: RoundIconButton(
                icon: Icons.arrow_back,
                tooltip: 'Back',
                onPressed: () => Navigator.of(context).maybePop(),
              ),
            ),
          ),
          SafeArea(
            child: Align(
              alignment: Alignment.topRight,
              child: Padding(
                padding: const EdgeInsets.all(8),
                child: RoundIconButton(
                  icon: Icons.picture_as_pdf_outlined,
                  tooltip: 'Export to PDF',
                  onPressed: _exportPdf,
                ),
              ),
            ),
          ),
          if (_slideCount > 0)
            SafeArea(
              child: Align(
                alignment: Alignment.bottomCenter,
                child: Padding(
                  padding: const EdgeInsets.only(bottom: 16),
                  child: _buildSlidePill(cs),
                ),
              ),
            ),
        ],
      ),
      ),
    );
  }

  Widget _buildCanvas() {
    final sizes = _slideSizes;
    if (sizes == null) {
      return const Center(child: CircularProgressIndicator());
    }
    if (_slideCount == 0) {
      return const Center(child: Text('No slides.'));
    }
    final slide = sizes[_slideIndex] ?? _fallbackSlide;
    return LayoutBuilder(
      builder: (context, constraints) {
        final fitW = constraints.maxWidth / slide.width;
        final fitH = constraints.maxHeight / slide.height;
        final scale = fitW < fitH ? fitW : fitH;
        final pageSize = Size(slide.width * scale, slide.height * scale);

        if (_needsCenter) {
          WidgetsBinding.instance.addPostFrameCallback((_) {
            if (!mounted) return;
            _centerPage(
                Size(constraints.maxWidth, constraints.maxHeight), pageSize);
            setState(() => _needsCenter = false);
          });
        }

        return PenCanvas(
          key: ValueKey(_slideIndex),
          pageSize: pageSize,
          strokes: _currentStrokes,
          transformationController: _transform,
          tool: editorToolToCanvas(_tool),
          brush: _penBrush,
          shapeKind: _shapeKind,
          color: _color,
          strokeWidth: _strokeWidth,
          selectedStrokeIndex: _selectedStroke,
          onSelectStroke: _selectStroke,
          onMoveStroke: _moveStroke,
          pressureGamma:
              _penConfig?.value.pressureGamma ?? kNaturalPressureGamma,
          eraserRadius: _penConfig?.value.eraserRadius ?? kDefaultEraserRadius,
          eraserWholeStroke: _penConfig?.value.eraserWholeStroke ?? false,
          sideButtonAction:
              _penConfig?.value.sideButton ?? PenButtonAction.eraser,
          eraserEndAction:
              _penConfig?.value.eraserEnd ?? PenButtonAction.eraser,
          allowFingerDrawing: _allowFingerDrawing,
          onStrokeComplete: _commitStroke,
          onEraseStroke: _eraseStroke,
          pageWidget: Image.file(
            File(widget.slideImagePaths[_slideIndex]),
            fit: BoxFit.fill,
            gaplessPlayback: true,
          ),
        );
      },
    );
  }

  Widget _buildToolPalette(ColorScheme cs) {
    return Material(
      color: cs.surfaceContainerHigh,
      elevation: 3,
      borderRadius: BorderRadius.circular(28),
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            BrushPickerButton(
              selected: _penBrush,
              active: _tool == EditorToolKind.brush,
              tooltip: 'Brush',
              labelFor: brushLabelEn,
              colorFor: (b) => _brushColors[b] ?? Colors.black,
              onSelected: (b) => setState(() {
                _penBrush = b;
                _tool = EditorToolKind.brush;
              }),
            ),
            ToolButton(
              icon: Icons.brush_outlined,
              selected: _tool == EditorToolKind.highlighter,
              tooltip: 'Highlighter',
              onPressed: () => setState(() => _tool = EditorToolKind.highlighter),
            ),
            ToolButton(
              icon: Icons.cleaning_services_outlined,
              selected: _tool == EditorToolKind.eraser,
              tooltip: 'Eraser',
              onPressed: () => setState(() => _tool = EditorToolKind.eraser),
            ),
            ToolButton(
              icon: Icons.ads_click,
              selected: _tool == EditorToolKind.select,
              tooltip: 'Select',
              onPressed: () => setState(() => _tool = EditorToolKind.select),
            ),
            ShapePickerButton(
              selected: _shapeKind,
              active: _tool == EditorToolKind.shape,
              tooltip: 'Shape',
              labelFor: shapeLabelEn,
              onActivate: () => setState(() => _tool = EditorToolKind.shape),
              onSelected: (s) => setState(() {
                _shapeKind = s;
                _tool = EditorToolKind.shape;
              }),
            ),
            if (_tool == EditorToolKind.select && _selectedStroke != null)
              ToolButton(
                icon: Icons.delete_outline,
                selected: false,
                tooltip: 'Delete selection',
                onPressed: _deleteSelected,
              ),
            PaletteDivider(cs: cs),
            ToolButton(
              icon: Icons.undo,
              selected: false,
              tooltip: 'Undo',
              onPressed:
                  (_undo[_slideIndex]?.isNotEmpty ?? false) ? _performUndo : null,
            ),
            ToolButton(
              icon: Icons.redo,
              selected: false,
              tooltip: 'Redo',
              onPressed:
                  (_redo[_slideIndex]?.isNotEmpty ?? false) ? _performRedo : null,
            ),
            PaletteDivider(cs: cs),
            for (final c in _palette) _colorDot(c, cs),
            PaletteDivider(cs: cs),
            ToolButton(
              icon: _allowFingerDrawing ? Icons.touch_app : Icons.do_not_touch,
              selected: _allowFingerDrawing,
              tooltip: _allowFingerDrawing
                  ? 'Finger drawing ON'
                  : 'Finger drawing OFF (pen only)',
              onPressed: _toggleFingerDrawing,
            ),
            ToolButton(
              icon: Icons.settings_outlined,
              selected: false,
              tooltip: 'Pen settings (width, pressure, eraser…)',
              onPressed: _penConfig != null
                  ? () => showPenSettingsSheet(context, _penConfig!)
                  : null,
            ),
          ],
        ),
      ),
    );
  }

  Widget _colorDot(Color c, ColorScheme cs) {
    final selected = _color.toARGB32() == c.toARGB32() &&
        _tool != EditorToolKind.eraser &&
        _tool != EditorToolKind.select;
    return GestureDetector(
      onTap: () => setState(() {
        if (_tool == EditorToolKind.eraser ||
            _tool == EditorToolKind.select) {
          _tool = EditorToolKind.brush;
        }
        _brushColors[_activeColorBrush] = c;
      }),
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 150),
        margin: const EdgeInsets.symmetric(horizontal: 3),
        width: 24,
        height: 24,
        decoration: BoxDecoration(
          color: c,
          shape: BoxShape.circle,
          border: Border.all(
            color: selected ? cs.onSurface : cs.outlineVariant,
            width: selected ? 3 : 1,
          ),
        ),
      ),
    );
  }

  Widget _buildSlidePill(ColorScheme cs) {
    final shown = (_scrub ?? (_slideIndex + 1).toDouble()).round();
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        if (_showSlider && _slideCount > 1)
          Container(
            margin: const EdgeInsets.only(bottom: 8),
            constraints: const BoxConstraints(maxWidth: 420),
            child: Material(
              color: cs.surfaceContainerHigh,
              elevation: 3,
              borderRadius: BorderRadius.circular(28),
              child: Padding(
                padding: const EdgeInsets.symmetric(horizontal: 12),
                child: Slider(
                  min: 1,
                  max: _slideCount.toDouble(),
                  value: (_scrub ?? (_slideIndex + 1).toDouble())
                      .clamp(1, _slideCount.toDouble()),
                  divisions: _slideCount > 1 ? _slideCount - 1 : null,
                  onChanged: (v) => setState(() => _scrub = v),
                  onChangeEnd: (v) {
                    setState(() => _scrub = null);
                    _goToSlide(v.round() - 1);
                  },
                ),
              ),
            ),
          ),
        Material(
          color: cs.surfaceContainerHigh,
          elevation: 3,
          borderRadius: BorderRadius.circular(28),
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
            child: Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                IconButton(
                  tooltip: 'Previous slide',
                  icon: const Icon(Icons.chevron_left),
                  onPressed:
                      _slideIndex > 0 ? () => _goToSlide(_slideIndex - 1) : null,
                ),
                TextButton(
                  onPressed: _slideCount > 1
                      ? () => setState(() => _showSlider = !_showSlider)
                      : null,
                  child: Text(
                    '$shown / $_slideCount',
                    style: TextStyle(
                      color: cs.onSurface,
                      fontWeight: FontWeight.w600,
                    ),
                  ),
                ),
                IconButton(
                  tooltip: 'Next slide',
                  icon: const Icon(Icons.chevron_right),
                  onPressed: _slideIndex < _slideCount - 1
                      ? () => _goToSlide(_slideIndex + 1)
                      : null,
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }
}
