/* SPDX-FileCopyrightText: 2024 Google LLC */
/* SPDX-License-Identifier: Apache-2.0 */

#include "notification_jumboji_table.h"
#include "notification_layout.h"
#include "timeline_layout.h"

#include "applib/fonts/fonts.h"
#include "applib/graphics/gtypes.h"
#include "applib/graphics/text.h"
#include "apps/system_apps/timeline/peek_layer.h"
#include "font_resource_keys.auto.h"
#include "kernel/pbl_malloc.h"
#include "kernel/ui/kernel_ui.h"
#include "resource/resource_ids.auto.h"
#include "resource/timeline_resource_ids.auto.h"
#include "services/common/analytics/analytics.h"
#include "services/common/clock.h"
#include "services/common/clock.h"
#include "services/common/i18n/i18n.h"
#include "services/normal/blob_db/pin_db.h"
#include "services/normal/notifications/alerts_preferences_private.h"
#include "services/normal/timeline/timeline_resources.h"
#include "shell/system_theme.h"
#include "system/hexdump.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/math.h"
#include "util/size.h"
#include "util/string.h"
#include "util/trig.h"

#if !TINTIN_FORCE_FIT

// NOTIFICATION
// Title -> Sender/App
// Subtitle -> Subject (Emails)
// Body -> Body
// Footer -> Friendly Timestamp

// REMINDER
// Title -> Friendly Timestamp
// Subtitle -> NA
// Body -> Title
// Footer -> Location

#define LAYOUT_MAX_HEIGHT 2500
#define CARD_MARGIN PBL_IF_ROUND_ELSE(12, 10)
// All paddings relate to padding above the object unless othersize noted
#define CARD_BOTTOM_PADDING 18
// The y-position of a layout frame when its banner is peeking
#define BANNER_PEEK_STATIC_Y (DISP_ROWS - STATUS_BAR_LAYER_HEIGHT)
#define BOTTOM_BANNER_CIRCLE_RADIUS 8

static void prv_card_render(NotificationLayout *layout, GContext *ctx, bool render);
static const LayoutColors *prv_layout_get_colors(const LayoutLayer *layout);

static time_t prv_get_parent_timestamp(TimelineItem *reminder) {
  TimelineItem pin;
  if (S_SUCCESS != pin_db_get(&reminder->header.parent_id, &pin)) {
    return reminder->header.timestamp;
  }
  timeline_item_free_allocated_buffer(&pin);
  return pin.header.timestamp;
}

//////////////////////////////////////////
//  Card Mode
//////////////////////////////////////////

static const NotificationStyle s_notification_styles[NumPreferredContentSizes] = {
  [PreferredContentSizeSmall] = {
    .header_padding = 3,
    .title_padding = 3,
    .subtitle_upper_padding = PBL_IF_RECT_ELSE(1, 4),
    .subtitle_lower_padding = PBL_IF_RECT_ELSE(2, 1),
    .location_offset = PBL_IF_RECT_ELSE(3, 7),
    .location_margin = PBL_IF_RECT_ELSE(5, 9),
    .body_icon_offset = 3,
    .body_icon_margin = -5,
    .body_padding = PBL_IF_RECT_ELSE(1, 1),
#if PBL_ROUND
    .timestamp_upper_padding = 6,
    .timestamp_lower_padding = -3,
#else
    .timestamp_upper_padding = 3,
#endif
  },
  [PreferredContentSizeMedium] = {
#if PBL_ROUND
    .body_padding = 3,
    .subtitle_upper_padding = 3,
#endif
    .header_padding = 3,
    .title_padding = 3,
    .title_line_delta = -1,
    .subtitle_lower_padding = PBL_IF_RECT_ELSE(6, 2),
    .subtitle_line_delta = -1,
    .location_offset = PBL_IF_RECT_ELSE(-2, 6),
    .location_margin = PBL_IF_RECT_ELSE(3, 10),
    .body_icon_offset = 3,
    .body_icon_margin = -5,
    .body_line_delta = -1,
#if PBL_ROUND
    .timestamp_upper_padding = 6,
    .timestamp_lower_padding = -3,
#else
    .timestamp_upper_padding = 3
#endif
  },
  [PreferredContentSizeLarge] = {
    .title_offset_if_body_icon = -2,
    .subtitle_upper_padding = 2,
    .subtitle_lower_padding = PBL_IF_RECT_ELSE(4, 2),
    .subtitle_line_delta = -2,
    .location_offset = 6,
    .location_margin = 10,
    .body_icon_margin = -10,
    .body_padding = 2,
    .body_line_delta = -2,
#if PBL_ROUND
    .timestamp_upper_padding = 6,
#else
    .timestamp_upper_padding = 3,
#endif
  },
  [PreferredContentSizeExtraLarge] = {
    .subtitle_upper_padding = 2,
    .subtitle_lower_padding = 4,
    .subtitle_line_delta = -2,
    .location_offset = 6,
    .location_margin = 10,
    .body_icon_offset = 6,
    .body_icon_margin = -10,
    .body_line_delta = -2,
    .timestamp_upper_padding = 6,
  },
};

static bool prv_is_reminder(const NotificationLayout *layout) {
  return (layout->info.item->header.type == TimelineItemTypeReminder);
}

static void prv_reminder_timestamp_update(const LayoutLayer *layout_ref,
                                          const LayoutNodeTextDynamicConfig *config, char *buffer,
                                          bool render) {
  const NotificationLayout *layout = (NotificationLayout *)layout_ref;
  const int max_relative_hrs = 1;
  clock_get_until_time(buffer, config->buffer_size, prv_get_parent_timestamp(layout->info.item),
                       max_relative_hrs);
  const char *buffer_ptr = string_strip_leading_whitespace(buffer);
  memmove(buffer, buffer_ptr, config->buffer_size - (buffer_ptr - buffer));
}

static void prv_notification_timestamp_update(const LayoutLayer *layout_ref,
                                              const LayoutNodeTextDynamicConfig *config,
                                              char *buffer,
                                              bool render) {
  const NotificationLayout *layout = (NotificationLayout *)layout_ref;
  clock_get_since_time(buffer, config->buffer_size, layout->info.item->header.timestamp);
}

#if !PLATFORM_TINTIN
static const EmojiEntry s_emoji_table[] = JUMBOJI_TABLE(EMOJI_ENTRY);

static bool prv_each_emoji_codepoint(int index, Codepoint codepoint, void *context) {
  Codepoint *emoji_codepoint = context;
  if (codepoint_is_end_of_word(codepoint) ||
      codepoint_is_formatting_indicator(codepoint) ||
      codepoint_is_skin_tone_modifier(codepoint) ||
      codepoint_is_special(codepoint) ||
      codepoint_is_zero_width(codepoint) ||
      codepoint_should_skip(codepoint)) {
    // Skip this codepoint
    return true;
  } else if (codepoint_is_emoji(codepoint)) {
    if (*emoji_codepoint) {
      // This has more than one emoji
      goto fail;
    }
    // Found an emoji
    *emoji_codepoint = codepoint;
    return true;
  }
  // This is not an emoji-only string
fail:
  *emoji_codepoint = NULL_CODEPOINT;
  return false;
}

T_STATIC ResourceId prv_get_emoji_icon_by_string(const EmojiEntry *table, const char *str) {
  if (!str) {
    return INVALID_RESOURCE;
  }
  Codepoint emoji_codepoint = NULL_CODEPOINT;
  utf8_each_codepoint(str, prv_each_emoji_codepoint, &emoji_codepoint);
  for (unsigned int i = 0; i < ARRAY_LENGTH(s_emoji_table); i++) {
    const EmojiEntry *emoji = &s_emoji_table[i];
    if (emoji->codepoint == emoji_codepoint) {
      return emoji->resource_id;
    }
  }
  return INVALID_RESOURCE;
}

static ResourceId prv_get_emoji_icon(NotificationLayout *layout) {
  const char *body = attribute_get_string(layout->layout.attributes, AttributeIdBody, NULL);
  return prv_get_emoji_icon_by_string(s_emoji_table, body);
}

static bool prv_should_enlarge_emoji(NotificationLayout *layout) {
  return (!layout->info.show_notification_timestamp &&
          prv_get_emoji_icon(layout) != INVALID_RESOURCE);
}
#endif

//! Creates a GTextNode view node representing the inner content of the notification
//! @param layout NotificationLayout of the notification
//! @param use_body_icon Whether to display a body icon. Currently used by Jumboji
//! @return the GTextNode view node of the notification
static NOINLINE GTextNode *prv_create_view(NotificationLayout *layout, bool use_body_icon) {
  const NotificationStyle *style = &s_notification_styles[system_theme_get_content_size()];

  const bool is_reminder = prv_is_reminder(layout);
  const LayoutNodeTextAttributeConfig header_config = {
    .attr_id = AttributeIdAppName,
    .text.style_font = TextStyleFont_Header,
    .text.extent.offset.y = style->header_padding,
    .text.extent.margin.h = style->header_padding,
  };
  const LayoutNodeTextDynamicConfig notification_timestamp_config = {
    .text.extent.node.type = LayoutNodeType_TextDynamic,
    .update = prv_notification_timestamp_update,
    .buffer_size = TIME_STRING_REQUIRED_LENGTH,
    .text.style_font = PBL_IF_RECT_ELSE(TextStyleFont_Footer, TextStyleFont_Caption),
    .text.extent.offset.y = style->timestamp_upper_padding,
    .text.extent.margin.h = style->timestamp_upper_padding + style->timestamp_lower_padding,
  };
  const LayoutNodeTextDynamicConfig reminder_timestamp_config = {
    .text.extent.node.type = LayoutNodeType_TextDynamic,
    .update = prv_reminder_timestamp_update,
    .buffer_size = TIME_STRING_REQUIRED_LENGTH,
    .text.style_font = TextStyleFont_Header,
    .text.extent.offset.y = style->header_padding,
    .text.extent.margin.h = style->header_padding,
  };
  const LayoutNodeTextAttributeConfig title_config = {
    .attr_id = is_reminder ? AttributeIdUnused : AttributeIdTitle,
    .text.style_font = TextStyleFont_Header,
    .text.line_spacing_delta = style->title_line_delta,
    .text.alignment = use_body_icon ? LayoutTextAlignment_Center : LayoutTextAlignment_Auto,
    .text.extent.offset.y =
        style->title_padding + (use_body_icon ? style->title_offset_if_body_icon : 0),
    .text.extent.margin.h = style->title_padding,
  };
  const LayoutNodeTextAttributeConfig subtitle_config = {
    .attr_id = is_reminder ? AttributeIdTitle : AttributeIdSubtitle,
    .text.style_font = TextStyleFont_Title,
    .text.line_spacing_delta = style->subtitle_line_delta,
    .text.alignment = use_body_icon ? LayoutTextAlignment_Center : LayoutTextAlignment_Auto,
    .text.extent.offset.y = style->subtitle_upper_padding,
    .text.extent.margin.h = style->subtitle_upper_padding + style->subtitle_lower_padding,
  };
#if !PLATFORM_TINTIN
  const LayoutNodeIconConfig body_icon_config = {
    .extent.node.type = LayoutNodeType_Icon,
    .res_info = &(AppResourceInfo) {
      .res_app_num = SYSTEM_APP,
      .res_id = prv_get_emoji_icon(layout),
    },
    .align = GAlignCenter,
    .icon_layer = &layout->detail_icon_layer,
    .extent.offset.y = style->body_icon_offset,
    .extent.margin.h = style->body_icon_margin,
  };
#endif
  const LayoutNodeTextAttributeConfig location_config = {
    .attr_id = AttributeIdLocationName,
    .text.style_font = TextStyleFont_Footer,
    .text.extent.offset.y = style->location_offset,
    .text.extent.margin.h = style->location_margin,
  };
  const int reminder_body_line_delta = 0;
  const LayoutNodeTextAttributeConfig body_config = {
    .attr_id = AttributeIdBody,
    .text.style_font = is_reminder ? TextStyleFont_Caption : TextStyleFont_Body,
    .text.line_spacing_delta = is_reminder ? reminder_body_line_delta :
                                             style->body_line_delta,
    .text.extent.offset.y = style->body_padding,
    .text.extent.margin.h = style->body_padding,
  };
  const LayoutNodeHeadingsParagraphsConfig headings_paragraphs_node = {
    .extent.node.type = LayoutNodeType_HeadingsParagraphs,
    .extent.offset.y = 12,
    .extent.margin.h = 5,
    .heading_style_font = TextStyleFont_Header,
    .paragraph_style_font = TextStyleFont_Body,
  };
  const LayoutNodeConfig *reminder_timestamp_node_config = NULL;
  const LayoutNodeConfig *notification_timestamp_node_config = NULL;
  const LayoutNodeConfig *header_node_config = NULL;

  if (is_reminder) {
    reminder_timestamp_node_config = &reminder_timestamp_config.text.extent.node;
  } else {
    notification_timestamp_node_config = &notification_timestamp_config.text.extent.node;
    header_node_config = &header_config.text.extent.node;
  }
#if !PLATFORM_TINTIN
  if (!layout->info.show_notification_timestamp && PBL_IF_RECT_ELSE(use_body_icon, true)) {
    notification_timestamp_node_config = NULL;
  }
#endif
  const LayoutNodeConfig * const vertical_config_nodes[] = {
    reminder_timestamp_node_config,
#if PBL_ROUND
    notification_timestamp_node_config,
#endif
    header_node_config,
    &title_config.text.extent.node,
    &subtitle_config.text.extent.node,
    &location_config.text.extent.node,
#if PLATFORM_TINTIN
    &body_config.text.extent.node,
#else
    use_body_icon ? &body_icon_config.extent.node :
                    &body_config.text.extent.node,
#endif
    &headings_paragraphs_node.extent.node,
#if PBL_RECT
    notification_timestamp_node_config,
#endif
  };
  const LayoutNodeVerticalConfig vertical_config = {
    .container.extent.node.type = LayoutNodeType_Vertical,
    .container.num_nodes = ARRAY_LENGTH(vertical_config_nodes),
    .container.nodes = (LayoutNodeConfig **)&vertical_config_nodes,
  };
  return layout_create_text_node_from_config(&layout->layout,
                                             &vertical_config.container.extent.node);
}

static void prv_destroy_view(NotificationLayout *layout) {
  graphics_text_node_destroy(layout->view_node);
  layout->view_node = NULL;
#if !PLATFORM_TINTIN
  kino_layer_destroy(layout->detail_icon_layer);
  layout->detail_icon_layer = NULL;
#endif
}

//! Do common init related tasks
static void prv_card_init(NotificationLayout *layout, AttributeList *attributes,
                          const Uuid *app_id) {
  // init the icon
  const TimelineResourceId fallback_icon_id =
      notification_layout_get_fallback_icon_id(layout->info.item->header.type);
  const uint32_t timeline_res_id = attribute_get_uint32(attributes, AttributeIdIconTiny,
                                                        fallback_icon_id);
  const TimelineResourceInfo timeline_res = {
    .res_id = timeline_res_id,
    .app_id = app_id,
    .fallback_id = fallback_icon_id
  };
  timeline_resources_get_id(&timeline_res, TimelineResourceSizeTiny, &layout->icon_res_info);

  const GRect *frame = &layout->layout.layer.frame;
  const GSize icon_size = NOTIFICATION_TINY_RESOURCE_SIZE;
  const int16_t origin_x = frame->origin.x + (frame->size.w / 2) - (icon_size.w / 2);
  const int16_t origin_y = frame->origin.y + CARD_ICON_UPPER_PADDING;
  kino_layer_init(&layout->icon_layer, &GRect(origin_x, origin_y, icon_size.w, icon_size.h));
#if PBL_BW
  const bool use_alternative_design = alerts_preferences_get_notification_alternative_design();
  kino_layer_set_reel_with_resource_system(&layout->icon_layer, layout->icon_res_info.res_app_num,
                                           layout->icon_res_info.res_id, use_alternative_design);
#else
  kino_layer_set_reel_with_resource_system(&layout->icon_layer, layout->icon_res_info.res_app_num,
                                           layout->icon_res_info.res_id, false);
#endif
  layer_add_child(&layout->layout.layer, kino_layer_get_layer(&layout->icon_layer));
}

static void NOINLINE prv_init_view(NotificationLayout *layout) {
#if PLATFORM_TINTIN
  layout->view_node = prv_create_view(layout, false /* use_body_icon */);
#else
  const bool use_body_icon = prv_should_enlarge_emoji(layout);
  layout->view_node = prv_create_view(layout, use_body_icon);

  if (use_body_icon) {
    // Only calculate size if using a body icon, calculating size is stack expensive
    prv_card_render(layout, graphics_context_get_current_context(), false /* render */);

    if ((layout->view_size.h > (LAYOUT_HEIGHT + LAYOUT_ARROW_HEIGHT))) {
      // The large emoji won't fit in a single screen, so don't use the large emoji
      prv_destroy_view(layout);
      layout->view_node = prv_create_view(layout, false /* use_body_icon */);
    } else {
      analytics_inc(ANALYTICS_DEVICE_METRIC_NOTIFICATION_JUMBOJI_COUNT, AnalyticsClient_System);
    }
  }
#endif
}

#if PBL_ROUND
static void prv_hide_or_show_banner_icon(KinoLayer *icon_layer,
                                         const GRect *notification_layout_frame) {
  const int32_t frame_too_high_for_icon_threshold = -2;
  const int32_t top_banner_not_visible_threshold = 18;
  const bool icon_hidden =
      (notification_layout_frame->origin.y < frame_too_high_for_icon_threshold) ||
      (notification_layout_frame->origin.y > top_banner_not_visible_threshold);
  layer_set_hidden(&icon_layer->layer, icon_hidden);
}
#endif

#if PBL_ROUND
static CONST_FUNC int32_t prv_interpolate_linear(int32_t out_min, int32_t out_max, int32_t in_min,
                                                 int32_t in_max, int32_t progress) {
  return out_min + (out_max - out_min) * (progress - in_min) / (in_max - in_min);
}

static void prv_draw_banner_round(NotificationLayout *notification_layout, GContext *ctx,
                                  const GRect *const notification_layout_frame,
                                  LayoutColors colors) {
  // We use DISP_ROWS and DISP_COLS instead of the layer's frame or bounds because the
  // notification layout's frame is not the same size as the display
  const int32_t half_screen_width = DISP_COLS / 2;
  graphics_context_set_fill_color(ctx, colors.bg_color);
  const int32_t saved_clip_box_size_h = ctx->draw_state.clip_box.size.h;
  const int32_t saved_clip_box_origin_y = ctx->draw_state.clip_box.origin.y;
  ctx->draw_state.clip_box.origin.y =
      MAX(ctx->draw_state.clip_box.origin.y - STATUS_BAR_LAYER_HEIGHT, 0);
  ctx->draw_state.clip_box.size.h = DISP_ROWS;
  grect_clip(&ctx->draw_state.clip_box, &DISP_FRAME);

  const int32_t banner_movement_raw_offset =
      CLIP(BANNER_PEEK_STATIC_Y - notification_layout_frame->origin.y,
           0, BANNER_PEEK_STATIC_Y);
  const int32_t banner_radius = prv_interpolate_linear(BOTTOM_BANNER_CIRCLE_RADIUS,
                                                       BANNER_CIRCLE_RADIUS,
                                                       0, BANNER_PEEK_STATIC_Y,
                                                       banner_movement_raw_offset);
  const int32_t banner_diameter = banner_radius * 2;
  const int32_t banner_center_y = prv_interpolate_linear(0, LAYOUT_TOP_BANNER_ORIGIN_Y,
                                                         0, BANNER_PEEK_STATIC_Y,
                                                         banner_movement_raw_offset);
  const GRect banner_frame = GRect(half_screen_width - banner_radius,
                                   banner_center_y - banner_radius,
                                   banner_diameter, banner_diameter);
  graphics_fill_oval(ctx, banner_frame, GOvalScaleModeFitCircle);
  ctx->draw_state.clip_box.origin.y = saved_clip_box_origin_y;
  ctx->draw_state.clip_box.size.h = saved_clip_box_size_h;
}
#endif

static NOINLINE void prv_card_render_internal(NotificationLayout *layout, GContext *ctx,
                                              bool render) {
#if PBL_ROUND
  const int orig_clip_height = ctx->draw_state.clip_box.size.h;
  const GRect *notification_layout_frame = &layout->layout.layer.frame;
#endif

  // get layout colors and fill in the banner at the top
  if (render) {
    const LayoutColors *colors = prv_layout_get_colors((LayoutLayer *)layout);
    graphics_context_set_fill_color(ctx, colors->bg_color);

#if PBL_ROUND
    prv_hide_or_show_banner_icon(&layout->icon_layer, notification_layout_frame);
    prv_draw_banner_round(layout, ctx, notification_layout_frame, *colors);
    // work around the clip box and smaller layout_height for circular text paging
    ctx->draw_state.clip_box.size.h = MIN(ctx->draw_state.clip_box.size.h, LAYOUT_HEIGHT);
#else
    static const GRect banner_box = { .size = { DISP_COLS, LAYOUT_BANNER_HEIGHT_RECT } };
    graphics_fill_rect(ctx, &banner_box);
#endif
  }

#if PBL_ROUND
  const bool text_visible =
      (render && WITHIN(notification_layout_frame->origin.y,
                        TEXT_VISIBLE_LOWER_THRESHOLD(notification_layout_frame->size.h),
                        TEXT_VISIBLE_UPPER_THRESHOLD));
#else
  const bool text_visible = render;
#endif
  static const GRect box = {
    .origin = { CARD_MARGIN, LAYOUT_TOP_BANNER_HEIGHT },
    .size = { DISP_COLS - 2 * CARD_MARGIN, LAYOUT_MAX_HEIGHT },
  };
  static const GRect page_frame_on_screen = {
    .origin = { 0, STATUS_BAR_LAYER_HEIGHT },
    .size = { DISP_COLS, DISP_ROWS - STATUS_BAR_LAYER_HEIGHT - LAYOUT_ARROW_HEIGHT }
  };
  static const GTextNodeDrawConfig config = {
    .page_frame = &page_frame_on_screen,
    .origin_on_screen = &page_frame_on_screen.origin,
    .content_inset = 8, // text flow inset
    .text_flow = PBL_IF_ROUND_ELSE(true, false),
    .paging = PBL_IF_ROUND_ELSE(true, false),
  };
  graphics_context_set_text_color(ctx, GColorBlack);
  (text_visible ? graphics_text_node_draw :
                  graphics_text_node_get_size)(layout->view_node, ctx, &box, &config,
                                               &layout->view_size);

#if PBL_ROUND
  if (render) {
    // restore original clip box
    ctx->draw_state.clip_box.size.h = orig_clip_height;
  }
#endif

  layout->view_size.h += LAYOUT_TOP_BANNER_HEIGHT;

#if PBL_ROUND
  // Notification text is paged by LAYOUT_HEIGHT, so make full page height
  layout->view_size.h = ROUND_TO_MOD_CEIL(layout->view_size.h, LAYOUT_HEIGHT);
  // Notifications are swapped using frame height, so last page includes additional arrow height
  layout->view_size.h += LAYOUT_ARROW_HEIGHT;
#else
  layout->view_size.h += CARD_BOTTOM_PADDING;
#endif
}

static void prv_card_render(NotificationLayout *layout, GContext *ctx, bool render) {
  if (!layout->view_node) {
    prv_init_view(layout);
  }
  prv_card_render_internal(layout, ctx, render);
}

//////////////////////////////////////////
// LayoutLayer API
//////////////////////////////////////////

static void prv_layout_update_proc(Layer *layer, GContext *ctx) {
  NotificationLayout *layout = (NotificationLayout *)layer;
  switch (layout->layout.mode) {
    case LayoutLayerModeCard:
      prv_card_render(layout, ctx, true);
      break;
    default:
      break;
  }
}

static void prv_layout_init(NotificationLayout *layout, const LayoutLayerConfig *config);

LayoutLayer *notification_layout_create(const LayoutLayerConfig *config) {
  NotificationLayout *layout = task_zalloc_check(sizeof(NotificationLayout));
  if (!layout) {
    return NULL;
  }
  prv_layout_init(layout, config);
  return (LayoutLayer *)layout;
}

bool notification_layout_verify(bool existing_attributes[]) {
  return existing_attributes[AttributeIdTitle];
}

static void prv_layout_init_colors(NotificationLayout *notification_layout) {
  LayoutColors *colors = &notification_layout->colors;
  
#if PBL_BW
  const bool use_alternative_design = alerts_preferences_get_notification_alternative_design();
  if (use_alternative_design) {
    *colors = (LayoutColors){
        .primary_color = GColorWhite,
        .secondary_color = GColorBlack,
        .bg_color = GColorBlack,
    };
  } else {
    *colors = (LayoutColors){
        .primary_color = GColorBlack,
        .secondary_color = GColorBlack,
        .bg_color = GColorLightGray,
    };
  }
#else
  *colors = (LayoutColors){
      .primary_color = GColorBlack,
      .secondary_color = GColorBlack,
      .bg_color = GColorLightGray,
  };
#endif

#if PBL_COLOR
  const bool is_notification =
      (notification_layout->info.item->header.type == TimelineItemTypeNotification);
  const GColor default_bg_color = is_notification ? DEFAULT_NOTIFICATION_COLOR :
                                                    DEFAULT_REMINDER_COLOR;

  LayoutLayer *layout = &notification_layout->layout;
  colors->bg_color = (GColor) attribute_get_uint8(layout->attributes, AttributeIdBgColor,
                                                  default_bg_color.argb);
  colors->primary_color = (GColor) attribute_get_uint8(layout->attributes, AttributeIdPrimaryColor,
                                                       GColorBlack.argb);
#endif
}

static const LayoutColors *prv_layout_get_colors(const LayoutLayer *layout_ref) {
  return &((NotificationLayout *)layout_ref)->colors;
}

static void *prv_layout_get_context(LayoutLayer *layout) {
  NotificationLayout *notification_layout = (NotificationLayout *)layout;
  return (void *)notification_layout->info.item;
}

static GSize prv_layout_get_content_size(GContext *ctx, LayoutLayer *layout_ref) {
  NotificationLayout *layout = (NotificationLayout *)layout_ref;
  if (layout->view_size.h == 0) {
    prv_card_render(layout, graphics_context_get_current_context(), false);
  }
  return layout->view_size;
}

static void prv_layout_destroy(LayoutLayer *layout) {
  NotificationLayout *notification_layout = (NotificationLayout *)layout;
  prv_destroy_view(notification_layout);
  kino_layer_deinit(&notification_layout->icon_layer);
  task_free(notification_layout);
}

static void prv_layout_init(NotificationLayout *layout, const LayoutLayerConfig *config) {
  NotificationLayoutInfo *layout_info = config->context;
  static const LayoutLayerImpl s_layout_layer_impl = {
    .size_getter = prv_layout_get_content_size,
    .destructor = prv_layout_destroy,
#if PBL_COLOR
    .color_getter = prv_layout_get_colors,
#endif
    .context_getter = prv_layout_get_context,
  };
  // init the layout struct
  layout->layout = (LayoutLayer) {
    .mode = config->mode,
    .attributes = config->attributes,
    .impl = &s_layout_layer_impl,
  };
  layout->info = *layout_info;

  // init the layer in the layout
  layer_init(&layout->layout.layer, config->frame);
  layer_set_update_proc(&layout->layout.layer, prv_layout_update_proc);
#if PBL_ROUND
  layer_set_clips(&layout->layout.layer, false);
#endif

  prv_layout_init_colors(layout);

  switch (layout->layout.mode) {
    case LayoutLayerModeCard:
      prv_card_init(layout, config->attributes, config->app_id);
      break;
    default:
      break;
  }

  layer_mark_dirty(&(layout->layout.layer));
}
#else
LayoutLayer *notification_layout_create(const LayoutLayerConfig *config) { return NULL; }

bool notification_layout_verify(bool existing_attributes[]) { return false; }
#endif

TimelineResourceId notification_layout_get_fallback_icon_id(TimelineItemType item_type) {
  return (item_type == TimelineItemTypeNotification) ? NOTIF_FALLBACK_ICON :
                                                       REMINDER_FALLBACK_ICON;
}
