#' @title Extract Dates from FASTA Sequence Names
#' @description Extracts date strings from the sequence name strings of an
#'   \code{AAStringSet} object loaded via
#'   \code{\link[Biostrings]{readAAStringSet}}. Several built-in date patterns
#'   are provided for the common date conventions used in NCBI and GISAID
#'   exports; a fully custom regex can also be supplied.
#'
#' @details
#' Date strings of the form \code{yyyy-mm-dd} are matched between pipe
#' characters (\code{|...|}) by default. Day value \code{00} (a common
#' GISAID convention indicating unknown collection day) is accepted in the
#' raw string and corrected to \code{01} before coercion to \code{Date}.
#' Both raw and corrected versions are returned, so the caller can decide
#' how to treat unknown-day records downstream.
#'
#' \strong{Choosing a built-in pattern.} The four options correspond to
#' the four most common date conventions in viral sequence repositories:
#' \itemize{
#'   \item \code{option = 1}: \code{yyyy-mm-dd} between pipes — GISAID
#'     export format, where the date is followed by additional
#'     pipe-delimited fields.
#'   \item \code{option = 2}: \code{yyyy-dd-mm} between pipes — some
#'     European data sources reverse day and month.
#'   \item \code{option = 3}: \code{yyyy-mm} between pipes — month-level
#'     resolution, useful when the source omits or hides the day. Pair
#'     with \code{date_format = "\%Y-\%m"}.
#'   \item \code{option = 4}: \code{yyyy-mm-dd} at end of header — NCBI
#'     Virus export format, where the collection date is the final
#'     field with no trailing delimiter. This is the format of the
#'     bundled \code{sarscov2_sample}.
#' }
#' For datasets where the date does not lie between pipes, supply a
#' \code{custom_pattern} matching whatever surrounding context the headers
#' provide.
#'
#' \strong{Coercion to Date.} When \code{date_format = "\%Y-\%m"} the
#' function uses \code{\link[zoo]{as.yearmon}} for coercion so that
#' year-month strings are handled correctly (base \code{as.Date} cannot
#' parse \code{"2021-05"} alone). For all other formats, base
#' \code{as.Date} with the supplied \code{date_format} is used.
#'
#' \strong{Output alignment.} All six elements of the return list are the
#' same length as the input \code{sequence}. Where extraction fails for a
#' record, the corresponding entries are \code{NA}; \code{missing_id}
#' lists the affected indices.
#'
#' @param sequence An \code{AAStringSet} object.
#' @param option Integer (1, 2, 3, or 4). Selects the built-in pattern when
#'   \code{custom_pattern} is not supplied. See Details.
#' @param date_format Character. \code{strptime}-style format string used
#'   to coerce extracted strings to \code{Date}. Default is
#'   \code{"\%Y-\%m-\%d"}. Use \code{"\%Y-\%m"} together with
#'   \code{option = 3}.
#' @param custom_pattern Character or \code{NULL}. A custom regex passed
#'   directly to \code{\link[stringr]{str_extract}}. When supplied,
#'   \code{option} is ignored. Default is \code{NULL}.
#'
#' @return A named list of six elements, each aligned with the input
#'   \code{sequence}:
#' \item{raw_date_strings}{Character vector of extracted date strings
#'   before any correction. \code{NA} where extraction failed.}
#' \item{corrected_date_strings}{Character vector with \code{-00}
#'   substrings replaced by \code{-01}. \code{NA} where extraction
#'   failed.}
#' \item{raw_dates}{\code{Date} vector coerced from
#'   \code{raw_date_strings}. \code{NA} for unparseable or missing
#'   strings (including any record where day = \code{00}).}
#' \item{corrected_dates}{\code{Date} vector coerced from
#'   \code{corrected_date_strings}. \code{NA} for unparseable or missing
#'   strings.}
#' \item{message}{Character string summarising extraction success.}
#' \item{missing_id}{Integer vector of indices where extraction failed,
#'   or \code{NA} if all extractions succeeded.}
#'
#' @seealso \code{\link{extract_fasta_countries}} for the country-extraction
#'   companion; \code{\link[Biostrings]{readAAStringSet}} for loading the
#'   input \code{AAStringSet}; \code{\link[zoo]{as.yearmon}} for the
#'   year-month coercion path.
#'
#' @importFrom stringr str_extract
#' @importFrom zoo as.Date as.yearmon
#' @export
#'
#' @examples
#' \donttest{
#' path_sample  <- system.file("extdata", "sarscov2_sample.fasta.gz",
#'                              package = "ViralEntropR")
#' fasta_sample <- Biostrings::readAAStringSet(path_sample)
#'
#' # Inspect header structure to confirm date field position.
#' sample(names(fasta_sample), 1)
#' # The bundled sample uses NCBI Virus format: date is at end of header.
#'
#' # Default usage on bundled sample: option = 4 for end-of-header dates.
#' dates <- extract_fasta_dates(fasta_sample, option = 4)
#' dates$message
#' head(dates$corrected_dates)
#' range(dates$corrected_dates, na.rm = TRUE)
#'
#' # Custom regex for non-standard headers:
#' dates_custom <- extract_fasta_dates(
#'   fasta_sample,
#'   custom_pattern = "[0-9]{4}-(0?[1-9]|1[0-2])-(0?[1-9]|[12][0-9]|3[01]|00)"
#' )
#' }
extract_fasta_dates <- function(sequence,
                                option         = 1,
                                date_format    = "%Y-%m-%d",
                                custom_pattern = NULL) {
  
  if (!requireNamespace("Biostrings", quietly = TRUE)) {
    stop("Package 'Biostrings' is required. Install it with:\n",
         "  install.packages('BiocManager')\n",
         "  BiocManager::install('Biostrings')",
         call. = FALSE)
  }
  
  # Use names() — public accessor — instead of internal slot access
  seq_names <- names(sequence)
  
  # Select regex pattern
  if (!is.null(custom_pattern)) {
    pattern <- custom_pattern
  } else {
    pattern <- switch(as.character(option),
                      "1" = "(?<=\\|)[0-9]{4}-(0?[1-9]|1[0-2])-(0?[1-9]|[12][0-9]|3[01]|00)(?=\\|)",
                      "2" = "(?<=\\|)[0-9]{4}-(0?[1-9]|[12][0-9]|3[01]|00)-(0?[1-9]|1[0-2])(?=\\|)",
                      "3" = "(?<=\\|)[0-9]{4}-(0?[1-9]|1[0-2])(?=\\|)",
                      "4" = "(?<=\\|)[0-9]{4}-(0?[1-9]|1[0-2])-(0?[1-9]|[12][0-9]|3[01]|00)$",
                      stop("`option` must be 1, 2, 3, or 4.", call. = FALSE)
    )
  }
  
  date_strings <- stringr::str_extract(seq_names, pattern)
  is_missing   <- is.na(date_strings)
  
  if (!any(is_missing)) {
    msg      <- "All date strings have been successfully extracted"
    miss_id  <- NA
  } else {
    msg      <- "There are date strings that have not been recognized"
    miss_id  <- which(is_missing)
  }
  
  raw_date_strings       <- date_strings
  corrected_date_strings <- gsub("-00", "-01", raw_date_strings)
  
  # Coerce to Date — yearmon path for yyyy-mm format
  case_yyyy_mm <- identical(date_format, "%Y-%m")
  
  if (case_yyyy_mm) {
    raw_dates       <- zoo::as.Date(zoo::as.yearmon(raw_date_strings,       format = "%Y-%m"))
    corrected_dates <- zoo::as.Date(zoo::as.yearmon(corrected_date_strings, format = "%Y-%m"))
  } else {
    raw_dates       <- as.Date(raw_date_strings,       format = date_format)
    corrected_dates <- as.Date(corrected_date_strings, format = date_format)
  }
  
  list(raw_date_strings       = raw_date_strings,
       corrected_date_strings = corrected_date_strings,
       raw_dates              = raw_dates,
       corrected_dates        = corrected_dates,
       message                = msg,
       missing_id             = miss_id)
}