enum PixType { document, work, resource, delivery, cost, finance, text, risk }

enum DefectClass {
  lambdaDefect,
  documentation,
  technology,
  resource,
  logistics,
  cost,
  finance,
  visualization,
}

class Reper4 {
  const Reper4({required this.u, required this.i, required this.r, required this.d});

  final double u;
  final double i;
  final double r;
  final double d;

  factory Reper4.fromJson(Map<String, dynamic> json) {
    return Reper4(
      u: _num(json['U'] ?? json['u'] ?? 0.0),
      i: _num(json['I'] ?? json['i'] ?? 0.0),
      r: _num(json['R'] ?? json['r'] ?? 0.0),
      d: _num(json['D'] ?? json['d'] ?? 0.0),
    );
  }

  Map<String, dynamic> toJson() => {'U': u, 'I': i, 'R': r, 'D': d};
}

class LambdaResult {
  const LambdaResult({this.lambda, this.delta, this.auth, required this.status, this.message = ''});

  final double? lambda;
  final double? delta;
  final double? auth;
  final String status;
  final String message;

  Map<String, dynamic> toJson() => {
        'lambda': lambda,
        'delta': delta,
        'auth': auth,
        'status': status,
        'message': message,
      };
}

class PeakState {
  const PeakState({
    this.linked = false,
    this.scheduled = false,
    this.priced = false,
    this.resourced = false,
    this.justified = false,
    this.physicallyFeasible = true,
  });

  final bool linked;
  final bool scheduled;
  final bool priced;
  final bool resourced;
  final bool justified;
  final bool physicallyFeasible;

  double get completeness {
    final values = [linked, scheduled, priced, resourced, justified, physicallyFeasible];
    return values.where((v) => v).length / values.length;
  }

  Map<String, dynamic> toJson() => {
        'linked': linked,
        'scheduled': scheduled,
        'priced': priced,
        'resourced': resourced,
        'justified': justified,
        'physicallyFeasible': physicallyFeasible,
        'completeness': completeness,
      };
}

class PixNode {
  const PixNode({
    required this.id,
    required this.label,
    required this.type,
    this.peak = const PeakState(),
    this.reper,
    this.payload = const {},
  });

  final String id;
  final String label;
  final PixType type;
  final PeakState peak;
  final Reper4? reper;
  final Map<String, dynamic> payload;

  Map<String, dynamic> toJson() => {
        'id': id,
        'label': label,
        'type': type.name,
        'peak': peak.toJson(),
        'reper': reper?.toJson(),
        'payload': payload,
      };
}

class GraphEdge {
  const GraphEdge({required this.from, required this.to, required this.type, this.label = ''});

  final String from;
  final String to;
  final String type;
  final String label;

  Map<String, dynamic> toJson() => {'from': from, 'to': to, 'type': type, 'label': label};
}

class KltDefect {
  const KltDefect({
    required this.id,
    required this.defectClass,
    required this.title,
    required this.message,
    required this.severity,
    required this.pixIds,
    this.payload = const {},
  });

  final String id;
  final DefectClass defectClass;
  final String title;
  final String message;
  final double severity;
  final List<String> pixIds;
  final Map<String, dynamic> payload;

  Map<String, dynamic> toJson() => {
        'id': id,
        'class': defectClass.name,
        'title': title,
        'message': message,
        'severity': severity,
        'pixIds': pixIds,
        'payload': payload,
      };
}

class KltAnalysis {
  const KltAnalysis({
    required this.projectId,
    required this.title,
    required this.nodes,
    required this.edges,
    required this.defects,
    required this.lambdaByPix,
    required this.cri,
    required this.msiMax,
    required this.diMax,
    required this.generatedAt,
  });

  final String projectId;
  final String title;
  final List<PixNode> nodes;
  final List<GraphEdge> edges;
  final List<KltDefect> defects;
  final Map<String, LambdaResult> lambdaByPix;
  final double cri;
  final double msiMax;
  final double diMax;
  final DateTime generatedAt;

  double get deltaAvg {
    final values = lambdaByPix.values.map((r) => r.delta).whereType<double>().toList();
    if (values.isEmpty) return 0;
    return values.reduce((a, b) => a + b) / values.length;
  }

  Map<String, dynamic> toJson() => {
        'projectId': projectId,
        'title': title,
        'generatedAt': generatedAt.toIso8601String(),
        'cri': cri,
        'deltaAvg': deltaAvg,
        'msiMax': msiMax,
        'diMax': diMax,
        'nodes': nodes.map((n) => n.toJson()).toList(),
        'edges': edges.map((e) => e.toJson()).toList(),
        'defects': defects.map((d) => d.toJson()).toList(),
        'lambdaByPix': lambdaByPix.map((key, value) => MapEntry(key, value.toJson())),
      };
}

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