
# --- Shared constants and helpers ---
DIGITS <- as.character(0:9)

# Read a #N tag starting at position i in character vector chars.
# Returns list(tag, tag_num, next_i).
read_tag <- function(chars, i) {
  tag <- "#"
  i <- i + 1L
  while (i <= length(chars) && chars[i] %in% DIGITS) {
    tag <- paste0(tag, chars[i])
    i <- i + 1L
  }
  list(tag = tag, tag_num = as.integer(sub("#", "", tag)), next_i = i)
}

# --- Helper: parse a type string into a structured list ---
parse_type <- function(type_str) {
  if (is.na(type_str) || type_str == "") {
    return(list(is_operator = FALSE, word_type = NA_character_))
  }
  s <- type_str
  s <- gsub("x", "\u2612", s, fixed = TRUE)
  s <- gsub("->", "\u2192", s, fixed = TRUE)

  if (!grepl("\u2612", s, fixed = TRUE)) {
    return(list(is_operator = FALSE, word_type = s))
  }

  parts <- strsplit(s, "\u2612", fixed = TRUE)[[1]]
  left_arg <- parts[1]

  arrow_parts <- strsplit(parts[2], "\u2192", fixed = TRUE)[[1]]
  right_arg <- arrow_parts[1]
  result    <- arrow_parts[2]

  list(
    is_operator = TRUE,
    left_arg    = left_arg,
    right_arg   = right_arg,
    result      = result
  )
}

# --- Helper: determine the result type of a parsed type ---
get_result_type <- function(parsed) {
  if (parsed$is_operator) parsed$result else parsed$word_type
}

# --- Helper: tokenize a concatenated type string into type tokens ---
# Parses e.g. "SSEN" into c("S", "SEN"), longest match first.
tokenize_types <- function(s) {
  if (nchar(s) == 0) return(character(0))
  known <- c("SEN", "S", "A", "V")
  tokens <- character(0)
  pos <- 1L
  n <- nchar(s)
  while (pos <= n) {
    matched <- FALSE
    for (k in known) {
      end <- pos + nchar(k) - 1L
      if (end <= n && substr(s, pos, end) == k) {
        tokens <- c(tokens, k)
        pos <- end + 1L
        matched <- TRUE
        break
      }
    }
    if (!matched)
      stop(paste0("Unknown type token at position ", pos, " in '", s, "'"))
  }
  tokens
}

# --- Shared rule definitions ---

# Extract the base type of a type string (uppercase prefix).
# E.g. "Vt" -> "V", "So" -> "S", "SEN" -> "SEN", "Vi" -> "V".
base_type <- function(x) {
  sub("[a-z]+$", "", x)
}

# Check whether x has base type y (i.e. the uppercase prefix of x equals y).
has_base_type <- function(x, y) {
  if (is.na(x)) return(FALSE)
  identical(base_type(x), y)
}

# Check if a type matches an expected type (NA-safe).
# If expected is a pure base type (no lowercase suffix), any extension
# of that base type matches (e.g. "Vt" matches "V").
# If expected is an extended type (has lowercase suffix), only an exact
# match counts (e.g. "Vt" matches "Vt" but "V" does not).
type_matches <- function(actual, expected) {
  if (is.na(actual) || is.na(expected)) return(FALSE)
  if (grepl("[a-z]", expected)) {
    actual == expected
  } else {
    has_base_type(actual, expected)
  }
}

# Composition rules: (type1, type2) -> result.
# Used by determine_group_type and add_brackets.
# Note: add_brackets adds separator conditions to S+S rules.
# More specific rules (extended types) must precede general rules.
COMP_RULES <- list(
  list(t1 = "S",   t2 = "A",   result = "S"),
  list(t1 = "S",   t2 = "S",   result = "S"),
  list(t1 = "S",   t2 = "Vt",  result = "Vi"),
  list(t1 = "S",   t2 = "Vi",  result = "SEN"),
  list(t1 = "S",   t2 = "V",   result = "SEN"),
  list(t1 = "SEN", t2 = "SEN", result = "SEN")
)


# --- Helper: build named args list from elements ---
#
# Derives placeholder names from the element base types.  Base types
# appearing once get a plain name (S, A, V, SEN); base types appearing
# more than once get indexed names (S1, S2, ...).
#
# Each arg is c(name, translation, original_type).  The original
# (possibly extended) type is preserved for type inheritance in
# eval_operator.
#
# @param elements List of c(type_str, translation) vectors
# @return List of c(name, translation, original_type) vectors
build_args <- function(elements) {
  types      <- vapply(elements, `[`, character(1), 1)
  base_types <- vapply(types, base_type, character(1), USE.NAMES = FALSE)
  freq       <- table(base_types)
  counter    <- integer(0)
  args       <- vector("list", length(elements))
  for (i in seq_along(elements)) {
    tok <- base_types[i]
    if (freq[[tok]] > 1L) {
      counter[tok] <- if (is.na(counter[tok])) 1L else counter[tok] + 1L
      name <- paste0(tok, counter[tok])
    } else {
      name <- tok
    }
    args[[i]] <- c(name, elements[[i]][2], types[i])
  }
  args
}


compose_skeleton_entry <- function(df) {
  n <- nrow(df)
  if (n < 2) return("No children to compose")

  for (i in 2:n) {
    if (is.na(df$type[i]) || df$type[i] == "")
      return(paste0("Missing type for: ", df$expr[i]))
    if (is.na(df$translation[i]) || df$translation[i] == "")
      return(paste0("Missing translation for: ", df$expr[i]))
  }

  # Step a) Build structure string
  structure_str <- df$expr[1]
  for (i in 2:n) {
    structure_str <- sub(df$expr[i], paste0("#", i - 1), structure_str, fixed = TRUE)
  }
  # Remove token-group wrappers: <> angle brackets (from raw input) and
  # {} curly braces that wrap a single #N tag (artifact of <token> -> {}
  # conversion in mark_skeleton_entries/extract_skeleton_entries pipeline).
  # Multi-child groups like {#1#2} are preserved (user-defined groupings).
  structure_str <- gsub("[<>]", "", structure_str)
  structure_str <- gsub("\\{(#[0-9]+)\\}", "\\1", structure_str)

  # Child type and translation vectors
  child_type <- df$type[-1]
  translations <- df$translation[-1]

  # Validate: operator types must have matching placeholder in translation
  for (i in seq_along(child_type)) {
    parsed <- parse_type(child_type[i])
    if (!parsed$is_operator) next

    raw <- translations[i]
    m <- regmatches(raw, regexpr("(?<=\\{)[^}]+(?=\\})", raw, perl = TRUE))
    check_trans <- if (length(m) > 0 && nchar(m) > 0) m else raw

    # Collect expected placeholder types from operator arguments
    placeholders <- unique(c(
      tokenize_types(parsed$left_arg),
      tokenize_types(parsed$right_arg)
    ))

    found <- any(vapply(placeholders, function(ph)
      grepl(paste0("\\b", ph, "[0-9]*\\b"), check_trans), logical(1)))

    if (!found) {
      return(paste0("Missing placeholder in translation of ", df$expr[i + 1],
                     " (", child_type[i], "): expected '",
                     paste(unique(placeholders), collapse = "' or '"),
                     "' in '", raw, "'"))
    }
  }

  # Validate: Vt translations must contain placeholder S
  for (i in seq_along(child_type)) {
    if (!identical(child_type[i], "Vt")) next
    raw <- translations[i]
    m <- regmatches(raw, regexpr("(?<=\\{)[^}]+(?=\\})", raw, perl = TRUE))
    check_trans <- if (length(m) > 0 && nchar(m) > 0) m else raw
    if (!grepl("\\bS\\b", check_trans)) {
      return(paste0("Missing placeholder in translation of ", df$expr[i + 1],
                     " (Vt): expected 'S' in '", raw, "'"))
    }
  }

  # Step b) Add brackets
  res_b <- add_brackets(structure_str, child_type)
  if (identical(res_b$type, "ERROR")) return(res_b$string)

  # Step c) Apply translation rules
  result <- apply_translation_rules(res_b$string, child_type, translations)

  if (length(result) == 1) return(result)

  df$type[1]        <- result[1]
  df$translation[1] <- result[2]
  df
}
