///|
pub struct Slicer {
  name : String
  cache : String
  source_name : String
  cell : String
  table_sheet : String
  table_name : String
  caption : String
  macro_name : String
  width : Int
  height : Int
  display_header : Bool?
  item_desc : Bool
  format : GraphicOptions
  priv drawing_offset_x_emu : Int?
  priv drawing_offset_y_emu : Int?
  priv drawing_width_emu : Int?
  priv drawing_height_emu : Int?
  priv drawing_order : Int?
} derive(Debug)

///|
pub fn Slicer::new(name : String) -> Slicer raise XlsxError {
  if name == "" {
    raise InvalidSheetOperation(msg="slicer name empty")
  }
  fn sanitize_cache_stub(value : StringView) -> String {
    let name = value.to_owned()
    let sb = StringBuilder::new()
    for i, ch in name {
      let code = ch.to_int()
      let is_letter = (code >= 65 && code <= 90) || (code >= 97 && code <= 122)
      let is_digit = code >= 48 && code <= 57
      if is_letter {
        sb.write_view(ch.to_string())
      } else if i > 0 && (is_digit || ch == '.') {
        sb.write_view(ch.to_string())
      } else {
        sb.write_view("_")
      }
    }
    sb.to_string()
  }

  let cache = "Slicer_" + sanitize_cache_stub(name)
  {
    name,
    cache,
    source_name: name,
    cell: "",
    table_sheet: "",
    table_name: "",
    caption: "",
    macro_name: "",
    width: 200,
    height: 200,
    display_header: None,
    item_desc: false,
    format: GraphicOptions::new(),
    drawing_offset_x_emu: None,
    drawing_offset_y_emu: None,
    drawing_width_emu: None,
    drawing_height_emu: None,
    drawing_order: None,
  }
}

///|
let slicer_ns_main = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"

///|
let slicer_ns_x = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"

///|
let slicer_ns_x15 = "http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"

///|
let slicer_ns_x14 = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"

///|
let slicer_ns_xr10 = "http://schemas.microsoft.com/office/spreadsheetml/2016/revision10"

///|
let slicer_ext_uri_pivot_cache_definition = "{725AE2AE-9491-48be-B2B4-4EB974FC3084}"

///|
fn write_slicers_part_xml(
  slicers : ArrayView[Slicer],
  budget? : WritePartBudget,
) -> String raise XlsxError {
  let sb = LimitedXmlBuilder::new(budget)
  sb.write_view("\n")
  sb.write_view(
    "\n",
  )
  for slicer in slicers {
    sb.write_view("   sb.write_view(" showCaption=\"\{bool_attr(value)}\"")
      None => ()
    }
    sb.write_view(" rowHeight=\"251883\"/>\n")
  }
  sb.write_view("")
  sb.to_string()
}

///|
fn slicer_sort_order_attr(item_desc : Bool) -> String {
  if item_desc {
    " sortOrder=\"descending\""
  } else {
    ""
  }
}

///|
fn write_slicer_cache_xml_pivot(
  cache_name : StringView,
  source_name : StringView,
  table_sheet_id : Int,
  pivot_table_name : StringView,
  pivot_cache_id : Int,
  item_desc : Bool,
  budget? : WritePartBudget,
) -> String raise XlsxError {
  let sb = LimitedXmlBuilder::new(budget)
  sb.write_view("\n")
  sb.write_view(
    "\n")
  sb.write_view("  \n")
  sb.write_view("    \n")
  sb.write_view("  \n")
  sb.write_view("  \n")
  sb.write_view("    \n")
  sb.write_view("      \n")
  sb.write_view("    \n")
  sb.write_view("  \n")
  sb.write_view("")
  sb.to_string()
}

///|
fn write_slicer_cache_xml_table(
  cache_name : StringView,
  source_name : StringView,
  table_id : Int,
  column : Int,
  item_desc : Bool,
  budget? : WritePartBudget,
) -> String raise XlsxError {
  let sb = LimitedXmlBuilder::new(budget)
  sb.write_view("\n")
  sb.write_view(
    "\n")
  sb.write_view("  \n")
  sb.write_view("    \n")
  sb.write_view(
    "      \n")
  sb.write_view("    \n")
  sb.write_view("  \n")
  sb.write_view("")
  sb.to_string()
}

///|
fn parse_pivot_cache_slicer_id(cache_xml : StringView) -> Int? raise XlsxError {
  match tag_attributes_in(cache_xml, "x14:pivotCacheDefinition") {
    Some(tag) =>
      match attr_value(tag, "pivotCacheId") {
        Some(raw) =>
          try @string.parse_int(raw, base=10) catch {
            _ => None
          } noraise {
            value => Some(value)
          }
        None => None
      }
    None => None
  }
}

///|
fn ensure_pivot_cache_slicer_id(
  cache_xml : String,
  pivot_cache_id : Int,
  budget? : WritePartBudget,
) -> String raise XlsxError {
  if parse_pivot_cache_slicer_id(cache_xml) is Some(_) {
    return cache_xml
  }
  let namespace_at = if !cache_xml.contains("xmlns:x14=\"\{slicer_ns_x14}\"") {
    let needle = " {
        let rest = cache_xml[start:]
        match rest.find(">") {
          Some(end_rel) => Some(start + end_rel)
          None => None
        }
      }
      None => None
    }
  } else {
    None
  }
  let extension_insertion : (Int, String)? = if cache_xml.contains(
      "uri=\"\{slicer_ext_uri_pivot_cache_definition}\"",
    ) {
    None
  } else {
    let ext_entry = "    \n      \n    \n"
    match cache_xml.find("") {
      Some(pos) => Some((pos, ext_entry))
      None =>
        match cache_xml.find("") {
          Some(pos) => Some((pos, "  \n\{ext_entry}  \n"))
          None => None
        }
    }
  }
  if namespace_at is None && extension_insertion is None {
    return cache_xml
  }
  let sb = LimitedXmlBuilder::new(budget)
  let mut cursor = 0
  match namespace_at {
    Some(pos) => {
      sb.write_view(cache_xml[cursor:pos])
      sb.write_view(" xmlns:x14=\"\{slicer_ns_x14}\"")
      cursor = pos
    }
    None => ()
  }
  match extension_insertion {
    Some((pos, fragment)) => {
      sb.write_view(cache_xml[cursor:pos])
      sb.write_view(fragment)
      cursor = pos
    }
    None => ()
  }
  sb.write_view(cache_xml[cursor:])
  sb.to_string()
}