//! Types and utilities for references to this or other works.

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

use crate::{
	identifiers::Identifier,
	names::{EntityName, Name},
	Date, License,
};

/// A reference for a work.
#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Reference {
	/// The type of the referenced work.
	///
	/// This is required.
	#[serde(rename = "type")]
	pub work_type: RefType,

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

	/// The abbreviation of a work.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub abbreviation: Option<String>,

	/// The abstract of the work.
	///
	/// - If the work is a journal paper or other academic work,
	///   The abstract of the work.
	///
	/// - If the work is a film broadcast or similar,
	///   The synopsis of the work.
	#[serde(default, skip_serializing_if = "Option::is_none", rename = "abstract")]
	pub abstract_text: Option<String>,

	/// The DOI of a collection containing the work.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub collection_doi: Option<String>,

	/// The title of a collection or proceedings.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub collection_title: Option<String>,

	/// The type of a collection.
	///
	/// By convention this should be in lowercase.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub collection_type: Option<String>,

	/// The commit hash or revision number of the work, if it is software.
	///
	/// 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 conference where the work was presented.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub conference: Option<EntityName>,

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

	/// The copyright information pertaining to the work.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub copyright: Option<String>,

	/// The data type of a data set.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub data_type: Option<String>,

	/// The provider of the database where a work was accessed/is stored.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub database_provider: Option<EntityName>,

	/// The name of the database where a work was accessed/is stored.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub database: Option<String>,

	/// The date the work was accessed.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub date_accessed: Option<Date>,

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

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

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

	/// The department where a work has been produced.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub department: Option<String>,

	/// The DOI of the work.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub doi: Option<String>,

	/// The edition of the work.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub edition: Option<String>,

	/// The editor(s) of a work.
	#[serde(default, skip_serializing_if = "Vec::is_empty")]
	pub editors: Vec<Name>,

	/// The editor(s) of a series in which the work has been published.
	#[serde(default, skip_serializing_if = "Vec::is_empty")]
	pub editors_series: Vec<Name>,

	/// The start page of the work.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub start: Option<u64>,

	/// The end page of the work.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub end: Option<u64>,

	/// An entry in the collection that constitutes the work.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub entry: Option<String>,

	/// The name of the electronic file containing the work.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub filename: Option<String>,

	/// The format in which a work is represented.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub format: Option<String>,

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

	/// The institution where a work has been produced or published.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub institution: Option<EntityName>,

	/// The [ISBN] of the work.
	///
	/// The value is not validated.
	///
	/// [ISBN]: https://en.wikipedia.org/wiki/International_Standard_Book_Number
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub isbn: Option<String>,

	/// The [ISSN] of the work.
	///
	/// The value is not validated.
	///
	/// [ISSN]: https://en.wikipedia.org/wiki/International_Standard_Serial_Number
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub issn: Option<String>,

	/// The issue of a periodical in which a work appeared.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub issue: Option<String>,

	/// The publication date of the issue of a periodical in which a work appeared.
	///
	/// Note this is a freeform string.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub issue_date: Option<String>,

	/// The name of the issue of a periodical in which the work appeared.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub issue_title: Option<String>,

	/// The name of the journal/magazine/newspaper/periodical where the work was published.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub journal: Option<String>,

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

	/// The language identifier(s) of the work.
	///
	/// These should be ISO639 strings in lowercase alpha-2 or alpha-3, but this
	/// library does not validate this.
	#[serde(default, skip_serializing_if = "Vec::is_empty")]
	pub languages: Vec<String>,

	/// [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 line of code in the file where the work ends.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub loc_end: Option<u64>,

	/// The line of code in the file where the work starts.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub loc_start: Option<u64>,

	/// The location of the work.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub location: Option<EntityName>,

	/// The medium of the work.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub medium: Option<String>,

	/// The month in which a work has been published.
	///
	/// Should be an integer in the range 1-12. Note this is not validated.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub month: Option<u8>,

	/// The [NIHMSID] of a work.
	///
	/// [NIHMSID]: https://web.archive.org/web/20210802210057/https://www.ncbi.nlm.nih.gov/pmc/about/public-access-info/
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub nihmsid: Option<String>,

	/// Notes pertaining to the work.
	///
	/// Note that this key should contain notes that may be picked up by some
	/// downstream tooling (e.g., reference managers), but not others
	/// (e.g., a software index).
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub notes: Option<String>,

	/// The (library) [accession number] for a work.
	///
	/// [accession number]: https://en.wikipedia.org/wiki/Accession_number
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub number: Option<String>,

	/// The number of volumes making up the collection in which the work has
	/// been published.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub number_volumes: Option<u64>,

	/// The number of pages of the work.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub pages: Option<u64>,

	/// The states for which a patent is granted.
	#[serde(default, skip_serializing_if = "Vec::is_empty")]
	pub patent_states: Vec<String>,

	/// The [PMCID] of a work.
	///
	/// The value is not validated.
	///
	/// [PMCID]: https://web.archive.org/web/20210802210057/https://www.ncbi.nlm.nih.gov/pmc/about/public-access-info/
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub pmcid: Option<String>,

	/// The publisher who has published the work.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub publisher: Option<EntityName>,

	/// The recipient(s) of a personal communication.
	#[serde(default, skip_serializing_if = "Vec::is_empty")]
	pub recipients: Vec<Name>,

	/// 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>,

	/// The scope of the reference, e.g., the section of the work it adheres to.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub scope: Option<String>,

	/// The section of a work that is referenced.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub section: Option<String>,

	/// The sender(s) of a personal communication.
	#[serde(default, skip_serializing_if = "Vec::is_empty")]
	pub senders: Vec<Name>,

	/// The publication status of the work.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub status: Option<PublicationStatus>,

	/// The term being referenced if the work is a dictionary or encyclopedia.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub term: Option<String>,

	/// The type of the thesis that is the work.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub thesis_type: Option<String>,

	/// The title of the work.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub title: Option<String>,

	/// The translator(s) of a work.
	#[serde(default, skip_serializing_if = "Vec::is_empty")]
	pub translators: Vec<Name>,

	/// The URL of the work.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub url: Option<Url>,

	/// The version of the work.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub version: Option<String>,

	/// The volume of the periodical in which a work appeared.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub volume: Option<u64>,

	/// The title of the volume in which the work appeared.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub volume_title: Option<String>,

	/// The year in which a work has been published.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub year: Option<u64>,

	/// The year of the original publication.
	#[serde(default, skip_serializing_if = "Option::is_none")]
	pub year_original: Option<i64>,
}

/// Publication statuses.
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[allow(missing_docs)]
pub enum PublicationStatus {
	Abstract,
	AdvanceOnline,
	InPreparation,
	InPress,
	Preprint,
	Submitted,
}

/// Types of referenced works.
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[allow(missing_docs)]
pub enum RefType {
	Art,
	Article,
	Audiovisual,
	Bill,
	Blog,
	Book,
	Catalogue,
	ConferencePaper,
	Conference,
	Data,
	Database,
	Dictionary,
	EditedWork,
	Encyclopedia,
	FilmBroadcast,
	Generic,
	GovernmentDocument,
	Grant,
	Hearing,
	HistoricalWork,
	LegalCase,
	LegalRule,
	MagazineArticle,
	Manual,
	Map,
	Multimedia,
	Music,
	NewspaperArticle,
	Pamphlet,
	Patent,
	PersonalCommunication,
	Proceedings,
	Report,
	Serial,
	Slides,
	SoftwareCode,
	SoftwareContainer,
	SoftwareExecutable,
	SoftwareVirtualMachine,
	Software,
	SoundRecording,
	Standard,
	Statute,
	Thesis,
	Unpublished,
	Video,
	Website,
}

impl Default for RefType {
	fn default() -> Self {
		Self::Generic
	}
}