module Invidious::Comments
  extend self

  def fetch_youtube(id, cursor, format, locale, thin_mode, region, sort_by = "top")
    case cursor
    when nil, ""
      ctoken = Comments.produce_continuation(id, cursor: "", sort_by: sort_by)
    when .starts_with? "ADSJ"
      ctoken = Comments.produce_continuation(id, cursor: cursor, sort_by: sort_by)
    else
      ctoken = cursor
    end

    client_config = YoutubeAPI::ClientConfig.new(region: region)
    response = YoutubeAPI.next(continuation: ctoken, client_config: client_config)
    return parse_youtube(id, response, format, locale, thin_mode, sort_by)
  end

  def fetch_community_post_comments(ucid, post_id)
    object = {
      "2:string"    => "community",
      "25:embedded" => {
        "22:string" => post_id,
      },
      "45:embedded" => {
        "2:varint" => 1_i64,
        "3:varint" => 1_i64,
      },
      "53:embedded" => {
        "4:embedded" => {
          "6:varint"  => 0_i64,
          "27:varint" => 1_i64,
          "29:string" => post_id,
          "30:string" => ucid,
        },
        "8:string" => "comments-section",
      },
    }

    object_parsed = object.try { |i| Protodec::Any.cast_json(i) }
      .try { |i| Protodec::Any.from_json(i) }
      .try { |i| Base64.urlsafe_encode(i) }

    object2 = {
      "80226972:embedded" => {
        "2:string" => ucid,
        "3:string" => object_parsed,
      },
    }

    continuation = object2.try { |i| Protodec::Any.cast_json(i) }
      .try { |i| Protodec::Any.from_json(i) }
      .try { |i| Base64.urlsafe_encode(i) }
      .try { |i| URI.encode_www_form(i) }

    initial_data = YoutubeAPI.browse(continuation: continuation)
    return initial_data
  end

  def parse_youtube(id, response, format, locale, thin_mode, sort_by = "top", isPost = false)
    contents = nil

    if on_response_received_endpoints = response["onResponseReceivedEndpoints"]?
      header = nil
      on_response_received_endpoints.as_a.each do |item|
        if item["reloadContinuationItemsCommand"]?
          case item["reloadContinuationItemsCommand"]["slot"]
          when "RELOAD_CONTINUATION_SLOT_HEADER"
            header = item["reloadContinuationItemsCommand"]["continuationItems"][0]
          when "RELOAD_CONTINUATION_SLOT_BODY"
            # continuationItems is nil when video has no comments
            contents = item["reloadContinuationItemsCommand"]["continuationItems"]?
          end
        elsif item["appendContinuationItemsAction"]?
          contents = item["appendContinuationItemsAction"]["continuationItems"]
        end
      end
    elsif response["continuationContents"]?
      response = response["continuationContents"]
      if response["commentRepliesContinuation"]?
        body = response["commentRepliesContinuation"]
      else
        body = response["itemSectionContinuation"]
      end
      contents = body["contents"]?
      header = body["header"]?
    else
      raise NotFoundException.new("Comments not found.")
    end

    if !contents
      if format == "json"
        return {"comments" => [] of String}.to_json
      else
        return {"contentHtml" => "", "commentCount" => 0}.to_json
      end
    end

    continuation_item_renderer = nil
    contents.as_a.reject! do |item|
      if item["continuationItemRenderer"]?
        continuation_item_renderer = item["continuationItemRenderer"]
        true
      end
    end

    response = JSON.build do |json|
      json.object do
        if header
          count_text = header["commentsHeaderRenderer"]["countText"]
          comment_count = (count_text["simpleText"]? || count_text["runs"]?.try &.[0]?.try &.["text"]?)
            .try &.as_s.gsub(/\D/, "").to_i? || 0
          json.field "commentCount", comment_count
        end

        if isPost
          json.field "postId", id
        else
          json.field "videoId", id
        end

        json.field "comments" do
          json.array do
            contents.as_a.each do |node|
              json.object do
                if node["commentThreadRenderer"]?
                  node = node["commentThreadRenderer"]
                end

                if node["replies"]?
                  node_replies = node["replies"]["commentRepliesRenderer"]
                end

                if node["comment"]?
                  node_comment = node["comment"]["commentRenderer"]
                else
                  node_comment = node["commentRenderer"]
                end

                content_html = node_comment["contentText"]?.try { |t| parse_content(t, id) } || ""
                author = node_comment["authorText"]?.try &.["simpleText"]? || ""

                json.field "verified", (node_comment["authorCommentBadge"]? != nil)

                json.field "author", author
                json.field "authorThumbnails" do
                  json.array do
                    node_comment["authorThumbnail"]["thumbnails"].as_a.each do |thumbnail|
                      json.object do
                        json.field "url", thumbnail["url"]
                        json.field "width", thumbnail["width"]
                        json.field "height", thumbnail["height"]
                      end
                    end
                  end
                end

                if node_comment["authorEndpoint"]?
                  json.field "authorId", node_comment["authorEndpoint"]["browseEndpoint"]["browseId"]
                  json.field "authorUrl", node_comment["authorEndpoint"]["browseEndpoint"]["canonicalBaseUrl"]
                else
                  json.field "authorId", ""
                  json.field "authorUrl", ""
                end

                published_text = node_comment["publishedTimeText"]["runs"][0]["text"].as_s
                published = decode_date(published_text.rchop(" (edited)"))

                if published_text.includes?(" (edited)")
                  json.field "isEdited", true
                else
                  json.field "isEdited", false
                end

                json.field "content", html_to_content(content_html)
                json.field "contentHtml", content_html

                json.field "isPinned", (node_comment["pinnedCommentBadge"]? != nil)
                json.field "isSponsor", (node_comment["sponsorCommentBadge"]? != nil)
                if node_comment["sponsorCommentBadge"]?
                  # Sponsor icon thumbnails always have one object and there's only ever the url property in it
                  json.field "sponsorIconUrl", node_comment.dig("sponsorCommentBadge", "sponsorCommentBadgeRenderer", "customBadge", "thumbnails", 0, "url").to_s
                end
                json.field "published", published.to_unix
                json.field "publishedText", translate(locale, "`x` ago", recode_date(published, locale))

                comment_action_buttons_renderer = node_comment["actionButtons"]["commentActionButtonsRenderer"]

                json.field "likeCount", comment_action_buttons_renderer["likeButton"]["toggleButtonRenderer"]["accessibilityData"]["accessibilityData"]["label"].as_s.scan(/\d/).map(&.[0]).join.to_i
                json.field "commentId", node_comment["commentId"]
                json.field "authorIsChannelOwner", node_comment["authorIsChannelOwner"]

                if comment_action_buttons_renderer["creatorHeart"]?
                  hearth_data = comment_action_buttons_renderer["creatorHeart"]["creatorHeartRenderer"]["creatorThumbnail"]
                  json.field "creatorHeart" do
                    json.object do
                      json.field "creatorThumbnail", hearth_data["thumbnails"][-1]["url"]
                      json.field "creatorName", hearth_data["accessibility"]["accessibilityData"]["label"]
                    end
                  end
                end

                if node_replies && !response["commentRepliesContinuation"]?
                  if node_replies["continuations"]?
                    continuation = node_replies["continuations"]?.try &.as_a[0]["nextContinuationData"]["continuation"].as_s
                  elsif node_replies["contents"]?
                    continuation = node_replies["contents"]?.try &.as_a[0]["continuationItemRenderer"]["continuationEndpoint"]["continuationCommand"]["token"].as_s
                  end
                  continuation ||= ""

                  json.field "replies" do
                    json.object do
                      json.field "replyCount", node_comment["replyCount"]? || 1
                      json.field "continuation", continuation
                    end
                  end
                end
              end
            end
          end
        end

        if continuation_item_renderer
          if continuation_item_renderer["continuationEndpoint"]?
            continuation_endpoint = continuation_item_renderer["continuationEndpoint"]
          elsif continuation_item_renderer["button"]?
            continuation_endpoint = continuation_item_renderer["button"]["buttonRenderer"]["command"]
          end
          if continuation_endpoint
            json.field "continuation", continuation_endpoint["continuationCommand"]["token"].as_s
          end
        end
      end
    end

    if format == "html"
      response = JSON.parse(response)
      content_html = Frontend::Comments.template_youtube(response, locale, thin_mode)

      response = JSON.build do |json|
        json.object do
          json.field "contentHtml", content_html

          if response["commentCount"]?
            json.field "commentCount", response["commentCount"]
          else
            json.field "commentCount", 0
          end
        end
      end
    end

    return response
  end

  def produce_continuation(video_id, cursor = "", sort_by = "top")
    object = {
      "2:embedded" => {
        "2:string"    => video_id,
        "25:varint"   => 0_i64,
        "28:varint"   => 1_i64,
        "36:embedded" => {
          "5:varint" => -1_i64,
          "8:varint" => 0_i64,
        },
        "40:embedded" => {
          "1:varint" => 4_i64,
          "3:string" => "https://www.youtube.com",
          "4:string" => "",
        },
      },
      "3:varint"   => 6_i64,
      "6:embedded" => {
        "1:string"   => cursor,
        "4:embedded" => {
          "4:string" => video_id,
          "6:varint" => 0_i64,
        },
        "5:varint" => 20_i64,
      },
    }

    case sort_by
    when "top"
      object["6:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 0_i64
    when "new", "newest"
      object["6:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 1_i64
    else # top
      object["6:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 0_i64
    end

    continuation = object.try { |i| Protodec::Any.cast_json(i) }
      .try { |i| Protodec::Any.from_json(i) }
      .try { |i| Base64.urlsafe_encode(i) }
      .try { |i| URI.encode_www_form(i) }

    return continuation
  end
end
