#import "CustomTagRenderer.h"
#import "ASTNodeWrapper.h"
#import "RenderContext.h"
#import "StyleAttributes.h"
#import "StyleConfig.h"

#import <CoreText/CoreText.h>

NSString *const MarkdownCustomTagKey = @"MarkdownCustomTag";
NSString *const MarkdownCustomTagPropsKey = @"MarkdownCustomTagProps";
NSString *const MarkdownSpoilerRangeKey = @"MarkdownSpoilerRange";
NSString *const MarkdownSpoilerIsBlockKey = @"MarkdownSpoilerIsBlock";
NSString *const MarkdownMentionKey = @"MarkdownMention";

// Built-in mention tags.
static NSString *const kUserMentionTag = @"UserMention";
static NSString *const kChannelMentionTag = @"ChannelMention";
static NSString *const kCommandTag = @"Command";

static NSString *const kSpoilerTag = @"Spoiler";
static NSString *const kSuperscriptTag = @"Superscript";

@implementation CustomTagRenderer

- (void)renderNode:(ASTNodeWrapper *)node
              into:(NSMutableAttributedString *)output
           context:(RenderContext *)context {
  NSString *tag = node.tagName;

  if ([tag isEqualToString:kUserMentionTag]) {
    [self renderMentionNode:node
                       type:@"user"
                     prefix:@"@"
                      style:context.styleConfig.mentionUser
                       into:output
                    context:context];
  } else if ([tag isEqualToString:kChannelMentionTag]) {
    [self renderMentionNode:node
                       type:@"channel"
                     prefix:@"#"
                      style:context.styleConfig.mentionChannel
                       into:output
                    context:context];
  } else if ([tag isEqualToString:kCommandTag]) {
    [self renderMentionNode:node
                       type:@"command"
                     prefix:@"/"
                      style:context.styleConfig.mentionCommand
                       into:output
                    context:context];
  } else if ([tag isEqualToString:kSpoilerTag]) {
    [self renderSpoiler:node into:output context:context];
  } else if ([tag isEqualToString:kSuperscriptTag]) {
    [self renderSuperscript:node into:output context:context];
  } else {
    [self renderGenericTag:node into:output context:context];
  }
}

#pragma mark - Mentions

- (void)renderMentionNode:(ASTNodeWrapper *)node
                     type:(NSString *)type
                   prefix:(NSString *)prefix
                    style:(MarkdownElementStyle *)style
                     into:(NSMutableAttributedString *)output
                  context:(RenderContext *)context {
  NSDictionary<NSString *, NSString *> *tagProps = node.tagProps;
  NSString *mentionId = tagProps[@"id"] ?: @"";
  NSString *mentionName = tagProps[@"name"] ?: @"";

  // Everything beyond id/name is an "extra prop" passed through to
  // onMentionPress.
  NSMutableDictionary<NSString *, NSString *> *extras =
      [NSMutableDictionary new];
  for (NSString *key in tagProps) {
    if ([key isEqualToString:@"id"] || [key isEqualToString:@"name"]) {
      continue;
    }
    extras[key] = tagProps[key] ?: @"";
  }

  // The data that onMentionPress will receive, stored directly on
  // the attributed string — no URL round-trip needed.
  NSDictionary *mentionData = @{
    @"type" : type,
    @"id" : mentionId,
    @"name" : mentionName,
    @"props" : [extras copy],
  };

  // Visual attrs come from the matching style key
  // (mentionUser/mentionChannel/mentionCommand).
  NSMutableDictionary *attrs = [context.currentAttributes mutableCopy];
  [StyleAttributes applyStyle:style toAttrs:attrs];

  attrs[MarkdownCustomTagKey] = node.tagName;
  attrs[MarkdownCustomTagPropsKey] = tagProps;
  attrs[MarkdownMentionKey] = mentionData;
  // Intentionally no NSLinkAttributeName here — we don't want
  // UITextView to treat mention ranges as links (long-press menu,
  // drag-to-drop, accessibility as link, etc.). Tap handling is
  // provided by MarkdownMentionOverlay which installs pressable
  // overlay views over the mention rects after layout.

  // Display the prefix plus the name (or id as a fallback when name
  // is missing — typical for command mentions like `/help` that
  // don't carry a separate label).
  NSString *label = mentionName.length > 0 ? mentionName : mentionId;
  NSString *displayText = [prefix stringByAppendingString:label];

  [output appendAttributedString:
      [[NSAttributedString alloc] initWithString:displayText attributes:attrs]];
}

#pragma mark - Spoiler

- (void)renderSpoiler:(ASTNodeWrapper *)node
                 into:(NSMutableAttributedString *)output
              context:(RenderContext *)context {
  NSMutableDictionary *attrs = [context.currentAttributes mutableCopy];

  // Mark the range as a spoiler — the overlay system will cover it.
  // Use the character offset as a stable ID so reveal state persists
  // across re-renders of the same markdown (random UUIDs would reset
  // every time).
  NSString *spoilerId =
      [NSString stringWithFormat:@"spoiler_%lu", (unsigned long)output.length];
  attrs[MarkdownSpoilerRangeKey] = spoilerId;
  attrs[MarkdownCustomTagKey] = kSpoilerTag;

  [context pushAttributes:attrs];
  [context renderChildren:node into:output];
  [context popAttributes];
}

#pragma mark - Superscript

- (void)renderSuperscript:(ASTNodeWrapper *)node
                     into:(NSMutableAttributedString *)output
                  context:(RenderContext *)context {
  MarkdownElementStyle *style = context.styleConfig.superscript;
  NSMutableDictionary *attrs = [context.currentAttributes mutableCopy];

  // Apply user style first — color, font family, etc.
  [StyleAttributes applyStyle:style toAttrs:attrs];

  // Use kCTSuperscriptAttributeName to activate the font's native
  // superscript variant (OpenType `sups` feature). If the font
  // doesn't have dedicated superscript glyphs, Core Text
  // automatically falls back to scaling + baseline-shifting, so
  // this works universally — but looks best with fonts that ship
  // a proper superscript variant.
  attrs[(NSString *)kCTSuperscriptAttributeName] = @1;

  attrs[MarkdownCustomTagKey] = kSuperscriptTag;

  [context pushAttributes:attrs];
  [context renderChildren:node into:output];
  [context popAttributes];
}

#pragma mark - Generic fallback

- (void)renderGenericTag:(ASTNodeWrapper *)node
                    into:(NSMutableAttributedString *)output
                 context:(RenderContext *)context {
  NSMutableDictionary *attrs = [context.currentAttributes mutableCopy];
  attrs[MarkdownCustomTagKey] = node.tagName;
  attrs[MarkdownCustomTagPropsKey] = node.tagProps;

  [context pushAttributes:attrs];
  [context renderChildren:node into:output];
  [context popAttributes];
}

@end
