knitr::opts_chunk$set(echo = TRUE, message = TRUE, warning = FALSE, dev = "ragg_png")
# gganimate가 장치를 인치 단위로 해석하지 않도록 픽셀 단위를 기본으로 강제
options(gganimate.dev_args = list(units = "px", res = NA))

# 설치는 콘솔에서 미리: HistData, ggplot2, dplyr, gganimate, gifski, transformr, lubridate
need <- c("HistData","ggplot2","dplyr","gganimate","gifski","transformr","lubridate","tibble")
miss <- need[!sapply(need, requireNamespace, quietly = TRUE)]
if (length(miss) > 0) {
  stop(sprintf("미설치 패키지: %s\n콘솔에서 install.packages()로 먼저 설치하세요.", paste(miss, collapse=", ")))
}
lapply(c("HistData","ggplot2","dplyr","gganimate","lubridate","tibble"),
       library, character.only = TRUE)
dir.create("figs", showWarnings = FALSE)

1) 날짜 앵커 정의 + 날짜 파싱(안전판)

# ---- 후퇴(R): Minard.temp의 long–date 사용 ----
temps_raw <- HistData::Minard.temp

# date가 문자/팩터 섞임, 연도 없는 경우가 있어 강건 파싱
mon_candidates <- c("month","Month","mon","Mon")
day_candidates <- c("day","Day","DAY")
mon_col <- mon_candidates[mon_candidates %in% names(temps_raw)][1]
day_col <- day_candidates[day_candidates %in% names(temps_raw)][1]

temps <- temps_raw %>%
  mutate(
    date_chr = if ("date" %in% names(.)) as.character(date) else NA_character_,
    # 1) ISO(YYYY-MM-DD 등)
    date_iso = suppressWarnings(lubridate::ymd(date_chr, quiet = TRUE)),
    # 2) "Nov 24" 등 연도 없는 표기에는 1812 붙여 파싱
    date_1812_from_date = suppressWarnings(
      lubridate::parse_date_time(paste("1812", date_chr),
                                 orders = c("Y d b","Y d B","Y b d","Y B d"),
                                 quiet = TRUE)
    ),
    # 3) month/day 컬럼이 따로 있으면 그것으로 1812년 파싱
    mon_chr = if (!is.na(mon_col)) as.character(.data[[mon_col]]) else NA_character_, 
    day_chr = if (!is.na(day_col)) as.character(.data[[day_col]]) else NA_character_,
    date_1812_from_cols = suppressWarnings(
      lubridate::parse_date_time(paste("1812", mon_chr, day_chr),
                                 orders = c("Y b d","Y B d","Y d b","Y d B"),
                                 quiet = TRUE)
    ),
    date = dplyr::coalesce(as.Date(date_iso),
                           as.Date(date_1812_from_date),
                           as.Date(date_1812_from_cols))
  ) %>%
  arrange(long) %>%
  filter(!is.na(long)) %>%
  distinct(long, .keep_all = TRUE)

# 파싱 실패한 행은 경고만 띄우고 제외
bad <- temps %>% filter(is.na(date))
if (nrow(bad) > 0) {
  warning(sprintf("Minard.temp에서 날짜 파싱 실패 %d건 제외: %s",
                  nrow(bad),
                  paste(unique(na.omit(bad$date_chr)), collapse=", ")))
  temps <- temps %>% filter(!is.na(date))
}
stopifnot(nrow(temps) >= 2)

# 라벨 타입 고정(gganimate labelcolumn 타입 충돌 방지)
temps <- temps %>% mutate(lbl = as.character(paste0(temp, "°C"))) %>% mutate(gid = 1L)

# 후퇴(R): long → date 보간 함수
ret_fun <- approxfun(temps$long, as.numeric(temps$date), rule = 2)

# ---- 진군(A) 앵커: 데이터셋 표기에 맞춰 퍼지 매칭 사용 ----
anchors_adv_raw <- tibble::tribble(
  ~city,       ~date,
  "Kowno",     "1812-06-24",
  "Wilna",     "1812-06-28",
  "Witebsk",   "1812-07-28",  # Vitebsk
  "Orscha",    "1812-07-26",  # Orsha
  "Smolensk",  "1812-08-17",
  "Mojaisk",   "1812-09-08",  # (Borodino 인근, 데이터셋 표기)
  "Moskva",    "1812-09-14"   # Moscow/Moscou 등과 표기 차이를 퍼지 매칭으로 해결
) %>%
  mutate(date = as.Date(date))

# 데이터셋의 실제 표기 목록
cities_vec <- sort(unique(HistData::Minard.cities$city))

# 간단한 퍼지 매칭 함수(adist)
closest_city <- function(x, pool, max_dist = 3) {
  if (is.na(x) || !nzchar(x)) return(NA_character_)
  d <- adist(x, pool)
  i <- which.min(d)
  if (length(i) == 0) return(NA_character_)
  if (d[i] <= max_dist) pool[i] else NA_character_
}

anchors_adv <- anchors_adv_raw %>%
  rowwise() %>%
  mutate(city_std = closest_city(city, cities_vec, max_dist = 3)) %>%
  ungroup() %>%
  mutate(city_std = dplyr::coalesce(city_std, city)) %>%
  left_join(HistData::Minard.cities, by = c("city_std" = "city")) %>%
  arrange(long) %>%
  distinct(long, .keep_all = TRUE)

missing_long <- anchors_adv %>% filter(is.na(long))
if (nrow(missing_long) > 0) {
  warning(sprintf(
    "앵커 좌표를 찾지 못해 제외된 도시: %s",
    paste(missing_long$city, collapse=", ")
  ))
  anchors_adv <- anchors_adv %>% filter(!is.na(long))
}
stopifnot(nrow(anchors_adv) >= 2)

# 진군(A): long → date 보간 함수
adv_fun <- approxfun(anchors_adv$long, as.numeric(anchors_adv$date), rule = 2)

# 도시 라벨은 character로 고정 (labelcolumn 타입 불일치 방지)
cities_df <- HistData::Minard.cities %>% dplyr::mutate(city = as.character(city))

2) 부대 경로에 날짜 부여

troops <- HistData::Minard.troops %>%
  mutate(
    date_num = if_else(direction == "R",
                       ret_fun(long),   # 후퇴: temp 기반
                       adv_fun(long)    # 진군: 앵커 기반
    ),
    date = as.Date(date_num, origin = "1970-01-01")
  ) %>%
  group_by(group) %>%
  arrange(date, .by_group = TRUE) %>%
  ungroup()

3) 날짜(date) 기반 경로 애니메이션 → GIF 저장 & 포함

p <- ggplot(troops, aes(long, lat, group = group)) +
  geom_path(aes(size = survivors, color = direction), lineend = "round") +
  geom_point(data = cities_df, aes(long, lat), inherit.aes = FALSE, size = 2) +
  geom_text(data = cities_df, aes(long, lat, label = city),
            inherit.aes = FALSE, vjust = -1, size = 2) +
  scale_size(range = c(0.5, 4.5), guide = "none") +
  scale_color_manual(values = c(A = "#c79a56", R = "#333333"),
                     labels = c(A = "Advance", R = "Retreat")) +
  coord_quickmap() +
  theme_minimal(base_size = 6) +
  labs(title = "Napoleon's 1812 Campaign (Date-based)",
       subtitle = "Date: {frame_along}")

anim <- p + gganimate::transition_reveal(date)

gif <- animate(anim,
               nframes = 120, fps = 12,
               width = 900, height = 360,
               units = "px",
               bg = "white",
               renderer = gifski_renderer())
anim_save("figs/minard_date.gif", gif)
knitr::include_graphics("figs/minard_date.gif")

4) 온도(후퇴) 애니메이션 (옵션)

# Create the base plot
p_temp <- ggplot(temps, aes(long, temp)) +
  # Add the full line as a static background
  geom_line(aes(group = 1), na.rm = TRUE) +
  # Animate the points along the line
  geom_point(aes(group = seq_along(long)), na.rm = TRUE, size = 2) +
  # Animate the labels for the points
  geom_text(aes(label = lbl, group = seq_along(long)), vjust = -1, na.rm = TRUE, size = 2) +
  theme_minimal(base_size = 6) +
  labs(title = "Temperature during Retreat", subtitle = "Date: {frame_along}")

# Apply the animation
anim_temp <- p_temp + gganimate::transition_reveal(date)

# Save the animation
gif_temp <- animate(anim_temp,
                    nframes = 60, fps = 8,
                    width = 900, height = 360,
                    units = "px",
                    bg = "white",
                    renderer = gifski_renderer())
anim_save("figs/minard_temp.gif", gif_temp)