///|
let root_rels_part_path = "_rels/.rels"

///|
let content_types_part_path = "[Content_Types].xml"

///|
let workbook_part_path = "xl/workbook.xml"

///|
let workbook_rels_part_path = "xl/_rels/workbook.xml.rels"

///|
let shared_strings_part_path = "xl/sharedStrings.xml"

///|
let calc_chain_part_path = "xl/calcChain.xml"

///|
let styles_part_path = "xl/styles.xml"

///|
let theme_part_path = "xl/theme/theme1.xml"

///|
let core_props_part_path = "docProps/core.xml"

///|
let app_props_part_path = "docProps/app.xml"

///|
let custom_props_part_path = "docProps/custom.xml"

///|
let vba_project_part_path = "xl/vbaProject.bin"

///|
fn chart_part_path(file_name : String) -> String {
  "xl/charts/\{file_name}"
}

///|
fn worksheet_part_path(sheet_id : Int) -> String {
  "xl/worksheets/sheet\{sheet_id}.xml"
}

///|
fn worksheet_rels_part_path(sheet_id : Int) -> String {
  "xl/worksheets/_rels/sheet\{sheet_id}.xml.rels"
}

///|
fn drawing_part_path(drawing_id : Int) -> String {
  "xl/drawings/drawing\{drawing_id}.xml"
}

///|
fn drawing_rels_part_path(drawing_id : Int) -> String {
  "xl/drawings/_rels/drawing\{drawing_id}.xml.rels"
}

///|
fn chartsheet_part_path(sheet_id : Int) -> String {
  "xl/chartsheets/sheet\{sheet_id}.xml"
}

///|
fn chartsheet_rels_part_path(sheet_id : Int) -> String {
  "xl/chartsheets/_rels/sheet\{sheet_id}.xml.rels"
}

///|
fn table_part_path(table_id : Int) -> String {
  "xl/tables/table\{table_id}.xml"
}

///|
fn pivot_table_part_path(pivot_id : Int) -> String {
  "xl/pivotTables/pivotTable\{pivot_id}.xml"
}

///|
fn pivot_table_rels_part_path(pivot_id : Int) -> String {
  "xl/pivotTables/_rels/pivotTable\{pivot_id}.xml.rels"
}

///|
fn pivot_cache_definition_part_path(cache_id : Int) -> String {
  "xl/pivotCache/pivotCacheDefinition\{cache_id}.xml"
}

///|
fn pivot_cache_definition_rels_part_path(cache_id : Int) -> String {
  "xl/pivotCache/_rels/pivotCacheDefinition\{cache_id}.xml.rels"
}

///|
fn pivot_cache_records_part_path(cache_id : Int) -> String {
  "xl/pivotCache/pivotCacheRecords\{cache_id}.xml"
}

///|
fn slicer_part_path(part_id : Int) -> String {
  "xl/slicers/slicer\{part_id}.xml"
}

///|
fn slicer_cache_part_path(cache_id : Int) -> String {
  "xl/slicerCaches/slicerCache\{cache_id}.xml"
}

///|
fn comment_part_path(comment_id : Int) -> String {
  "xl/comments\{comment_id}.xml"
}

///|
fn vml_part_path(vml_id : Int) -> String {
  "xl/drawings/vmlDrawing\{vml_id}.vml"
}

///|
fn vml_rels_part_path(vml_id : Int) -> String {
  "xl/drawings/_rels/vmlDrawing\{vml_id}.vml.rels"
}

///|
fn media_part_path(file_name : String) -> String {
  "xl/media/\{file_name}"
}

///|
fn content_override_path(part_path : String) -> String {
  "/\{part_path}"
}

///|
fn archive_path_from_part_name(
  part_name : StringView,
) -> String raise XlsxError {
  let part = part_name.to_owned()
  if part == "" {
    raise InvalidXml(msg="content type part name empty")
  }
  match part.strip_prefix("/") {
    Some(rest) => {
      let path = rest.to_owned()
      if path == "" {
        raise InvalidXml(msg="content type part name empty")
      }
      path
    }
    None => part
  }
}

///|
fn rels_path_for_part(part_path : StringView) -> String raise XlsxError {
  let part = part_path.to_owned()
  if part == "" || part.has_prefix("/") {
    raise InvalidXml(msg="part rels path invalid")
  }
  let segments = Array::from_iter(part.split("/"))
  let file_name = segments[segments.length() - 1].to_owned()
  if file_name == "" {
    raise InvalidXml(msg="part rels path invalid")
  }
  if segments.length() == 1 {
    return "_rels/\{file_name}.rels"
  }
  let folder_parts : Array[String] = []
  for i, segment in segments {
    if i + 1 < segments.length() {
      folder_parts.push(segment.to_owned())
    }
  }
  let folder = folder_parts.join("/")
  "\{folder}/_rels/\{file_name}.rels"
}

///|
fn drawing_rel_target(sheet_id : Int) -> String {
  "../drawings/drawing\{sheet_id}.xml"
}

///|
fn table_rel_target(table_id : Int) -> String {
  "../tables/table\{table_id}.xml"
}

///|
fn pivot_table_rel_target(pivot_id : Int) -> String {
  "../pivotTables/pivotTable\{pivot_id}.xml"
}

///|
fn slicer_rel_target(part_id : Int) -> String {
  "../slicers/slicer\{part_id}.xml"
}

///|
fn comment_rel_target(comment_id : Int) -> String {
  "../comments\{comment_id}.xml"
}

///|
fn vml_rel_target(vml_id : Int) -> String {
  "../drawings/vmlDrawing\{vml_id}.vml"
}

///|
fn media_rel_target(file_name : String) -> String {
  "../media/\{file_name}"
}

///|
fn chart_rel_target(file_name : String) -> String {
  "../charts/\{file_name}"
}

///|
fn chartsheet_workbook_rel_target(sheet_id : Int) -> String {
  "chartsheets/sheet\{sheet_id}.xml"
}

///|
fn slicer_cache_workbook_rel_target(cache_id : Int) -> String {
  "slicerCaches/slicerCache\{cache_id}.xml"
}

///|
fn pivot_cache_workbook_rel_target(cache_id : Int) -> String {
  "pivotCache/pivotCacheDefinition\{cache_id}.xml"
}

///|
test "write part paths: worksheet and drawing paths are stable" {
  debug_inspect(
    (worksheet_part_path(4), worksheet_rels_part_path(4)),
    content="(\"xl/worksheets/sheet4.xml\", \"xl/worksheets/_rels/sheet4.xml.rels\")",
  )
  debug_inspect(
    (drawing_part_path(3), drawing_rels_part_path(3)),
    content="(\"xl/drawings/drawing3.xml\", \"xl/drawings/_rels/drawing3.xml.rels\")",
  )
  debug_inspect(
    (chartsheet_part_path(2), chartsheet_rels_part_path(2)),
    content="(\"xl/chartsheets/sheet2.xml\", \"xl/chartsheets/_rels/sheet2.xml.rels\")",
  )
  inspect(chart_part_path("chart7.xml"), content="xl/charts/chart7.xml")
}

///|
test "write part paths: pivot and slicer paths are stable" {
  debug_inspect(
    (
      pivot_table_part_path(9),
      pivot_table_rels_part_path(9),
      pivot_cache_definition_part_path(9),
      pivot_cache_definition_rels_part_path(9),
      pivot_cache_records_part_path(9),
    ),
    content=(
      #|(
      #|  "xl/pivotTables/pivotTable9.xml",
      #|  "xl/pivotTables/_rels/pivotTable9.xml.rels",
      #|  "xl/pivotCache/pivotCacheDefinition9.xml",
      #|  "xl/pivotCache/_rels/pivotCacheDefinition9.xml.rels",
      #|  "xl/pivotCache/pivotCacheRecords9.xml",
      #|)
    ),
  )
  debug_inspect(
    (slicer_part_path(5), slicer_cache_part_path(5)),
    content=(
      #|("xl/slicers/slicer5.xml", "xl/slicerCaches/slicerCache5.xml")
    ),
  )
}

///|
test "write part paths: static package part names and override path" {
  debug_inspect(
    (
      root_rels_part_path, content_types_part_path, workbook_part_path, workbook_rels_part_path,
    ),
    content=(
      #|(
      #|  "_rels/.rels",
      #|  "[Content_Types].xml",
      #|  "xl/workbook.xml",
      #|  "xl/_rels/workbook.xml.rels",
      #|)
    ),
  )
  debug_inspect(
    (
      shared_strings_part_path, calc_chain_part_path, styles_part_path, theme_part_path,
      vba_project_part_path,
    ),
    content=(
      #|(
      #|  "xl/sharedStrings.xml",
      #|  "xl/calcChain.xml",
      #|  "xl/styles.xml",
      #|  "xl/theme/theme1.xml",
      #|  "xl/vbaProject.bin",
      #|)
    ),
  )
  debug_inspect(
    (
      core_props_part_path,
      app_props_part_path,
      custom_props_part_path,
      content_override_path(drawing_part_path(6)),
      media_part_path("image3.png"),
    ),
    content=(
      #|(
      #|  "docProps/core.xml",
      #|  "docProps/app.xml",
      #|  "docProps/custom.xml",
      #|  "/xl/drawings/drawing6.xml",
      #|  "xl/media/image3.png",
      #|)
    ),
  )
}

///|
test "write part paths: read-side path helpers are stable" {
  debug_inspect(
    (
      archive_path_from_part_name("/xl/workbook.xml"),
      archive_path_from_part_name("xl/workbook.xml"),
      rels_path_for_part("xl/workbook.xml"),
      rels_path_for_part("workbook.xml"),
    ),
    content=(
      #|(
      #|  "xl/workbook.xml",
      #|  "xl/workbook.xml",
      #|  "xl/_rels/workbook.xml.rels",
      #|  "_rels/workbook.xml.rels",
      #|)
    ),
  )
}

///|
test "write part paths: invalid read-side path helpers raise InvalidXml" {
  let empty_part_result = Ok(archive_path_from_part_name("")) catch {
    e => Err(e)
  }
  match empty_part_result {
    Err(InvalidXml(msg~)) =>
      inspect(msg, content="content type part name empty")
    _ => fail("expected content type part name empty")
  }

  let slash_part_result = Ok(archive_path_from_part_name("/")) catch {
    e => Err(e)
  }
  match slash_part_result {
    Err(InvalidXml(msg~)) =>
      inspect(msg, content="content type part name empty")
    _ => fail("expected content type part name empty for /")
  }

  let empty_rels_result = Ok(rels_path_for_part("")) catch { e => Err(e) }
  match empty_rels_result {
    Err(InvalidXml(msg~)) => inspect(msg, content="part rels path invalid")
    _ => fail("expected part rels path invalid")
  }

  let slash_rels_result = Ok(rels_path_for_part("/xl/workbook.xml")) catch {
    e => Err(e)
  }
  match slash_rels_result {
    Err(InvalidXml(msg~)) => inspect(msg, content="part rels path invalid")
    _ => fail("expected part rels path invalid for leading slash")
  }

  let trailing_slash_rels_result = Ok(rels_path_for_part("xl/")) catch {
    e => Err(e)
  }
  match trailing_slash_rels_result {
    Err(InvalidXml(msg~)) => inspect(msg, content="part rels path invalid")
    _ => fail("expected part rels path invalid for trailing slash")
  }
}

///|
test "write part paths: relationship targets are stable" {
  debug_inspect(
    (
      drawing_rel_target(3),
      table_rel_target(4),
      pivot_table_rel_target(5),
      slicer_rel_target(6),
      comment_rel_target(7),
      vml_rel_target(8),
      media_rel_target("image1.png"),
      chart_rel_target("chart9.xml"),
    ),
    content=(
      #|(
      #|  "../drawings/drawing3.xml",
      #|  "../tables/table4.xml",
      #|  "../pivotTables/pivotTable5.xml",
      #|  "../slicers/slicer6.xml",
      #|  "../comments7.xml",
      #|  "../drawings/vmlDrawing8.vml",
      #|  "../media/image1.png",
      #|  "../charts/chart9.xml",
      #|)
    ),
  )
  debug_inspect(
    (
      chartsheet_workbook_rel_target(2),
      slicer_cache_workbook_rel_target(3),
      pivot_cache_workbook_rel_target(4),
    ),
    content=(
      #|(
      #|  "chartsheets/sheet2.xml",
      #|  "slicerCaches/slicerCache3.xml",
      #|  "pivotCache/pivotCacheDefinition4.xml",
      #|)
    ),
  )
}