///|
/// xCell Demo.
///
/// This example demonstrates immune/stromal cell type scoring using
/// single-sample GSEA (ssGSEA) as in Bioconductor's xCell package, including:
///   1. Understanding the default 64 immune+stromal signatures
///   2. Single-sample GSEA scoring on custom gene sets
///   3. Full xCell pipeline on expression matrix
///   4. Querying scores by cell type, sample, or category
///   5. Immune / Stromal / microenvironment scores summary
///
/// Inspired by Aran, Hu, Butte (2017); xCell Bioconductor package.
fn format_score(s : Double) -> String {
  s.to_string()[0:if s < 0.0 { 7 } else { 6 }].to_owned()
}

///|
fn pad_right(s : String, n : Int32) -> String {
  if s.length() >= n {
    s
  } else {
    s + " ".repeat(n - s.length())
  }
}

///|
fn pad_left(s : String, n : Int32) -> String {
  if s.length() >= n {
    s
  } else {
    " ".repeat(n - s.length()) + s
  }
}

///|
fn array_index_of(arr : Array[String], target : String) -> Int32 {
  for i = 0; i < arr.length(); i = i + 1 {
    if arr[i] == target {
      return i
    }
  }
  -1
}

///|
fn main {
  println("=== xCell Demo ===")
  println("Immune and Stromal Cell Type Enrichment Scoring (ssGSEA)")
  println("Inspired by Aran et al. xCell Bioconductor package")
  println("")

  println("=== 1. Explore Default xCell Signatures ===")
  let sigs = @bio.xcell_default_signatures()
  println("  Total built-in signatures: \{sigs.length()}")

  let categories : Map[String, Int32] = Map([], capacity=0)
  for idx = 0; idx < sigs.length(); idx = idx + 1 {
    let s = sigs[idx]
    let cat = s.category
    let c = categories.get_or_default(cat, 0)
    categories.set(cat, c + 1)
  }
  let cat_keys = categories.keys()
  println("\n  Signatures by category:")
  cat_keys.each(fn(cat) {
    println("    \{cat}: \{categories.get_or_default(cat, 0)} signatures")
  })

  println("\n  First 6 signatures sample:")
  for i = 0; i < 6; i = i + 1 {
    let s = sigs[i]
    println(
      "    [\{i}] \{s.cell_type} (\{s.category}): " +
      "\{s.genes.length()} marker genes, e.g. [\{s.genes[0]}, ...]",
    )
  }

  println("\n=== 2. Single-Sample ssGSEA Scoring (Custom Gene Sets) ===")
  // Mock gene expression profile
  let expression_profile = [
    120.0, 80.0, 220.0, 150.0, 95.0, 75.0, 310.0, 280.0, // GeneA-H
     140.0, 50.0, 340.0, 180.0, 45.0, 220.0, 290.0, 380.0, // GeneI-P
     260.0, 110.0, 420.0, 350.0, // GeneQ-T
  ]
  let genes_profile = [
    "GeneA", "GeneB", "GeneC", "GeneD", "GeneE", "GeneF", "GeneG", "GeneH", "GeneI",
    "GeneJ", "GeneK", "GeneL", "GeneM", "GeneN", "GeneO", "GeneP", "GeneQ", "GeneR",
    "GeneS", "GeneT",
  ]
  // Mock signature: up-regulated genes are GeneC, GeneG, GeneK, GeneM, GeneQ
  let set_genes = [
    "GeneC", "GeneG", "GeneK", "GeneM", "GeneQ", "GeneZ_notexpressed",
  ]

  let single_score = @bio.xcell_ssgsea_single(
    expression_profile,
    genes_profile,
    set_genes,
    alpha=0.25,
  )
  println("  Expression: \{genes_profile.length()} genes")
  println(
    "  Signature set: \{set_genes.length()} genes (\{set_genes.join(", ")})",
  )
  println(
    "  ssGSEA enrichment score (alpha=0.25): \{format_score(single_score)}",
  )

  // With higher alpha
  let single_score2 = @bio.xcell_ssgsea_single(
    expression_profile,
    genes_profile,
    set_genes,
    alpha=0.75,
  )
  println(
    "  ssGSEA enrichment score (alpha=0.75): \{format_score(single_score2)}",
  )

  println("\n=== 3. Build Custom xCell Signatures ===")
  let custom_sigs = [
    @bio.XcellSignature::new("Tcells_Custom", "Lymphoid", [
      "CD3D", "CD3E", "CD3G", "CD4", "CD8A", "CD8B", "LCK", "ZAP70",
    ]),
    @bio.XcellSignature::new("Bcells_Custom", "Lymphoid", [
      "CD19", "MS4A1", "CD79A", "CD79B", "BLNK", "PAX5", "IGHM", "IGHD",
    ]),
    @bio.XcellSignature::new("Myeloid_Custom", "Myeloid", [
      "LYZ", "CD14", "CSF1R", "FCGR3A", "CD68", "S100A8", "S100A9", "MPO",
    ]),
    @bio.XcellSignature::new("Fibroblasts_Custom", "Stromal", [
      "COL1A1", "COL1A2", "COL3A1", "FN1", "VIM", "SPARC", "DCN", "LUM",
    ]),
    @bio.XcellSignature::new("Endothelial_Custom", "Stromal", [
      "PECAM1", "CDH5", "VWF", "KDR", "FLT1", "ENG", "TEK", "ESAM",
    ]),
  ]
  println("  Custom signatures built: \{custom_sigs.length()} types")
  for idx = 0; idx < custom_sigs.length(); idx = idx + 1 {
    let s = custom_sigs[idx]
    println("    - \{s.cell_type} [\{s.category}]: \{s.genes.length()} markers")
  }

  println("\n=== 4. Synthetic Bulk RNA-seq Expression Matrix ===")
  // 5 samples x 30 genes matrix, simulating different cell-type mixtures
  let gene_names_all = [
    "CD3D", "CD3E", "CD3G", "CD4", "CD8A", "CD8B", "LCK", "ZAP70", // T cell
     "CD19", "MS4A1", "CD79A", "CD79B", "BLNK", "PAX5", "IGHM", "IGHD", // B cell
     "LYZ", "CD14", "CSF1R", "FCGR3A", "CD68", "S100A8", "S100A9", "MPO", // Myeloid
     "COL1A1", "COL1A2", "VIM", "FN1", "PECAM1", "CDH5", // Stromal/Endo
  ]
  let sample_names_x = [
    "Tumor_ImmuneHigh", "Tumor_ImmuneLow", "Tumor_Desmoplastic", "Blood_PBMC", "Normal_LymphNode",
  ]
  // Each row: one gene. Columns: samples.
  let exprs_data : Array[Array[Double]] = [
    // Sample 0: Immune high (high lymphoid+myeloid)
    // Sample 1: Immune low
    // Sample 2: Desmoplastic (high stromal)
    // Sample 3: PBMC (high all immune, low stromal)
    // Sample 4: Lymph node (very high lymphoid)
    // T cell genes:
    [280.0, 20.0, 35.0, 190.0, 350.0], // CD3D
    [260.0, 18.0, 30.0, 180.0, 340.0], // CD3E
    [180.0, 15.0, 25.0, 150.0, 290.0], // CD3G
    [140.0, 10.0, 20.0, 100.0, 200.0], // CD4
    [120.0, 8.0, 15.0, 90.0, 180.0], // CD8A
    [90.0, 6.0, 12.0, 70.0, 140.0], // CD8B
    [220.0, 16.0, 30.0, 170.0, 300.0], // LCK
    [200.0, 14.0, 25.0, 160.0, 280.0], // ZAP70
    // B cell genes:
    [120.0, 12.0, 25.0, 150.0, 220.0], // CD19
    [140.0, 10.0, 20.0, 160.0, 250.0], // MS4A1=CD20
    [90.0, 8.0, 15.0, 110.0, 180.0], // CD79A
    [85.0, 7.0, 14.0, 105.0, 170.0], // CD79B
    [75.0, 6.0, 12.0, 95.0, 150.0], // BLNK
    [100.0, 9.0, 18.0, 120.0, 190.0], // PAX5
    [200.0, 15.0, 30.0, 230.0, 280.0], // IGHM
    [110.0, 9.0, 16.0, 140.0, 200.0], // IGHD
    // Myeloid genes:
    [320.0, 30.0, 80.0, 250.0, 160.0], // LYZ
    [240.0, 25.0, 60.0, 200.0, 120.0], // CD14
    [160.0, 15.0, 50.0, 140.0, 70.0], // CSF1R
    [180.0, 18.0, 55.0, 160.0, 80.0], // FCGR3A=CD16
    [220.0, 22.0, 70.0, 190.0, 95.0], // CD68
    [280.0, 28.0, 90.0, 220.0, 100.0], // S100A8
    [260.0, 26.0, 85.0, 200.0, 95.0], // S100A9
    [150.0, 20.0, 60.0, 180.0, 80.0], // MPO
    // Stromal/Endothelial:
    [40.0, 30.0, 600.0, 10.0, 60.0], // COL1A1
    [35.0, 28.0, 540.0, 9.0, 55.0], // COL1A2
    [80.0, 40.0, 480.0, 25.0, 70.0], // VIM
    [50.0, 32.0, 440.0, 12.0, 45.0], // FN1
    [15.0, 10.0, 200.0, 40.0, 90.0], // PECAM1=CD31
    [18.0, 12.0, 180.0, 35.0, 85.0], // CDH5=VE-cad
  ]

  println("  Expression matrix:")
  println("    Genes: \{exprs_data.length()}")
  println("    Samples: \{sample_names_x.length()}")
  let mut header_line = ""
  for idx = 0; idx < sample_names_x.length(); idx = idx + 1 {
    let s = sample_names_x[idx]
    header_line = header_line + "    | " + pad_right(s, 22)
  }
  println(header_line)
  for i = 0; i < gene_names_all.length(); i = i + 1 {
    let mut row_line = "    " + pad_right(gene_names_all[i], 10) + " |"
    for j = 0; j < sample_names_x.length(); j = j + 1 {
      let v = exprs_data[i][j]
      row_line = row_line + " " + pad_left(v.to_string(), 6) + " |"
    }
    println(row_line)
  }

  println("\n=== 5. Run xCell Scoring (Custom Signatures) ===")
  let params = @bio.XcellParams::new()
  println("  ssGSEA alpha: \{params.alpha}")
  println("  (Using custom 5-signature set above)")
  let custom_result = @bio.xcell_score(
    exprs_data, gene_names_all, sample_names_x, custom_sigs, params,
  )
  println(
    "  Result matrix: cell types=\{custom_result.cell_types.length()}, " +
    "samples=\{sample_names_x.length()}",
  )

  // Pretty-print score matrix
  println("\n  --- xCell scores (custom signatures) ---")
  let mut line = "    " + pad_right("Cell type", 28)
  for idx = 0; idx < sample_names_x.length(); idx = idx + 1 {
    let s = sample_names_x[idx]
    line = line + " | " + pad_left(s, 14)
  }
  println(line)
  println("    " + "-".repeat(28 + 16 * sample_names_x.length()))
  for ct = 0; ct < custom_result.cell_types.length(); ct = ct + 1 {
    let name = custom_result.cell_types[ct]
    line = "    " + pad_right(name, 28)
    for j = 0; j < sample_names_x.length(); j = j + 1 {
      line = line +
        " | " +
        pad_left(format_score(custom_result.scores[ct][j]), 14)
    }
    println(line)
  }

  println("\n=== 6. Query Scores By Cell Type / Sample ===")
  let tcell_scores = custom_result.get_cell_type_scores("Tcells_Custom")
  println("  Tcells_Custom scores across samples:")
  for j = 0; j < sample_names_x.length(); j = j + 1 {
    println("    - \{sample_names_x[j]}: \{format_score(tcell_scores[j])}")
  }

  let sample1_name = sample_names_x[2]
  let sample_scores = custom_result.get_sample_scores(sample1_name)
  println("\n  Sample '\{sample1_name}' scores across cell types:")
  for ct = 0; ct < custom_result.cell_types.length(); ct = ct + 1 {
    println(
      "    - \{custom_result.cell_types[ct]}: \{format_score(sample_scores[ct])}",
    )
  }

  println("\n=== 7. Top Enriched Cell Types per Sample ===")
  for j = 0; j < sample_names_x.length(); j = j + 1 {
    let sname = sample_names_x[j]
    let top = custom_result.get_top_cell_types(sname, 3)
    println("  Sample '\{sname}' top 3 cell types:")
    for k = 0; k < top.length(); k = k + 1 {
      let (ct_name, ct_score) = top[k]
      println("    \{k + 1}. \{ct_name}: \{format_score(ct_score)}")
    }
  }

  println("\n=== 8. Scores Grouped By Category (Lymphoid/Myeloid/Stromal) ===")
  let cat_scores = custom_result.scores_by_category()
  let cat_names_ordered : Array[String] = ["Lymphoid", "Myeloid", "Stromal"]
  line = "    " + pad_right("Category", 16)
  for idx = 0; idx < sample_names_x.length(); idx = idx + 1 {
    let s = sample_names_x[idx]
    line = line + " | " + pad_left(s, 14)
  }
  println(line)
  println("    " + "-".repeat(16 + 16 * sample_names_x.length()))
  for idx = 0; idx < cat_names_ordered.length(); idx = idx + 1 {
    let cat = cat_names_ordered[idx]
    match cat_scores.get(cat) {
      Some(arr) => {
        line = "    " + pad_right(cat, 16)
        for j = 0; j < arr.length(); j = j + 1 {
          line = line + " | " + pad_left(format_score(arr[j]), 14)
        }
        println(line)
      }
      None => println("    " + cat + ": (no data)")
    }
  }

  println("\n=== 9. Run Default xCell (64 Immune+Stromal) Pipeline ===")
  println("  Using built-in 64 signatures on the same expression data...")
  let default_result = @bio.xcell_run_default(
    exprs_data, gene_names_all, sample_names_x,
  )
  println(
    "  Completed! Cell type signatures scored: \{default_result.cell_types.length()}",
  )

  // Top 5 per sample
  println("\n  Top 5 cell types (from default 64) per sample:")
  for j = 0; j < sample_names_x.length(); j = j + 1 {
    let sname = sample_names_x[j]
    let top5 = default_result.get_top_cell_types(sname, 5)
    println("  --- \{sname} ---")
    for k = 0; k < top5.length(); k = k + 1 {
      let (ct_name, ct_score) = top5[k]
      let found_idx = array_index_of(default_result.cell_types, ct_name)
      let cat_idx = if found_idx >= 0 {
        default_result.categories[found_idx]
      } else {
        "?"
      }
      println(
        "    \{k + 1}. " +
        pad_right(ct_name, 26) +
        " [" +
        pad_right(cat_idx, 10) +
        "]" +
        " score=\{format_score(ct_score)}",
      )
    }
  }

  println(
    "\n=== 10. Immune / Stromal / Microenvironment Summary (default pipeline) ===",
  )
  println(
    "  " +
    pad_right("Sample", 26) +
    " | Immune | Stromal | Microenv | ImmuneHigh?",
  )
  println("  " + "-".repeat(26 + 54))
  for j = 0; j < sample_names_x.length(); j = j + 1 {
    let sname = sample_names_x[j]
    let imm = default_result.immune_scores[j]
    let str = default_result.stromal_scores[j]
    let menv = default_result.microenvironment_scores[j]
    let high = if imm > 0.4 { "YES" } else { "no" }
    println(
      "  " +
      pad_right(sname, 26) +
      " | " +
      pad_left(format_score(imm), 6) +
      " |" +
      " " +
      pad_left(format_score(str), 7) +
      " | " +
      pad_left(format_score(menv), 8) +
      " |" +
      "  " +
      high,
    )
  }

  println("\n=== xCell Demo Complete ===")
}