enum JobStatus { pending, inProgress, sent, delivered, cancelled }

enum JobStep {
  olcu, // legacy fallback
  olcuKontrol, // geleneksel/arjinat ölçü veya model kontrolü
  dijitalTasarim, // dijital tasarım klinik onayı
  modelHazirlik, // internal hazırlık/model döküm
  altYapiProva, // sabit seramik/metal — alt yapı (coping)
  ustYapiProva, // sabit seramik — bisküvi prova
  mumProva, // hareketli protez — mum prova
  dislerProva, // hareketli protez — dişler prova
  dayanakProva, // implant — dayanak prova
  kronProva, // implant — kron prova
  fotografOnay, // foto/mockup ile klinik onayı
  kaliteKontrol, // internal kalite kontrol
  teslimOncesiKontrol, // internal final kontrol
  cilaBitim, // son cila / bitim (her şablonda son adım)
}

enum JobLocation { atClinic, atLab }

enum JobWorkflowType { arjinat, geleneksel, dijital }

enum ProstheticFamily { sabit, hareketli, gecici, ozel }

enum ProstheticType {
  sabit,
  hareketli,
  gecici,
  diger,
  metalPorselen,
  zirkonyum,
  implantUstuZirkonyum,
  eMax,
  tamProtez,
  parsiyel,
}

const visibleProstheticTypes = <ProstheticType>[
  ProstheticType.sabit,
  ProstheticType.hareketli,
  ProstheticType.gecici,
  ProstheticType.diger,
];

// ── Status ────────────────────────────────────────────────────────────────────

extension JobStatusExt on JobStatus {
  String get label => switch (this) {
        JobStatus.pending => 'Bekliyor',
        JobStatus.inProgress => 'İşlemde',
        JobStatus.sent => 'Gönderildi',
        JobStatus.delivered => 'Teslim Alındı',
        JobStatus.cancelled => 'İptal',
      };
  String get value => switch (this) {
        JobStatus.pending => 'pending',
        JobStatus.inProgress => 'in_progress',
        JobStatus.sent => 'sent',
        JobStatus.delivered => 'delivered',
        JobStatus.cancelled => 'cancelled',
      };
}

// ── Step ──────────────────────────────────────────────────────────────────────

extension JobStepExt on JobStep {
  String get label => switch (this) {
        JobStep.olcu => 'Ölçü',
        JobStep.olcuKontrol => 'Ölçü / Model Kontrol',
        JobStep.dijitalTasarim => 'Dijital Tasarım Onayı',
        JobStep.modelHazirlik => 'Model Hazırlık',
        JobStep.altYapiProva => 'Alt Yapı Prova',
        JobStep.ustYapiProva => 'Üst Yapı Prova',
        JobStep.mumProva => 'Mum Prova',
        JobStep.dislerProva => 'Dişler Prova',
        JobStep.dayanakProva => 'Dayanak Prova',
        JobStep.kronProva => 'Kron Prova',
        JobStep.fotografOnay => 'Fotoğraf / Mockup Onayı',
        JobStep.kaliteKontrol => 'Kalite Kontrol',
        JobStep.teslimOncesiKontrol => 'Teslim Öncesi Kontrol',
        JobStep.cilaBitim => 'Cila / Bitim',
      };

  /// One-liner shown under the step on the stepper
  String get description => switch (this) {
        JobStep.olcu => 'İlk ölçü alındı',
        JobStep.olcuKontrol => 'Ölçü, model veya kapanış kaydı kontrolü',
        JobStep.dijitalTasarim =>
          'Dijital tasarım ekranı veya mockup klinik onayı',
        JobStep.modelHazirlik =>
          'Model hazırlık, döküm veya artikülatör aşaması',
        JobStep.altYapiProva => 'Metal/zirkonyum coping klinik onayı',
        JobStep.ustYapiProva => 'Bisküvi pişirimi sonrası klinik onayı',
        JobStep.mumProva => 'Mum prova klinik onayı',
        JobStep.dislerProva => 'Diş dizimi klinik onayı',
        JobStep.dayanakProva => 'Dayanak klinik onayı',
        JobStep.kronProva => 'Kron klinik onayı',
        JobStep.fotografOnay => 'Fotoğraf veya mockup üzerinden klinik teyidi',
        JobStep.kaliteKontrol => 'Laboratuvar iç kalite kontrol aşaması',
        JobStep.teslimOncesiKontrol => 'Teslimat öncesi son iç kontrol',
        JobStep.cilaBitim => 'Son cila ve teslim hazırlığı',
      };

  String get value => switch (this) {
        JobStep.olcu => 'olcu',
        JobStep.olcuKontrol => 'olcu_kontrol',
        JobStep.dijitalTasarim => 'dijital_tasarim',
        JobStep.modelHazirlik => 'model_hazirlik',
        JobStep.altYapiProva => 'alt_yapi_prova',
        JobStep.ustYapiProva => 'ust_yapi_prova',
        JobStep.mumProva => 'mum_prova',
        JobStep.dislerProva => 'disler_prova',
        JobStep.dayanakProva => 'dayanak_prova',
        JobStep.kronProva => 'kron_prova',
        JobStep.fotografOnay => 'fotograf_onay',
        JobStep.kaliteKontrol => 'kalite_kontrol',
        JobStep.teslimOncesiKontrol => 'teslim_oncesi_kontrol',
        JobStep.cilaBitim => 'cila_bitim',
      };

  bool get requiresClinicApproval => switch (this) {
        JobStep.modelHazirlik ||
        JobStep.kaliteKontrol ||
        JobStep.teslimOncesiKontrol =>
          false,
        _ => true,
      };

  bool get isLabOptional => switch (this) {
        JobStep.dijitalTasarim ||
        JobStep.modelHazirlik ||
        JobStep.fotografOnay ||
        JobStep.kaliteKontrol ||
        JobStep.teslimOncesiKontrol =>
          true,
        _ => false,
      };
}

extension JobWorkflowTypeExt on JobWorkflowType {
  String get label => switch (this) {
        JobWorkflowType.arjinat => 'Arjinat',
        JobWorkflowType.geleneksel => 'Geleneksel',
        JobWorkflowType.dijital => 'Dijital',
      };

  String get value => switch (this) {
        JobWorkflowType.arjinat => 'arjinat',
        JobWorkflowType.geleneksel => 'geleneksel',
        JobWorkflowType.dijital => 'dijital',
      };
}

// ── Prosthetic type ───────────────────────────────────────────────────────────

extension ProstheticTypeExt on ProstheticType {
  String get label => switch (this) {
        ProstheticType.sabit => 'Sabit',
        ProstheticType.hareketli => 'Hareketli',
        ProstheticType.gecici => 'Geçici',
        ProstheticType.diger => 'Diğer',
        ProstheticType.metalPorselen => 'Metal Porselen',
        ProstheticType.zirkonyum => 'Zirkonyum',
        ProstheticType.implantUstuZirkonyum => 'İmplant Üstü Zirkonyum',
        ProstheticType.eMax => 'E-Max',
        ProstheticType.tamProtez => 'Tam Protez',
        ProstheticType.parsiyel => 'Parsiyel Protez',
      };
  String get value => switch (this) {
        ProstheticType.sabit => 'sabit',
        ProstheticType.hareketli => 'hareketli',
        ProstheticType.gecici => 'gecici',
        ProstheticType.diger => 'diger',
        ProstheticType.metalPorselen => 'metal_porselen',
        ProstheticType.zirkonyum => 'zirkonyum',
        ProstheticType.implantUstuZirkonyum => 'implant_ustu_zirkonyum',
        ProstheticType.eMax => 'e_max',
        ProstheticType.tamProtez => 'tam_protez',
        ProstheticType.parsiyel => 'parsiyel',
      };

  ProstheticFamily get family => switch (this) {
        ProstheticType.sabit ||
        ProstheticType.metalPorselen ||
        ProstheticType.zirkonyum ||
        ProstheticType.implantUstuZirkonyum ||
        ProstheticType.eMax =>
          ProstheticFamily.sabit,
        ProstheticType.hareketli ||
        ProstheticType.tamProtez ||
        ProstheticType.parsiyel =>
          ProstheticFamily.hareketli,
        ProstheticType.gecici => ProstheticFamily.gecici,
        ProstheticType.diger => ProstheticFamily.ozel,
      };
}

ProstheticType parseProstheticTypeValue(String value) => switch (value) {
      'sabit' ||
      'metal_porselen' ||
      'zirkonyum' ||
      'implant_ustu_zirkonyum' ||
      'e_max' =>
        ProstheticType.sabit,
      'hareketli' || 'tam_protez' || 'parsiyel' => ProstheticType.hareketli,
      'gecici' => ProstheticType.gecici,
      'diger' => ProstheticType.diger,
      _ => ProstheticType.diger,
    };

// ── Step template ─────────────────────────────────────────────────────────────

class JobWorkflowPreset {
  const JobWorkflowPreset({
    required this.title,
    required this.summary,
    required this.steps,
  });

  final String title;
  final String summary;
  final List<JobStep> steps;
}

const optionalLabStepCatalog = <JobStep>[
  JobStep.dijitalTasarim,
  JobStep.modelHazirlik,
  JobStep.fotografOnay,
  JobStep.kaliteKontrol,
  JobStep.teslimOncesiKontrol,
];

bool isOptionalStepApplicable(
  JobStep step, {
  required JobWorkflowType workflowType,
  required ProstheticFamily family,
  required bool provaRequired,
}) {
  switch (step) {
    case JobStep.dijitalTasarim:
      return workflowType == JobWorkflowType.dijital;
    case JobStep.modelHazirlik:
      return workflowType != JobWorkflowType.dijital &&
          family != ProstheticFamily.gecici;
    case JobStep.fotografOnay:
      return family != ProstheticFamily.hareketli || provaRequired;
    case JobStep.kaliteKontrol:
    case JobStep.teslimOncesiKontrol:
      return true;
    default:
      return false;
  }
}

List<JobStep> mergeOptionalLabSteps({
  required List<JobStep> baseSteps,
  required List<JobStep> optionalSteps,
  required JobWorkflowType workflowType,
  required ProstheticFamily family,
  required bool provaRequired,
}) {
  final merged = List<JobStep>.from(baseSteps);
  for (final step in optionalSteps) {
    if (!step.isLabOptional ||
        merged.contains(step) ||
        !isOptionalStepApplicable(
          step,
          workflowType: workflowType,
          family: family,
          provaRequired: provaRequired,
        )) {
      continue;
    }

    final finalIndex = merged.indexOf(JobStep.cilaBitim);
    switch (step) {
      case JobStep.dijitalTasarim:
        merged.insert(0, step);
      case JobStep.modelHazirlik:
        final afterControl = merged.contains(JobStep.olcuKontrol)
            ? merged.indexOf(JobStep.olcuKontrol) + 1
            : 0;
        merged.insert(afterControl, step);
      case JobStep.fotografOnay:
      case JobStep.kaliteKontrol:
      case JobStep.teslimOncesiKontrol:
        merged.insert(finalIndex.clamp(0, merged.length), step);
      default:
        break;
    }
  }
  return merged;
}

JobWorkflowPreset buildJobWorkflowPreset({
  required ProstheticType prostheticType,
  JobWorkflowType? workflowType,
  required bool provaRequired,
  List<JobStep> optionalSteps = const [],
}) {
  final normalizedWorkflow = workflowType ?? JobWorkflowType.geleneksel;
  final family = prostheticType.family;

  List<JobStep> steps;
  switch (normalizedWorkflow) {
    case JobWorkflowType.dijital:
      steps = switch (family) {
        ProstheticFamily.sabit => provaRequired
            ? const [
                JobStep.ustYapiProva,
                JobStep.cilaBitim,
              ]
            : const [
                JobStep.cilaBitim,
              ],
        ProstheticFamily.hareketli => provaRequired
            ? const [
                JobStep.mumProva,
                JobStep.dislerProva,
                JobStep.cilaBitim,
              ]
            : const [
                JobStep.cilaBitim,
              ],
        ProstheticFamily.gecici => const [
            JobStep.cilaBitim,
          ],
        ProstheticFamily.ozel => provaRequired
            ? const [
                JobStep.altYapiProva,
                JobStep.cilaBitim,
              ]
            : const [
                JobStep.cilaBitim,
              ],
      };
    case JobWorkflowType.arjinat:
    case JobWorkflowType.geleneksel:
      steps = switch (family) {
        ProstheticFamily.sabit => provaRequired
            ? const [
                JobStep.olcuKontrol,
                JobStep.altYapiProva,
                JobStep.ustYapiProva,
                JobStep.cilaBitim,
              ]
            : const [
                JobStep.olcuKontrol,
                JobStep.cilaBitim,
              ],
        ProstheticFamily.hareketli => provaRequired
            ? const [
                JobStep.olcuKontrol,
                JobStep.mumProva,
                JobStep.dislerProva,
                JobStep.cilaBitim,
              ]
            : const [
                JobStep.olcuKontrol,
                JobStep.cilaBitim,
              ],
        ProstheticFamily.gecici => const [
            JobStep.olcuKontrol,
            JobStep.cilaBitim,
          ],
        ProstheticFamily.ozel => provaRequired
            ? const [
                JobStep.olcuKontrol,
                JobStep.altYapiProva,
                JobStep.cilaBitim,
              ]
            : const [
                JobStep.olcuKontrol,
                JobStep.cilaBitim,
              ],
      };
  }

  final title =
      '${normalizedWorkflow.label} · ${provaRequired ? "Provalı" : "Provasız"}';
  final summary = switch ((normalizedWorkflow, family, provaRequired)) {
    (JobWorkflowType.dijital, ProstheticFamily.sabit, false) =>
      'Dijital tasarım onayı sonrası üretim ve teslim odaklı akış.',
    (JobWorkflowType.dijital, _, false) =>
      'Fiziksel prova azaltılmış, dijital onay ve hızlı teslim akışı.',
    (JobWorkflowType.dijital, _, true) =>
      'Dijital hazırlık üzerine klinik prova kapıları eklenmiş hibrit akış.',
    (JobWorkflowType.arjinat, _, false) =>
      'Ölçü/model kontrolü sonrası doğrudan üretim ve teslim akışı.',
    (JobWorkflowType.arjinat, _, true) =>
      'Arjinat ölçüden gelen işlerde klinik prova kapılarıyla ilerleyen akış.',
    (JobWorkflowType.geleneksel, _, false) =>
      'Klasik ölçüden gelen, minimum temaslı ve hızlı teslim akışı.',
    (JobWorkflowType.geleneksel, _, true) =>
      'Klasik laboratuvar süreçlerine uygun, prova bazlı aşamalı akış.',
  };

  return JobWorkflowPreset(
    title: title,
    summary: summary,
    steps: mergeOptionalLabSteps(
      baseSteps: steps,
      optionalSteps: optionalSteps,
      workflowType: normalizedWorkflow,
      family: family,
      provaRequired: provaRequired,
    ),
  );
}

/// Returns the ordered clinic-facing approval steps for a job.
List<JobStep> jobStepTemplate(
  ProstheticType type,
  bool provaRequired, {
  JobWorkflowType? workflowType,
  List<JobStep> optionalSteps = const [],
}) {
  return buildJobWorkflowPreset(
    prostheticType: type,
    workflowType: workflowType,
    provaRequired: provaRequired,
    optionalSteps: optionalSteps,
  ).steps;
}

// ── Job ───────────────────────────────────────────────────────────────────────

class Job {
  const Job({
    required this.id,
    required this.clinicTenantId,
    required this.labTenantId,
    required this.patientCode,
    required this.prostheticType,
    required this.memberCount,
    required this.status,
    required this.dateCreated,
    this.patientId,
    this.patientName,
    this.prostheticId,
    this.prostheticName,
    this.teeth = const [],
    this.color,
    this.description,
    this.price,
    this.currency,
    this.currentStep,
    this.location = JobLocation.atClinic,
    this.workflowType,
    this.dueDate,
    this.clinicName,
    this.labName,
    this.attachments = const [],
    this.provaRequired = true,
    this.workflowSteps = const [],
  });

  final String id;
  final String clinicTenantId;
  final String labTenantId;
  final String? patientId;
  final String? patientName;
  final String patientCode;
  final String? prostheticId;
  final String? prostheticName;
  final ProstheticType prostheticType;
  final int memberCount;
  final List<String> teeth;
  final String? color;
  final String? description;
  final double? price;
  final String? currency;
  final JobStatus status;
  final JobStep? currentStep;
  final JobLocation location;
  final JobWorkflowType? workflowType;
  final DateTime? dueDate;
  final DateTime dateCreated;
  final List<String> attachments;
  final bool provaRequired;
  final List<JobStep> workflowSteps;

  // Denormalized from relation joins — list views only
  final String? clinicName;
  final String? labName;

  // ── copyWith ──────────────────────────────────────────────────────────────

  Job copyWith({
    JobStatus? status,
    JobStep? currentStep,
    JobLocation? location,
    JobWorkflowType? workflowType,
    String? clinicName,
    String? labName,
    bool clearCurrentStep = false,
  }) =>
      Job(
        id: id,
        clinicTenantId: clinicTenantId,
        labTenantId: labTenantId,
        patientId: patientId,
        patientName: patientName,
        patientCode: patientCode,
        prostheticId: prostheticId,
        prostheticName: prostheticName,
        prostheticType: prostheticType,
        memberCount: memberCount,
        teeth: teeth,
        color: color,
        description: description,
        price: price,
        currency: currency,
        status: status ?? this.status,
        currentStep:
            clearCurrentStep ? null : (currentStep ?? this.currentStep),
        location: location ?? this.location,
        workflowType: workflowType ?? this.workflowType,
        dueDate: dueDate,
        dateCreated: dateCreated,
        attachments: attachments,
        provaRequired: provaRequired,
        workflowSteps: workflowSteps,
        clinicName: clinicName ?? this.clinicName,
        labName: labName ?? this.labName,
      );

  // ── Step helpers ──────────────────────────────────────────────────────────

  List<JobStep> get stepTemplate => workflowSteps.isNotEmpty
      ? workflowSteps
      : jobStepTemplate(
          prostheticType,
          provaRequired,
          workflowType: workflowType,
          optionalSteps:
              workflowSteps.where((step) => step.isLabOptional).toList(),
        );

  JobWorkflowPreset get workflowPreset {
    final preset = buildJobWorkflowPreset(
      prostheticType: prostheticType,
      workflowType: workflowType,
      provaRequired: provaRequired,
      optionalSteps: workflowSteps.where((step) => step.isLabOptional).toList(),
    );
    if (workflowSteps.isEmpty) return preset;
    return JobWorkflowPreset(
      title: preset.title,
      summary: preset.summary,
      steps: workflowSteps,
    );
  }

  bool get isLastStep =>
      currentStep != null && currentStep == stepTemplate.last;

  /// The next step after currentStep in this job's template, or null if done.
  JobStep? get nextStep {
    if (currentStep == null) return stepTemplate.firstOrNull;
    final idx = stepTemplate.indexOf(currentStep!);
    if (idx < 0 || idx >= stepTemplate.length - 1) return null;
    return stepTemplate[idx + 1];
  }

  factory Job.fromJson(Map<String, dynamic> j) {
    final expand = j['expand'] as Map<String, dynamic>?;
    final clinicExp = expand?['clinic_tenant_id'] as Map<String, dynamic>?;
    final labExp = expand?['lab_tenant_id'] as Map<String, dynamic>?;
    final patientExp = expand?['patient_id'] as Map<String, dynamic>?;
    final prostheticExp = expand?['prosthetic_id'] as Map<String, dynamic>?;
    String? str(dynamic v) {
      final s = v as String?;
      return (s == null || s.isEmpty) ? null : s;
    }

    return Job(
      id: j['id'] as String,
      clinicTenantId: j['clinic_tenant_id'] as String,
      labTenantId: j['lab_tenant_id'] as String,
      patientId: str(j['patient_id']),
      patientName: _patientName(patientExp),
      patientCode: j['patient_code'] as String,
      prostheticId: str(j['prosthetic_id']),
      prostheticName: prostheticExp?['name'] as String?,
      prostheticType: parseProstheticTypeValue(j['prosthetic_type'] as String),
      memberCount: (j['member_count'] as num).toInt(),
      teeth: j['teeth'] is List
          ? (j['teeth'] as List).map((e) => e.toString()).toList()
          : [],
      color: str(j['color']),
      description: str(j['description']),
      price: (j['price'] as num?)?.toDouble(),
      currency: str(j['currency']),
      status: _parseStatus(j['status'] as String),
      currentStep: str(j['current_step']) != null
          ? _parseStep(j['current_step'] as String)
          : null,
      location:
          j['location'] == 'at_lab' ? JobLocation.atLab : JobLocation.atClinic,
      workflowType: str(j['workflow_type']) != null
          ? _parseWorkflowType(j['workflow_type'] as String)
          : null,
      dueDate: str(j['due_date']) != null
          ? DateTime.parse(j['due_date'] as String)
          : null,
      dateCreated: DateTime.parse(j['created'] as String),
      clinicName: clinicExp?['company_name'] as String?,
      labName: labExp?['company_name'] as String?,
      attachments: j['attachments'] is List
          ? (j['attachments'] as List).map((e) => e.toString()).toList()
          : [],
      provaRequired: (j['prova_required'] as bool?) ?? true,
      workflowSteps: j['workflow_steps'] is List
          ? (j['workflow_steps'] as List)
              .map((e) => _parseStep(e.toString()))
              .toList()
          : const [],
    );
  }

  static JobStatus _parseStatus(String s) => switch (s) {
        'in_progress' => JobStatus.inProgress,
        'sent' => JobStatus.sent,
        'delivered' => JobStatus.delivered,
        'cancelled' => JobStatus.cancelled,
        _ => JobStatus.pending,
      };

  static JobStep _parseStep(String s) => switch (s) {
        'olcu_kontrol' => JobStep.olcuKontrol,
        'dijital_tasarim' => JobStep.dijitalTasarim,
        'model_hazirlik' => JobStep.modelHazirlik,
        'alt_yapi_prova' => JobStep.altYapiProva,
        'ust_yapi_prova' => JobStep.ustYapiProva,
        'mum_prova' => JobStep.mumProva,
        'disler_prova' => JobStep.dislerProva,
        'dayanak_prova' => JobStep.dayanakProva,
        'kron_prova' => JobStep.kronProva,
        'fotograf_onay' => JobStep.fotografOnay,
        'kalite_kontrol' => JobStep.kaliteKontrol,
        'teslim_oncesi_kontrol' => JobStep.teslimOncesiKontrol,
        'cila_bitim' => JobStep.cilaBitim,
        _ => JobStep.olcu,
      };

  static JobStep parseStepValue(String s) => _parseStep(s);

  static JobWorkflowType _parseWorkflowType(String s) => switch (s) {
        'arjinat' => JobWorkflowType.arjinat,
        'dijital' => JobWorkflowType.dijital,
        _ => JobWorkflowType.geleneksel,
      };

  static String? _patientName(Map<String, dynamic>? patientExp) {
    if (patientExp == null) return null;
    final first = (patientExp['first_name'] as String?)?.trim();
    final last = (patientExp['last_name'] as String?)?.trim();
    final parts = [first, last]
        .where((part) => part != null && part.isNotEmpty)
        .cast<String>()
        .toList();
    if (parts.isNotEmpty) return parts.join(' ');
    final code = (patientExp['patient_code'] as String?)?.trim();
    return (code == null || code.isEmpty) ? null : code;
  }
}
