// lib/screens/split_view_screen.dart
//
// Anchor-keyed split view: opened by tapping a PDF-anchored scratch link.
//   LEFT  pane = the source PDF (pdfrx PdfViewer, read-only reference + page
//                nav), opened at the anchor's page.
//   RIGHT pane = an INFINITE freehand scratchpad that BELONGS TO THE ANCHOR,
//                keyed by [scratchLinkId] (not the documentId). Each anchor has
//                its own private scratch space, persisted via the existing
//                scratchpad storage (InkStroke JSON, format unchanged).
//
// The right pane reuses the pen-first PenCanvas world-coord engine and the
// Material You brush palette shared with pen_note_screen (BrushPickerButton /
// ToolButton / color dots), replacing the old AnnotationToolbar.

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:pdfrx/pdfrx.dart';
import 'package:uuid/uuid.dart';

import '../editor/canvas/pen_canvas.dart';
import '../editor/canvas/pen_palette_widgets.dart';
import '../editor/canvas/pen_stroke.dart';
import '../editor/engine/brush.dart';
import '../editor/input/pen_config.dart' show kDefaultEraserRadius;
import '../editor/notebook/ink_stroke_adapter.dart';
import '../editor/persistence/sidecar_repository.dart';
import '../models/ink_stroke.dart';
import '../services/undo_manager.dart';
import '../storage/badnote_sidecar.dart';

/// Split-view derivation surface for a single scratch-link anchor: left pane =
/// the reference PDF (at [initialPage]), right pane = the anchor's private
/// infinite scratchpad. Scratchpad strokes persist inside the source file's
/// SIDECAR (`<filePath>.badnote.json`), embedded in the anchor's
/// `scratchLinks[id].scratchpad` (keyed by [scratchLinkId]). Strokes keep the
/// absolute world-pixel [InkStroke] format unchanged.
class SplitViewScreen extends StatefulWidget {
  final String filePath;

  /// The owning anchor id. Selects which `scratchLinks[].scratchpad` in the
  /// sidecar this is the private scratch space for.
  final String scratchLinkId;

  /// 0-based page the anchor sits on; the left PDF opens here.
  final int initialPage;

  const SplitViewScreen({
    super.key,
    required this.filePath,
    required this.scratchLinkId,
    this.initialPage = 0,
  });

  @override
  State<SplitViewScreen> createState() => _SplitViewState();
}

class _SplitViewState extends State<SplitViewScreen> {
  // -- PDF (left pane) --
  final PdfViewerController _pdfController = PdfViewerController();
  int _currentPage = 0; // 0-based
  int _pageCount = 0;

  // -- Split divider --
  double _leftPaneFraction = 0.5;
  bool _isDraggingDivider = false;

  // -- Scratchpad (right pane) --
  // Infinite WORLD: strokes stored in absolute world pixels ([InkStroke],
  // unchanged persistence format), rendered through PenCanvas by normalizing
  // against the CURRENT world size. World auto-expands without moving ink.
  final UndoManager _undoManager = UndoManager();
  List<InkStroke> _strokes = [];
  double _canvasWidth = 4000;
  double _canvasHeight = 4000;
  static const _uuid = Uuid();

  final TransformationController _scratchTransform = TransformationController();
  bool _scratchCentered = false;

  Size get _worldSize => Size(_canvasWidth, _canvasHeight);

  // -- Tool state (new Material You brush palette) --
  CanvasTool _tool = CanvasTool.pen;
  BrushKind _penBrush = BrushKind.fountainPen;
  Color _color = Colors.black;

  static const double _penWidthFraction = 0.006;
  static const double _highlighterWidthFraction = 0.02;

  static const List<Color> _palette = kInkPalette;

  // -- Auto-save debounce --
  Timer? _saveTimer;
  bool _dirty = false;

  /// Sidecar persistence for the source file (the scratchpad is embedded in the
  /// anchor's `scratchLinks[id].scratchpad`). Null until [_loadScratchpad]
  /// resolves.
  SidecarRepository? _repo;

  static const double _edgeThreshold = 200.0;
  static const double _expandAmount = 1000.0;

  @override
  void initState() {
    super.initState();
    _currentPage = widget.initialPage;
    _loadScratchpad();
  }

  @override
  void dispose() {
    _saveTimer?.cancel();
    // Schedule a final flush; retain-counted repo may still be held by the
    // PDF editor, so dispose only drops our retain.
    if (_dirty) {
      unawaited(_saveImmediate());
    } else {
      unawaited(_repo?.flush() ?? Future<void>.value());
    }
    _repo?.dispose();
    // PdfViewerController (pdfrx) has no dispose(); it detaches with the viewer.
    _scratchTransform.dispose();
    super.dispose();
  }

  // -- Persistence (sidecar's scratchLinks[id].scratchpad, keyed by anchor id) --

  Future<void> _loadScratchpad() async {
    final repo = await SidecarRepository.open(widget.filePath, docType: 'pdf');
    if (!mounted) {
      repo.dispose();
      return;
    }
    _repo = repo;
    final pad = repo.scratchpadFor(widget.scratchLinkId);
    setState(() {
      if (pad != null) {
        // Restore the world size so the infinite canvas reopens at its grown
        // extent (previously always reset to 4000×4000).
        _canvasWidth = pad.canvasWidth;
        _canvasHeight = pad.canvasHeight;
      }
      final strokes = pad?.strokes ?? const <InkStroke>[];
      // Keep only freehand strokes so the canvas list stays 1:1 with the undo
      // manager (shapes/text have no pen-canvas representation).
      final freehand = strokes.where((s) => isFreehandTool(s.tool)).toList();
      _strokes = freehand;
      for (final s in freehand) {
        _undoManager.addStroke(s);
      }
    });
  }

  void _scheduleSave() {
    _dirty = true;
    _saveTimer?.cancel();
    _saveTimer = Timer(const Duration(seconds: 3), _saveImmediate);
  }

  Future<void> _saveImmediate() async {
    if (!_dirty) return;
    final repo = _repo;
    if (repo == null) return;
    // Keep dirty until schedule succeeds so a race during load can't swallow ink.
    repo.scheduleScratchpadSave(
      widget.scratchLinkId,
      SidecarScratchpad(
        canvasWidth: _canvasWidth,
        canvasHeight: _canvasHeight,
        strokes: List<InkStroke>.of(_strokes),
      ),
    );
    _dirty = false;
    await repo.flush();
  }

  // -- Scratchpad stroke callbacks --

  void _onStrokeComplete(PenStroke pen) {
    final stroke = inkStrokeFromPen(pen, _worldSize,
        id: _uuid.v4(), createdAt: DateTime.now());
    setState(() {
      _strokes.add(stroke);
      _undoManager.addStroke(stroke);
      _checkCanvasExpansion(stroke);
    });
    _scheduleSave();
  }

  void _onErase(int index, List<PenStroke> replacements) {
    if (index < 0 || index >= _strokes.length) return;
    setState(() {
      final original = _strokes[index];
      final inkReplacements = [
        for (final r in replacements)
          inkStrokeFromPen(r, _worldSize,
              id: _uuid.v4(), createdAt: DateTime.now()),
      ];
      _undoManager.removeStroke(original, replacements: inkReplacements);
      _strokes = List.from(_undoManager.currentStrokes);
    });
    _scheduleSave();
  }

  void _undo() {
    setState(() {
      _undoManager.undo();
      _strokes = List.from(_undoManager.currentStrokes);
    });
    _scheduleSave();
  }

  void _redo() {
    setState(() {
      _undoManager.redo();
      _strokes = List.from(_undoManager.currentStrokes);
    });
    _scheduleSave();
  }

  // -- Auto-expand canvas --

  void _checkCanvasExpansion(InkStroke stroke) {
    double maxRight = 0;
    double maxBottom = 0;
    for (final p in stroke.points) {
      if (p.x > maxRight) maxRight = p.x;
      if (p.y > maxBottom) maxBottom = p.y;
    }
    bool expanded = false;
    if (maxRight > _canvasWidth - _edgeThreshold) {
      _canvasWidth += _expandAmount;
      expanded = true;
    }
    if (maxBottom > _canvasHeight - _edgeThreshold) {
      _canvasHeight += _expandAmount;
      expanded = true;
    }
    if (expanded) setState(() {});
  }

  // -- Divider drag --

  void _onDividerDragStart(DragStartDetails details) {
    setState(() => _isDraggingDivider = true);
  }

  void _onDividerDragUpdate(
    DragUpdateDetails details,
    BoxConstraints constraints,
  ) {
    final renderWidth = constraints.maxWidth;
    if (renderWidth <= 0) return;
    final delta = details.delta.dx / renderWidth;
    setState(() {
      _leftPaneFraction = (_leftPaneFraction + delta).clamp(0.2, 0.8);
    });
  }

  void _onDividerDragEnd(DragEndDetails details) {
    setState(() => _isDraggingDivider = false);
  }

  // -- PDF page navigation (left pane) --

  void _prevPage() {
    if (_currentPage > 0) _pdfController.goToPage(pageNumber: _currentPage);
  }

  void _nextPage() {
    if (_currentPage < _pageCount - 1) {
      _pdfController.goToPage(pageNumber: _currentPage + 2);
    }
  }

  CanvasTool get _activeTool => _tool;

  double get _strokeWidth => _tool == CanvasTool.highlighter
      ? _highlighterWidthFraction
      : _penWidthFraction;

  // -- Build --

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    return Scaffold(
      appBar: AppBar(
        title: const Text('Scratch link', style: TextStyle(fontSize: 16)),
        leading: IconButton(
          icon: const Icon(Icons.arrow_back),
          onPressed: () async {
            await _saveImmediate();
            if (context.mounted) Navigator.of(context).pop();
          },
        ),
        actions: [
          IconButton(
            icon: const Icon(Icons.navigate_before),
            tooltip: 'Previous page (PDF)',
            onPressed: _currentPage > 0 ? _prevPage : null,
          ),
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 4),
            child: Center(
              child: Text(
                '${_currentPage + 1} / $_pageCount',
                style: const TextStyle(fontSize: 13),
              ),
            ),
          ),
          IconButton(
            icon: const Icon(Icons.navigate_next),
            tooltip: 'Next page (PDF)',
            onPressed: _currentPage < _pageCount - 1 ? _nextPage : null,
          ),
          const SizedBox(width: 8),
          Tooltip(
            message:
                'Scratchpad size: ${_canvasWidth.round()} x ${_canvasHeight.round()}',
            child: Padding(
              padding: const EdgeInsets.symmetric(horizontal: 8),
              child: Center(
                child: Text(
                  '${_canvasWidth.round()}x${_canvasHeight.round()}',
                  style: const TextStyle(fontSize: 11, color: Colors.grey),
                ),
              ),
            ),
          ),
        ],
      ),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.only(left: 12, top: 4),
            child: Align(
              alignment: Alignment.centerLeft,
              child: Text(
                'Scratchpad tools',
                style: Theme.of(context).textTheme.labelSmall?.copyWith(
                      color: Theme.of(context).colorScheme.onSurfaceVariant,
                    ),
              ),
            ),
          ),
          _buildBrushPalette(cs),
          Expanded(
            child: LayoutBuilder(
              builder: (context, constraints) {
                final totalWidth = constraints.maxWidth;
                final leftWidth = totalWidth * _leftPaneFraction;
                final rightWidth = totalWidth - leftWidth - 12;

                return Row(
                  children: [
                    SizedBox(width: leftWidth, child: _buildPdfPane()),
                    GestureDetector(
                      onHorizontalDragStart: _onDividerDragStart,
                      onHorizontalDragUpdate: (d) =>
                          _onDividerDragUpdate(d, constraints),
                      onHorizontalDragEnd: _onDividerDragEnd,
                      child: MouseRegion(
                        cursor: SystemMouseCursors.resizeColumn,
                        child: SizedBox(
                          width: 12,
                          child: Center(
                            child: Container(
                              width: 4,
                              color: _isDraggingDivider
                                  ? Theme.of(context).colorScheme.primary
                                  : Theme.of(context).dividerColor,
                            ),
                          ),
                        ),
                      ),
                    ),
                    SizedBox(width: rightWidth, child: _buildScratchpadPane()),
                  ],
                );
              },
            ),
          ),
        ],
      ),
    );
  }

  /// Material You brush palette (shared chrome with the pen-first editors):
  /// brush picker + highlighter + eraser, undo/redo, color dots.
  Widget _buildBrushPalette(ColorScheme cs) {
    return Material(
      color: cs.surfaceContainerHigh,
      elevation: 3,
      borderRadius: BorderRadius.circular(28),
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
        child: SingleChildScrollView(
          scrollDirection: Axis.horizontal,
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              BrushPickerButton(
                selected: _penBrush,
                active: _tool == CanvasTool.pen,
                tooltip: 'Brush',
                labelFor: brushLabelEn,
                // The scratchpad keeps a single shared color (no per-brush color
                // memory in this surface); show it for every brush.
                colorFor: (_) => _color,
                onSelected: (b) => setState(() {
                  _penBrush = b;
                  _tool = CanvasTool.pen;
                }),
              ),
              ToolButton(
                icon: Icons.brush_outlined,
                selected: _tool == CanvasTool.highlighter,
                tooltip: 'Highlighter',
                onPressed: () => setState(() => _tool = CanvasTool.highlighter),
              ),
              ToolButton(
                icon: Icons.cleaning_services_outlined,
                selected: _tool == CanvasTool.eraser,
                tooltip: 'Eraser',
                onPressed: () => setState(() => _tool = CanvasTool.eraser),
              ),
              PaletteDivider(cs: cs),
              ToolButton(
                icon: Icons.undo,
                selected: false,
                tooltip: 'Undo',
                onPressed: _undoManager.canUndo ? _undo : null,
              ),
              ToolButton(
                icon: Icons.redo,
                selected: false,
                tooltip: 'Redo',
                onPressed: _undoManager.canRedo ? _redo : null,
              ),
              PaletteDivider(cs: cs),
              for (final c in _palette) _colorDot(c, cs),
            ],
          ),
        ),
      ),
    );
  }

  Widget _colorDot(Color c, ColorScheme cs) {
    final selected =
        _color.toARGB32() == c.toARGB32() && _tool != CanvasTool.eraser;
    return GestureDetector(
      onTap: () => setState(() {
        _color = c;
        if (_tool == CanvasTool.eraser) _tool = CanvasTool.pen;
      }),
      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 _buildPdfPane() {
    // Read-only reference PDF on the same engine (pdfrx) as the rest of the
    // app, opened at the anchor's page.
    return PdfViewer.file(
      widget.filePath,
      controller: _pdfController,
      params: PdfViewerParams(
        onViewerReady: (document, controller) {
          if (!mounted) return;
          setState(() {
            _pageCount = document.pages.length;
          });
          final target = widget.initialPage.clamp(0, _pageCount - 1);
          if (target > 0) {
            controller.goToPage(pageNumber: target + 1);
          }
        },
        onPageChanged: (pageNumber) {
          if (pageNumber == null || !mounted) return;
          final idx = pageNumber - 1;
          if (idx != _currentPage) setState(() => _currentPage = idx);
        },
      ),
    );
  }

  Widget _buildScratchpadPane() {
    return LayoutBuilder(
      builder: (context, constraints) {
        if (!_scratchCentered) {
          WidgetsBinding.instance.addPostFrameCallback((_) {
            if (!mounted) return;
            _frameScratchpad(
                Size(constraints.maxWidth, constraints.maxHeight));
            setState(() => _scratchCentered = true);
          });
        }
        return Container(
          color: Theme.of(context).scaffoldBackgroundColor,
          child: PenCanvas(
            pageSize: _worldSize,
            strokes: penStrokesFromInk(_strokes, _worldSize),
            transformationController: _scratchTransform,
            tool: _activeTool,
            brush: _penBrush,
            color: _color,
            strokeWidth: _strokeWidth,
            eraserRadius: kDefaultEraserRadius,
            minScale: 0.1,
            maxScale: 8.0,
            onStrokeComplete: _onStrokeComplete,
            onEraseStroke: _onErase,
            pageWidget: const ColoredBox(color: Colors.white),
          ),
        );
      },
    );
  }

  /// Position the scratchpad so existing ink is on-screen (fit its bbox into
  /// [pane], scale clamped); an empty scratchpad shows the top-left at 1:1.
  void _frameScratchpad(Size pane) {
    if (pane.isEmpty) return;
    if (_strokes.isEmpty) {
      _scratchTransform.value = Matrix4.identity();
      return;
    }
    double minX = double.infinity, minY = double.infinity;
    double maxX = -double.infinity, maxY = -double.infinity;
    for (final s in _strokes) {
      for (final p in s.points) {
        if (p.x < minX) minX = p.x;
        if (p.y < minY) minY = p.y;
        if (p.x > maxX) maxX = p.x;
        if (p.y > maxY) maxY = p.y;
      }
    }
    if (minX > maxX) {
      _scratchTransform.value = Matrix4.identity();
      return;
    }
    const pad = 80.0;
    final boxW = (maxX - minX) + pad * 2;
    final boxH = (maxY - minY) + pad * 2;
    final scale = (pane.width / boxW < pane.height / boxH
            ? pane.width / boxW
            : pane.height / boxH)
        .clamp(0.15, 1.5);
    final cx = (minX + maxX) / 2;
    final cy = (minY + maxY) / 2;
    final tx = pane.width / 2 - scale * cx;
    final ty = pane.height / 2 - scale * cy;
    _scratchTransform.value = Matrix4.identity()
      ..setEntry(0, 0, scale)
      ..setEntry(1, 1, scale)
      ..setEntry(2, 2, scale)
      ..setTranslationRaw(tx, ty, 0);
  }
}
