/* open_memstream() is POSIX (2008), not C99 - needs this feature-test
 * macro under -std=c99 (glibc and Android's bionic both support it from
 * a plain #define, no libc version gate below Android API 23, well
 * under this app's minSdk 24). */
#define _POSIX_C_SOURCE 200809L

#include "reading_json.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "../../../../../engine/src/i18n.h"
#include "../../../../../engine/src/reading.h"
#include "../../../../../interpreter/src/guidance.h"
#include "../../../../../interpreter/src/narrative.h"
#include "../../../../../interpreter/src/significance.h"

/* Same convention as engine/src/main.c's own default_i18n_path() -
 * duplicated rather than shared, same reasoning CLAUDE.md gives for why
 * engine/src/main.c and interpreter/src/main.c each keep their own copy
 * (the binaries - here, shared libraries - are never linked together). */
static void i18n_path(const char *dir, const char *lang, char *out, size_t out_size) {
  snprintf(out, out_size, "%s/%s.lang", dir, lang);
}

static const char *const k_day_level_slug[DAY_SIGNIFICANCE_COUNT] = {
  "quiet", "notable", "significant", "major",
};

static const char *day_level_name(DaySignificance level) {
  switch (level) {
    case DAY_QUIET: return i18n_get("interp.day_level.quiet", "Quiet");
    case DAY_NOTABLE: return i18n_get("interp.day_level.notable", "Notable");
    case DAY_SIGNIFICANT: return i18n_get("interp.day_level.significant", "Significant");
    case DAY_MAJOR: return i18n_get("interp.day_level.major", "Major");
    default: return "Unknown";
  }
}

/* "Transiting Saturn Square natal Sun" - the same title text
 * interpreter/src/main.c's --format text prints per transit item, built
 * here (rather than left to Kotlin) since it's fully localized via
 * astro_body_name()/astro_aspect_name() - Kotlin never needs its own
 * planet/aspect display-name table. */
static char *build_transit_title(const Aspect *a) {
  char buf[160];
  snprintf(buf, sizeof buf, "%s %s %s %s %s", i18n_get("ui.transiting", "Transiting"),
           astro_body_name(a->transiting_planet), astro_aspect_name(a->type),
           i18n_get("ui.natal", "natal"), astro_body_name(a->natal_planet));
  size_t len = strlen(buf);
  char *out = malloc(len + 1);
  memcpy(out, buf, len + 1);
  return out;
}

static char *capture_narrative(const Aspect *aspect, int house) {
  char stack_buf[NARRATIVE_TEXT_MAX];
  narrative_write(stack_buf, sizeof stack_buf, "", aspect, house);
  size_t len = strlen(stack_buf);
  if (len > 0 && stack_buf[len - 1] == '\n') stack_buf[--len] = '\0';
  char *buf = malloc(len + 1);
  memcpy(buf, stack_buf, len + 1);
  return buf;
}

static char *capture_guidance(const DailyInterpretation *interp, const CelticCrossSpread *spread) {
  char stack_buf[GUIDANCE_TEXT_MAX];
  guidance_write(stack_buf, sizeof stack_buf, "", interp, spread);
  size_t len = strlen(stack_buf);
  if (len > 0 && stack_buf[len - 1] == '\n') stack_buf[--len] = '\0';
  char *buf = malloc(len + 1);
  memcpy(buf, stack_buf, len + 1);
  return buf;
}

static void json_string(FILE *out, const char *s) {
  fputc('"', out);
  for (; *s; s++) {
    unsigned char c = (unsigned char)*s;
    switch (c) {
      case '"': fputs("\\\"", out); break;
      case '\\': fputs("\\\\", out); break;
      case '\n': fputs("\\n", out); break;
      case '\r': fputs("\\r", out); break;
      case '\t': fputs("\\t", out); break;
      default:
        if (c < 0x20) fprintf(out, "\\u%04x", c);
        else fputc((char)c, out);
    }
  }
  fputc('"', out);
}

static void format_date(time_t utc_moment, char *out, size_t out_size) {
  struct tm *utc = gmtime(&utc_moment);
  snprintf(out, out_size, "%04d-%02d-%02d", utc->tm_year + 1900, utc->tm_mon + 1, utc->tm_mday);
}

int reading_json_generate(const BirthData *birth, Gender gender, time_t utc_moment,
                           const char *i18n_dir, const char *lang, char **out_json) {
  char path[512];
  i18n_path(i18n_dir, lang, path, sizeof path);
  i18n_load(path); /* non-fatal on failure - falls back to built-in English, same as engine/src/main.c */

  /* The tarot seed is today's UTC date, same convention
   * dist/run-engine.sh and watch/src/c/reading_state.c use. */
  char seed[16];
  format_date(utc_moment, seed, sizeof seed);

  DailyReading reading;
  reading_generate(seed, birth, gender, utc_moment, &reading);

  DailyInterpretation interp;
  interpret_daily_reading(&reading, &interp);

  size_t json_len = 0;
  char *json_buf = NULL;
  FILE *out = open_memstream(&json_buf, &json_len);
  if (!out) return 1;

  fprintf(out, "{\n");

  char date_buf[32];
  format_date(reading.utc_moment, date_buf, sizeof date_buf);
  fprintf(out, "  \"date\": ");
  json_string(out, date_buf);
  fprintf(out, ",\n  \"gender\": ");
  json_string(out, tarot_gender_slug(reading.gender));
  fprintf(out, ",\n");

  fprintf(out, "  \"day_significance\": {\n");
  fprintf(out, "    \"level_slug\": \"%s\",\n", k_day_level_slug[interp.day_level]);
  fprintf(out, "    \"level_label\": ");
  json_string(out, day_level_name(interp.day_level));
  fprintf(out, ",\n    \"rank\": %d,\n    \"count\": %d,\n    \"deserves_framing\": %s\n",
          interp.day_level + 1, DAY_SIGNIFICANCE_COUNT,
          interpretation_deserves_framing(&interp) ? "true" : "false");
  fprintf(out, "  },\n");

  fprintf(out, "  \"transits\": [\n");
  for (int i = 0; i < interp.top_item_count; i++) {
    const SignificantItem *item = &interp.top_items[i];
    const Aspect *a = &item->aspect;
    const PlanetPosition *tp = &reading.transits.bodies[a->transiting_planet];
    char *title = build_transit_title(a);
    char *narrative = capture_narrative(a, tp->house);

    fprintf(out, "    {\n");
    fprintf(out, "      \"title\": ");
    json_string(out, title);
    fprintf(out, ",\n      \"transiting_planet_slug\": \"%s\",\n", astro_body_slug(a->transiting_planet));
    fprintf(out, "      \"natal_planet_slug\": \"%s\",\n", astro_body_slug(a->natal_planet));
    fprintf(out, "      \"aspect_slug\": \"%s\",\n", astro_aspect_slug(a->type));
    fprintf(out, "      \"orb\": %.4f,\n", a->orb);
    fprintf(out, "      \"score\": %.4f,\n", item->score);
    fprintf(out, "      \"narrative\": ");
    json_string(out, narrative);
    fprintf(out, "\n    }%s\n", i + 1 < interp.top_item_count ? "," : "");

    free(title);
    free(narrative);
  }
  fprintf(out, "  ],\n");

  char *guidance = capture_guidance(&interp, &reading.spread);
  fprintf(out, "  \"guidance\": ");
  json_string(out, guidance);
  fprintf(out, ",\n");
  free(guidance);

  fprintf(out, "  \"spread\": [\n");
  for (int i = 0; i < TAROT_SPREAD_SIZE; i++) {
    CelticCrossPosition position = (CelticCrossPosition)i;
    const TarotDraw *draw = &reading.spread.positions[i];

    fprintf(out, "    {\n");
    fprintf(out, "      \"position_slug\": \"%s\",\n", tarot_position_slug(position));
    fprintf(out, "      \"position_name\": ");
    json_string(out, tarot_position_name(position, reading.gender));
    fprintf(out, ",\n      \"position_description\": ");
    json_string(out, tarot_position_description(position, reading.gender));
    fprintf(out, ",\n      \"card_slug\": \"%s\",\n", tarot_card_slug(draw->card));
    fprintf(out, "      \"card_name\": ");
    json_string(out, tarot_card_name(draw->card));
    fprintf(out, ",\n      \"reversed\": %s,\n", draw->reversed ? "true" : "false");
    fprintf(out, "      \"card_meaning\": ");
    json_string(out, tarot_card_meaning(draw->card, draw->reversed));
    fprintf(out, "\n    }%s\n", i + 1 < TAROT_SPREAD_SIZE ? "," : "");
  }
  fprintf(out, "  ]\n");

  fprintf(out, "}\n");
  fclose(out);

  *out_json = json_buf;
  return 0;
}
