import 'dart:math' as math;

import 'package:flutter/material.dart';

import '../models/klt_models.dart';

class KltSchemePainter extends CustomPainter {
  KltSchemePainter(this.analysis);

  final KltAnalysis? analysis;

  @override
  void paint(Canvas canvas, Size size) {
    final a = analysis;
    final bg = Paint()..color = const Color(0xFFF8FAFC);
    canvas.drawRect(Offset.zero & size, bg);
    if (a == null || a.nodes.isEmpty) {
      _text(canvas, 'KLT 5.1 scheme', const Offset(24, 36), 20, Colors.black87);
      return;
    }
    final positions = <String, Offset>{};
    final nodes = a.nodes;
    for (var i = 0; i < nodes.length; i++) {
      final angle = 2 * math.pi * i / nodes.length;
      final center = Offset(size.width / 2, size.height / 2);
      final radius = math.min(size.width, size.height) * 0.34;
      positions[nodes[i].id] = center + Offset(math.cos(angle), math.sin(angle)) * radius;
    }
    final linePaint = Paint()
      ..color = Colors.blueGrey.shade300
      ..strokeWidth = 1.5;
    for (final e in a.edges) {
      final p1 = positions[e.from];
      final p2 = positions[e.to];
      if (p1 != null && p2 != null) canvas.drawLine(p1, p2, linePaint);
    }
    for (final n in nodes) {
      final p = positions[n.id]!;
      final sev = _severity(a, n.id);
      final color = sev > 0.66 ? Colors.red.shade200 : (sev > 0.33 ? Colors.amber.shade200 : Colors.lightBlue.shade100);
      final rect = Rect.fromCenter(center: p, width: 112, height: 52);
      canvas.drawRRect(RRect.fromRectAndRadius(rect, const Radius.circular(10)), Paint()..color = color);
      canvas.drawRRect(RRect.fromRectAndRadius(rect, const Radius.circular(10)), Paint()..style = PaintingStyle.stroke..color = Colors.black87);
      _text(canvas, _short(n.label, 14), p + const Offset(-48, -5), 11, Colors.black87);
      _text(canvas, n.type.name, p + const Offset(-38, 13), 9, Colors.black54);
    }
  }

  @override
  bool shouldRepaint(covariant KltSchemePainter oldDelegate) => oldDelegate.analysis != analysis;
}

class KltBarChartPainter extends CustomPainter {
  KltBarChartPainter(this.analysis);

  final KltAnalysis? analysis;

  @override
  void paint(Canvas canvas, Size size) {
    final bg = Paint()..color = Colors.white;
    canvas.drawRect(Offset.zero & size, bg);
    final a = analysis;
    if (a == null) {
      _text(canvas, 'KLT 5.1 graph', const Offset(24, 36), 20, Colors.black87);
      return;
    }
    final metrics = <String, double>{
      'CRI': a.cri,
      'delta': (a.deltaAvg / 3).clamp(0, 1).toDouble(),
      'MSI': (a.msiMax / 2).clamp(0, 1).toDouble(),
      'DI': a.diMax.clamp(0, 1).toDouble(),
      'DEF': (a.defects.length / math.max(a.nodes.length, 1)).clamp(0, 1).toDouble(),
    };
    final barW = size.width / (metrics.length * 1.8);
    var i = 0;
    metrics.forEach((label, value) {
      final h = value * (size.height - 70);
      final x = 28 + i * (barW * 1.8);
      final y = size.height - 36 - h;
      final color = value > 0.66 ? Colors.red.shade300 : (value > 0.33 ? Colors.amber.shade400 : Colors.green.shade300);
      canvas.drawRRect(RRect.fromRectAndRadius(Rect.fromLTWH(x, y, barW, h), const Radius.circular(6)), Paint()..color = color);
      _text(canvas, label, Offset(x, size.height - 22), 11, Colors.black87);
      _text(canvas, value.toStringAsFixed(2), Offset(x, y - 18), 10, Colors.black54);
      i++;
    });
  }

  @override
  bool shouldRepaint(covariant KltBarChartPainter oldDelegate) => oldDelegate.analysis != analysis;
}

class KltBlueprintPainter extends CustomPainter {
  KltBlueprintPainter(this.analysis);

  final KltAnalysis? analysis;

  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawRect(Offset.zero & size, Paint()..color = Colors.white);
    final a = analysis;
    if (a == null) {
      _text(canvas, 'KLT 5.1 drawing', const Offset(24, 36), 20, Colors.black87);
      return;
    }
    final works = a.nodes.where((n) => n.type == PixType.work).toList();
    if (works.isEmpty) return;
    final zones = works.map((w) => (w.payload['zone'] ?? 'zone').toString()).toSet().toList()..sort();
    final laneH = (size.height - 70) / math.max(zones.length, 1);
    for (var z = 0; z < zones.length; z++) {
      final y = 44 + z * laneH;
      canvas.drawRect(Rect.fromLTWH(20, y, size.width - 40, laneH - 8), Paint()..color = const Color(0xFFF8FAFC));
      canvas.drawRect(Rect.fromLTWH(20, y, size.width - 40, laneH - 8), Paint()..style = PaintingStyle.stroke..color = Colors.blueGrey.shade200);
      _text(canvas, zones[z], Offset(30, y + 18), 12, Colors.blueGrey.shade900);
    }
    final maxEnd = works.map((w) => _num(w.payload['end_day'] ?? w.payload['endDay'])).fold<double>(1, math.max);
    for (final w in works) {
      final zone = (w.payload['zone'] ?? 'zone').toString();
      final z = zones.indexOf(zone).clamp(0, zones.length - 1);
      final start = _num(w.payload['start_day'] ?? w.payload['startDay']);
      final end = _num(w.payload['end_day'] ?? w.payload['endDay']);
      final x = 120 + (start / maxEnd) * (size.width - 180);
      final width = math.max(46.0, ((end - start + 1) / maxEnd) * (size.width - 180));
      final y = 70 + z * laneH;
      canvas.drawRRect(RRect.fromRectAndRadius(Rect.fromLTWH(x, y, width, 34), const Radius.circular(5)), Paint()..color = Colors.cyan.shade100);
      canvas.drawRRect(RRect.fromRectAndRadius(Rect.fromLTWH(x, y, width, 34), const Radius.circular(5)), Paint()..style = PaintingStyle.stroke..color = Colors.black87);
      _text(canvas, _short(w.label, 18), Offset(x + 4, y + 20), 10, Colors.black87);
    }
  }

  @override
  bool shouldRepaint(covariant KltBlueprintPainter oldDelegate) => oldDelegate.analysis != analysis;
}

void _text(Canvas canvas, String text, Offset offset, double size, Color color) {
  final painter = TextPainter(
    text: TextSpan(text: text, style: TextStyle(fontSize: size, color: color)),
    textDirection: TextDirection.ltr,
  )..layout(maxWidth: 220);
  painter.paint(canvas, offset);
}

String _short(String s, int n) => s.length > n ? '${s.substring(0, n - 1)}…' : s;

double _severity(KltAnalysis a, String id) {
  final hits = a.defects.where((d) => d.pixIds.contains(id)).map((d) => d.severity).toList();
  return hits.isEmpty ? 0.0 : hits.reduce(math.max);
}

double _num(dynamic v) {
  if (v is num) return v.toDouble();
  if (v is String) return double.tryParse(v.replaceAll(',', '.')) ?? 0.0;
  return 0.0;
}
