import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_layout_grid/flutter_layout_grid.dart';

import '../../theming/app_theme.dart';
import 'pm_document_view.dart';
import 'pm_node.dart';
import 'pm_render_scope.dart';

/// Table display mode, mirroring the web ticker's three-way toggle. Switching
/// one table switches all of them at once — the choice is shared, exactly like
/// the web view (which persists it in `localStorage`; here it lives for the
/// duration of the app session).
enum PmTableMode { scroll, wrap, zoom }

final ValueNotifier<PmTableMode> pmTableMode = ValueNotifier(PmTableMode.scroll);

/// Renders a ProseMirror table. Flutter's built-in `Table` cannot span cells,
/// so `flutter_layout_grid` places each cell explicitly, honouring
/// colspan/rowspan via a simple HTML-style occupancy scan.
///
/// Three display modes match the web ticker:
/// - `scroll`: content-sized columns, single-line rows, horizontal scroll with
///   edge shadows that hint the overflow.
/// - `wrap`: columns share the width and text wraps — no scrolling.
/// - `zoom`: the full table is scaled down to fit on screen at a glance.
class PmTableView extends StatefulWidget {
  final PmTable node;

  const PmTableView({required this.node, super.key});

  @override
  State<PmTableView> createState() => _PmTableViewState();
}

class _PmTableViewState extends State<PmTableView> {
  final ScrollController _scroll = ScrollController();
  bool _showLeftShadow = false;
  bool _showRightShadow = false;

  /// Whether the table is too wide to fit and therefore needs the mode toggle.
  /// Measured from the natural (scroll) layout's overflow — see [_updateShadows].
  bool _overflows = false;

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

  @override
  Widget build(BuildContext context) {
    final rows = widget.node.children.whereType<PmTableRow>().toList();
    if (rows.isEmpty) return const SizedBox.shrink();

    final occupied = <int, Set<int>>{};
    final placements = <Widget>[];
    var columnCount = 0;

    for (var r = 0; r < rows.length; r++) {
      var col = 0;
      for (final cell in rows[r].children.whereType<PmTableCell>()) {
        while (occupied[r]?.contains(col) ?? false) {
          col++;
        }
        placements.add(
          GridPlacement(
            columnStart: col,
            columnSpan: cell.colspan,
            rowStart: r,
            rowSpan: cell.rowspan,
            child: _cell(context, cell, r),
          ),
        );
        for (var dr = 0; dr < cell.rowspan; dr++) {
          final set = occupied[r + dr] ??= <int>{};
          for (var dc = 0; dc < cell.colspan; dc++) {
            set.add(col + dc);
          }
        }
        col += cell.colspan;
        columnCount = max(columnCount, col);
      }
    }
    if (columnCount == 0) return const SizedBox.shrink();

    return ValueListenableBuilder<PmTableMode>(
      valueListenable: pmTableMode,
      builder: (context, mode, _) {
        // When the table already fits, the mode is moot: render the natural
        // grid (which shows identically and won't scroll) and hide the toggle,
        // exactly like the web ticker. The scroll layout keeps measuring the
        // overflow so the toggle reappears if the width or content changes.
        final effectiveMode = _overflows ? mode : PmTableMode.scroll;
        return Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          mainAxisSize: MainAxisSize.min,
          children: [
            if (_overflows) ...[
              _Toolbar(mode: mode),
              const SizedBox(height: AppSpacing.xs),
            ],
            _body(context, effectiveMode, rows.length, columnCount, placements),
          ],
        );
      },
    );
  }

  Widget _body(
    BuildContext context,
    PmTableMode mode,
    int rowCount,
    int columnCount,
    List<Widget> placements,
  ) {
    switch (mode) {
      case PmTableMode.wrap:
        return PmCellTextFlow(
          softWrap: true,
          child: LayoutGrid(
            columnSizes: List.filled(columnCount, flex(1)),
            rowSizes: List.filled(rowCount, auto),
            children: placements,
          ),
        );
      case PmTableMode.zoom:
        final grid = _naturalGrid(rowCount, columnCount, placements);
        return LayoutBuilder(
          builder: (context, constraints) => SizedBox(
            width: constraints.maxWidth,
            child: Align(
              alignment: Alignment.topLeft,
              child: FittedBox(
                fit: BoxFit.scaleDown,
                alignment: Alignment.topLeft,
                child: grid,
              ),
            ),
          ),
        );
      case PmTableMode.scroll:
        WidgetsBinding.instance.addPostFrameCallback((_) => _updateShadows());
        return Stack(
          children: [
            NotificationListener<ScrollNotification>(
              onNotification: (_) {
                _updateShadows();
                return false;
              },
              child: SingleChildScrollView(
                controller: _scroll,
                scrollDirection: Axis.horizontal,
                child: _naturalGrid(rowCount, columnCount, placements),
              ),
            ),
            _edgeShadow(context, left: true, visible: _showLeftShadow),
            _edgeShadow(context, left: false, visible: _showRightShadow),
          ],
        );
    }
  }

  /// Content-sized, single-line grid shared by the scroll and zoom modes.
  Widget _naturalGrid(int rowCount, int columnCount, List<Widget> placements) =>
      PmCellTextFlow(
        softWrap: false,
        child: LayoutGrid(
          columnSizes: List.filled(columnCount, auto),
          rowSizes: List.filled(rowCount, auto),
          children: placements,
        ),
      );

  void _updateShadows() {
    if (!_scroll.hasClients) return;
    final pos = _scroll.position;
    final canScroll = pos.maxScrollExtent > 0.5;
    final showLeft = canScroll && pos.pixels > 0.5;
    final showRight = canScroll && pos.pixels < pos.maxScrollExtent - 0.5;
    if (canScroll != _overflows ||
        showLeft != _showLeftShadow ||
        showRight != _showRightShadow) {
      setState(() {
        _overflows = canScroll;
        _showLeftShadow = showLeft;
        _showRightShadow = showRight;
      });
    }
  }

  Widget _edgeShadow(
    BuildContext context, {
    required bool left,
    required bool visible,
  }) {
    final dark = Theme.of(context).brightness == Brightness.dark;
    final color = Colors.black.withValues(alpha: dark ? 0.28 : 0.12);
    return Positioned(
      top: 0,
      bottom: 0,
      left: left ? 0 : null,
      right: left ? null : 0,
      child: IgnorePointer(
        child: AnimatedOpacity(
          opacity: visible ? 1 : 0,
          duration: const Duration(milliseconds: 150),
          child: Container(
            width: 22,
            decoration: BoxDecoration(
              gradient: LinearGradient(
                begin: left ? Alignment.centerLeft : Alignment.centerRight,
                end: left ? Alignment.centerRight : Alignment.centerLeft,
                colors: [color, color.withValues(alpha: 0)],
              ),
            ),
          ),
        ),
      ),
    );
  }

  Widget _cell(BuildContext context, PmTableCell cell, int rowIndex) {
    final theme = Theme.of(context);
    Color? background;
    if (cell.header) {
      background = theme.colorScheme.surfaceContainerHighest;
    } else if (rowIndex.isOdd) {
      background = theme.colorScheme.surfaceContainerHighest.withValues(
        alpha: 0.4,
      );
    }
    return DecoratedBox(
      decoration: BoxDecoration(
        color: background,
        border: Border.all(color: theme.colorScheme.outlineVariant),
      ),
      // The grid lays cells out with loose constraints, so a shorter cell would
      // shrink below its row's height (set by the tallest cell) and its border
      // would not line up. Align stretches the box to fill the whole cell area
      // while still reporting the content height for the row's intrinsic size.
      child: Align(
        alignment: Alignment.topLeft,
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
          child: pmBlocks(cell.children, gap: AppSpacing.xs),
        ),
      ),
    );
  }
}

/// Compact segmented control that toggles [pmTableMode]. Only shown for tables
/// wide enough to need it; a tap switches every table on screen at once.
class _Toolbar extends StatelessWidget {
  final PmTableMode mode;

  const _Toolbar({required this.mode});

  static const List<(PmTableMode, IconData, String)> _modes = [
    (PmTableMode.scroll, Icons.swap_horiz, 'Originalbreite (seitlich scrollen)'),
    (PmTableMode.wrap, Icons.wrap_text, 'Spalten umbrechen'),
    (PmTableMode.zoom, Icons.fit_screen, 'Verkleinern (alles auf einen Blick)'),
  ];

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return Align(
      alignment: Alignment.centerRight,
      child: Container(
        decoration: BoxDecoration(
          color: theme.colorScheme.surface,
          border: Border.all(color: theme.colorScheme.outlineVariant),
          borderRadius: BorderRadius.circular(8),
        ),
        clipBehavior: Clip.antiAlias,
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            for (var i = 0; i < _modes.length; i++) ...[
              if (i > 0)
                Container(
                  width: 1,
                  height: 26,
                  color: theme.colorScheme.outlineVariant,
                ),
              _button(context, _modes[i].$1, _modes[i].$2, _modes[i].$3),
            ],
          ],
        ),
      ),
    );
  }

  Widget _button(
    BuildContext context,
    PmTableMode target,
    IconData icon,
    String tooltip,
  ) {
    final theme = Theme.of(context);
    final active = target == mode;
    return Tooltip(
      message: tooltip,
      child: InkWell(
        onTap: () => pmTableMode.value = target,
        child: Container(
          width: 34,
          height: 30,
          alignment: Alignment.center,
          color: active ? theme.colorScheme.primaryContainer : Colors.transparent,
          child: Icon(
            icon,
            size: 16,
            color: active
                ? theme.colorScheme.onPrimaryContainer
                : theme.colorScheme.onSurfaceVariant,
          ),
        ),
      ),
    );
  }
}
