// lib/editor/canvas/pen_note_screen.dart
//
// Pen-first blank-note editor. Reuses the single performant inking engine
// (PenCanvas) over a white logical page instead of a PDF page, and persists
// strokes back to the Note model via the InkStroke<->PenStroke adapter. This is
// the note half of "all note features on the pen-first canvas".

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

import '../../models/ink_stroke.dart';
import '../../models/note.dart';
import '../../providers/note_provider.dart';
import '../../providers/ocr_provider.dart';
import '../engine/brush.dart';
import '../engine/shape_geometry.dart';
import '../engine/stroke_model.dart';
import '../persistence/sidecar_repository.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 '../notebook/ink_stroke_adapter.dart';
import '../ui/pen_settings_page.dart';
import 'editor_tool.dart';
import 'note_background.dart';
import 'pen_canvas.dart';
import 'pen_palette_widgets.dart';
import 'pen_stroke.dart';

class PenNoteScreen extends ConsumerStatefulWidget {
  const PenNoteScreen({super.key, this.note});

  /// Existing note to edit, or null for a new note.
  final Note? note;

  @override
  ConsumerState<PenNoteScreen> createState() => _PenNoteScreenState();
}

class _PenNoteScreenState extends ConsumerState<PenNoteScreen> {
  static const _uuid = Uuid();

  /// Live strokes in normalized coords (the canvas source of truth). Persisted
  /// back to the note as InkStroke via the adapter on save.
  List<PenStroke> _strokes = const [];

  /// Snapshot-before-change undo/redo of the stroke list.
  final List<List<PenStroke>> _undo = [];
  final List<List<PenStroke>> _redo = [];

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

  /// Selected brush for the BRUSH tool (fountain/ballpoint/pencil). The
  /// highlighter tool always uses [BrushKind.highlighter]; local state only for
  /// this increment (not persisted — see 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: each brush (and the highlighter)
  /// remembers its own color. Selecting a brush restores its color; picking a
  /// color updates ONLY the active brush's entry. In-memory only for this
  /// increment (TODO(brush-color-persist)).
  final Map<BrushKind, Color> _brushColors = {
    BrushKind.fountainPen: Colors.black,
    BrushKind.ballpoint: Colors.blue,
    BrushKind.pencil: Colors.green,
    BrushKind.highlighter: Colors.orange,
  };

  /// The brush whose color the color-dots edit: the highlighter when the
  /// highlighter tool is active, otherwise the selected pen brush.
  BrushKind get _activeColorBrush => _tool == EditorToolKind.highlighter
      ? BrushKind.highlighter
      : _penBrush;

  /// The active drawing color (the active brush's remembered color).
  Color get _color => _brushColors[_activeColorBrush] ?? Colors.black;

  /// The page-background template painted behind the ink (rnote-style). Default
  /// blank; persisted per-notebook in the sidecar.
  NoteBackground _background = NoteBackground.blank;

  bool _allowFingerDrawing = false;
  bool _dirty = false;
  bool _needsCenter = true;

  /// The note's synthetic source path `<folder>/notebook` (also the note id).
  /// Persistence flows through this note's `notebook.badnote.json` sidecar.
  String? _notePath;

  /// Per-file sidecar persistence sink (strokes page 0 + title), debounced and
  /// atomic — replaces the old SQLite Note/noteListProvider write path here.
  SidecarRepository? _repo;

  /// Page index a standalone note's strokes live under in the sidecar.
  static const int _notePageIndex = 0;

  final TextEditingController _titleController = TextEditingController();

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

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

  static const List<Color> _palette = kInkPalette;

  @override
  void initState() {
    super.initState();
    PenInputService.instance.start();
    final note = widget.note;
    if (note != null) {
      _notePath = note.id;
      _titleController.text = note.title;
      // Seed from the in-memory note's strokes (e.g. tests) until the sidecar
      // load resolves and (if present) overrides with persisted strokes.
      _strokes = penStrokesFromInk(note.strokes, kNoteLogicalPage);
    } else {
      _titleController.text = 'Untitled';
    }
    _initPenConfig();
    if (_notePath != null) _initPersistence(_notePath!);
  }

  /// Open the note's `notebook.badnote.json` sidecar and, if it holds persisted
  /// strokes / a title, hydrate the canvas from them. Strokes load as page-0
  /// [EditorStroke]s converted to [PenStroke] (mirrors the PDF editor).
  Future<void> _initPersistence(String notePath) async {
    final repo = await SidecarRepository.open(notePath, docType: 'notebook');
    if (!mounted) {
      repo.dispose();
      return;
    }
    _repo = repo;
    final loaded = repo.loadedStrokes[_notePageIndex];
    setState(() {
      if (loaded != null && loaded.isNotEmpty) {
        _strokes = [for (final es in loaded) _penStrokeFromEditor(es)];
      }
      final title = repo.loadedTitle;
      if (title != null && title.isNotEmpty) {
        _titleController.text = title;
      }
      _background = noteBackgroundFromName(repo.loadedBackground);
    });
  }

  /// EditorStroke → live PenStroke (mirror of the PDF editor's loader). Brush
  /// is persisted on the EditorStroke now, so carry it through; old sidecars
  /// without the field decode to fountainPen (back-compat default).
  PenStroke _penStrokeFromEditor(EditorStroke es) => PenStroke(
        points: es.points
            .map((ep) => PenPoint(ep.x, ep.y, ep.pressure, tilt: ep.tilt))
            .toList(),
        color: es.color,
        width: es.width,
        kind: es.tool == EditorTool.highlighter
            ? PenStrokeKind.highlighter
            : PenStrokeKind.pen,
        brush: es.brush,
      );

  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() {
    // Flush any pending sidecar write before tearing down (atomic write
    // completes off the widget tree).
    final repo = _repo;
    if (repo != null) {
      repo.flush();
      repo.dispose();
    }
    _penConfig?.removeListener(_onPenConfigChanged);
    _penConfig?.dispose();
    _titleController.dispose();
    _transform.dispose();
    super.dispose();
  }

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

  void _pushUndo() {
    _undo.add(List<PenStroke>.from(_strokes));
    _redo.clear();
  }

  void _commitStroke(PenStroke stroke) {
    setState(() {
      _pushUndo();
      _strokes = [..._strokes, stroke];
      _dirty = true;
    });
  }

  void _eraseStroke(int index, List<PenStroke> replacements) {
    if (index < 0 || index >= _strokes.length) return;
    setState(() {
      _pushUndo();
      _strokes = [
        ..._strokes.sublist(0, index),
        ...replacements,
        ..._strokes.sublist(index + 1),
      ];
      _dirty = true;
    });
  }

  void _performUndo() {
    if (_undo.isEmpty) return;
    setState(() {
      _redo.add(List<PenStroke>.from(_strokes));
      _strokes = _undo.removeLast();
      _dirty = true;
    });
  }

  void _performRedo() {
    if (_redo.isEmpty) return;
    setState(() {
      _undo.add(List<PenStroke>.from(_strokes));
      _strokes = _redo.removeLast();
      _dirty = true;
    });
  }

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

  // ── Persistence ──────────────────────────────────────────────────────────────

  /// Persist the live pen strokes + title to the note's `notebook.badnote.json`
  /// sidecar (strokes as page-0 [EditorStroke]s; title via the sidecar's title
  /// field), debounced/atomic via [SidecarRepository]. Creates the notebook
  /// folder lazily on first save when the screen was opened without a path.
  /// Refreshes the home list and triggers local OCR for search indexing.
  Future<void> _save() async {
    if (!_dirty) return;
    final notifier = ref.read(noteListProvider.notifier);
    final now = DateTime.now();
    final title = _titleController.text.trim().isEmpty
        ? 'Untitled'
        : _titleController.text.trim();

    // Lazily create the notebook folder + sidecar repo on first save.
    if (_repo == null) {
      final created = await notifier.createNote(title: title);
      if (!mounted) return;
      _notePath = created.id;
      final repo =
          await SidecarRepository.open(created.id, docType: 'notebook');
      if (!mounted) {
        repo.dispose();
        return;
      }
      _repo = repo;
    }
    final repo = _repo!;

    final editorStrokes = <EditorStroke>[
      for (final s in _strokes) EditorStroke.fromPenStroke(s),
    ];
    repo.scheduleTitleSave(title);
    repo.scheduleBackgroundSave(_background.name);
    repo.scheduleStrokeSave(_notePageIndex, editorStrokes);
    await repo.flush();

    // Refresh the home list so the title/recency update is visible on return.
    await notifier.loadNotes();
    if (!mounted) return;
    setState(() => _dirty = false);

    // Build an in-memory Note (id = note path) for OCR/FTS indexing only.
    final inkStrokes = <InkStroke>[
      for (final s in _strokes)
        inkStrokeFromPen(s, kNoteLogicalPage, id: _uuid.v4(), createdAt: now),
    ];
    _runLocalOcr(Note(
      id: _notePath!,
      title: title,
      strokes: inkStrokes,
      createdAt: now,
      updatedAt: now,
    ));
  }

  void _runLocalOcr(Note note) {
    final id = note.id;
    ref.read(ocrStatusProvider.notifier).state = {
      ...ref.read(ocrStatusProvider),
      id: OcrStatus.processing,
    };
    ref.read(ocrServiceProvider).processNote(note).then((_) {
      if (!mounted) return;
      ref.read(ocrStatusProvider.notifier).state = {
        ...ref.read(ocrStatusProvider),
        id: OcrStatus.done,
      };
    }).catchError((_) {
      if (!mounted) return;
      ref.read(ocrStatusProvider.notifier).state = {
        ...ref.read(ocrStatusProvider),
        id: OcrStatus.failed,
      };
    });
  }

  // ── Layout helpers ──────────────────────────────────────────────────────────

  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 (reuses the undo stacks) ─────────────

  /// Set (or clear) the selected stroke from a SELECT-tool tap.
  void _selectStroke(int? index) {
    setState(() => _selectedStroke = index);
  }

  /// Translate the selected stroke by ([dx],[dy]) normalized. On the first delta
  /// of a drag ([isDragStart]) push ONE undo snapshot so the whole drag is a
  /// single undo step.
  void _moveStroke(int index, double dx, double dy, bool isDragStart) {
    if (index < 0 || index >= _strokes.length) return;
    setState(() {
      if (isDragStart) _pushUndo();
      final next = List<PenStroke>.from(_strokes);
      next[index] = translateStroke(next[index], dx, dy);
      _strokes = next;
      _dirty = true;
    });
  }

  /// Delete the selected stroke (button or long-press), as one undo step.
  void _deleteSelected() {
    final idx = _selectedStroke;
    if (idx == null || idx < 0 || idx >= _strokes.length) return;
    setState(() {
      _pushUndo();
      _strokes = [
        ..._strokes.sublist(0, idx),
        ..._strokes.sublist(idx + 1),
      ];
      _selectedStroke = null;
      _dirty = true;
    });
  }

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    return PopScope(
      canPop: true,
      onPopInvokedWithResult: (didPop, _) {
        if (didPop && _dirty) _save();
      },
      child: Scaffold(
        body: Stack(
          children: [
            Positioned.fill(child: _buildCanvas()),
            // Tool palette (top-center) — identical chrome to the PDF editor.
            SafeArea(
              child: Align(
                alignment: Alignment.topCenter,
                child: Padding(
                  padding: const EdgeInsets.only(top: 8),
                  child: _buildToolPalette(cs),
                ),
              ),
            ),
            // Back (saves on the way out).
            SafeArea(
              child: Padding(
                padding: const EdgeInsets.all(8),
                child: RoundIconButton(
                  icon: Icons.arrow_back,
                  tooltip: 'Back',
                  onPressed: () async {
                    final navigator = Navigator.of(context);
                    await _save();
                    if (mounted) navigator.maybePop();
                  },
                ),
              ),
            ),
            // Title pill (bottom-center).
            SafeArea(
              child: Align(
                alignment: Alignment.bottomCenter,
                child: Padding(
                  padding: const EdgeInsets.only(bottom: 16),
                  child: _buildTitlePill(cs),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildCanvas() {
    return LayoutBuilder(
      builder: (context, constraints) {
        // Fit the logical note page into the viewport at scale 1.0.
        final fitW = constraints.maxWidth / kNoteLogicalPage.width;
        final fitH = constraints.maxHeight / kNoteLogicalPage.height;
        final scale = fitW < fitH ? fitW : fitH;
        final pageSize = Size(
          kNoteLogicalPage.width * scale,
          kNoteLogicalPage.height * scale,
        );

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

        return PenCanvas(
          pageSize: pageSize,
          strokes: _strokes,
          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,
          // A white sheet with a soft shadow — the note "paper" — overlaid with
          // the selected background template, painted in page-pixel space (so it
          // scales with zoom) and BEHIND the ink layers.
          pageWidget: DecoratedBox(
            decoration: BoxDecoration(
              color: Colors.white,
              boxShadow: [
                BoxShadow(
                  color: Colors.black.withValues(alpha: 0.18),
                  blurRadius: 12,
                  spreadRadius: 1,
                ),
              ],
            ),
            child: CustomPaint(
              painter: NoteBackgroundPainter(_background),
              size: Size.infinite,
            ),
          ),
        );
      },
    );
  }

  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: [
            // Pen tool with brush picker (fountain / ballpoint / pencil), each
            // brush showing its own remembered color.
            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),
            ),
            // Select (cursor) + shape tools.
            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.isNotEmpty ? _performUndo : null,
            ),
            ToolButton(
              icon: Icons.redo,
              selected: false,
              tooltip: 'Redo',
              onPressed: _redo.isNotEmpty ? _performRedo : null,
            ),
            PaletteDivider(cs: cs),
            for (final c in _palette) _colorDot(c, cs),
            PaletteDivider(cs: cs),
            // Page-background template picker (rnote-style: blank / dots / ruled
            // / grid / cornell). Persists per-notebook in the sidecar.
            PopupMenuButton<NoteBackground>(
              tooltip: 'Page background',
              initialValue: _background,
              onSelected: (b) {
                setState(() {
                  _background = b;
                  _dirty = true;
                });
              },
              itemBuilder: (context) => [
                for (final b in NoteBackground.values)
                  PopupMenuItem<NoteBackground>(
                    value: b,
                    child: Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Icon(noteBackgroundIcon(b), size: 20),
                        const SizedBox(width: 10),
                        Text(noteBackgroundLabel(b)),
                        if (b == _background) ...[
                          const SizedBox(width: 8),
                          Icon(Icons.check, size: 18, color: cs.primary),
                        ],
                      ],
                    ),
                  ),
              ],
              child: Padding(
                padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 8),
                child: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Icon(
                      noteBackgroundIcon(_background),
                      size: 22,
                      color: cs.onSurfaceVariant,
                    ),
                    Icon(
                      Icons.arrow_drop_down,
                      size: 18,
                      color: cs.onSurfaceVariant,
                    ),
                  ],
                ),
              ),
            ),
            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) {
    // Selected against the ACTIVE brush's remembered color. A color tap updates
    // only that brush's entry (rnote per-brush color memory).
    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 _buildTitlePill(ColorScheme cs) {
    return Material(
      color: cs.surfaceContainerHigh,
      elevation: 3,
      borderRadius: BorderRadius.circular(28),
      child: ConstrainedBox(
        constraints: const BoxConstraints(maxWidth: 360),
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
          child: TextField(
            controller: _titleController,
            textAlign: TextAlign.center,
            style: TextStyle(color: cs.onSurface, fontWeight: FontWeight.w600),
            decoration: const InputDecoration(
              border: InputBorder.none,
              hintText: 'Note title…',
              isDense: true,
            ),
            onChanged: (_) => _dirty = true,
          ),
        ),
      ),
    );
  }
}
