use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::io::{Read, Seek};
use std::sync::atomic::{AtomicU64, Ordering};

use crate::schemas::opc_content_types::{Types, TypesChoice};
use crate::schemas::opc_relationships::{
  Relationship as OpcRelationship, Relationships, TargetMode,
};
use crate::sdk::PackageOpenMode;

#[cfg(feature = "flat-opc")]
use super::unexpected_eof;
use super::{
  SdkError, part_relationships_path, resolve_relationship_target_path, resolve_zip_file_path,
};

#[cfg(feature = "flat-opc")]
const FLAT_OPC_PACKAGE_NS: &str = "http://schemas.microsoft.com/office/2006/xmlPackage";
#[cfg(feature = "flat-opc")]
const RELATIONSHIP_CONTENT_TYPE: &str = "application/vnd.openxmlformats-package.relationships+xml";

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct PartId(u32);

impl PartId {
  #[inline]
  pub const fn from_index(index: usize) -> Self {
    Self(index as u32)
  }

  #[inline]
  pub const fn index(self) -> usize {
    self.0 as usize
  }
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(crate) struct PackageId(u64);

impl PackageId {
  fn new() -> Self {
    static NEXT_PACKAGE_ID: AtomicU64 = AtomicU64::new(1);
    Self(NEXT_PACKAGE_ID.fetch_add(1, Ordering::Relaxed))
  }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct NewPartDescriptor {
  pub(crate) relationship_type: Cow<'static, str>,
  pub(crate) content_type: Cow<'static, str>,
  pub(crate) path_prefix: &'static str,
  pub(crate) target_name: &'static str,
  pub(crate) extension: Cow<'static, str>,
}

pub(crate) fn default_part_extension_for_content_type(content_type: &str) -> Option<&'static str> {
  match content_type {
    "image/unknown" => Some(".bin"),
    "image/bmp" => Some(".bmp"),
    "image/gif" => Some(".gif"),
    "image/png" => Some(".png"),
    "image/jp2" => Some(".jp2"),
    "image/tif" => Some(".tif"),
    "image/tiff" => Some(".tiff"),
    "image/xbm" => Some(".xbm"),
    "image/x-icon" => Some(".ico"),
    "image/x-pcx" => Some(".pcx"),
    "image/x-pcz" => Some(".pcz"),
    "image/x-emz" => Some(".emz"),
    "image/x-wmz" => Some(".wmz"),
    "image/jpeg" => Some(".jpeg"),
    "image/x-emf" => Some(".emf"),
    "image/x-wmf" => Some(".wmf"),
    "image/svg+xml" => Some(".svg"),
    "audio/aiff" => Some(".aiff"),
    "audio/midi" => Some(".midi"),
    "audio/mp3" => Some(".mp3"),
    "audio/mpegurl" => Some(".m3u"),
    "audio/wav" => Some(".wav"),
    "audio/x-ms-wma" => Some(".wma"),
    "audio/mpeg" => Some(".mpeg"),
    "audio/ogg" => Some(".ogg"),
    "video/x-ms-asf-plugin" => Some(".asx"),
    "video/avi" => Some(".avi"),
    "video/mp4" => Some(".mp4"),
    "video/mpg" => Some(".mpg"),
    "video/mpeg" => Some(".mpeg"),
    "video/x-ms-wmv" => Some(".wmv"),
    "video/x-ms-wmx" => Some(".wmx"),
    "video/x-ms-wvx" => Some(".wvx"),
    "video/quicktime" => Some(".mov"),
    "video/ogg" => Some(".ogg"),
    "video/vc1" => Some(".wmv"),
    _ => None,
  }
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum NewPartTargetMode {
  Fixed,
  #[default]
  Indexed,
}

#[derive(Clone, Debug)]
pub(crate) enum StoredPartData {
  Raw { bytes: Vec<u8> },
}

impl StoredPartData {
  #[inline]
  pub(crate) fn bytes(&self) -> &[u8] {
    match self {
      Self::Raw { bytes, .. } => bytes,
    }
  }

  #[inline]
  pub(crate) fn bytes_mut(&mut self) -> &mut Vec<u8> {
    match self {
      Self::Raw { bytes, .. } => bytes,
    }
  }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct RelationshipInfo {
  id: Box<str>,
  relationship_type: super::XmlRelationshipNamespaceUri,
  target: Box<str>,
  target_mode: Option<TargetMode>,
  target_kind: RelationshipTargetKind,
  target_part_id: Option<PartId>,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RelationshipTargetKind {
  InternalPart,
  External,
  Null,
  Missing,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ReferenceRelationshipKind {
  External,
  Hyperlink,
  Audio,
  Media,
  Video,
}

impl RelationshipInfo {
  fn internal_part(
    id: String,
    relationship_type: String,
    target: String,
    target_part_id: PartId,
  ) -> Self {
    Self {
      id: id.into_boxed_str(),
      relationship_type: super::XmlRelationshipNamespaceUri::from_uri(&relationship_type),
      target: target.into_boxed_str(),
      target_mode: None,
      target_kind: RelationshipTargetKind::InternalPart,
      target_part_id: Some(target_part_id),
    }
  }

  fn external(
    id: String,
    relationship_type: String,
    target: String,
    target_mode: Option<TargetMode>,
  ) -> Self {
    Self {
      id: id.into_boxed_str(),
      relationship_type: super::XmlRelationshipNamespaceUri::from_uri(&relationship_type),
      target: target.into_boxed_str(),
      target_mode,
      target_kind: RelationshipTargetKind::External,
      target_part_id: None,
    }
  }

  fn reference(
    id: String,
    relationship_type: String,
    target: String,
    target_mode: Option<TargetMode>,
  ) -> Self {
    let effective_target_mode = target_mode.unwrap_or(TargetMode::Internal);
    let target_kind = if matches!(effective_target_mode, TargetMode::External) {
      RelationshipTargetKind::External
    } else if target.eq_ignore_ascii_case("NULL") {
      RelationshipTargetKind::Null
    } else {
      RelationshipTargetKind::Missing
    };
    Self {
      id: id.into_boxed_str(),
      relationship_type: super::XmlRelationshipNamespaceUri::from_uri(&relationship_type),
      target: target.into_boxed_str(),
      target_mode,
      target_kind,
      target_part_id: None,
    }
  }

  #[inline]
  pub fn id(&self) -> &str {
    &self.id
  }

  #[inline]
  pub fn relationship_type(&self) -> &str {
    self.relationship_type.as_str()
  }

  #[inline]
  pub(crate) fn relationship_type_bytes(&self) -> &[u8] {
    self.relationship_type.uri_bytes()
  }

  #[inline]
  pub fn target(&self) -> &str {
    &self.target
  }

  #[inline]
  pub fn target_mode(&self) -> TargetMode {
    self.target_mode.unwrap_or(TargetMode::Internal)
  }

  #[inline]
  pub fn target_kind(&self) -> RelationshipTargetKind {
    self.target_kind
  }

  #[inline]
  pub fn target_part_id(&self) -> Option<PartId> {
    self.target_part_id
  }

  #[inline]
  pub fn is_reference_relationship(&self) -> bool {
    self.reference_kind().is_some()
  }

  #[inline]
  pub fn reference_kind(&self) -> Option<ReferenceRelationshipKind> {
    let relationship_type = self.relationship_type_bytes();
    if super::relationship_type_matches_bytes(relationship_type, super::REL_HYPERLINK) {
      Some(ReferenceRelationshipKind::Hyperlink)
    } else if super::relationship_type_matches_bytes(relationship_type, super::REL_AUDIO) {
      Some(ReferenceRelationshipKind::Audio)
    } else if super::relationship_type_matches_bytes(relationship_type, super::REL_MEDIA) {
      Some(ReferenceRelationshipKind::Media)
    } else if super::relationship_type_matches_bytes(relationship_type, super::REL_VIDEO) {
      Some(ReferenceRelationshipKind::Video)
    } else if self.target_kind == RelationshipTargetKind::External {
      Some(ReferenceRelationshipKind::External)
    } else {
      None
    }
  }

  fn to_relationship(&self) -> OpcRelationship {
    OpcRelationship {
      id: self.id().to_string(),
      r#type: self.relationship_type().to_string(),
      target: self.target().to_string(),
      target_mode: self.target_mode,
    }
  }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Relationship {
  inner: RelationshipInfo,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RelationshipRef<'a> {
  inner: &'a RelationshipInfo,
}

macro_rules! impl_relationship_accessors {
  ($ident:ident) => {
    impl $ident {
      #[inline]
      pub fn id(&self) -> &str {
        self.inner.id()
      }

      #[inline]
      pub fn relationship_type(&self) -> &str {
        self.inner.relationship_type()
      }

      #[inline]
      pub fn target(&self) -> &str {
        self.inner.target()
      }

      #[inline]
      pub fn target_mode(&self) -> TargetMode {
        self.inner.target_mode()
      }

      #[inline]
      pub fn target_kind(&self) -> RelationshipTargetKind {
        self.inner.target_kind()
      }

      #[inline]
      pub fn target_part_id(&self) -> Option<PartId> {
        self.inner.target_part_id()
      }

      #[inline]
      pub fn reference_kind(&self) -> Option<ReferenceRelationshipKind> {
        self.inner.reference_kind()
      }

      #[inline]
      pub fn is_reference_relationship(&self) -> bool {
        self.inner.is_reference_relationship()
      }
    }
  };
}

impl_relationship_accessors!(Relationship);

impl<'a> RelationshipRef<'a> {
  pub const HYPERLINK_RELATIONSHIP_TYPE: &'static str =
    "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
  pub const AUDIO_REFERENCE_RELATIONSHIP_TYPE: &'static str =
    "http://schemas.openxmlformats.org/officeDocument/2006/relationships/audio";
  pub const MEDIA_REFERENCE_RELATIONSHIP_TYPE: &'static str =
    "http://schemas.microsoft.com/office/2007/relationships/media";
  pub const VIDEO_REFERENCE_RELATIONSHIP_TYPE: &'static str =
    "http://schemas.openxmlformats.org/officeDocument/2006/relationships/video";

  #[inline]
  pub(crate) const fn new(inner: &'a RelationshipInfo) -> Self {
    Self { inner }
  }

  #[inline]
  pub fn id(&self) -> &'a str {
    self.inner.id()
  }

  #[inline]
  pub fn relationship_type(&self) -> &'a str {
    self.inner.relationship_type()
  }

  #[inline]
  pub fn target(&self) -> &'a str {
    self.inner.target()
  }

  #[inline]
  pub fn target_mode(&self) -> TargetMode {
    self.inner.target_mode()
  }

  #[inline]
  pub fn target_kind(&self) -> RelationshipTargetKind {
    self.inner.target_kind()
  }

  #[inline]
  pub fn target_part_id(&self) -> Option<PartId> {
    self.inner.target_part_id()
  }

  #[inline]
  pub fn reference_kind(&self) -> Option<ReferenceRelationshipKind> {
    self.inner.reference_kind()
  }

  #[inline]
  pub fn is_reference_relationship(&self) -> bool {
    self.inner.is_reference_relationship()
  }
}

impl From<RelationshipInfo> for Relationship {
  #[inline]
  fn from(inner: RelationshipInfo) -> Self {
    Self { inner }
  }
}

impl From<RelationshipRef<'_>> for Relationship {
  #[inline]
  fn from(value: RelationshipRef<'_>) -> Self {
    Self {
      inner: value.inner.clone(),
    }
  }
}

impl<'a> From<&'a RelationshipInfo> for RelationshipRef<'a> {
  #[inline]
  fn from(inner: &'a RelationshipInfo) -> Self {
    Self::new(inner)
  }
}

#[doc(hidden)]
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct RelationshipSet {
  xml_header: super::XmlHeaderType,
  relationships: Vec<RelationshipInfo>,
  by_id: HashMap<Box<str>, usize>,
  raw_bytes: Option<Box<[u8]>>,
}

fn next_relationship_id<'a>(relationships: impl Iterator<Item = &'a RelationshipInfo>) -> String {
  let next = relationships
    .filter_map(|relationship| relationship.id().strip_prefix("rId"))
    .filter_map(|suffix| suffix.parse::<u32>().ok())
    .max()
    .unwrap_or_default()
    + 1;
  format!("rId{next}")
}

impl RelationshipSet {
  pub(crate) const HYPERLINK_RELATIONSHIP_TYPE: &'static str =
    "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
  pub(crate) const AUDIO_REFERENCE_RELATIONSHIP_TYPE: &'static str =
    "http://schemas.openxmlformats.org/officeDocument/2006/relationships/audio";
  pub(crate) const MEDIA_REFERENCE_RELATIONSHIP_TYPE: &'static str =
    "http://schemas.microsoft.com/office/2007/relationships/media";
  pub(crate) const VIDEO_REFERENCE_RELATIONSHIP_TYPE: &'static str =
    "http://schemas.openxmlformats.org/officeDocument/2006/relationships/video";

  #[inline]
  pub(crate) fn is_empty(&self) -> bool {
    self.relationships.is_empty() && self.raw_bytes.is_none()
  }

  #[inline]
  pub(crate) fn iter(&self) -> impl Iterator<Item = &RelationshipInfo> {
    self.relationships.iter()
  }

  #[inline]
  pub(crate) fn get(&self, relationship_id: &str) -> Option<&RelationshipInfo> {
    self
      .by_id
      .get(relationship_id)
      .and_then(|index| self.relationships.get(*index))
  }

  #[inline]
  pub(crate) fn contains_id(&self, relationship_id: &str) -> bool {
    self.by_id.contains_key(relationship_id)
  }

  pub(crate) fn next_relationship_id(&self) -> String {
    next_relationship_id(self.relationships.iter())
  }

  pub(crate) fn add_external_relationship(
    &mut self,
    relationship_id: impl Into<String>,
    relationship_type: impl Into<String>,
    target: impl Into<String>,
  ) -> Result<&RelationshipInfo, SdkError> {
    self.push_relationship(RelationshipInfo::external(
      relationship_id.into(),
      relationship_type.into(),
      target.into(),
      Some(TargetMode::External),
    ))
  }

  pub(crate) fn add_hyperlink_relationship(
    &mut self,
    relationship_id: impl Into<String>,
    target: impl Into<String>,
  ) -> Result<&RelationshipInfo, SdkError> {
    self.add_hyperlink_relationship_with_mode(relationship_id, target, TargetMode::External)
  }

  pub(crate) fn add_hyperlink_relationship_with_mode(
    &mut self,
    relationship_id: impl Into<String>,
    target: impl Into<String>,
    target_mode: TargetMode,
  ) -> Result<&RelationshipInfo, SdkError> {
    let target_mode = match target_mode {
      TargetMode::External => Some(TargetMode::External),
      TargetMode::Internal => None,
    };
    self.push_relationship(RelationshipInfo::reference(
      relationship_id.into(),
      Self::HYPERLINK_RELATIONSHIP_TYPE.to_string(),
      target.into(),
      target_mode,
    ))
  }

  pub(crate) fn add_internal_part_relationship(
    &mut self,
    relationship_id: impl Into<String>,
    relationship_type: impl Into<String>,
    target: impl Into<String>,
    target_part_id: PartId,
  ) -> Result<&RelationshipInfo, SdkError> {
    self.push_relationship(RelationshipInfo::internal_part(
      relationship_id.into(),
      relationship_type.into(),
      target.into(),
      target_part_id,
    ))
  }

  pub(crate) fn add_relationship_info(
    &mut self,
    relationship: RelationshipInfo,
  ) -> Result<&RelationshipInfo, SdkError> {
    self.push_relationship(relationship)
  }

  pub(crate) fn remove(&mut self, relationship_id: &str) -> Option<RelationshipInfo> {
    let index = *self.by_id.get(relationship_id)?;
    self.raw_bytes = None;
    let removed = self.relationships.remove(index);
    self.rebuild_index();
    Some(removed)
  }

  pub(crate) fn remove_reference_relationship(
    &mut self,
    relationship_id: &str,
  ) -> Result<RelationshipInfo, SdkError> {
    let relationship = self.get(relationship_id).ok_or_else(|| {
      SdkError::CommonError(format!(
        "reference relationship id {relationship_id} does not exist"
      ))
    })?;
    if !relationship.is_reference_relationship() {
      return Err(SdkError::CommonError(format!(
        "relationship id {relationship_id} is not a reference relationship"
      )));
    }
    Ok(
      self
        .remove(relationship_id)
        .expect("relationship was already resolved"),
    )
  }

  pub(crate) fn get_external_relationship(
    &self,
    relationship_id: &str,
  ) -> Option<&RelationshipInfo> {
    self.get(relationship_id).filter(|relationship| {
      matches!(
        relationship.reference_kind(),
        Some(ReferenceRelationshipKind::External)
      )
    })
  }

  pub(crate) fn remove_external_relationship(
    &mut self,
    relationship_id: &str,
  ) -> Result<RelationshipInfo, SdkError> {
    let relationship = self.get(relationship_id).ok_or_else(|| {
      SdkError::CommonError(format!(
        "external relationship id {relationship_id} does not exist"
      ))
    })?;
    if !matches!(
      relationship.reference_kind(),
      Some(ReferenceRelationshipKind::External)
    ) {
      return Err(SdkError::CommonError(format!(
        "relationship id {relationship_id} is not an external relationship"
      )));
    }
    Ok(
      self
        .remove(relationship_id)
        .expect("relationship was already resolved"),
    )
  }

  pub(crate) fn get_hyperlink_relationship(
    &self,
    relationship_id: &str,
  ) -> Option<&RelationshipInfo> {
    self.get(relationship_id).filter(|relationship| {
      matches!(
        relationship.reference_kind(),
        Some(ReferenceRelationshipKind::Hyperlink)
      )
    })
  }

  pub(crate) fn change_relationship_id(
    &mut self,
    relationship_id: &str,
    new_relationship_id: impl Into<String>,
  ) -> Result<(), SdkError> {
    let new_relationship_id = new_relationship_id.into();
    if relationship_id == new_relationship_id {
      return Ok(());
    }
    if self.contains_id(&new_relationship_id) {
      return Err(SdkError::CommonError(format!(
        "relationship id {new_relationship_id} already exists"
      )));
    }

    let Some(index) = self.by_id.get(relationship_id).copied() else {
      return Err(SdkError::CommonError(format!(
        "relationship id {relationship_id} does not exist"
      )));
    };

    self.relationships[index].id = new_relationship_id.into_boxed_str();
    self.raw_bytes = None;
    self.rebuild_index();
    Ok(())
  }

  #[inline]
  pub(crate) fn part_relationships(&self) -> impl Iterator<Item = &RelationshipInfo> {
    self
      .relationships
      .iter()
      .filter(|relationship| relationship.target_kind() == RelationshipTargetKind::InternalPart)
  }

  #[inline]
  pub(crate) fn external_relationships(&self) -> impl Iterator<Item = &RelationshipInfo> {
    self.relationships.iter().filter(|relationship| {
      relationship.target_kind() == RelationshipTargetKind::External
        && !super::relationship_type_matches_bytes(
          relationship.relationship_type_bytes(),
          super::REL_HYPERLINK,
        )
    })
  }

  #[inline]
  pub(crate) fn hyperlink_relationships(&self) -> impl Iterator<Item = &RelationshipInfo> {
    self.relationships.iter().filter(|relationship| {
      super::relationship_type_matches_bytes(
        relationship.relationship_type_bytes(),
        super::REL_HYPERLINK,
      )
    })
  }

  #[inline]
  pub(crate) fn data_part_reference_relationships(
    &self,
  ) -> impl Iterator<Item = &RelationshipInfo> {
    self.relationships.iter().filter(|relationship| {
      crate::common::is_data_part_reference_relationship_type_bytes(
        relationship.relationship_type_bytes(),
      )
    })
  }

  #[inline]
  pub(crate) fn first_target_part_by_relationship_type(
    &self,
    relationship_type: &str,
  ) -> Option<PartId> {
    self.relationships.iter().find_map(|relationship| {
      super::relationship_type_matches_bytes(
        relationship.relationship_type_bytes(),
        relationship_type.as_bytes(),
      )
      .then(|| relationship.target_part_id())
      .flatten()
    })
  }

  pub(crate) fn to_relationships(&self) -> Relationships {
    Relationships {
      xmlns: vec![super::XmlNamespace::raw(
        "",
        "http://schemas.openxmlformats.org/package/2006/relationships",
      )],
      xml_header: self.xml_header,
      relationship: self
        .relationships
        .iter()
        .map(RelationshipInfo::to_relationship)
        .collect(),
    }
  }

  #[cfg(feature = "flat-opc")]
  pub(crate) fn to_bytes_cow(&self) -> Result<Cow<'_, [u8]>, SdkError> {
    if let Some(bytes) = &self.raw_bytes {
      return Ok(Cow::Borrowed(bytes));
    }
    Ok(Cow::Owned(self.to_relationships().to_bytes()?))
  }

  #[inline]
  pub(crate) fn write_xml<W: std::io::Write>(&self, writer: &mut W) -> Result<(), SdkError> {
    if let Some(bytes) = &self.raw_bytes {
      writer.write_all(bytes)?;
    } else {
      writer.write_all(&self.to_relationships().to_bytes()?)?;
    }
    Ok(())
  }

  fn from_relationships(
    relationships: Option<Relationships>,
    source_path: &str,
    by_path: &HashMap<Box<str>, PartId>,
  ) -> Self {
    let Some(relationships) = relationships else {
      return Self::default();
    };

    let source_parent_path = super::parent_zip_path(source_path);
    let mut set = Self {
      xml_header: relationships.xml_header,
      relationships: Vec::with_capacity(relationships.relationship.len()),
      by_id: HashMap::with_capacity(relationships.relationship.len()),
      raw_bytes: None,
    };

    for relationship in relationships.relationship {
      let info = relationship_info(relationship, &source_parent_path, by_path);
      set.push_relationship_unchecked(info);
    }

    set
  }

  fn from_raw_bytes(bytes: Box<[u8]>) -> Self {
    Self {
      xml_header: super::XmlHeaderType::default(),
      relationships: Vec::new(),
      by_id: HashMap::new(),
      raw_bytes: Some(bytes),
    }
  }

  fn push_relationship(
    &mut self,
    relationship: RelationshipInfo,
  ) -> Result<&RelationshipInfo, SdkError> {
    if self.contains_id(relationship.id()) {
      return Err(SdkError::CommonError(format!(
        "relationship id {} already exists",
        relationship.id()
      )));
    }
    self.push_relationship_unchecked(relationship);
    Ok(self.relationships.last().expect("pushed relationship"))
  }

  fn push_relationship_unchecked(&mut self, relationship: RelationshipInfo) {
    self.raw_bytes = None;
    let index = self.relationships.len();
    self.by_id.insert(relationship.id.clone(), index);
    self.relationships.push(relationship);
  }

  fn rebuild_index(&mut self) {
    self.by_id.clear();
    self.by_id.reserve(self.relationships.len());
    for (index, relationship) in self.relationships.iter().enumerate() {
      self.by_id.insert(relationship.id.clone(), index);
    }
  }
}

#[derive(Clone, Debug)]
pub(crate) struct StoredPart {
  path: Box<str>,
  content_type: Box<str>,
  relationship_type: Option<super::XmlRelationshipNamespaceUri>,
  relationships: RelationshipSet,
  data: StoredPartData,
  deleted: bool,
}

impl StoredPart {
  #[inline]
  pub(crate) fn is_deleted(&self) -> bool {
    self.deleted
  }

  #[inline]
  pub(crate) fn path(&self) -> &str {
    &self.path
  }

  #[inline]
  pub(crate) fn content_type(&self) -> &str {
    &self.content_type
  }

  #[inline]
  pub(crate) fn relationship_type(&self) -> Option<&str> {
    self
      .relationship_type
      .as_ref()
      .map(super::XmlRelationshipNamespaceUri::as_str)
  }

  #[inline]
  pub(crate) fn relationship_type_bytes(&self) -> Option<&[u8]> {
    self
      .relationship_type
      .as_ref()
      .map(super::XmlRelationshipNamespaceUri::uri_bytes)
  }

  #[inline]
  pub(crate) fn relationships(&self) -> &RelationshipSet {
    &self.relationships
  }

  #[inline]
  pub(crate) fn relationships_mut(&mut self) -> &mut RelationshipSet {
    &mut self.relationships
  }

  #[inline]
  pub(crate) fn data(&self) -> &StoredPartData {
    &self.data
  }

  #[inline]
  pub(crate) fn data_mut(&mut self) -> &mut StoredPartData {
    &mut self.data
  }
}

#[doc(hidden)]
#[derive(Debug)]
pub struct SdkPackageStorage {
  id: PackageId,
  content_types: Types,
  package_relationships: RelationshipSet,
  parts: Vec<StoredPart>,
  by_path: HashMap<Box<str>, PartId>,
  preferred_main_part_content_type: Option<Box<str>>,
  open_mode: PackageOpenMode,
}

impl Clone for SdkPackageStorage {
  fn clone(&self) -> Self {
    Self {
      id: PackageId::new(),
      content_types: self.content_types.clone(),
      package_relationships: self.package_relationships.clone(),
      parts: self.parts.clone(),
      by_path: self.by_path.clone(),
      preferred_main_part_content_type: self.preferred_main_part_content_type.clone(),
      open_mode: self.open_mode,
    }
  }
}

impl SdkPackageStorage {
  pub(crate) fn create(
    open_mode: PackageOpenMode,
    preferred_main_part_content_type: Option<&str>,
  ) -> Self {
    Self {
      id: PackageId::new(),
      content_types: empty_content_types(),
      package_relationships: RelationshipSet::default(),
      parts: Vec::new(),
      by_path: HashMap::new(),
      preferred_main_part_content_type: preferred_main_part_content_type.map(Into::into),
      open_mode,
    }
  }

  pub(crate) fn open<R: Read + Seek>(
    reader: R,
    open_mode: PackageOpenMode,
  ) -> Result<Self, SdkError> {
    let mut archive = zip::ZipArchive::new(reader)?;
    let content_types = read_content_types(&mut archive)?;
    let mut raw_parts = read_raw_parts(&mut archive, &content_types)?;
    let mut by_path = HashMap::with_capacity(raw_parts.len());

    for (index, raw_part) in raw_parts.iter().enumerate() {
      by_path.insert(raw_part.path.clone(), PartId::from_index(index));
    }

    let package_relationships = read_relationships(&mut archive, "_rels/.rels")?;
    let package_relationships =
      RelationshipSet::from_relationships(package_relationships, "", &by_path);

    let mut part_relationships = Vec::with_capacity(raw_parts.len());
    for raw_part in &raw_parts {
      let rels_path = part_relationships_path(&raw_part.path);
      part_relationships.push(read_part_relationships(
        &mut archive,
        &rels_path,
        &raw_part.path,
        &by_path,
      )?);
    }

    let relationship_types =
      relationship_types_by_part(&package_relationships, &part_relationships)?;

    let mut parts = Vec::with_capacity(raw_parts.len());
    for (index, (raw_part, relationships)) in
      raw_parts.drain(..).zip(part_relationships).enumerate()
    {
      parts.push(StoredPart {
        path: raw_part.path,
        content_type: raw_part.content_type,
        relationship_type: relationship_types.get(&PartId::from_index(index)).cloned(),
        relationships,
        data: StoredPartData::Raw {
          bytes: raw_part.bytes,
        },
        deleted: false,
      });
    }

    Ok(Self {
      id: PackageId::new(),
      content_types,
      package_relationships,
      parts,
      by_path,
      preferred_main_part_content_type: None,
      open_mode,
    })
  }

  #[cfg(feature = "flat-opc")]
  pub(crate) fn open_flat_opc<R: std::io::BufRead>(
    reader: R,
    open_mode: PackageOpenMode,
  ) -> Result<Self, SdkError> {
    let mut flat_parts = read_flat_opc_parts(reader)?;
    flat_parts.sort_by(|left, right| left.path.cmp(&right.path));

    let mut raw_parts = Vec::new();
    let mut relationship_parts = HashMap::new();
    for flat_part in flat_parts {
      if is_relationships_part_path(&flat_part.path) {
        relationship_parts.insert(flat_part.path, flat_part.bytes);
      } else {
        raw_parts.push(RawPart {
          path: flat_part.path.into_boxed_str(),
          content_type: flat_part.content_type.into_boxed_str(),
          bytes: flat_part.bytes,
        });
      }
    }

    let mut by_path = HashMap::with_capacity(raw_parts.len());
    for (index, raw_part) in raw_parts.iter().enumerate() {
      by_path.insert(raw_part.path.clone(), PartId::from_index(index));
    }

    let content_types = content_types_from_raw_parts(&raw_parts);
    let package_relationships = relationships_from_flat_opc_part(
      relationship_parts.remove("_rels/.rels").as_deref(),
      "",
      &by_path,
    )?;

    let mut part_relationships = Vec::with_capacity(raw_parts.len());
    for raw_part in &raw_parts {
      let rels_path = part_relationships_path(&raw_part.path);
      part_relationships.push(relationships_from_flat_opc_part(
        relationship_parts.remove(&rels_path).as_deref(),
        &raw_part.path,
        &by_path,
      )?);
    }

    let relationship_types =
      relationship_types_by_part(&package_relationships, &part_relationships)?;

    let mut parts = Vec::with_capacity(raw_parts.len());
    for (index, (raw_part, relationships)) in
      raw_parts.into_iter().zip(part_relationships).enumerate()
    {
      parts.push(StoredPart {
        path: raw_part.path,
        content_type: raw_part.content_type,
        relationship_type: relationship_types.get(&PartId::from_index(index)).cloned(),
        relationships,
        data: StoredPartData::Raw {
          bytes: raw_part.bytes,
        },
        deleted: false,
      });
    }

    Ok(Self {
      id: PackageId::new(),
      content_types,
      package_relationships,
      parts,
      by_path,
      preferred_main_part_content_type: None,
      open_mode,
    })
  }

  #[cfg(feature = "flat-opc")]
  pub(crate) fn write_flat_opc<W, F>(
    &self,
    writer: &mut W,
    mut part_data: F,
  ) -> Result<(), SdkError>
  where
    W: std::io::Write,
    F: FnMut(PartId, &StoredPart) -> Result<Vec<u8>, SdkError>,
  {
    writer.write_all(br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>"#)?;
    writer.write_all(b"\n<pkg:package")?;
    super::xml::write_xmlns_attr(writer, Some(b"pkg"), FLAT_OPC_PACKAGE_NS.as_bytes())?;
    writer.write_all(b">\n")?;

    if !self.package_relationships.is_empty() {
      let bytes = self.package_relationships.to_bytes_cow()?;
      write_flat_opc_xml_part(
        writer,
        "/_rels/.rels",
        RELATIONSHIP_CONTENT_TYPE,
        bytes.as_ref(),
      )?;
    }

    let alt_chunk_part_ids = self.alt_chunk_part_ids();
    for (index, part) in self.parts.iter().enumerate() {
      if part.is_deleted() {
        continue;
      }

      let part_id = PartId::from_index(index);
      let data = part_data(part_id, part)?;
      let part_name = format!("/{}", part.path());
      if flat_opc_part_is_xml(part, part_id, &alt_chunk_part_ids) {
        write_flat_opc_xml_part(writer, &part_name, part.content_type(), &data)?;
      } else {
        write_flat_opc_binary_part(writer, &part_name, part.content_type(), &data)?;
      }

      if !part.relationships().is_empty() {
        let bytes = part.relationships().to_bytes_cow()?;
        let rels_name = format!("/{}", part_relationships_path(part.path()));
        write_flat_opc_xml_part(
          writer,
          &rels_name,
          RELATIONSHIP_CONTENT_TYPE,
          bytes.as_ref(),
        )?;
      }
    }

    writer.write_all(b"</pkg:package>")?;
    Ok(())
  }

  #[inline]
  pub(crate) fn id(&self) -> PackageId {
    self.id
  }

  #[inline]
  pub(crate) fn content_types(&self) -> &Types {
    &self.content_types
  }

  #[inline]
  pub(crate) fn package_relationships(&self) -> &RelationshipSet {
    &self.package_relationships
  }

  #[inline]
  pub(crate) fn package_relationships_mut(&mut self) -> &mut RelationshipSet {
    &mut self.package_relationships
  }

  #[inline]
  pub(crate) fn parts(&self) -> &[StoredPart] {
    &self.parts
  }

  #[inline]
  pub(crate) fn part(&self, part_id: PartId) -> Option<&StoredPart> {
    self
      .parts
      .get(part_id.index())
      .filter(|part| !part.is_deleted())
  }

  #[inline]
  pub(crate) fn part_mut(&mut self, part_id: PartId) -> Option<&mut StoredPart> {
    self
      .parts
      .get_mut(part_id.index())
      .filter(|part| !part.is_deleted())
  }

  #[inline]
  pub(crate) fn preferred_main_part_content_type(&self) -> Option<&str> {
    self.preferred_main_part_content_type.as_deref()
  }

  #[inline]
  pub(crate) fn set_preferred_main_part_content_type(&mut self, content_type: impl Into<Box<str>>) {
    self.preferred_main_part_content_type = Some(content_type.into());
  }

  #[inline]
  pub(crate) fn media_data_parts(&self) -> impl Iterator<Item = (PartId, &StoredPart)> {
    self
      .parts
      .iter()
      .enumerate()
      .filter(|(_, part)| !part.is_deleted() && is_media_data_part(part))
      .map(|(index, part)| (PartId::from_index(index), part))
  }

  #[inline]
  pub(crate) fn relationships(&self, part_id: PartId) -> Option<&RelationshipSet> {
    self.part(part_id).map(StoredPart::relationships)
  }

  #[inline]
  pub(crate) fn relationships_mut(&mut self, part_id: PartId) -> Option<&mut RelationshipSet> {
    self.part_mut(part_id).map(StoredPart::relationships_mut)
  }

  #[inline]
  pub(crate) fn target_part_id(
    &self,
    source_part_id: PartId,
    relationship_id: &str,
  ) -> Option<PartId> {
    self
      .relationships(source_part_id)?
      .get(relationship_id)?
      .target_part_id()
  }

  pub(crate) fn delete_package_part(&mut self, relationship_id: &str) -> Result<bool, SdkError> {
    let Some(target_part_id) = self
      .package_relationships
      .get(relationship_id)
      .and_then(RelationshipInfo::target_part_id)
    else {
      return Ok(false);
    };

    self.package_relationships.remove(relationship_id);
    if !self.is_part_reachable(target_part_id) {
      self.delete_unreachable_part_tree(target_part_id);
    }
    Ok(true)
  }

  pub(crate) fn delete_child_part(
    &mut self,
    source_part_id: PartId,
    relationship_id: &str,
  ) -> Result<bool, SdkError> {
    let Some(target_part_id) = self.target_part_id(source_part_id, relationship_id) else {
      return Ok(false);
    };

    let relationships = self.relationships_mut(source_part_id).ok_or_else(|| {
      SdkError::CommonError(format!(
        "part id {source_part_id:?} is not present in package storage"
      ))
    })?;
    relationships.remove(relationship_id);
    if !self.is_part_reachable(target_part_id) {
      self.delete_unreachable_part_tree(target_part_id);
    }
    Ok(true)
  }

  pub(crate) fn add_package_relationship_to_part(
    &mut self,
    relationship_id: impl Into<String>,
    target_part_id: PartId,
  ) -> Result<String, SdkError> {
    let relationship_id = relationship_id.into();
    if let Some(existing_relationship_id) =
      self.existing_relationship_id_for_target(None, target_part_id)
    {
      if existing_relationship_id == relationship_id {
        return Ok(existing_relationship_id);
      }
      return Err(SdkError::CommonError(format!(
        "part id {target_part_id:?} is already referenced by relationship id {existing_relationship_id}"
      )));
    }

    let (relationship_type, target) = {
      let target_part = self.part(target_part_id).ok_or_else(|| {
        SdkError::CommonError(format!(
          "part id {target_part_id:?} is not present in package storage"
        ))
      })?;
      let relationship_type = target_part.relationship_type().ok_or_else(|| {
        SdkError::CommonError(format!(
          "part id {target_part_id:?} does not have a relationship type"
        ))
      })?;
      (
        relationship_type.to_string(),
        target_part.path().to_string(),
      )
    };

    self.package_relationships.add_internal_part_relationship(
      relationship_id.clone(),
      relationship_type,
      target,
      target_part_id,
    )?;
    Ok(relationship_id)
  }

  pub(crate) fn add_child_relationship_to_part(
    &mut self,
    source_part_id: PartId,
    relationship_id: impl Into<String>,
    target_part_id: PartId,
  ) -> Result<String, SdkError> {
    let relationship_id = relationship_id.into();
    if let Some(existing_relationship_id) =
      self.existing_relationship_id_for_target(Some(source_part_id), target_part_id)
    {
      if existing_relationship_id == relationship_id {
        return Ok(existing_relationship_id);
      }
      return Err(SdkError::CommonError(format!(
        "part id {target_part_id:?} is already referenced by relationship id {existing_relationship_id}"
      )));
    }

    let (relationship_type, relationship_target) = {
      let source_part_path = self
        .part(source_part_id)
        .ok_or_else(|| {
          SdkError::CommonError(format!(
            "part id {source_part_id:?} is not present in package storage"
          ))
        })?
        .path()
        .to_string();
      let target_part = self.part(target_part_id).ok_or_else(|| {
        SdkError::CommonError(format!(
          "part id {target_part_id:?} is not present in package storage"
        ))
      })?;
      let relationship_type = target_part.relationship_type().ok_or_else(|| {
        SdkError::CommonError(format!(
          "part id {target_part_id:?} does not have a relationship type"
        ))
      })?;
      (
        relationship_type.to_string(),
        relationship_target_from_source(&source_part_path, target_part.path()),
      )
    };

    self
      .relationships_mut(source_part_id)
      .expect("source part was already resolved")
      .add_internal_part_relationship(
        relationship_id.clone(),
        relationship_type,
        relationship_target,
        target_part_id,
      )?;
    Ok(relationship_id)
  }

  pub(crate) fn create_media_data_part(
    &mut self,
    content_type: impl Into<String>,
    extension: impl AsRef<str>,
  ) -> Result<PartId, SdkError> {
    let content_type = content_type.into();
    if content_type.is_empty() {
      return Err(SdkError::CommonError(
        "cannot add a media data part with an empty content type".to_string(),
      ));
    }
    let extension = normalized_part_extension(extension.as_ref());
    let path = self.next_data_part_path("media/mediadata", &extension);
    let part_id = self.push_part(path, &content_type, None);
    Ok(part_id)
  }

  pub(crate) fn add_data_part_reference_relationship(
    &mut self,
    source_part_id: PartId,
    relationship_id: impl Into<String>,
    relationship_type: &str,
    target_part_id: PartId,
  ) -> Result<String, SdkError> {
    let relationship_id = relationship_id.into();
    let relationship_target = {
      let source_part_path = self
        .part(source_part_id)
        .ok_or_else(|| {
          SdkError::CommonError(format!(
            "part id {source_part_id:?} is not present in package storage"
          ))
        })?
        .path()
        .to_string();
      let target_part = self.part(target_part_id).ok_or_else(|| {
        SdkError::CommonError(format!(
          "part id {target_part_id:?} is not present in package storage"
        ))
      })?;
      relationship_target_from_source(&source_part_path, target_part.path())
    };

    self
      .relationships_mut(source_part_id)
      .expect("source part was already resolved")
      .add_internal_part_relationship(
        relationship_id.clone(),
        relationship_type,
        relationship_target,
        target_part_id,
      )?;
    Ok(relationship_id)
  }

  pub(crate) fn import_part_tree_from(
    &mut self,
    source: &Self,
    source_part_id: PartId,
    parent_part_id: Option<PartId>,
    relationship_id: impl Into<String>,
    mut part_data: impl FnMut(PartId, &StoredPart) -> Result<Vec<u8>, SdkError>,
  ) -> Result<(PartId, usize), SdkError> {
    let relationship_id = relationship_id.into();
    let source_part = source.part(source_part_id).ok_or_else(|| {
      SdkError::CommonError(format!(
        "source part id {source_part_id:?} is not present in package storage"
      ))
    })?;
    let relationship_type = source_part.relationship_type().ok_or_else(|| {
      SdkError::CommonError(format!(
        "source part id {source_part_id:?} does not have a relationship type"
      ))
    })?;

    match parent_part_id {
      Some(parent_part_id) => {
        let relationships = self.relationships(parent_part_id).ok_or_else(|| {
          SdkError::CommonError(format!(
            "part id {parent_part_id:?} is not present in package storage"
          ))
        })?;
        if relationships.contains_id(&relationship_id) {
          return Err(SdkError::CommonError(format!(
            "relationship id {relationship_id} already exists"
          )));
        }
      }
      None => {
        if self.package_relationships.contains_id(&relationship_id) {
          return Err(SdkError::CommonError(format!(
            "relationship id {relationship_id} already exists"
          )));
        }
      }
    }

    let mut part_map = HashMap::new();
    let mut added_count = 0;
    let imported_part_id = self.import_part_tree_recursive(
      source,
      source_part_id,
      &mut part_map,
      &mut added_count,
      &mut part_data,
    )?;
    self.add_imported_part_relationship(
      parent_part_id,
      relationship_id,
      relationship_type,
      imported_part_id,
    )?;
    Ok((imported_part_id, added_count))
  }

  #[inline]
  pub(crate) fn data_part_reference_relationships_to(
    &self,
    target_part_id: PartId,
  ) -> impl Iterator<Item = &RelationshipInfo> {
    std::iter::once(self.package_relationships())
      .chain(
        self
          .parts
          .iter()
          .filter(|part| !part.is_deleted())
          .map(StoredPart::relationships),
      )
      .flat_map(RelationshipSet::data_part_reference_relationships)
      .filter(move |relationship| relationship.target_part_id() == Some(target_part_id))
  }

  pub(crate) fn delete_unused_media_data_parts(&mut self) -> usize {
    let unused_part_ids: Vec<_> = self
      .media_data_parts()
      .filter_map(|(part_id, _)| {
        self
          .data_part_reference_relationships_to(part_id)
          .next()
          .is_none()
          .then_some(part_id)
      })
      .collect();

    let deleted_count = unused_part_ids.len();
    for part_id in unused_part_ids {
      let Some(part) = self.part(part_id) else {
        continue;
      };
      let path = part.path().to_string();
      if let Some(part) = self.parts.get_mut(part_id.index()) {
        part.deleted = true;
      }
      self.by_path.remove(path.as_str());
      self.remove_content_type_override(&path);
    }
    deleted_count
  }

  fn import_part_tree_recursive(
    &mut self,
    source: &Self,
    source_part_id: PartId,
    part_map: &mut HashMap<PartId, PartId>,
    added_count: &mut usize,
    part_data: &mut impl FnMut(PartId, &StoredPart) -> Result<Vec<u8>, SdkError>,
  ) -> Result<PartId, SdkError> {
    if let Some(imported_part_id) = part_map.get(&source_part_id).copied() {
      return Ok(imported_part_id);
    }

    let source_part = source.part(source_part_id).ok_or_else(|| {
      SdkError::CommonError(format!(
        "source part id {source_part_id:?} is not present in package storage"
      ))
    })?;
    let imported_path = self.unique_import_part_path(source_part.path());
    let imported_part_id = self.push_part(
      imported_path,
      source_part.content_type(),
      source_part.relationship_type(),
    );
    self.set_part_data(imported_part_id, part_data(source_part_id, source_part)?)?;
    *added_count += 1;
    part_map.insert(source_part_id, imported_part_id);

    for relationship in source_part.relationships().iter() {
      if let Some(target_part_id) = relationship.target_part_id() {
        let imported_target_part_id = self.import_part_tree_recursive(
          source,
          target_part_id,
          part_map,
          added_count,
          part_data,
        )?;
        if relationship.is_reference_relationship() {
          self.add_data_part_reference_relationship(
            imported_part_id,
            relationship.id(),
            relationship.relationship_type(),
            imported_target_part_id,
          )?;
        } else {
          self.add_imported_part_relationship(
            Some(imported_part_id),
            relationship.id().to_string(),
            relationship.relationship_type(),
            imported_target_part_id,
          )?;
        }
      } else if relationship.is_reference_relationship()
        || relationship.target_kind() == RelationshipTargetKind::External
      {
        self
          .relationships_mut(imported_part_id)
          .expect("imported part was just created")
          .add_relationship_info(relationship.clone())?;
      }
    }

    Ok(imported_part_id)
  }

  fn add_imported_part_relationship(
    &mut self,
    parent_part_id: Option<PartId>,
    relationship_id: String,
    relationship_type: &str,
    target_part_id: PartId,
  ) -> Result<(), SdkError> {
    let target_part_path = self
      .part(target_part_id)
      .ok_or_else(|| {
        SdkError::CommonError(format!(
          "part id {target_part_id:?} is not present in package storage"
        ))
      })?
      .path()
      .to_string();
    let target = if let Some(parent_part_id) = parent_part_id {
      let parent_part_path = self
        .part(parent_part_id)
        .ok_or_else(|| {
          SdkError::CommonError(format!(
            "part id {parent_part_id:?} is not present in package storage"
          ))
        })?
        .path()
        .to_string();
      relationship_target_from_source(&parent_part_path, &target_part_path)
    } else {
      target_part_path
    };
    let relationships = match parent_part_id {
      Some(parent_part_id) => self.relationships_mut(parent_part_id).ok_or_else(|| {
        SdkError::CommonError(format!(
          "part id {parent_part_id:?} is not present in package storage"
        ))
      })?,
      None => self.package_relationships_mut(),
    };
    relationships.add_internal_part_relationship(
      relationship_id,
      relationship_type,
      target,
      target_part_id,
    )?;
    Ok(())
  }

  pub(crate) fn add_child_part(
    &mut self,
    source_part_id: PartId,
    relationship_id: impl Into<String>,
    descriptor: NewPartDescriptor,
  ) -> Result<PartId, SdkError> {
    if descriptor.relationship_type.is_empty() {
      return Err(SdkError::CommonError(
        "cannot add a part with an empty relationship type".to_string(),
      ));
    }
    if descriptor.content_type.is_empty() {
      return Err(SdkError::CommonError(
        "cannot add a part with an empty content type".to_string(),
      ));
    }
    if descriptor.target_name.is_empty() {
      return Err(SdkError::CommonError(
        "cannot add a part with an empty target name".to_string(),
      ));
    }

    let relationship_id = relationship_id.into();
    let source_part_path = self
      .part(source_part_id)
      .ok_or_else(|| {
        SdkError::CommonError(format!(
          "part id {source_part_id:?} is not present in package storage"
        ))
      })?
      .path()
      .to_string();
    if self
      .relationships(source_part_id)
      .is_some_and(|relationships| relationships.contains_id(&relationship_id))
    {
      return Err(SdkError::CommonError(format!(
        "relationship id {relationship_id} already exists"
      )));
    }

    let child_path = self.next_child_part_path(
      &source_part_path,
      descriptor.path_prefix,
      descriptor.target_name,
      descriptor.extension.as_ref(),
    );
    let relationship_target = relationship_target_from_source(&source_part_path, &child_path);
    let part_id = self.push_part(
      child_path,
      &descriptor.content_type,
      Some(descriptor.relationship_type.as_ref()),
    );
    self
      .relationships_mut(source_part_id)
      .expect("source part was already resolved")
      .add_internal_part_relationship(
        relationship_id,
        descriptor.relationship_type.as_ref(),
        relationship_target,
        part_id,
      )?;
    Ok(part_id)
  }

  pub(crate) fn add_package_part(
    &mut self,
    relationship_id: impl Into<String>,
    descriptor: NewPartDescriptor,
    target_mode: NewPartTargetMode,
  ) -> Result<PartId, SdkError> {
    if descriptor.relationship_type.is_empty() {
      return Err(SdkError::CommonError(
        "cannot add a part with an empty relationship type".to_string(),
      ));
    }
    if descriptor.content_type.is_empty() {
      return Err(SdkError::CommonError(
        "cannot add a part with an empty content type".to_string(),
      ));
    }
    if descriptor.target_name.is_empty() {
      return Err(SdkError::CommonError(
        "cannot add a part with an empty target name".to_string(),
      ));
    }

    let relationship_id = relationship_id.into();
    if self.package_relationships.contains_id(&relationship_id) {
      return Err(SdkError::CommonError(format!(
        "relationship id {relationship_id} already exists"
      )));
    }

    let part_path = self.package_part_path(&descriptor, target_mode)?;
    let part_id = self.push_part(
      part_path.clone(),
      &descriptor.content_type,
      Some(descriptor.relationship_type.as_ref()),
    );
    self.package_relationships.add_internal_part_relationship(
      relationship_id,
      descriptor.relationship_type.as_ref(),
      part_path,
      part_id,
    )?;
    Ok(part_id)
  }

  pub(crate) fn add_child_part_with_path(
    &mut self,
    source_part_id: PartId,
    relationship_id: impl Into<String>,
    descriptor: NewPartDescriptor,
    part_path: impl AsRef<str>,
  ) -> Result<PartId, SdkError> {
    if descriptor.relationship_type.is_empty() {
      return Err(SdkError::CommonError(
        "cannot add a part with an empty relationship type".to_string(),
      ));
    }
    if descriptor.content_type.is_empty() {
      return Err(SdkError::CommonError(
        "cannot add a part with an empty content type".to_string(),
      ));
    }

    let relationship_id = relationship_id.into();
    let source_part_path = self
      .part(source_part_id)
      .ok_or_else(|| {
        SdkError::CommonError(format!(
          "part id {source_part_id:?} is not present in package storage"
        ))
      })?
      .path()
      .to_string();
    if self
      .relationships(source_part_id)
      .is_some_and(|relationships| relationships.contains_id(&relationship_id))
    {
      return Err(SdkError::CommonError(format!(
        "relationship id {relationship_id} already exists"
      )));
    }

    let child_path =
      self.unique_new_part_path(part_path.as_ref(), descriptor.content_type.as_ref())?;

    let relationship_target = relationship_target_from_source(&source_part_path, &child_path);
    let part_id = self.push_part(
      child_path,
      &descriptor.content_type,
      Some(descriptor.relationship_type.as_ref()),
    );
    self
      .relationships_mut(source_part_id)
      .expect("source part was already resolved")
      .add_internal_part_relationship(
        relationship_id,
        descriptor.relationship_type.as_ref(),
        relationship_target,
        part_id,
      )?;
    Ok(part_id)
  }

  fn push_part(
    &mut self,
    path: String,
    content_type: &str,
    relationship_type: Option<&str>,
  ) -> PartId {
    let part_id = PartId::from_index(self.parts.len());
    self.parts.push(StoredPart {
      path: path.clone().into_boxed_str(),
      content_type: content_type.into(),
      relationship_type: relationship_type.map(super::XmlRelationshipNamespaceUri::from_uri),
      relationships: RelationshipSet::default(),
      data: StoredPartData::Raw { bytes: Vec::new() },
      deleted: false,
    });
    self.by_path.insert(path.clone().into_boxed_str(), part_id);
    self.add_content_type_override(&path, content_type);
    part_id
  }

  pub(crate) fn set_part_data(
    &mut self,
    part_id: PartId,
    data: impl Into<Vec<u8>>,
  ) -> Result<(), SdkError> {
    let part = self.part_mut(part_id).ok_or_else(|| {
      SdkError::CommonError(format!(
        "part id {part_id:?} is not present in package storage"
      ))
    })?;
    *part.data.bytes_mut() = data.into();
    Ok(())
  }

  pub(crate) fn feed_part_data<R: Read>(
    &mut self,
    part_id: PartId,
    reader: &mut R,
  ) -> Result<(), SdkError> {
    let part = self.part_mut(part_id).ok_or_else(|| {
      SdkError::CommonError(format!(
        "part id {part_id:?} is not present in package storage"
      ))
    })?;
    let bytes = part.data.bytes_mut();
    bytes.clear();
    reader.read_to_end(bytes)?;
    Ok(())
  }

  fn add_content_type_override(&mut self, path: &str, content_type: &str) {
    let part_name = format!("/{path}");
    if let Some(existing) = self
      .content_types
      .types_choice
      .iter_mut()
      .find_map(|child| match child {
        TypesChoice::Override(override_type) if override_type.part_name == part_name => {
          Some(override_type)
        }
        _ => None,
      })
    {
      existing.content_type = content_type.to_string();
      return;
    }

    self
      .content_types
      .types_choice
      .push(TypesChoice::Override(Box::new(
        crate::schemas::opc_content_types::Override {
          content_type: content_type.to_string(),
          part_name,
        },
      )));
  }

  fn remove_content_type_override(&mut self, path: &str) {
    let part_name = format!("/{path}");
    self
      .content_types
      .types_choice
      .retain(|child| !matches!(child, TypesChoice::Override(override_type) if override_type.part_name == part_name));
  }

  pub(crate) fn set_part_content_type(
    &mut self,
    part_id: PartId,
    content_type: impl Into<Box<str>>,
  ) -> Result<(), SdkError> {
    let content_type = content_type.into();
    if content_type.is_empty() {
      return Err(SdkError::CommonError(
        "cannot set a part to an empty content type".to_string(),
      ));
    }
    let path = {
      let part = self.part_mut(part_id).ok_or_else(|| {
        SdkError::CommonError(format!(
          "part id {part_id:?} is not present in package storage"
        ))
      })?;
      part.content_type = content_type.clone();
      part.path().to_string()
    };
    self.add_content_type_override(&path, &content_type);
    Ok(())
  }

  fn existing_relationship_id_for_target(
    &self,
    source_part_id: Option<PartId>,
    target_part_id: PartId,
  ) -> Option<String> {
    let relationships = match source_part_id {
      Some(source_part_id) => self.relationships(source_part_id)?,
      None => self.package_relationships(),
    };
    relationships.iter().find_map(|relationship| {
      (relationship.target_part_id() == Some(target_part_id)).then(|| relationship.id().to_string())
    })
  }

  fn is_part_reachable(&self, target_part_id: PartId) -> bool {
    let mut visited = HashSet::new();
    let mut stack: Vec<_> = self
      .package_relationships
      .part_relationships()
      .filter_map(RelationshipInfo::target_part_id)
      .collect();

    while let Some(part_id) = stack.pop() {
      if !visited.insert(part_id) {
        continue;
      }
      let Some(part) = self.part(part_id) else {
        continue;
      };
      if part_id == target_part_id {
        return true;
      }
      stack.extend(
        part
          .relationships()
          .part_relationships()
          .filter_map(RelationshipInfo::target_part_id),
      );
    }

    false
  }

  fn delete_unreachable_part_tree(&mut self, part_id: PartId) {
    let Some(part) = self.part(part_id) else {
      return;
    };
    let child_part_ids: Vec<_> = part
      .relationships()
      .part_relationships()
      .filter_map(RelationshipInfo::target_part_id)
      .collect();
    let path = part.path().to_string();

    if let Some(part) = self.parts.get_mut(part_id.index()) {
      part.deleted = true;
    }
    self.by_path.remove(path.as_str());
    self.remove_content_type_override(&path);

    for child_part_id in child_part_ids {
      if !self.is_part_reachable(child_part_id) {
        self.delete_unreachable_part_tree(child_part_id);
      }
    }
  }

  fn unique_import_part_path(&self, source_path: &str) -> String {
    if !self.by_path.contains_key(source_path) {
      return source_path.to_string();
    }

    let (base, extension) = match source_path.rsplit_once('.') {
      Some((base, extension)) => (base, format!(".{extension}")),
      None => (source_path, String::new()),
    };
    for index in 1.. {
      let candidate = format!("{base}_copy{index}{extension}");
      if !self.by_path.contains_key(candidate.as_str()) {
        return candidate;
      }
    }
    unreachable!("unbounded import path search should always find a candidate")
  }

  fn next_child_part_path(
    &self,
    source_part_path: &str,
    path_prefix: &str,
    target_name: &str,
    extension: &str,
  ) -> String {
    let directory_path = child_part_directory_path(source_part_path, path_prefix);
    let extension = if extension.is_empty() {
      ".xml"
    } else {
      extension
    };
    let extension = if extension.starts_with('.') {
      extension.to_string()
    } else {
      format!(".{extension}")
    };

    for index in 1.. {
      let path = if directory_path.is_empty() {
        format!("{target_name}{index}{extension}")
      } else {
        format!("{directory_path}{target_name}{index}{extension}")
      };
      if !self.by_path.contains_key(path.as_str()) {
        return path;
      }
    }

    unreachable!("usize iteration should always find a free part path")
  }

  fn package_part_path(
    &self,
    descriptor: &NewPartDescriptor,
    target_mode: NewPartTargetMode,
  ) -> Result<String, SdkError> {
    let directory_path = package_part_directory_path(descriptor.path_prefix);
    let extension = normalized_part_extension(descriptor.extension.as_ref());

    if matches!(target_mode, NewPartTargetMode::Fixed) {
      let path = if directory_path.is_empty() {
        format!("{}{}", descriptor.target_name, extension)
      } else {
        format!("{directory_path}{}{}", descriptor.target_name, extension)
      };
      if self.by_path.contains_key(path.as_str()) {
        return Err(SdkError::CommonError(format!(
          "part path {path} already exists"
        )));
      }
      return Ok(path);
    }

    for index in 1.. {
      let path = if directory_path.is_empty() {
        format!("{}{index}{}", descriptor.target_name, extension)
      } else {
        format!(
          "{directory_path}{}{index}{}",
          descriptor.target_name, extension
        )
      };
      if !self.by_path.contains_key(path.as_str()) {
        return Ok(path);
      }
    }

    unreachable!("usize iteration should always find a free part path")
  }

  fn next_data_part_path(&self, stem: &str, extension: &str) -> String {
    for index in 1.. {
      let path = format!("{stem}{index}{extension}");
      if !self.by_path.contains_key(path.as_str()) {
        return path;
      }
    }

    unreachable!("usize iteration should always find a free data part path")
  }

  fn unique_new_part_path(&self, path: &str, content_type: &str) -> Result<String, SdkError> {
    let normalized = normalized_new_part_path(path)?;
    let (stem, extension) = part_path_stem_and_extension(&normalized);

    let mut sequence = if is_numbered_part_content_type(content_type) {
      1
    } else {
      0
    };

    loop {
      let candidate = if sequence == 0 {
        normalized.clone()
      } else {
        format!("{stem}{sequence}{extension}")
      };
      if !self.by_path.contains_key(candidate.as_str()) {
        return Ok(candidate);
      }
      sequence = if sequence == 0 { 2 } else { sequence + 1 };
    }
  }

  #[cfg(feature = "flat-opc")]
  fn alt_chunk_part_ids(&self) -> HashSet<PartId> {
    self
      .parts
      .iter()
      .filter(|part| !part.is_deleted())
      .flat_map(|part| part.relationships().iter())
      .chain(self.package_relationships().iter())
      .filter(|relationship| {
        super::relationship_type_matches_bytes(
          relationship.relationship_type_bytes(),
          super::REL_AF_CHUNK,
        )
      })
      .filter_map(RelationshipInfo::target_part_id)
      .collect()
  }
}

struct RawPart {
  path: Box<str>,
  content_type: Box<str>,
  bytes: Vec<u8>,
}

#[cfg(feature = "flat-opc")]
struct FlatOpcPart {
  path: String,
  content_type: String,
  bytes: Vec<u8>,
}

#[cfg(feature = "flat-opc")]
fn content_types_from_raw_parts(raw_parts: &[RawPart]) -> Types {
  Types {
    types_choice: raw_parts
      .iter()
      .map(|part| {
        TypesChoice::Override(Box::new(crate::schemas::opc_content_types::Override {
          content_type: part.content_type.to_string(),
          part_name: format!("/{}", part.path),
        }))
      })
      .collect(),
    ..empty_content_types()
  }
}

fn empty_content_types() -> Types {
  Types {
    xmlns: vec![super::XmlNamespace::raw(
      "",
      "http://schemas.openxmlformats.org/package/2006/content-types",
    )],
    xml_header: super::XmlHeaderType::Standalone,
    types_choice: Vec::new(),
  }
}

#[cfg(feature = "flat-opc")]
fn relationships_from_flat_opc_part(
  bytes: Option<&[u8]>,
  source_path: &str,
  by_path: &HashMap<Box<str>, PartId>,
) -> Result<RelationshipSet, SdkError> {
  let relationships = bytes.map(Relationships::from_bytes).transpose()?;
  Ok(RelationshipSet::from_relationships(
    relationships,
    source_path,
    by_path,
  ))
}

#[cfg(feature = "flat-opc")]
fn read_flat_opc_parts<R: std::io::BufRead>(reader: R) -> Result<Vec<FlatOpcPart>, SdkError> {
  let mut xml_reader = super::from_reader_inner(reader)?;
  let mut parts = Vec::new();

  loop {
    match xml_reader.next_tag_event()? {
      super::IoTagEvent::Start(start, empty_tag)
        if qname_matches(start.name().as_ref(), b"part") =>
      {
        if empty_tag {
          return Err(SdkError::CommonError(
            "Flat OPC part must contain xmlData or binaryData".to_string(),
          ));
        }
        parts.push(read_flat_opc_part(&mut xml_reader, start)?);
      }
      super::IoTagEvent::Eof => break,
      _ => {}
    }
  }

  Ok(parts)
}

#[cfg(feature = "flat-opc")]
fn read_flat_opc_part<R: std::io::BufRead>(
  xml_reader: &mut super::IoReader<R>,
  start: quick_xml::events::BytesStart<'static>,
) -> Result<FlatOpcPart, SdkError> {
  let decoder = xml_reader.decoder();
  let mut path = None;
  let mut content_type = None;

  for attr in start.attributes() {
    let attr = attr?;
    if qname_matches(attr.key.as_ref(), b"name") {
      let value = super::decode_attr_value(&attr, decoder)?;
      path = Some(normalize_flat_opc_part_name(&value));
    } else if qname_matches(attr.key.as_ref(), b"contentType") {
      content_type = Some(super::decode_attr_value(&attr, decoder)?);
    }
  }

  let path =
    path.ok_or_else(|| SdkError::CommonError("Flat OPC part missing pkg:name".to_string()))?;
  let content_type = content_type
    .ok_or_else(|| SdkError::CommonError("Flat OPC part missing pkg:contentType".to_string()))?;
  let mut bytes = None;

  loop {
    match xml_reader.next_tag_event()? {
      super::IoTagEvent::Start(start, empty_tag)
        if qname_matches(start.name().as_ref(), b"xmlData") =>
      {
        bytes = Some(read_flat_opc_xml_data(xml_reader, empty_tag)?);
      }
      super::IoTagEvent::Start(start, empty_tag)
        if qname_matches(start.name().as_ref(), b"binaryData") =>
      {
        bytes = Some(read_flat_opc_binary_data(xml_reader, empty_tag)?);
      }
      super::IoTagEvent::Start(_, true) => {}
      super::IoTagEvent::Start(_, false) => {
        return Err(SdkError::CommonError(
          "unsupported Flat OPC part child element".to_string(),
        ));
      }
      super::IoTagEvent::End(end) if qname_matches(end.name().as_ref(), b"part") => break,
      super::IoTagEvent::Eof => return Err(unexpected_eof("Flat OPC part")),
      _ => {}
    }
  }

  let bytes = bytes.ok_or_else(|| {
    SdkError::CommonError("Flat OPC part must contain xmlData or binaryData".to_string())
  })?;

  Ok(FlatOpcPart {
    path,
    content_type,
    bytes,
  })
}

#[cfg(feature = "flat-opc")]
fn read_flat_opc_xml_data<R: std::io::BufRead>(
  xml_reader: &mut super::IoReader<R>,
  empty_tag: bool,
) -> Result<Vec<u8>, SdkError> {
  if empty_tag {
    return Err(SdkError::CommonError(
      "Flat OPC xmlData must contain an XML root element".to_string(),
    ));
  }

  let mut bytes = None;
  loop {
    match xml_reader.next_tag_event()? {
      super::IoTagEvent::Start(start, empty_tag) => {
        bytes = Some(super::read_outer_xml_io(xml_reader, start, empty_tag)?.into_bytes());
      }
      super::IoTagEvent::End(end) if qname_matches(end.name().as_ref(), b"xmlData") => break,
      super::IoTagEvent::Eof => return Err(unexpected_eof("Flat OPC xmlData")),
      _ => {}
    }
  }

  bytes.ok_or_else(|| {
    SdkError::CommonError("Flat OPC xmlData must contain an XML root element".to_string())
  })
}

#[cfg(feature = "flat-opc")]
fn read_flat_opc_binary_data<R: std::io::BufRead>(
  xml_reader: &mut super::IoReader<R>,
  empty_tag: bool,
) -> Result<Vec<u8>, SdkError> {
  if empty_tag {
    return Ok(Vec::new());
  }

  let mut text = String::new();
  loop {
    match xml_reader.next()? {
      quick_xml::events::Event::Text(event) => {
        text.push_str(event.xml10_content()?.as_ref());
      }
      quick_xml::events::Event::CData(event) => {
        text.push_str(event.xml10_content()?.as_ref());
      }
      quick_xml::events::Event::End(end) if qname_matches(end.name().as_ref(), b"binaryData") => {
        break;
      }
      quick_xml::events::Event::Eof => return Err(unexpected_eof("Flat OPC binaryData")),
      _ => {}
    }
  }

  let compact: Vec<u8> = text
    .bytes()
    .filter(|byte| !byte.is_ascii_whitespace())
    .collect();
  base64::Engine::decode(&base64::engine::general_purpose::STANDARD, compact)
    .map_err(|err| SdkError::CommonError(format!("invalid Flat OPC binaryData: {err}")))
}

#[cfg(feature = "flat-opc")]
fn write_flat_opc_xml_part<W: std::io::Write>(
  writer: &mut W,
  name: &str,
  content_type: &str,
  bytes: &[u8],
) -> Result<(), SdkError> {
  writer.write_all(b"  <pkg:part")?;
  super::write_attr_value_str(writer, "pkg:name", name)?;
  super::write_attr_value_str(writer, "pkg:contentType", content_type)?;
  writer.write_all(b">\n    <pkg:xmlData>")?;
  writer.write_all(root_xml_bytes(bytes)?.as_slice())?;
  writer.write_all(b"</pkg:xmlData>\n  </pkg:part>\n")?;
  Ok(())
}

#[cfg(feature = "flat-opc")]
fn write_flat_opc_binary_part<W: std::io::Write>(
  writer: &mut W,
  name: &str,
  content_type: &str,
  bytes: &[u8],
) -> Result<(), SdkError> {
  writer.write_all(b"  <pkg:part")?;
  super::write_attr_value_str(writer, "pkg:name", name)?;
  super::write_attr_value_str(writer, "pkg:contentType", content_type)?;
  super::write_attr_value_str(writer, "pkg:compression", "store")?;
  writer.write_all(b">\n    <pkg:binaryData>")?;
  let encoded = base64::Engine::encode(&base64::engine::general_purpose::STANDARD, bytes);
  writer.write_all(encoded.as_bytes())?;
  writer.write_all(b"</pkg:binaryData>\n  </pkg:part>\n")?;
  Ok(())
}

#[cfg(feature = "flat-opc")]
fn root_xml_bytes(bytes: &[u8]) -> Result<Vec<u8>, SdkError> {
  let mut xml_reader = super::from_bytes_inner(bytes)?;
  loop {
    match xml_reader.next_tag_event()? {
      super::SliceTagEvent::Start(start, empty_tag) => {
        return Ok(super::read_outer_xml_borrowed(&mut xml_reader, start, empty_tag)?.into_bytes());
      }
      super::SliceTagEvent::Eof => return Err(unexpected_eof("Flat OPC XML part")),
      _ => {}
    }
  }
}

#[cfg(feature = "flat-opc")]
fn flat_opc_part_is_xml(
  part: &StoredPart,
  part_id: PartId,
  alt_chunk_part_ids: &HashSet<PartId>,
) -> bool {
  part.content_type().ends_with("xml") && !alt_chunk_part_ids.contains(&part_id)
}

#[cfg(feature = "flat-opc")]
fn qname_matches(qname: &[u8], local_name: &[u8]) -> bool {
  qname == local_name || qname.rsplit(|byte| *byte == b':').next() == Some(local_name)
}

#[cfg(feature = "flat-opc")]
fn normalize_flat_opc_part_name(name: &str) -> String {
  resolve_zip_file_path(name.trim_start_matches('/'))
}

fn child_part_directory_path(source_part_path: &str, path_prefix: &str) -> String {
  let source_parent_path = super::parent_zip_path(source_part_path);
  let mut path = if path_prefix.is_empty() || path_prefix == "." {
    source_parent_path
  } else if path_prefix == "../media"
    && matches!(source_parent_path.as_str(), "word/" | "ppt/" | "xl/")
  {
    format!("{source_parent_path}media")
  } else if path_prefix.starts_with('/') {
    path_prefix.to_string()
  } else {
    let mut path = String::with_capacity(source_parent_path.len() + path_prefix.len() + 1);
    path.push_str(&source_parent_path);
    path.push_str(path_prefix);
    path
  };

  path = resolve_zip_file_path(&path);
  if !path.is_empty() && !path.ends_with('/') {
    path.push('/');
  }
  path
}

fn package_part_directory_path(path_prefix: &str) -> String {
  if path_prefix.is_empty() || path_prefix == "." {
    return String::new();
  }

  let mut path = resolve_zip_file_path(path_prefix);
  if !path.is_empty() && !path.ends_with('/') {
    path.push('/');
  }
  path
}

fn normalized_part_extension(extension: &str) -> String {
  let extension = if extension.is_empty() {
    ".xml"
  } else {
    extension
  };

  if extension.starts_with('.') {
    extension.to_string()
  } else {
    format!(".{extension}")
  }
}

fn normalized_new_part_path(path: &str) -> Result<String, SdkError> {
  let path = path.trim_start_matches('/');
  if path.is_empty() {
    return Err(SdkError::CommonError("part path is empty".to_string()));
  }
  if path.split('/').any(|component| component == "..") {
    return Err(SdkError::CommonError(format!(
      "part path {path} is not allowed"
    )));
  }

  let normalized = resolve_zip_file_path(path);
  if normalized.is_empty()
    || normalized.ends_with('/')
    || normalized == "[Content_Types].xml"
    || is_relationships_part_path(&normalized)
  {
    return Err(SdkError::CommonError(format!(
      "part path {path} is not allowed"
    )));
  }

  Ok(normalized)
}

fn part_path_stem_and_extension(path: &str) -> (&str, &str) {
  let file_name_start = path.rfind('/').map(|index| index + 1).unwrap_or(0);
  let file_name = &path[file_name_start..];
  if let Some(extension_start) = file_name.rfind('.') {
    let extension_start = file_name_start + extension_start;
    (&path[..extension_start], &path[extension_start..])
  } else {
    (path, "")
  }
}

fn is_numbered_part_content_type(content_type: &str) -> bool {
  matches!(
    content_type,
    "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"
      | "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml"
      | "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml"
      | "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"
      | "application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml"
      | "application/vnd.openxmlformats-officedocument.drawing+xml"
      | "application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml"
      | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml"
      | "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml"
      | "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml"
      | "application/vnd.openxmlformats-officedocument.spreadsheetml.queryTable+xml"
      | "application/vnd.openxmlformats-officedocument.spreadsheetml.revisionLog+xml"
      | "application/vnd.openxmlformats-officedocument.spreadsheetml.tableSingleCells+xml"
      | "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"
      | "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"
      | "application/vnd.openxmlformats-officedocument.presentationml.comments+xml"
      | "application/vnd.openxmlformats-officedocument.presentationml.handoutMaster+xml"
      | "application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml"
      | "application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml"
      | "application/vnd.openxmlformats-officedocument.presentationml.slide+xml"
      | "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"
      | "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"
      | "application/vnd.openxmlformats-officedocument.presentationml.slideUpdateInfo+xml"
      | "application/vnd.openxmlformats-officedocument.presentationml.tags+xml"
      | "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"
      | "application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml"
      | "application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml"
      | "application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml"
      | "application/vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml"
      | "application/vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml"
      | "application/vnd.openxmlformats-officedocument.theme+xml"
      | "application/vnd.openxmlformats-officedocument.themeOverride+xml"
      | "application/vnd.openxmlformats-officedocument.customXmlProperties+xml"
      | "application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings"
      | "application/vnd.openxmlformats-officedocument.wordprocessingml.printerSettings"
      | "application/vnd.openxmlformats-officedocument.presentationml.printerSettings"
  )
}

fn relationship_target_from_source(source_part_path: &str, child_part_path: &str) -> String {
  let source_parent_path = super::parent_zip_path(source_part_path);
  if let Some(relative) = child_part_path.strip_prefix(&source_parent_path) {
    relative.to_string()
  } else {
    format!("/{child_part_path}")
  }
}

fn relationship_types_by_part(
  package_relationships: &RelationshipSet,
  part_relationships: &[RelationshipSet],
) -> Result<HashMap<PartId, super::XmlRelationshipNamespaceUri>, SdkError> {
  let mut relationship_types: HashMap<PartId, super::XmlRelationshipNamespaceUri> = HashMap::new();

  for relationship_set in std::iter::once(package_relationships).chain(part_relationships) {
    for relationship in relationship_set.iter() {
      let Some(part_id) = relationship.target_part_id() else {
        continue;
      };
      if let Some(existing) = relationship_types.get(&part_id) {
        if !relationship_types_are_compatible_bytes(
          existing.uri_bytes(),
          relationship.relationship_type_bytes(),
        ) {
          return Err(SdkError::CommonError(format!(
            "same part {:?} is referenced by different relationship types: {} and {}",
            part_id,
            existing.as_str(),
            relationship.relationship_type(),
          )));
        }
      } else {
        relationship_types.insert(
          part_id,
          super::XmlRelationshipNamespaceUri::from_uri(relationship.relationship_type()),
        );
      }
    }
  }

  Ok(relationship_types)
}

fn relationship_types_are_compatible_bytes(left: &[u8], right: &[u8]) -> bool {
  left == right || data_part_reference_relationship_types_are_compatible_bytes(left, right)
}

fn data_part_reference_relationship_types_are_compatible_bytes(left: &[u8], right: &[u8]) -> bool {
  super::is_data_part_reference_relationship_type_bytes(left)
    && super::is_data_part_reference_relationship_type_bytes(right)
}

fn is_media_data_part(part: &StoredPart) -> bool {
  part
    .relationship_type_bytes()
    .is_some_and(super::is_data_part_reference_relationship_type_bytes)
    || media_data_part_content_type(part.content_type())
    || part
      .path()
      .rsplit('/')
      .next()
      .is_some_and(|file_name| file_name.starts_with("media"))
}

fn media_data_part_content_type(content_type: &str) -> bool {
  content_type.starts_with("audio/") || content_type.starts_with("video/")
}

fn read_content_types<R: Read + Seek>(archive: &mut zip::ZipArchive<R>) -> Result<Types, SdkError> {
  let mut entry = archive.by_name("[Content_Types].xml")?;
  let mut bytes = Vec::with_capacity(entry.size() as usize);
  entry.read_to_end(&mut bytes)?;
  Types::from_bytes(&bytes)
}

fn read_raw_parts<R: Read + Seek>(
  archive: &mut zip::ZipArchive<R>,
  content_types: &Types,
) -> Result<Vec<RawPart>, SdkError> {
  let mut parts = Vec::new();

  for index in 0..archive.len() {
    let mut entry = archive.by_index(index)?;
    if entry.is_dir() {
      continue;
    }

    let path = resolve_zip_file_path(entry.name());
    if path == "[Content_Types].xml" || is_relationships_part_path(&path) {
      continue;
    }

    let content_type = content_type_for_part(content_types, &path).unwrap_or_default();
    let mut bytes = Vec::with_capacity(entry.size() as usize);
    entry.read_to_end(&mut bytes)?;
    parts.push(RawPart {
      path: path.into_boxed_str(),
      content_type: content_type.into(),
      bytes,
    });
  }

  parts.sort_by(|left, right| left.path.cmp(&right.path));
  Ok(parts)
}

fn read_relationships<R: Read + Seek>(
  archive: &mut zip::ZipArchive<R>,
  path: &str,
) -> Result<Option<Relationships>, SdkError> {
  let Some(bytes) = read_relationship_bytes(archive, path)? else {
    return Ok(None);
  };
  Relationships::from_bytes(&bytes).map(Some)
}

fn read_part_relationships<R: Read + Seek>(
  archive: &mut zip::ZipArchive<R>,
  path: &str,
  source_path: &str,
  by_path: &HashMap<Box<str>, PartId>,
) -> Result<RelationshipSet, SdkError> {
  let Some(bytes) = read_relationship_bytes_with_lowercase_filename_fallback(archive, path)? else {
    return Ok(RelationshipSet::default());
  };
  Ok(match Relationships::from_bytes(&bytes) {
    Ok(relationships) => {
      RelationshipSet::from_relationships(Some(relationships), source_path, by_path)
    }
    Err(_) => RelationshipSet::from_raw_bytes(bytes.into_boxed_slice()),
  })
}

fn read_relationship_bytes<R: Read + Seek>(
  archive: &mut zip::ZipArchive<R>,
  path: &str,
) -> Result<Option<Vec<u8>>, SdkError> {
  let Some(index) = archive.index_for_name(path) else {
    return Ok(None);
  };

  let mut entry = archive.by_index(index)?;
  let mut bytes = Vec::with_capacity(entry.size() as usize);
  entry.read_to_end(&mut bytes)?;
  Ok(Some(bytes))
}

fn read_relationship_bytes_with_lowercase_filename_fallback<R: Read + Seek>(
  archive: &mut zip::ZipArchive<R>,
  path: &str,
) -> Result<Option<Vec<u8>>, SdkError> {
  if let Some(bytes) = read_relationship_bytes(archive, path)? {
    return Ok(Some(bytes));
  }

  let Some(fallback_path) = lowercase_zip_filename(path) else {
    return Ok(None);
  };
  read_relationship_bytes(archive, &fallback_path)
}

fn lowercase_zip_filename(path: &str) -> Option<String> {
  let (parent_path, file_name) = path.rsplit_once('/')?;
  let lower_file_name = file_name.to_ascii_lowercase();
  if file_name == lower_file_name {
    return None;
  }

  let mut fallback = String::with_capacity(path.len());
  fallback.push_str(parent_path);
  fallback.push('/');
  fallback.push_str(&lower_file_name);
  Some(fallback)
}

fn relationship_info(
  relationship: OpcRelationship,
  source_parent_path: &str,
  by_path: &HashMap<Box<str>, PartId>,
) -> RelationshipInfo {
  let target_mode = relationship.target_mode;
  let effective_target_mode = target_mode.unwrap_or(TargetMode::Internal);
  let (target_kind, target_part_id) = if matches!(effective_target_mode, TargetMode::Internal) {
    if relationship.target.eq_ignore_ascii_case("NULL") {
      (RelationshipTargetKind::Null, None)
    } else {
      let target_path = resolve_zip_file_path(&resolve_relationship_target_path(
        source_parent_path,
        &relationship.target,
      ));
      match by_path.get(target_path.as_str()).copied() {
        Some(part_id) => (RelationshipTargetKind::InternalPart, Some(part_id)),
        None => (RelationshipTargetKind::Missing, None),
      }
    }
  } else {
    (RelationshipTargetKind::External, None)
  };

  RelationshipInfo {
    id: relationship.id.into_boxed_str(),
    relationship_type: super::XmlRelationshipNamespaceUri::from_uri(&relationship.r#type),
    target: relationship.target.into_boxed_str(),
    target_mode,
    target_kind,
    target_part_id,
  }
}

fn content_type_for_part<'a>(content_types: &'a Types, path: &str) -> Option<&'a str> {
  let normalized_part_name = format!("/{path}");
  let extension = path.rsplit_once('.').map(|(_, extension)| extension);
  let mut default_content_type = None;

  for child in &content_types.types_choice {
    match child {
      TypesChoice::Override(override_type) if override_type.part_name == normalized_part_name => {
        return Some(override_type.content_type.as_str());
      }
      TypesChoice::Default(default_type) if extension == Some(default_type.extension.as_str()) => {
        default_content_type = Some(default_type.content_type.as_str());
      }
      _ => {}
    }
  }

  default_content_type
}

fn is_relationships_part_path(path: &str) -> bool {
  path == "_rels/.rels" || path.contains("/_rels/") && path.ends_with(".rels")
}

#[cfg(test)]
mod tests {
  use super::*;
  use std::io::{Cursor, Write};

  #[test]
  fn storage_resolves_package_relationship_target_part_id() {
    let mut buffer = Cursor::new(Vec::new());
    {
      let mut zip = zip::ZipWriter::new(&mut buffer);
      let options = zip::write::SimpleFileOptions::default();

      zip.start_file("[Content_Types].xml", options).unwrap();
      zip.write_all(
        br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
  <Default Extension="xml" ContentType="application/xml"/>
  <Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
</Types>"#,
      )
      .unwrap();

      zip.add_directory("_rels", options).unwrap();
      zip.start_file("_rels/.rels", options).unwrap();
      zip.write_all(
        br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
</Relationships>"#,
      )
      .unwrap();

      zip.add_directory("word", options).unwrap();
      zip.start_file("word/document.xml", options).unwrap();
      zip.write_all(br#"<w:document xmlns:w="w"/>"#).unwrap();
      zip.finish().unwrap();
    }

    buffer.set_position(0);
    let storage = SdkPackageStorage::open(buffer, PackageOpenMode::Lazy).unwrap();
    let relationship = storage.package_relationships().get("rId1").unwrap();
    let part_id = relationship.target_part_id().unwrap();
    let part = storage.part(part_id).unwrap();

    assert_eq!(part.path(), "word/document.xml");
    assert_eq!(
      part.relationship_type(),
      Some("http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument")
    );
    assert_eq!(
      relationship.target_kind(),
      RelationshipTargetKind::InternalPart
    );
  }

  #[test]
  fn storage_reads_part_relationships_with_lowercase_filename_fallback() {
    let mut buffer = Cursor::new(Vec::new());
    {
      let mut zip = zip::ZipWriter::new(&mut buffer);
      let options = zip::write::SimpleFileOptions::default();

      zip.start_file("[Content_Types].xml", options).unwrap();
      zip.write_all(
        br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
  <Default Extension="xml" ContentType="application/xml"/>
  <Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
  <Override PartName="/xl/worksheets/Sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
  <Override PartName="/xl/drawings/drawing1.xml" ContentType="application/vnd.openxmlformats-officedocument.drawing+xml"/>
</Types>"#,
      )
      .unwrap();

      zip.add_directory("_rels", options).unwrap();
      zip.start_file("_rels/.rels", options).unwrap();
      zip.write_all(
        br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
</Relationships>"#,
      )
      .unwrap();

      zip.add_directory("xl/_rels", options).unwrap();
      zip
        .start_file("xl/_rels/workbook.xml.rels", options)
        .unwrap();
      zip.write_all(
        br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/Sheet1.xml"/>
</Relationships>"#,
      )
      .unwrap();

      zip.add_directory("xl/worksheets/_rels", options).unwrap();
      zip
        .start_file("xl/worksheets/_rels/sheet1.xml.rels", options)
        .unwrap();
      zip.write_all(
        br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing" Target="../drawings/drawing1.xml"/>
</Relationships>"#,
      )
      .unwrap();

      zip.add_directory("xl", options).unwrap();
      zip.start_file("xl/workbook.xml", options).unwrap();
      zip.write_all(br#"<workbook/>"#).unwrap();
      zip.add_directory("xl/worksheets", options).unwrap();
      zip.start_file("xl/worksheets/Sheet1.xml", options).unwrap();
      zip.write_all(br#"<worksheet/>"#).unwrap();
      zip.add_directory("xl/drawings", options).unwrap();
      zip.start_file("xl/drawings/drawing1.xml", options).unwrap();
      zip.write_all(br#"<xdr:wsDr xmlns:xdr="xdr"/>"#).unwrap();
      zip.finish().unwrap();
    }

    buffer.set_position(0);
    let storage = SdkPackageStorage::open(buffer, PackageOpenMode::Lazy).unwrap();
    let sheet_part_id = storage
      .package_relationships()
      .get("rId1")
      .and_then(RelationshipInfo::target_part_id)
      .and_then(|workbook_part_id| storage.relationships(workbook_part_id))
      .and_then(|relationships| relationships.get("rId1"))
      .and_then(RelationshipInfo::target_part_id)
      .unwrap();
    let drawing_relationship = storage
      .relationships(sheet_part_id)
      .unwrap()
      .get("rId1")
      .unwrap();

    assert_eq!(
      drawing_relationship.target_kind(),
      RelationshipTargetKind::InternalPart
    );
    let drawing_part = storage
      .part(drawing_relationship.target_part_id().unwrap())
      .unwrap();
    assert_eq!(drawing_part.path(), "xl/drawings/drawing1.xml");
  }
}
