import 'package:fis_measure/configs/cardiac.dart';
import 'package:fis_measure/configs/patient.dart';
import 'package:fis_measure/interfaces/enums/calcuator.dart';
import 'package:fis_measure/interfaces/enums/species.dart';
import 'package:fis_measure/interfaces/process/calculators/values.dart';
import 'package:fis_measure/interfaces/process/items/terms.dart';
import 'package:fis_measure/process/items/top_item.dart';
import 'package:fis_measure/process/primitives/combos/lv_study.dart';
import 'package:fis_measure/process/primitives/straightline.dart';
import 'package:flutter/foundation.dart';
import 'package:vid/us/vid_us_unit.dart';

import 'calculator.dart';
import 'formulas/cardiac.dart';
import 'unit_formulas/index.dart';

class LvStudySimpleCal extends LvStudyCalculatorBase<LvStudy> {
  late final StraightLine kidLVIDd;
  late final StraightLine kidLVIDs;

  LvStudySimpleCal(super.ref) {
    kidLVIDd = ref.findChildByName("L1") as StraightLine;
    kidLVIDs = ref.findChildByName("L2") as StraightLine;
  }

  @override
  void calculate() {
    if (ref.feature == null) return;

    final feature = ref.feature!;

    restoreVals();
    uv.lvidd = pickChildToFloatValue(kidLVIDd)?.toUnitFloatValue();
    uv.lvids = pickChildToFloatValue(kidLVIDs)?.toUnitFloatValue();

    for (var output in ref.meta.outputs) {
      switch (output.name) {
        case MeasureTerms.LvStudySimple:
          feature.updateStringValue(output, "");
          break;
        case MeasureTerms.LVEDV:
          updateLVEDV();
          break;
        case MeasureTerms.LVESV:
          updateLVESV();
          break;
        case MeasureTerms.SV:
          updateSV();
          break;
        case MeasureTerms.EF:
          updateEF();
          break;
        case MeasureTerms.FS:
          updatePercentFS();
          break;
        case MeasureTerms.LVIDdIndex:
          updateLVIDdIndex();
          break;
        case MeasureTerms.LVIDsIndex:
          updateLVIDsIndex();
          break;
        case MeasureTerms.LVIDdN:
          updateLVIDdN();
          break;
        case MeasureTerms.LVIDsN:
          updateLVIDsN();
          break;
      }
    }
  }
}

class LvStudyDistanceGroupCal extends LvStudyCalculatorBase<LvStudy> {
  late final StraightLine kidIVSd;
  late final StraightLine kidLVIDd;
  late final StraightLine kidLVPWd;
  late final StraightLine kidIVSs;
  late final StraightLine kidLVIDs;
  late final StraightLine kidLVPWs;

  LvStudyDistanceGroupCal(super.ref) {
    kidIVSd = ref.findChildByName(MeasureTerms.IVSd) as StraightLine;
    kidLVIDd = ref.findChildByName(MeasureTerms.LVIDd) as StraightLine;
    kidLVPWd = ref.findChildByName(MeasureTerms.LVPWd) as StraightLine;
    kidIVSs = ref.findChildByName(MeasureTerms.IVSs) as StraightLine;
    kidLVIDs = ref.findChildByName(MeasureTerms.LVIDs) as StraightLine;
    kidLVPWs = ref.findChildByName(MeasureTerms.LVPWs) as StraightLine;
  }

  @override
  void calculate() {
    if (ref.feature == null) return;

    final feature = ref.feature!;
    final viewport = feature.hostVisualArea!.viewport!;
    VidUsUnit yUnit = viewport.yUnit;

    restoreVals();

    FloatValue? ivsd = pickChildToFloatValue(kidIVSd);
    FloatValue? lvidd = pickChildToFloatValue(kidLVIDd);
    FloatValue? lvpwd = pickChildToFloatValue(kidLVPWd);
    FloatValue? ivss = pickChildToFloatValue(kidIVSs);
    FloatValue? lvids = pickChildToFloatValue(kidLVIDs);
    FloatValue? lvpws = pickChildToFloatValue(kidLVPWs);

    uv.ivsd = ivsd?.toUnitFloatValue();
    uv.ivss = ivss?.toUnitFloatValue();
    uv.lvidd = lvidd?.toUnitFloatValue();
    uv.lvids = lvids?.toUnitFloatValue();
    uv.lvpwd = lvpwd?.toUnitFloatValue();
    uv.lvpws = lvpws?.toUnitFloatValue();

    for (var output in ref.meta.outputs) {
      switch (output.name) {
        case MeasureTerms.LvStudy:
          feature.updateStringValue(output, "");
          break;
        case MeasureTerms.LVEDV:
          updateLVEDV();
          break;
        case MeasureTerms.LVESV:
          updateLVESV();
          break;
        case MeasureTerms.LVdMass:
          updateLVdMass();
          break;
        case MeasureTerms.PercentIVS:
          updatePercentIVS();
          break;
        case MeasureTerms.SV:
          updateSV();
          break;
        case MeasureTerms.EF:
          updateEF();
          break;
        case MeasureTerms.FS:
          updatePercentFS();
          break;
        case MeasureTerms.PercentLVPW:
          updatePercentLVPW();
          break;
        case MeasureTerms.CO:
          updateCO();
          break;
        case MeasureTerms.CI:
          updateCI();
          break;
        case MeasureTerms.SI:
          updateSI();
          break;
        case MeasureTerms.LVIDdIndex:
          updateLVIDdIndex();
          break;
        case MeasureTerms.LVIDsIndex:
          updateLVIDsIndex();
          break;
        case MeasureTerms.LVIDdN:
          updateLVIDdN();
          break;
        case MeasureTerms.LVIDsN:
          updateLVIDsN();
          break;
      }
    }
  }

  double? updateCalculateValue(FloatValue? floatValue, VidUsUnit unit) {
    if (floatValue != null) {
      if (floatValue.unit == unit) {
        return floatValue.value;
      } else {
        if (floatValue.unit == VidUsUnit.mm &&
            floatValue.unit == VidUsUnit.cm) {
          return floatValue.value == null ? null : floatValue.value! * 10;
        } else if (floatValue.unit == VidUsUnit.cm &&
            floatValue.unit == VidUsUnit.mm) {
          return floatValue.value == null ? null : floatValue.value! / 10;
        }
      }
    }
    return null;
  }
}

class LvStudyCalculatorBase<T extends TopMeasureItem>
    extends Calculator<T, double> {
  LvStudyCalculatorBase(super.ref);

  @protected
  late _ValTemp v;
  @protected
  late _UnitValTemp uv;

  @override
  void calculate() {
    // TODO: implement calculate
  }

  void restoreVals() {
    v = _ValTemp();
    uv = _UnitValTemp();
  }

  @protected
  void updateLVEDV() {
    if (uv.lvidd == null) {
      return;
    }

    UnitFloatValue? unitValue;
    if (GlobalCardiacConfigs.EDVFormulaMode ==
        CardiacEDVFormulaMode.teichholz) {
      unitValue = UnitFormulas.cardiac.edvTech(uv.lvidd!);
    } else if (GlobalCardiacConfigs.EDVFormulaMode ==
        CardiacEDVFormulaMode.cube) {
      unitValue = UnitFormulas.cardiac.edvCube(uv.lvidd!);
    }

    if (unitValue != null) {
      updateFloatValueByName(
        MeasureTerms.LVEDV,
        unitValue.value,
        unit: unitValue.unit,
      );
    }
    uv.lvedv = unitValue;
  }

  @protected
  void updateLVESV() {
    if (uv.lvids == null) {
      return;
    }

    double value = 0;
    UnitFloatValue? unitValue;
    if (GlobalCardiacConfigs.EDVFormulaMode ==
        CardiacEDVFormulaMode.teichholz) {
      unitValue = UnitFormulas.cardiac.edvTech(uv.lvids!);
    } else if (GlobalCardiacConfigs.EDVFormulaMode ==
        CardiacEDVFormulaMode.cube) {
      unitValue = UnitFormulas.cardiac.esvCube(uv.lvids!);
    }
    updateFloatValueByName(MeasureTerms.LVESV, value, unit: VidUsUnit.ml);
    if (unitValue != null) {
      updateFloatValueByName(
        MeasureTerms.LVESV,
        unitValue.value,
        unit: unitValue.unit,
      );
    }
    uv.lvesv = unitValue;
  }

  @protected
  void updateLVdMass() {
    if (uv.ivsd == null || uv.lvidd == null || uv.lvpwd == null) {
      return;
    }

    UnitFloatValue unitValue =
        UnitFormulas.cardiac.lvdMass(uv.ivsd!, uv.ivsd!, uv.lvpwd!);
    updateFloatValueByName(
      MeasureTerms.LVdMass,
      unitValue.value,
      unit: unitValue.unit,
    );
  }

  @protected
  void updatePercentIVS() {
    if (uv.ivsd == null || uv.ivss == null) {
      return;
    }

    UnitFloatValue unitValue =
        UnitFormulas.cardiac.ivsPercent(uv.ivss!, uv.ivsd!);
    updateFloatValueByName(
      MeasureTerms.PercentIVS,
      unitValue.value,
      unit: unitValue.unit,
    );
  }

  @protected
  void updateSV() {
    if (uv.lvedv == null || uv.lvesv == null) {
      return;
    }

    UnitFloatValue unitValue = UnitFormulas.cardiac.sv(uv.lvedv!, uv.lvesv!);
    updateFloatValueByName(
      MeasureTerms.SV,
      unitValue.value,
      unit: unitValue.unit,
    );
    uv.sv = unitValue;
  }

  @protected
  void updateEF() {
    if (uv.lvedv == null || uv.lvesv == null) {
      return;
    }

    UnitFloatValue unitValue = UnitFormulas.cardiac.ef(uv.lvedv!, uv.lvesv!);
    updateFloatValueByName(
      MeasureTerms.EF,
      unitValue.value,
      unit: VidUsUnit.percent,
    );
  }

  @protected
  void updatePercentFS() {
    if (uv.lvidd == null || uv.lvids == null) {
      return;
    }

    UnitFloatValue unitValue = UnitFormulas.cardiac.ef(uv.lvedv!, uv.lvesv!);
    updateFloatValueByName(
      MeasureTerms.EF,
      unitValue.value,
      unit: VidUsUnit.percent,
    );
  }

  @protected
  void updatePercentLVPW() {
    if (uv.lvpws == null || uv.lvpwd == null) {
      return;
    }

    UnitFloatValue unitValue =
        UnitFormulas.cardiac.lvpwPercent(uv.lvpws!, uv.lvpwd!);
    updateFloatValueByName(
      MeasureTerms.PercentLVPW,
      unitValue.value,
      unit: unitValue.unit,
    );
  }

  @protected
  void updateCO() {
    if (uv.sv == null || uv.hr == null) {
      return;
    }

    UnitFloatValue unitValue = UnitFormulas.cardiac.co(uv.sv!, hr: uv.hr!);
    updateFloatValueByName(
      MeasureTerms.CO,
      unitValue.value,
      unit: unitValue.unit,
    );
  }

  @protected
  void updateCI() {
    if (uv.sv == null || uv.hr == null) {
      return;
    }
    if (GlobalPatientConfig.bsa == 0) {
      return;
    }

    UnitFloatValue unitValue = UnitFormulas.cardiac
        .ci(uv.sv!, hr: uv.hr!, bsa: GlobalPatientConfig.bsa);
    updateFloatValueByName(
      MeasureTerms.CI,
      unitValue.value,
      unit: unitValue.unit,
    );
  }

  @protected
  void updateLVEDVI() {
    if (uv.lvedv == null) {
      return;
    }
    if (GlobalPatientConfig.bsa == 0) {
      return;
    }

    UnitFloatValue unitValue =
        UnitFormulas.cardiac.lvevi(uv.lvedv!, bsa: GlobalPatientConfig.bsa);
    updateFloatValueByName(
      MeasureTerms.LVEDVI,
      unitValue.value,
      unit: unitValue.unit,
    );
  }

  @protected
  void updateLVESVI() {
    if (uv.lvesv == null) {
      return;
    }
    if (GlobalPatientConfig.bsa == 0) {
      return;
    }

    UnitFloatValue unitValue =
        UnitFormulas.cardiac.lvevi(uv.lvesv!, bsa: GlobalPatientConfig.bsa);
    updateFloatValueByName(
      MeasureTerms.LVESVI,
      unitValue.value,
      unit: unitValue.unit,
    );
  }

  @protected
  void updateSI() {
    if (uv.sv == null) {
      return;
    }
    if (GlobalPatientConfig.bsa == 0) {
      return;
    }

    UnitFloatValue unitValue =
        UnitFormulas.cardiac.si(uv.sv!, bsa: GlobalPatientConfig.bsa);
    updateFloatValueByName(
      MeasureTerms.SI,
      unitValue.value,
      unit: unitValue.unit,
    );
  }

  @protected
  void updateLVIDdIndex() {
    if (uv.lvidd == null) {
      return;
    }
    if (GlobalPatientConfig.bsa == 0) {
      return;
    }

    UnitFloatValue unitValue =
        UnitFormulas.cardiac.lvidIndex(uv.lvidd!, bsa: GlobalPatientConfig.bsa);
    updateFloatValueByName(
      MeasureTerms.LVIDdIndex,
      unitValue.value,
      unit: unitValue.unit,
    );
  }

  @protected
  void updateLVIDsIndex() {
    if (uv.lvids == null) {
      return;
    }
    if (GlobalPatientConfig.bsa == 0) {
      return;
    }

    UnitFloatValue unitValue =
        UnitFormulas.cardiac.lvidIndex(uv.lvids!, bsa: GlobalPatientConfig.bsa);
    updateFloatValueByName(
      MeasureTerms.LVIDsIndex,
      unitValue.value,
      unit: unitValue.unit,
    );
  }

  @protected
  void updateLVIDdN() {
    if (uv.lvidd == null) {
      return;
    }
    if (GlobalPatientConfig.weight == 0) {
      return;
    }

    UnitFloatValue unitValue =
        UnitFormulas.cardiac.lvidIndex(uv.lvidd!, bsa: GlobalPatientConfig.bsa);
    updateFloatValueByName(
      MeasureTerms.LVIDdN,
      unitValue.value,
      unit: unitValue.unit,
    );
  }

  @protected
  void updateLVIDsN() {
    if (uv.lvids == null) {
      return;
    }
    if (GlobalPatientConfig.weight == 0) {
      return;
    }

    var unitWeightValue =
        UnitFloatValue(GlobalPatientConfig.weight, VidUsUnit.kg);
    UnitFloatValue unitValue =
        UnitFormulas.cardiac.lvidN(uv.lvids!, unitWeightValue);
    updateFloatValueByName(
      MeasureTerms.LVIDsN,
      unitValue.value,
      unit: unitValue.unit,
    );
  }

  @protected
  void updateFloatValueByName(
    String name,
    double value, {
    VidUsUnit? unit,
  }) {
    ref.measuredFeatures;
    final feature = ref.feature!;
    final outputMeta = ref.meta.outputs.firstWhere((x) => x.name == name);
    feature.updateFloatValue(outputMeta, value, unit ?? outputMeta.unit);
  }

  VidUsUnit updateUnitBySpeciesType(VidUsUnit unit, String name) {
    if (GlobalPatientConfig.speciesType != SpeciesType.mouse) {
      return unit;
    }
    switch (name) {
      case MeasureTerms.LVEDV:
        return VidUsUnit.mil;
      case MeasureTerms.LVESV:
        return VidUsUnit.mil;
      case MeasureTerms.LVEDVI:
        return VidUsUnit.mil;
      case MeasureTerms.LVESVI:
        return VidUsUnit.mil;
      case MeasureTerms.SI:
        return VidUsUnit.mlm2;
      case MeasureTerms.CO:
        return VidUsUnit.mlmin;
      case MeasureTerms.CI:
        return VidUsUnit.mlmincm2;
      case MeasureTerms.LVdMass:
        return VidUsUnit.mg;
      case MeasureTerms.FS:
        return VidUsUnit.percent;
      case MeasureTerms.EF:
        return VidUsUnit.percent;
      case MeasureTerms.PercentLVPW:
        return VidUsUnit.percent;
      case MeasureTerms.SV:
        return VidUsUnit.mil;
      case MeasureTerms.SV_Card:
        return VidUsUnit.mlmin;
      case MeasureTerms.SV_Diam:
        return VidUsUnit.cm;
      case MeasureTerms.SV_Trace:
        return VidUsUnit.cm;
      case MeasureTerms.IVSd:
        return VidUsUnit.mm;
      case MeasureTerms.LVIDd:
        return VidUsUnit.mm;
      case MeasureTerms.LVPWd:
        return VidUsUnit.mm;
      case MeasureTerms.IVSs:
        return VidUsUnit.mg;
      case MeasureTerms.LVIDs:
        return VidUsUnit.mm;
      case MeasureTerms.LVPWs:
        return VidUsUnit.mm;
      case MeasureTerms.CO_2D:
        return VidUsUnit.mlmin;
      case MeasureTerms.CO_M: // 脉动心输出量
        return VidUsUnit.mlmin;
      case MeasureTerms.CO_P: // 脉动心输出量
        return VidUsUnit.mlmin;
      default:
        return unit;
    }
  }
}

class _ValTemp {
  double? ivsd;
  double? lvidd;
  double? lvpwd;
  double? ivss;
  double? lvids;
  double? lvpws;

  double? lvedv;
  double? lvesv;
  double? sv;
  int? hr = GlobalPatientConfig.hr; // TODO: from vid ext
}

class _UnitValTemp {
  UnitFloatValue? ivsd;
  UnitFloatValue? lvidd;
  UnitFloatValue? lvpwd;
  UnitFloatValue? ivss;
  UnitFloatValue? lvids;
  UnitFloatValue? lvpws;

  UnitFloatValue? lvedv;
  UnitFloatValue? lvesv;
  UnitFloatValue? sv;

  int? hr = GlobalPatientConfig.hr; // TODO: from vid ext
}