#include "ui_report_window.h"

#include <pebble.h>

#include "../../../engine/src/i18n.h"
#include "../../../interpreter/src/guidance.h"
#include "../../../interpreter/src/narrative.h"
#include "reading_state.h"
#include "resource_map.h"

#define MARGIN 6

/* Matches gen_card_images.py's own CARD_WIDTH - a hardware-verified
 * ceiling, not a design choice: full-display-width images reliably
 * crashed the real PNG decoder ("PNG memory allocation failed", a
 * firmware-level constraint independent of this app's own heap budget,
 * confirmed unaffected by lazy-loading only one image at a time). */
#define IMAGE_WIDTH 72

/* fonts_get_system_font() needs Pebble's runtime initialized, so these
 * are resolved once in window_load() rather than at static-init time. */
static GFont s_font_section;
static GFont s_font_subhead;
static GFont s_font_body;

/* Static buffers for every dynamically-built string this window shows -
 * tarot_position_description()/tarot_card_meaning()/i18n_get() fallback
 * text all point straight at flash/ROM data (see i18n.h's own comment on
 * i18n_load_table()), so only the pieces actually assembled at runtime
 * (via snprintf/narrative_write/guidance_write) need their own storage.
 * Static rather than malloc'd - fixed, known-in-advance sizes, and it
 * keeps report_window_reload() from having to reason about freeing the
 * previous run's buffers before reusing them. */
static char s_reading_date[48];
static char s_day_title[64];
static char s_transit_titles[MAX_SIGNIFICANT_ITEMS][96];
static char s_narratives[MAX_SIGNIFICANT_ITEMS][NARRATIVE_TEXT_MAX];
static char s_position_titles[TAROT_SPREAD_SIZE][96];
static char s_guidance[GUIDANCE_TEXT_MAX];

/* Loads/frees each position's GBitmap as scroll_offset_changed() sees it
 * enter/leave the visible viewport, rather than decoding all ten Celtic
 * Cross images up front - a defensive habit left over from an earlier,
 * larger IMAGE_WIDTH (see that constant's own comment); at 72px this
 * struct's images are small enough that it's no longer strictly
 * necessary, but it's harmless to keep. */
typedef struct {
  TarotCard card;
  int16_t y;
  int16_t height;
  BitmapLayer *bitmap_layer;
  GBitmap *bitmap;
} ImageSlot;

static ImageSlot s_slots[TAROT_SPREAD_SIZE];

/* Attitude (0) and Outcome (1) preview slots, drawn at half screen
 * width each so the two sit side by side - the BitmapLayer's own bounds
 * (narrower than IMAGE_WIDTH on 144px-wide platforms) clip the centered
 * image automatically, same as GAlignCenter already does for the
 * full-width slots above, no separate scaling logic needed. */
#define REPEAT_SLOT_COUNT 2
static ImageSlot s_repeat_slots[REPEAT_SLOT_COUNT];

#define MAX_TEXT_LAYERS 50
static TextLayer *s_text_layers[MAX_TEXT_LAYERS];
static int s_text_layer_count;

static Window *s_window;
static ScrollLayer *s_scroll_layer;
static int16_t s_screen_height;

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 "";
  }
}

static const char *reversed_marker(bool reversed) {
  return reversed ? i18n_get("ui.reversed", "(Reversed)") : "";
}

static int16_t add_text_at(int16_t x, int16_t width, int16_t y, const char *text, GFont font) {
  TextLayer *tl = text_layer_create(GRect(x, y, width, 4000));
  text_layer_set_font(tl, font);
  text_layer_set_text(tl, text);
  text_layer_set_overflow_mode(tl, GTextOverflowModeWordWrap);

  GSize size = text_layer_get_content_size(tl);
  size.w = width;
  size.h += 4;
  text_layer_set_size(tl, size);

  scroll_layer_add_child(s_scroll_layer, text_layer_get_layer(tl));
  s_text_layers[s_text_layer_count++] = tl;
  return y + size.h + MARGIN;
}

static int16_t add_text(int16_t width, int16_t y, const char *text, GFont font) {
  return add_text_at(0, width, y, text, font);
}

static void teardown_content(void) {
  for (int i = 0; i < s_text_layer_count; i++) {
    text_layer_destroy(s_text_layers[i]);
  }
  s_text_layer_count = 0;

  for (int i = 0; i < TAROT_SPREAD_SIZE; i++) {
    if (s_slots[i].bitmap) {
      gbitmap_destroy(s_slots[i].bitmap);
      s_slots[i].bitmap = NULL;
    }
    if (s_slots[i].bitmap_layer) {
      bitmap_layer_destroy(s_slots[i].bitmap_layer);
      s_slots[i].bitmap_layer = NULL;
    }
  }

  for (int i = 0; i < REPEAT_SLOT_COUNT; i++) {
    if (s_repeat_slots[i].bitmap) {
      gbitmap_destroy(s_repeat_slots[i].bitmap);
      s_repeat_slots[i].bitmap = NULL;
    }
    if (s_repeat_slots[i].bitmap_layer) {
      bitmap_layer_destroy(s_repeat_slots[i].bitmap_layer);
      s_repeat_slots[i].bitmap_layer = NULL;
    }
  }
}

static void update_slot_visibility(ImageSlot *slot, int16_t visible_top, int16_t visible_bottom) {
  bool visible = slot->y < visible_bottom && slot->y + slot->height > visible_top;

  if (visible && !slot->bitmap) {
    slot->bitmap = gbitmap_create_with_resource(card_resource_id(slot->card));
    bitmap_layer_set_bitmap(slot->bitmap_layer, slot->bitmap);
  } else if (!visible && slot->bitmap) {
    bitmap_layer_set_bitmap(slot->bitmap_layer, NULL);
    gbitmap_destroy(slot->bitmap);
    slot->bitmap = NULL;
  }
}

static void update_visible_images(void) {
  GPoint offset = scroll_layer_get_content_offset(s_scroll_layer);
  int16_t visible_top = -offset.y;
  int16_t visible_bottom = visible_top + s_screen_height;

  for (int i = 0; i < TAROT_SPREAD_SIZE; i++) {
    update_slot_visibility(&s_slots[i], visible_top, visible_bottom);
  }
  for (int i = 0; i < REPEAT_SLOT_COUNT; i++) {
    update_slot_visibility(&s_repeat_slots[i], visible_top, visible_bottom);
  }
}

static void scroll_offset_changed(ScrollLayer *scroll_layer, void *context) {
  update_visible_images();
}

static void build_content(int16_t width) {
  const DailyReading *reading = reading_state_get_reading();
  const DailyInterpretation *interp = reading_state_get_interpretation();

  int16_t y = MARGIN;

  /* The day this reading is for (reading->utc_moment, set by
   * reading_generate() - see reading_state_recompute()), not necessarily
   * "today" if the report is viewed right at a UTC-midnight boundary
   * before the next recompute. Plain gmtime(), same justification as
   * reading_state.c's own use of it. */
  struct tm *utc = gmtime(&reading->utc_moment);
  snprintf(s_reading_date, sizeof s_reading_date, "%s %04d-%02d-%02d",
           i18n_get("interp.reading_date", "Reading for:"), utc->tm_year + 1900,
           utc->tm_mon + 1, utc->tm_mday);
  y = add_text(width, y, s_reading_date, s_font_body);

  y = add_text(width, y, i18n_get("interp.heading_day_significance", "Day Significance"), s_font_section);
  snprintf(s_day_title, sizeof s_day_title, "%s (%d/%d)", day_level_name(interp->day_level),
           interp->day_level + 1, DAY_SIGNIFICANCE_COUNT);
  y = add_text(width, y, s_day_title, s_font_body);

  /* Build every transit's title + narrative up front (used both for the
   * repeated top-item line below and the full list further down), so
   * neither buffer is computed twice for item 0. */
  if (interp->top_item_count > 0) {
    for (int i = 0; i < interp->top_item_count; i++) {
      const Aspect *a = &interp->top_items[i].aspect;
      snprintf(s_transit_titles[i], sizeof s_transit_titles[i], "%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));
      narrative_write(s_narratives[i], sizeof s_narratives[i], "", a,
                       reading->transits.bodies[a->transiting_planet].house);
    }
  }

  y = add_text(width, y, i18n_get("interp.heading_guidance", "Guidance"), s_font_section);

  /* Attitude/Outcome preview, side by side, image only - no per-card
   * caption text. A "Position: Card Name (Reversed)" label doesn't fit
   * a half-width column without wrapping into 3+ short lines, and with
   * both columns wrapping independently the reader ends up reading
   * fragments across columns rather than down one column at a time
   * (confirmed on-device: it reads as one garbled interleaved sentence,
   * not two). Captions are unnecessary anyway - the guidance text
   * printed immediately below already names and quotes both cards in
   * full ("Deine Haltungskarte ist ..." / "Das wahrscheinliche Ergebnis
   * ... ist ..."), and both reappear with their full position/name/
   * meaning again in the Celtic Cross walkthrough further down. */
  {
    int16_t half_w = width / 2;
    const TarotDraw *attitude = &reading->spread.positions[POSITION_ATTITUDE];
    const TarotDraw *outcome = &reading->spread.positions[POSITION_OUTCOME];
    int16_t img_height = card_image_height_for_width(IMAGE_WIDTH);

    s_repeat_slots[0].card = attitude->card;
    s_repeat_slots[0].y = y;
    s_repeat_slots[0].height = img_height;
    s_repeat_slots[0].bitmap_layer = bitmap_layer_create(GRect(0, y, half_w, img_height));
    bitmap_layer_set_alignment(s_repeat_slots[0].bitmap_layer, GAlignCenter);
    scroll_layer_add_child(s_scroll_layer, bitmap_layer_get_layer(s_repeat_slots[0].bitmap_layer));

    s_repeat_slots[1].card = outcome->card;
    s_repeat_slots[1].y = y;
    s_repeat_slots[1].height = img_height;
    s_repeat_slots[1].bitmap_layer =
        bitmap_layer_create(GRect(half_w, y, width - half_w, img_height));
    bitmap_layer_set_alignment(s_repeat_slots[1].bitmap_layer, GAlignCenter);
    scroll_layer_add_child(s_scroll_layer, bitmap_layer_get_layer(s_repeat_slots[1].bitmap_layer));

    y += img_height + MARGIN;
  }

  if (interp->top_item_count > 0) {
    y = add_text(width, y, s_transit_titles[0], s_font_subhead);
    y = add_text(width, y, s_narratives[0], s_font_body);
  }
  guidance_write(s_guidance, sizeof s_guidance, "", interp, &reading->spread);
  y = add_text(width, y, s_guidance, s_font_body);

  y = add_text(width, y, i18n_get("interp.heading_transits", "Significant Transits"), s_font_section);
  if (interp->top_item_count == 0) {
    y = add_text(width, y, i18n_get("interp.no_notable_transits", "No notable transits today."), s_font_body);
  } else {
    for (int i = 0; i < interp->top_item_count; i++) {
      y = add_text(width, y, s_transit_titles[i], s_font_subhead);
      y = add_text(width, y, s_narratives[i], s_font_body);
    }
  }

  y = add_text(width, y, i18n_get("ui.celtic_cross", "Celtic Cross"), s_font_section);
  for (int i = 0; i < TAROT_SPREAD_SIZE; i++) {
    const TarotDraw *draw = &reading->spread.positions[i];

    snprintf(s_position_titles[i], sizeof s_position_titles[i], "%s: %s %s",
             tarot_position_name((CelticCrossPosition)i, reading->gender), tarot_card_name(draw->card),
             reversed_marker(draw->reversed));
    y = add_text(width, y, s_position_titles[i], s_font_subhead);

    s_slots[i].card = draw->card;
    s_slots[i].y = y;
    s_slots[i].height = card_image_height_for_width(IMAGE_WIDTH);
    int16_t image_x = (width - IMAGE_WIDTH) / 2;
    s_slots[i].bitmap_layer = bitmap_layer_create(GRect(image_x, y, IMAGE_WIDTH, s_slots[i].height));
    bitmap_layer_set_alignment(s_slots[i].bitmap_layer, GAlignCenter);
    scroll_layer_add_child(s_scroll_layer, bitmap_layer_get_layer(s_slots[i].bitmap_layer));
    y += s_slots[i].height + MARGIN;

    y = add_text(width, y, tarot_position_description((CelticCrossPosition)i, reading->gender), s_font_body);
    y = add_text(width, y, tarot_card_meaning(draw->card, draw->reversed), s_font_body);
  }

  scroll_layer_set_content_size(s_scroll_layer, GSize(width, y));
}

static void window_load(Window *window) {
  s_font_section = fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD);
  s_font_subhead = fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD);
  s_font_body = fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD);

  Layer *window_layer = window_get_root_layer(window);
  GRect bounds = layer_get_bounds(window_layer);
  s_screen_height = bounds.size.h;

  s_scroll_layer = scroll_layer_create(bounds);
  scroll_layer_set_click_config_onto_window(s_scroll_layer, window);
  scroll_layer_set_callbacks(s_scroll_layer, (ScrollLayerCallbacks){
    .content_offset_changed_handler = scroll_offset_changed,
  });
  layer_add_child(window_layer, scroll_layer_get_layer(s_scroll_layer));

  build_content(bounds.size.w);
  update_visible_images();
}

static void window_unload(Window *window) {
  teardown_content();
  scroll_layer_destroy(s_scroll_layer);
  s_scroll_layer = NULL;
}

void report_window_push(void) {
  if (!s_window) {
    s_window = window_create();
    window_set_window_handlers(s_window, (WindowHandlers){
      .load = window_load,
      .unload = window_unload,
    });
  }
  window_stack_push(s_window, true);
}

void report_window_reload(void) {
  if (!s_window || !s_scroll_layer) return;
  Layer *window_layer = window_get_root_layer(s_window);
  int16_t width = layer_get_bounds(window_layer).size.w;
  teardown_content();
  build_content(width);
  update_visible_images();
}

void report_window_deinit(void) {
  if (s_window) window_destroy(s_window);
  s_window = NULL;
}
