import 'dart:convert';
import 'dart:math' as math;

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

class VisualizationService {
  String schemeSvg(KltAnalysis analysis, {int width = 1200, int height = 780}) {
    final nodes = analysis.nodes;
    if (nodes.isEmpty) return _emptySvg(width, height, 'No PIX nodes');
    final positions = _layout(nodes, width, height);
    final buffer = StringBuffer();
    buffer.writeln('<svg xmlns="http://www.w3.org/2000/svg" width="$width" height="$height" viewBox="0 0 $width $height">');
    buffer.writeln('<rect width="100%" height="100%" fill="#F7FAFC"/>');
    buffer.writeln('<text x="24" y="36" font-family="Arial" font-size="24" fill="#14324B">KLT 5.1 PIX -> PEAKS -> Reper -> lambda scheme</text>');
    for (final edge in analysis.edges) {
      final a = positions[edge.from];
      final b = positions[edge.to];
      if (a == null || b == null) continue;
      buffer.writeln('<line x1="${a.x}" y1="${a.y}" x2="${b.x}" y2="${b.y}" stroke="#718096" stroke-width="2" marker-end="url(#arrow)"/>');
    }
    buffer.writeln('<defs><marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth"><path d="M0,0 L0,6 L9,3 z" fill="#718096"/></marker></defs>');
    for (final node in nodes) {
      final p = positions[node.id]!;
      final severity = _maxSeverity(analysis, node.id);
      final color = _colorFor(node.type, severity);
      buffer.writeln('<rect x="${p.x - 70}" y="${p.y - 30}" rx="12" ry="12" width="140" height="60" fill="$color" stroke="#1A202C" stroke-width="1.2"/>');
      buffer.writeln('<text x="${p.x}" y="${p.y - 4}" text-anchor="middle" font-family="Arial" font-size="13" fill="#111827">${_esc(node.label, 20)}</text>');
      buffer.writeln('<text x="${p.x}" y="${p.y + 16}" text-anchor="middle" font-family="Arial" font-size="11" fill="#374151">${node.type.name} | PEAKS ${(node.peak.completeness * 100).round()}%</text>');
    }
    buffer.writeln(_legend(width - 280, 60));
    buffer.writeln('</svg>');
    return buffer.toString();
  }

  String blueprintSvg(KltAnalysis analysis, {int width = 1200, int height = 780}) {
    final works = analysis.nodes.where((n) => n.type == PixType.work).toList();
    if (works.isEmpty) return _emptySvg(width, height, 'No work nodes for blueprint');
    final buffer = StringBuffer();
    buffer.writeln('<svg xmlns="http://www.w3.org/2000/svg" width="$width" height="$height" viewBox="0 0 $width $height">');
    buffer.writeln('<rect width="100%" height="100%" fill="#FFFFFF"/>');
    buffer.writeln('<text x="24" y="36" font-family="Arial" font-size="24" fill="#14324B">KLT 5.1 schematic drawing / project zones</text>');
    final zones = <String>{};
    for (final w in works) {
      zones.add((w.payload['zone'] ?? 'zone').toString());
    }
    final zoneList = zones.toList()..sort();
    final laneHeight = (height - 100) / math.max(zoneList.length, 1);
    for (var zi = 0; zi < zoneList.length; zi++) {
      final y = 70 + zi * laneHeight;
      buffer.writeln('<rect x="24" y="$y" width="${width - 48}" height="${laneHeight - 10}" fill="#F8FAFC" stroke="#CBD5E1"/>');
      buffer.writeln('<text x="36" y="${y + 24}" font-family="Arial" font-size="16" fill="#334155">${_xml(zoneList[zi])}</text>');
    }
    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 zi = zoneList.indexOf(zone).clamp(0, zoneList.length - 1);
      final start = _num(w.payload['start_day'] ?? w.payload['startDay']);
      final end = _num(w.payload['end_day'] ?? w.payload['endDay']);
      final x = 180 + (start / maxEnd) * (width - 260);
      final rectWidth = math.max(60.0, ((end - start + 1) / maxEnd) * (width - 260));
      final y = 92 + zi * laneHeight;
      final severity = _maxSeverity(analysis, w.id);
      final fill = severity > 0.5 ? '#FCA5A5' : '#BAE6FD';
      buffer.writeln('<rect x="$x" y="$y" width="$rectWidth" height="44" fill="$fill" stroke="#0F172A" rx="6"/>');
      buffer.writeln('<text x="${x + 8}" y="${y + 26}" font-family="Arial" font-size="12" fill="#0F172A">${_esc(w.label, 28)}</text>');
      buffer.writeln('<line x1="$x" y1="${y + 54}" x2="${x + rectWidth}" y2="${y + 54}" stroke="#0F172A"/>');
      buffer.writeln('<text x="$x" y="${y + 70}" font-family="Arial" font-size="10" fill="#475569">d$start</text>');
      buffer.writeln('<text x="${x + rectWidth - 24}" y="${y + 70}" font-family="Arial" font-size="10" fill="#475569">d$end</text>');
    }
    buffer.writeln('</svg>');
    return buffer.toString();
  }

  String chartSvg(KltAnalysis analysis, {int width = 1200, int height = 680}) {
    final metrics = <String, double>{
      'CRI': analysis.cri,
      'delta avg': analysis.deltaAvg.clamp(0.0, 3.0) / 3.0,
      'MSI max': analysis.msiMax.clamp(0.0, 2.0) / 2.0,
      'DI max': analysis.diMax.clamp(0.0, 1.0),
      'defects': (analysis.defects.length / math.max(analysis.nodes.length, 1)).clamp(0.0, 1.0).toDouble(),
    };
    final buffer = StringBuffer();
    buffer.writeln('<svg xmlns="http://www.w3.org/2000/svg" width="$width" height="$height" viewBox="0 0 $width $height">');
    buffer.writeln('<rect width="100%" height="100%" fill="#FFFFFF"/>');
    buffer.writeln('<text x="24" y="36" font-family="Arial" font-size="24" fill="#14324B">KLT 5.1 risk and lambda graph</text>');
    final barWidth = (width - 160) / metrics.length;
    var index = 0;
    metrics.forEach((label, value) {
      final x = 80 + index * barWidth;
      final h = value * (height - 160);
      final y = height - 80 - h;
      final color = value > 0.66 ? '#F87171' : (value > 0.33 ? '#FBBF24' : '#34D399');
      buffer.writeln('<rect x="$x" y="$y" width="${barWidth * 0.62}" height="$h" fill="$color" rx="8"/>');
      buffer.writeln('<text x="${x + barWidth * 0.31}" y="${height - 48}" text-anchor="middle" font-family="Arial" font-size="14" fill="#111827">${_xml(label)}</text>');
      buffer.writeln('<text x="${x + barWidth * 0.31}" y="${y - 8}" text-anchor="middle" font-family="Arial" font-size="14" fill="#111827">${value.toStringAsFixed(2)}</text>');
      index++;
    });
    buffer.writeln('</svg>');
    return buffer.toString();
  }

  Map<String, _Point> _layout(List<PixNode> nodes, int width, int height) {
    final map = <String, _Point>{};
    final groups = <PixType, List<PixNode>>{};
    for (final node in nodes) {
      groups.putIfAbsent(node.type, () => []).add(node);
    }
    final typeOrder = [PixType.document, PixType.work, PixType.resource, PixType.delivery, PixType.cost, PixType.finance, PixType.text, PixType.risk];
    final columns = typeOrder.where((t) => groups.containsKey(t)).toList();
    final colCount = math.max(1, columns.length);
    for (var c = 0; c < columns.length; c++) {
      final list = groups[columns[c]]!;
      final x = 110 + c * ((width - 220) / math.max(1, colCount - 1));
      for (var i = 0; i < list.length; i++) {
        final y = 110 + i * ((height - 180) / math.max(1, list.length - 1));
        map[list[i].id] = _Point(x, y);
      }
    }
    return map;
  }

  double _maxSeverity(KltAnalysis analysis, String pixId) {
    final hits = analysis.defects.where((d) => d.pixIds.contains(pixId)).map((d) => d.severity).toList();
    if (hits.isEmpty) return 0.0;
    return hits.reduce(math.max);
  }

  String _colorFor(PixType type, double severity) {
    if (severity > 0.66) return '#FCA5A5';
    if (severity > 0.33) return '#FDE68A';
    switch (type) {
      case PixType.work:
        return '#BFDBFE';
      case PixType.resource:
        return '#C7D2FE';
      case PixType.delivery:
        return '#BBF7D0';
      case PixType.cost:
        return '#FBCFE8';
      case PixType.finance:
        return '#DDD6FE';
      case PixType.document:
        return '#E2E8F0';
      case PixType.text:
        return '#FED7AA';
      case PixType.risk:
        return '#FECACA';
    }
  }

  String _legend(double x, double y) {
    return '<g><rect x="$x" y="$y" width="240" height="130" fill="#FFFFFF" stroke="#CBD5E1" rx="8"/>'
        '<text x="${x + 12}" y="${y + 26}" font-family="Arial" font-size="14" fill="#111827">Legend</text>'
        '<rect x="${x + 12}" y="${y + 42}" width="18" height="18" fill="#FCA5A5"/><text x="${x + 38}" y="${y + 56}" font-family="Arial" font-size="12">high defect</text>'
        '<rect x="${x + 12}" y="${y + 68}" width="18" height="18" fill="#FDE68A"/><text x="${x + 38}" y="${y + 82}" font-family="Arial" font-size="12">medium defect</text>'
        '<rect x="${x + 12}" y="${y + 94}" width="18" height="18" fill="#BBF7D0"/><text x="${x + 38}" y="${y + 108}" font-family="Arial" font-size="12">stable PEAKS</text></g>';
  }

  String _emptySvg(int width, int height, String label) {
    return '<svg xmlns="http://www.w3.org/2000/svg" width="$width" height="$height"><rect width="100%" height="100%" fill="#fff"/><text x="40" y="60" font-family="Arial" font-size="22">${_xml(label)}</text></svg>';
  }
}

String prettyJson(Object obj) => const JsonEncoder.withIndent('  ').convert(obj);

String _xml(String s) => s.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;');

String _esc(String s, int limit) {
  final t = s.length > limit ? '${s.substring(0, limit - 1)}…' : s;
  return _xml(t);
}

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

class _Point {
  const _Point(this.x, this.y);
  final double x;
  final double y;
}
