# Hex sticker ------------------------------------------------------------------
#
# The package logo is generated by the package's own SVG writer — there is
# no design tool in the loop, and no image file to keep in sync with the
# palette. `ggnext_logo()` writes the sticker; `man/figures/logo.svg` is
# the committed output.

#' Draw the ggnext hex sticker
#'
#' Generates the package logo as an SVG: a hexagon containing a small
#' scatter with a fitted trend, drawn with the same coordinate and color
#' machinery the package uses for real plots.
#'
#' Two styles are available. `"wordmark"` (the default) spells out
#' `ggnext` under the artwork with a tagline; `"monogram"` sets a large
#' `GG` in the middle, which stays legible at favicon and app-icon sizes
#' where a seven-character wordmark turns to mush.
#'
#' @param file Output path; `NULL` returns the SVG as a string.
#' @param width Sticker width in pixels (height follows the hex ratio).
#' @param dark Use the dark palette variant.
#' @param style `"wordmark"` or `"monogram"`.
#' @param tagline Text under the wordmark, as one string or a character
#'   vector of lines (two read better than one long line). `""` omits it.
#'   Ignored by the monogram style, which shows `ggnext` there instead.
#' @return The SVG document as a length-1 character vector, invisibly when
#'   `file` is supplied.
#' @examples
#' svg <- ggnext_logo()
#' substr(svg, 1, 30)
#'
#' # App-icon variant.
#' icon <- ggnext_logo(style = "monogram", width = 256)
#' @export
ggnext_logo <- function(file = NULL, width = 520, dark = FALSE,
                         style = c("wordmark", "monogram"),
                         tagline = c("NEXT-GENERATION",
                                     "GRAMMAR OF GRAPHICS")) {
  style <- match.arg(style)
  # A regular hexagon with a flat top: height = width * 2/sqrt(3).
  height <- width * 2 / sqrt(3)
  cx <- width / 2
  cy <- height / 2
  r <- width / 2

  pal <- if (dark) {
    list(
      bg = "#14141A", border = "#5FD0A5", grid = "#2A2A34",
      text = "#F2F2F7", accent = "#5FD0A5",
      points = c("#7AA2F7", "#F7A072", "#54D6B4")
    )
  } else {
    list(
      bg = "#1A2E59", border = "#5FD0A5", grid = "#2A4272",
      text = "#FFFFFF", accent = "#5FD0A5",
      points = c("#5FD0A5", "#7AA2F7", "#F7C96B")
    )
  }

  # Hexagon vertices, pointy-top, starting at 12 o'clock.
  ang <- seq(pi / 2, by = pi / 3, length.out = 6)
  hx <- cx + r * cos(ang)
  hy <- cy - (height / 2) * sin(ang)
  hex_pts <- paste(px(hx), px(hy), sep = ",", collapse = " ")

  out <- c(
    sprintf(
      paste0(
        "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"%s\" ",
        "height=\"%s\" viewBox=\"0 0 %s %s\" ",
        "font-family=\"Helvetica, Arial, sans-serif\">"
      ),
      px(width), px(height), px(width), px(height)
    ),
    # Clip everything to the hex so the plot bleeds to the edges.
    sprintf("<clipPath id=\"hex\"><polygon points=\"%s\"/></clipPath>", hex_pts),
    svg_tag("polygon", list(points = hex_pts, fill = pal$bg)),
    "<g clip-path=\"url(#hex)\">"
  )

  # A small scatter with a rising trend: the grammar in one glance.
  #
  # A pointy-top hexagon is only full width between 0.25h and 0.75h, so the
  # artwork lives in the upper band and the wordmark sits just below it,
  # inside the widest part — neither can collide with the tapering edges.
  n <- 26
  gx <- seq(0.16, 0.84, length.out = n)
  # Fixed seed so the sticker is byte-identical every time; local_rng()
  # draws from a private stream, never touching R's global RNG state.
  gy <- 0.30 + 0.34 * gx + local_rng(3)$norm(n, 0, 0.055)
  art_top <- height * 0.12
  art_bottom <- height * 0.52
  to_x <- function(t) t * width
  to_y <- function(t) art_bottom - t * (art_bottom - art_top)

  # Faint gridlines.
  for (g in seq(0.2, 0.8, by = 0.2)) {
    out <- c(out, svg_tag("line", list(
      x1 = 0, y1 = px(to_y(g)), x2 = px(width), y2 = px(to_y(g)),
      stroke = pal$grid, `stroke-width` = 1
    )))
  }

  # Trend line through the cloud.
  fit <- stats::lm(gy ~ gx)
  lx <- c(0.10, 0.90)
  ly <- stats::predict(fit, data.frame(gx = lx))
  out <- c(out, svg_tag("polyline", list(
    points = paste(px(to_x(lx)), px(to_y(ly)), sep = ",", collapse = " "),
    fill = "none", stroke = pal$accent, `stroke-width` = px(width * 0.012),
    `stroke-linecap` = "round", `stroke-opacity` = 0.85
  )))

  for (i in seq_len(n)) {
    out <- c(out, svg_tag("circle", list(
      cx = px(to_x(gx[i])), cy = px(to_y(gy[i])),
      r = px(width * 0.019),
      fill = pal$points[(i %% length(pal$points)) + 1],
      `fill-opacity` = 0.92
    )))
  }

  # Lettering, inside the clip group so nothing can overhang the border.
  # A pointy-top hex is only full width between 0.25h and 0.75h and tapers
  # below that, so all text sits at or above 0.75h and is sized against the
  # width actually available there.
  if (style == "monogram") {
    # A large GG reads at favicon and app-icon sizes where the wordmark
    # would be illegible; `ggnext` moves to the tagline slot.
    out <- c(out, svg_tag("text", list(
      x = px(cx), y = px(height * 0.665),
      `text-anchor` = "middle", `font-size` = px(width * 0.40),
      `font-weight` = "bold", fill = pal$text,
      `letter-spacing` = px(width * -0.02)
    ), "GG"))
    out <- c(out, svg_tag("text", list(
      x = px(cx), y = px(height * 0.745),
      `text-anchor` = "middle", `font-size` = px(width * 0.072),
      `font-weight` = "bold", fill = pal$accent,
      `letter-spacing` = px(width * 0.004)
    ), "ggnext"))
  } else {
    lines <- tagline[nzchar(tagline)]
    # The wordmark sits higher when a two-line tagline follows it.
    word_y <- if (length(lines) > 1) 0.645 else 0.668
    out <- c(out, svg_tag("text", list(
      x = px(cx), y = px(height * word_y),
      `text-anchor` = "middle", `font-size` = px(width * 0.165),
      `font-weight` = "bold", fill = pal$text,
      `letter-spacing` = px(width * -0.003)
    ), "ggnext"))
    if (length(lines) > 0) {
      # Fit the tagline to the band: shrink the type as the longest line
      # lengthens, so it never runs into the hex edge. Advance per
      # character is the glyph width (~0.66 em for bold uppercase
      # Helvetica) plus the letter-spacing, and 0.74 of the sticker width
      # is what sits safely inside the border at this height.
      ls_em <- 0.10
      widest <- max(nchar(lines))
      size <- min(width * 0.050,
                  width * 0.74 / (widest * (0.66 + ls_em)))
      first_y <- if (length(lines) > 1) 0.712 else 0.737
      for (i in seq_along(lines)) {
        out <- c(out, svg_tag("text", list(
          x = px(cx),
          y = px(height * first_y + (i - 1) * size * 1.35),
          `text-anchor` = "middle", `font-size` = px(size),
          `font-weight` = "600", fill = pal$accent,
          `letter-spacing` = px(size * ls_em)
        ), lines[i]))
      }
    }
  }

  out <- c(out, "</g>")

  # Hex border last so it sits above the bleeding plot.
  out <- c(out, svg_tag("polygon", list(
    points = hex_pts, fill = "none", stroke = pal$border,
    `stroke-width` = px(width * 0.022), `stroke-linejoin` = "round"
  )))
  out <- c(out, "</svg>")

  svg <- paste(out, collapse = "\n")
  if (!is.null(file)) {
    writeLines(svg, file, useBytes = TRUE)
    return(invisible(svg))
  }
  svg
}
