use semver::Version;
use serde::{Deserialize, Serialize};
use url::Url;

use crate::{
	date::Date, identifiers::Identifier, license::License, names::Name, references::Reference,
};

/// The top-level CFF document.
///
/// This contains the citation metadata for a project, and may also contain
/// reference information (the project's bibligraphy).
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Cff {
	/// Version of the CFF specification this document conforms to.
	///
	/// This is required and must be non-empty.
	pub cff_version: Version,

	/// What to do with the citation metadata, in a human-readable message.
	///
	/// This is required and must be non-empty.
	///
	/// # Examples
	///
	/// - "If you use this software, please cite it using the metadata from this file."
	/// - "Please cite this software using these metadata."
	/// - "Please cite this software using the metadata from 'preferred-citation'."
	/// - "If you use this dataset, please cite it using the metadata from this file."
	/// - "Please cite this dataset using these metadata."
	/// - "Please cite this dataset using the metadata from 'preferred-citation'."
	pub message: String,

	/// The name of the work.
	///
	/// This is required and must be non-empty.
	pub title: String,

	/// The type of the work.
	#[serde(default, skip_serializing_if = "Option::is_none", rename = "type")]
	pub work_type: Option<WorkType>,

	/// Version of the work.
	///
	/// There is no requirement that this be semver.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub version: Option<String>,

	/// The commit hash or revision number of the software version.
	///
	/// By convention:
	/// - if this is a Git hash, it should be bare lowercase hex, e.g.
	///   `1ff847d81f29c45a3a1a5ce73d38e45c2f319bba`;
	/// - if this is a decimal revision or build number, it should be preceded
	///   by a label, e.g. `Revision: 8612`.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub commit: Option<String>,

	/// The date the work has been released.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub date_released: Option<Date>,

	/// A description of the work.
	#[serde(default, skip_serializing_if = "Option::is_none", rename = "abstract")]
	pub abstract_text: Option<String>,

	/// Keywords that describe the work.
	#[serde(default, skip_serializing_if = "Vec::is_empty")]
	pub keywords: Vec<String>,

	/// The URL of a landing page/website for the software or dataset.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub url: Option<Url>,

	/// The URL of the work in a repository/archive.
	///
	/// This is to be used when the repository is neither a source code
	/// repository nor a build artifact repository. For source code, use the
	/// `repository_code` field; for binary releases or other built forms, use
	/// the `repository_artifact` field.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub repository: Option<Url>,

	/// The URL of the work in a build artifact/binary repository.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub repository_artifact: Option<Url>,

	/// The URL of the work in a source code repository.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub repository_code: Option<Url>,

	/// [SPDX][spdx] license expression(s).
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub license: Option<License>,

	/// The URL of the license text under which the work is licensed.
	///
	/// This should only be used for non-standard licenses not included in the
	/// SPDX License List.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub license_url: Option<Url>,

	/// The authors of the work.
	///
	/// This is required and must contain at least one author.
	pub authors: Vec<Name>,

	/// The contact person, group, company, etc. for the work.
	#[serde(default, skip_serializing_if = "Vec::is_empty")]
	pub contact: Vec<Name>,

	/// The DOI for the work.
	///
	/// Use this if the work has a single DOI. Otherwise, use the `identifiers`
	/// field.
	///
	/// Note that the DOI is not parsed or validated in any way.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub doi: Option<String>,

	/// The identifiers for the work.
	#[serde(default, skip_serializing_if = "Vec::is_empty")]
	pub identifiers: Vec<Identifier>,

	/// A reference to another work that should be cited instead of the work.
	///
	/// Note that the principles of [software citation] and [data citation]
	/// require that software should be cited on the same basis as any other
	/// research product such as a paper or a book. Adding a different preferred
	/// citation may result in a violation of the respective primary principle,
	/// "Importance", when others cite this work.
	///
	/// [software citation]: https://doi.org/10.7717/peerj-cs.86
	/// [data citation]: https://doi.org/10.25490/a97f-egyk
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub preferred_citation: Option<Reference>,

	/// Reference(s) to other creative works.
	///
	/// Similar to a list of references in a paper, references of the software
	/// or dataset may include other software (dependencies), or other research
	/// products that the software or dataset builds on, but not work describing
	/// the software or dataset.
	#[serde(default, skip_serializing_if = "Vec::is_empty")]
	pub references: Vec<Reference>,
}

impl Default for Cff {
	fn default() -> Self {
		Self {
			cff_version: Version::new(1, 2, 0),
			message: String::from("Please cite this software using these metadata."),
			title: Default::default(),
			work_type: Default::default(),
			version: Default::default(),
			commit: Default::default(),
			date_released: Default::default(),
			abstract_text: Default::default(),
			keywords: Default::default(),
			url: Default::default(),
			repository: Default::default(),
			repository_artifact: Default::default(),
			repository_code: Default::default(),
			license: Default::default(),
			license_url: Default::default(),
			authors: Default::default(),
			contact: Default::default(),
			doi: Default::default(),
			identifiers: Default::default(),
			preferred_citation: Default::default(),
			references: Default::default(),
		}
	}
}

/// Types of works recognised by CFF.
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum WorkType {
	/// A software project.
	Software,

	/// A dataset.
	Dataset,
}