import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:shadcn_ui/shadcn_ui.dart';

import '../util/external_link.dart';

/// GFM 表格的「填满 / 滚动」渲染。
///
/// flutter_markdown_plus 的内置 `_buildTable()` 二选一：FlexColumnWidth 填满但
/// 折行；IntrinsicColumnWidth 不折行但横向滚动里无法填满。自定义 builder 又被
/// 包强制覆盖。故把表格从 markdown 抽出，用本组件接管：
/// - 量出各列内容固有宽度；
/// - 总宽 ≤ 可用宽 → 按比例放大列宽**填满整行**（不滚动、不折行）；
/// - 总宽 > 可用宽 → 用固有列宽 + 横向滚动查看（不折行）。
/// 单元格支持 `**加粗**` 与 `[文字](链接)`（点击走外部承接弹窗）。
class MarkdownTable extends StatelessWidget {
  const MarkdownTable({
    super.key,
    required this.header,
    required this.rows,
    required this.aligns,
  });

  final List<String> header;
  final List<List<String>> rows;
  final List<TextAlign> aligns;

  static const double _hPad = 12;
  static const double _vPad = 8;
  static const double _border = 1;

  @override
  Widget build(BuildContext context) {
    final cs = ShadTheme.of(context).colorScheme;
    final n = header.length;
    // 阅读字号倍率（表格在 ReaderScaled 子树内）：量宽必须用同一 textScaler，
    // 否则缩放后列宽测算与实际渲染不符。
    final textScaler = MediaQuery.textScalerOf(context);

    final headStyle = TextStyle(
      fontWeight: FontWeight.w600,
      fontSize: 13,
      color: cs.foreground,
    );
    final bodyStyle = TextStyle(
      fontSize: 13,
      height: 1.5,
      color: cs.foreground,
      fontFeatures: const [FontFeature.tabularFigures()],
    );

    // 各列固有宽 = 该列最宽单元格(含表头) + 左右内距；按加粗量(偏宽)更稳妥
    final intrinsic = List<double>.filled(n, 0);
    void acc(List<String> cells) {
      for (var c = 0; c < n && c < cells.length; c++) {
        final w = _measure(
          _stripMd(cells[c]),
          bodyStyle.copyWith(fontWeight: FontWeight.w700),
          textScaler,
        );
        if (w > intrinsic[c]) intrinsic[c] = w;
      }
    }

    acc(header);
    for (final r in rows) {
      acc(r);
    }
    for (var c = 0; c < n; c++) {
      intrinsic[c] += _hPad * 2;
    }

    return LayoutBuilder(
      builder: (context, constraints) {
        final avail = constraints.maxWidth;
        final sum =
            intrinsic.fold<double>(0, (a, b) => a + b) + (n + 1) * _border;

        List<double> widths;
        bool scroll;
        if (sum <= avail) {
          // 够宽：把多余宽度按列固有宽比例分摊，填满整行
          final extra = avail - sum;
          final total = intrinsic.fold<double>(0, (a, b) => a + b);
          widths = [
            for (final w in intrinsic)
              w + (total > 0 ? extra * (w / total) : extra / n),
          ];
          scroll = false;
        } else {
          widths = intrinsic;
          scroll = true;
        }

        final table = Table(
          defaultColumnWidth: const IntrinsicColumnWidth(),
          columnWidths: {
            for (var c = 0; c < n; c++) c: FixedColumnWidth(widths[c]),
          },
          border: TableBorder.all(
            color: cs.border,
            width: _border,
            borderRadius: BorderRadius.circular(8),
          ),
          children: [
            _row(context, header, headStyle, cs, isHeader: true),
            for (final r in rows) _row(context, r, bodyStyle, cs),
          ],
        );

        if (!scroll) return table;
        return SingleChildScrollView(
          scrollDirection: Axis.horizontal,
          child: ConstrainedBox(
            constraints: BoxConstraints(minWidth: avail),
            child: table,
          ),
        );
      },
    );
  }

  TableRow _row(
    BuildContext context,
    List<String> cells,
    TextStyle style,
    ShadColorScheme cs, {
    bool isHeader = false,
  }) {
    return TableRow(
      decoration: isHeader ? BoxDecoration(color: cs.muted) : null,
      children: [
        for (var c = 0; c < header.length; c++)
          Padding(
            padding: const EdgeInsets.symmetric(
              horizontal: _hPad,
              vertical: _vPad,
            ),
            child: Align(
              alignment: _alignment(c),
              child: Text.rich(
                _inlineSpans(
                  context,
                  c < cells.length ? cells[c] : '',
                  style,
                  cs,
                ),
                textAlign: c < aligns.length ? aligns[c] : TextAlign.left,
                softWrap: false,
                overflow: TextOverflow.visible,
              ),
            ),
          ),
      ],
    );
  }

  Alignment _alignment(int c) {
    final a = c < aligns.length ? aligns[c] : TextAlign.left;
    return switch (a) {
      TextAlign.center => Alignment.center,
      TextAlign.right => Alignment.centerRight,
      _ => Alignment.centerLeft,
    };
  }

  static double _measure(String text, TextStyle style, TextScaler textScaler) {
    final tp = TextPainter(
      text: TextSpan(text: text, style: style),
      maxLines: 1,
      textDirection: TextDirection.ltr,
      textScaler: textScaler,
    )..layout();
    return tp.width;
  }
}

// ── 解析：从 markdown 抽出表格块，其余按原文保留 ──────────────────────────

class MdSegment {
  const MdSegment.text(this.text) : table = null;
  const MdSegment.table(this.table) : text = '';
  final String text;
  final MarkdownTable? table;
  bool get isTable => table != null;
}

final RegExp _sepLine = RegExp(r'^\s*\|?[\s:|-]*-[\s:|-]*\|?\s*$');

bool _isRowLine(String l) => l.contains('|');
bool _isSepLine(String l) =>
    l.contains('-') && l.contains('|') && _sepLine.hasMatch(l);

List<String> _splitCells(String line) {
  var t = line.trim();
  if (t.startsWith('|')) t = t.substring(1);
  if (t.endsWith('|')) t = t.substring(0, t.length - 1);
  return t.split('|').map((s) => s.trim()).toList();
}

/// 把 markdown 拆成「文本段」与「表格段」，顺序保留。
List<MdSegment> splitMarkdownTables(String md) {
  final lines = md.split('\n');
  final out = <MdSegment>[];
  final buf = <String>[];
  void flush() {
    if (buf.isNotEmpty) {
      out.add(MdSegment.text(buf.join('\n')));
      buf.clear();
    }
  }

  var i = 0;
  while (i < lines.length) {
    final isHeader =
        _isRowLine(lines[i]) &&
        i + 1 < lines.length &&
        _isSepLine(lines[i + 1]);
    if (!isHeader) {
      buf.add(lines[i]);
      i++;
      continue;
    }
    flush();
    final headerCells = _splitCells(lines[i]);
    // demo：忽略 markdown 列对齐，表格内容统一左对齐
    // （右对齐会让右列大片留白只显几个字）。
    final aligns = List<TextAlign>.filled(headerCells.length, TextAlign.left);
    final rows = <List<String>>[];
    var j = i + 2;
    while (j < lines.length && _isRowLine(lines[j]) && !_isSepLine(lines[j])) {
      final cs = _splitCells(lines[j]);
      rows.add([
        for (var c = 0; c < headerCells.length; c++) c < cs.length ? cs[c] : '',
      ]);
      j++;
    }
    out.add(
      MdSegment.table(
        MarkdownTable(header: headerCells, rows: rows, aligns: aligns),
      ),
    );
    i = j;
  }
  flush();
  return out;
}

// ── 单元格内联：**加粗** 与 [文字](链接) ────────────────────────────────

final RegExp _inlineToken = RegExp(r'\*\*(.+?)\*\*|\[([^\]]+)\]\(([^)]+)\)');

String _stripMd(String s) => s
    .replaceAllMapped(_inlineToken, (m) => m.group(1) ?? m.group(2) ?? '')
    .replaceAll('*', '');

InlineSpan _inlineSpans(
  BuildContext context,
  String raw,
  TextStyle base,
  ShadColorScheme cs,
) {
  final spans = <InlineSpan>[];
  var last = 0;
  for (final m in _inlineToken.allMatches(raw)) {
    if (m.start > last) {
      spans.add(TextSpan(text: raw.substring(last, m.start), style: base));
    }
    if (m.group(1) != null) {
      spans.add(
        TextSpan(
          text: m.group(1),
          style: base.copyWith(fontWeight: FontWeight.w700),
        ),
      );
    } else {
      final text = m.group(2)!;
      final url = m.group(3)!;
      spans.add(
        TextSpan(
          text: text,
          style: base.copyWith(
            color: cs.accentForeground,
            decoration: TextDecoration.underline,
            decorationColor: cs.accentForeground,
          ),
          recognizer: TapGestureRecognizer()
            ..onTap = () =>
                openExternalUrl(context, url, fallbackToast: '链接即将上线'),
        ),
      );
    }
    last = m.end;
  }
  if (last < raw.length) {
    spans.add(TextSpan(text: raw.substring(last), style: base));
  }
  return TextSpan(children: spans);
}
