#include "guidance.h"

/* i18n_get() only - see narrative.c's own comment on why this one extra
 * engine header is needed despite guidance.h's otherwise header-only
 * engine dependency. */
#include "../../engine/src/i18n.h"

/* Slugs for building "guidance.planet.<slug>"/i18n keys - mirrors
 * reading_io.c's own k_body_slug, same duplication policy (see
 * significance.c's comment for why each independently-testable module
 * keeps its own small table rather than linking astro.c). */
static const char *const k_body_slug[NUM_BODIES] = {
  "sun", "moon", "mercury", "venus", "mars",
  "jupiter", "saturn", "uranus", "neptune", "pluto",
};

/* English fallback only - "the Sun"/"the Moon" take a definite article
 * that the other eight planets don't; see engine/i18n/en.lang's own
 * comment on the guidance.planet.* keys this backs. */
static const char *const k_planet_name_fallback[NUM_BODIES] = {
  "the Sun", "the Moon", "Mercury", "Venus", "Mars",
  "Jupiter", "Saturn", "Uranus", "Neptune", "Pluto",
};

static const char *planet_display_name(Body body) {
  char key[32];
  snprintf(key, sizeof key, "guidance.planet.%s", k_body_slug[body]);
  return i18n_get(key, k_planet_name_fallback[body]);
}

typedef enum {
  CHAR_HARMONIOUS = 0,
  CHAR_DISCORDANT,
  CHAR_NEUTRAL,
  NUM_TRANSIT_CHARACTERS
} TransitCharacter;

typedef struct {
  const char *slug;       /* builds "guidance.stance.<slug>.*" keys */
  const char *upright;    /* Attitude card fell upright (English fallback) */
  const char *reversed;   /* Attitude card fell reversed (English fallback) */
} AttitudeStance;

/* Original guidance text for this project - see guidance.h's doc
 * comment for why. Fallback (English) text only; German translations
 * live in engine/i18n/de.lang under the matching "guidance.*" keys.
 * Indexed by TransitCharacter. */
static const AttitudeStance k_stance[NUM_TRANSIT_CHARACTERS] = {
  [CHAR_HARMONIOUS] = {
    "harmonious",
    "You're already meeting the day in the right spirit - lean into "
    "it rather than second-guessing a favorable position.",
    "The day itself is working in your favor even though you don't "
    "feel settled yet - trust the momentum more than your current "
    "doubts.",
  },
  [CHAR_DISCORDANT] = {
    "discordant",
    "Your instincts are sound, but the day is testing them - hold "
    "your position without forcing the issue.",
    "Both the day and your own footing are unsettled right now - "
    "this is a moment to steady yourself before pushing forward.",
  },
  [CHAR_NEUTRAL] = {
    "neutral",
    "Today concentrates whatever you already bring to it - your "
    "current approach will be amplified, so make sure it's the one "
    "you want.",
    "Today intensifies things, including whatever is currently "
    "unresolved in your own approach - worth sorting out before "
    "acting.",
  },
};

static TransitCharacter classify_transit(AspectType type) {
  if (type == ASPECT_TRINE || type == ASPECT_SEXTILE) return CHAR_HARMONIOUS;
  if (type == ASPECT_SQUARE || type == ASPECT_OPPOSITION) return CHAR_DISCORDANT;
  return CHAR_NEUTRAL;
}

static const char *stance_text(TransitCharacter character, bool reversed) {
  const AttitudeStance *s = &k_stance[character];
  char key[48];
  snprintf(key, sizeof key, "guidance.stance.%s.%s", s->slug, reversed ? "reversed" : "upright");
  return i18n_get(key, reversed ? s->reversed : s->upright);
}

/* How to introduce the top transit, tailored to how strongly it's
 * scoring (interp->day_level) - independent of the stance sentence
 * itself (k_stance, above), which is written to stay true regardless of
 * intensity. Each entry takes exactly one %s: the transiting planet's
 * display name, always as the sentence's grammatical subject (matters
 * for German, where the other three day levels put the planet name in
 * non-nominative position in a naive word-for-word translation - see
 * engine/i18n/de.lang's guidance.intro.* entries, which are phrased to
 * avoid that). Fallback (English) text only. */
static const char *const k_intro_slug[DAY_SIGNIFICANCE_COUNT] = {
  [DAY_QUIET] = "quiet",
  [DAY_NOTABLE] = "notable",
  [DAY_SIGNIFICANT] = "significant",
  [DAY_MAJOR] = "major",
};

static const char *const k_intro_fallback[DAY_SIGNIFICANCE_COUNT] = {
  [DAY_QUIET] = "%s is only faintly active today, but for what it's worth:",
  [DAY_NOTABLE] = "With a mild touch from %s today:",
  [DAY_SIGNIFICANT] = "With %s clearly active today:",
  [DAY_MAJOR] = "With %s as today's dominant influence:",
};

static const char *intro_template(DaySignificance level) {
  char key[32];
  snprintf(key, sizeof key, "guidance.intro.%s", k_intro_slug[level]);
  return i18n_get(key, k_intro_fallback[level]);
}

/* Fallback for a day with no aspects in orb at all (top_item_count ==
 * 0, always DAY_QUIET) - there's no transiting planet to name, so the
 * guidance falls back to the Attitude card's orientation alone. */
static const char *no_transit_text(bool reversed) {
  static const char *const upright =
    "There's no standout transit today - it's an astrologically quiet "
    "day. That leaves things mostly about steadiness: trust your "
    "current footing and use the calm to make headway.";
  static const char *const reversed_text =
    "There's no standout transit today - it's an astrologically quiet "
    "day. That's a good moment to quietly re-settle your own "
    "footing, since nothing external is forcing the pace.";
  return i18n_get(reversed ? "guidance.no_transit.reversed" : "guidance.no_transit.upright",
                   reversed ? reversed_text : upright);
}

static const char *reversed_marker(bool reversed) {
  static char buf[32];
  if (!reversed) return "";
  snprintf(buf, sizeof buf, " %s", i18n_get("ui.reversed", "(Reversed)"));
  return buf;
}

void guidance_print(FILE *out, const char *indent, const DailyInterpretation *interp,
                     const CelticCrossSpread *spread) {
  const TarotDraw *attitude = &spread->positions[POSITION_ATTITUDE];
  const TarotDraw *outcome = &spread->positions[POSITION_OUTCOME];

  if (interp->top_item_count == 0) {
    fprintf(out, "%s%s\n", indent, no_transit_text(attitude->reversed));
  } else {
    const Aspect *top = &interp->top_items[0].aspect;
    TransitCharacter character = classify_transit(top->type);
    const char *stance = stance_text(character, attitude->reversed);

    char intro[192];
    snprintf(intro, sizeof(intro), intro_template(interp->day_level),
             planet_display_name(top->transiting_planet));
    fprintf(out, "%s%s %s\n", indent, intro, stance);
  }

  fprintf(out, "%s%s %s%s: %s\n", indent, i18n_get("guidance.attitude_intro", "Your Attitude card is"),
          tarot_card_name(attitude->card), reversed_marker(attitude->reversed),
          tarot_card_meaning(attitude->card, attitude->reversed));
  fprintf(out, "%s%s %s%s: %s\n", indent, i18n_get("guidance.outcome_intro", "The spread's likely Outcome is"),
          tarot_card_name(outcome->card), reversed_marker(outcome->reversed),
          tarot_card_meaning(outcome->card, outcome->reversed));
}
