class Post
  toString: -> @ID

  constructor: (root, @thread, @board, flags={}) ->
    <% if (readJSON('/.tests_enabled')) { %>
    @normalizedOriginal = Test.normalize root
    <% } %>

    $.extend @, flags
    @ID       = +root.id.match(/\d*$/)[0]
    @postID   = @ID
    @threadID = @thread.ID
    @boardID  = @board.ID
    @siteID   = g.SITE.ID
    @fullID   = "#{@board}.#{@ID}"
    @context  = @
    @isReply  = (@ID isnt @threadID)

    root.dataset.fullID = @fullID

    @nodes = @parseNodes root

    if not @isReply
      @thread.OP = @
      for key in ['isSticky', 'isClosed', 'isArchived']
        if (selector = g.SITE.selectors.icons[key])
          @thread[key] = !!$(selector, @nodes.info)
      if @thread.isArchived
        @thread.isClosed = true
        @thread.kill()

    @info =
      subject:   @nodes.subject?.textContent or undefined
      name:      @nodes.name?.textContent
      email:     if @nodes.email then decodeURIComponent(@nodes.email.href.replace(/^mailto:/, ''))
      tripcode:  @nodes.tripcode?.textContent
      uniqueID:  @nodes.uniqueID?.textContent
      capcode:   @nodes.capcode?.textContent.replace '## ', ''
      pass:      @nodes.pass?.title.match(/\d*$/)[0]
      flagCode:  @nodes.flag?.className.match(/flag-(\w+)/)?[1].toUpperCase()
      flagCodeTroll: @nodes.flag?.src?.match(/(\w+)\.gif$/)?[1].toUpperCase()
      flag:      @nodes.flag?.title
      date:      if @nodes.date then g.SITE.parseDate(@nodes.date)

    if Conf['Anonymize']
      @info.nameBlock = 'Anonymous'
    else
      @info.nameBlock = "#{@info.name or ''} #{@info.tripcode or ''}".trim()
    @info.nameBlock += " ## #{@info.capcode}"     if @info.capcode
    @info.nameBlock += " (ID: #{@info.uniqueID})" if @info.uniqueID

    @parseComment()
    @parseQuotes()
    @parseFiles()

    @isDead   = false
    @isHidden = false

    @clones = []
    <% if (readJSON('/.tests_enabled')) { %>
    return if @forBuildTest
    <% } %>
    if g.posts.get(@fullID)
      @isRebuilt = true
      @clones = g.posts.get(@fullID).clones
      clone.origin = @ for clone in @clones

    @thread.lastPost = @ID if !@isFetchedQuote and @ID > @thread.lastPost
    @board.posts.push  @ID, @
    @thread.posts.push @ID, @
    g.posts.push   @fullID, @

  parseNodes: (root) ->
    s = g.SITE.selectors
    post = $(s.post, root) or root
    info = $ s.infoRoot, post
    nodes =
      root:       root
      bottom:     if @isReply or !g.SITE.isOPContainerThread then root else $(s.opBottom, root)
      post:       post
      info:       info
      comment:    $ s.comment, post
      quotelinks: []
      archivelinks: []
      embedlinks:   []
    for key, selector of s.info
      nodes[key] = $ selector, info
    g.SITE.parseNodes?(@, nodes)
    nodes.uniqueIDRoot or= nodes.uniqueID

    # XXX Edge invalidates HTMLCollections when an ancestor node is inserted into another node.
    # https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7560353/
    if $.engine is 'edge'
      Object.defineProperty nodes, 'backlinks',
        configurable: true
        enumerable:   true
        get: -> post.getElementsByClassName 'backlink'
    else
      nodes.backlinks = post.getElementsByClassName 'backlink'

    nodes

  parseComment: ->
    # Merge text nodes and remove empty ones.
    @nodes.comment.normalize()

    # Get the comment's text.
    # <br> -> \n
    # Remove:
    #   'Comment too long'...
    #   EXIF data. (/p/)
    @nodes.commentClean = bq = @nodes.comment.cloneNode true
    g.SITE.cleanComment?(bq)
    @info.comment = @nodesToText bq

  commentDisplay: ->
    # Get the comment's text for display purposes (e.g. notifications, excerpts).
    # In addition to what's done in generating `@info.comment`, remove:
    #   Spoilers. (filter to '[spoiler]')
    #   Rolls. (/tg/, /qst/)
    #   Fortunes. (/s4s/)
    #   Preceding and following new lines.
    #   Trailing spaces.
    bq = @nodes.commentClean.cloneNode true
    @cleanSpoilers bq unless Conf['Remove Spoilers'] or Conf['Reveal Spoilers']
    g.SITE.cleanCommentDisplay?(bq)
    @nodesToText(bq).trim().replace(/\s+$/gm, '')

  commentOrig: ->
    # Get the comment's text for reposting purposes.
    bq = @nodes.commentClean.cloneNode true
    g.SITE.insertTags?(bq)
    @nodesToText bq

  nodesToText: (bq) ->
    text = ""
    nodes = $.X './/br|.//text()', bq
    i = 0
    while node = nodes.snapshotItem i++
      text += node.data or '\n'
    text

  cleanSpoilers: (bq) ->
    spoilers = $$ g.SITE.selectors.spoiler, bq
    for node in spoilers
      $.replace node, $.tn '[spoiler]'
    return

  parseQuotes: ->
    @quotes = []
    for quotelink in $$ g.SITE.selectors.quotelink, @nodes.comment
      @parseQuote quotelink
    return

  parseQuote: (quotelink) ->
    # Only add quotes that link to posts on an imageboard.
    # Don't add:
    #  - board links. (>>>/b/)
    #  - catalog links. (>>>/b/catalog or >>>/b/search)
    #  - rules links. (>>>/a/rules)
    #  - text-board quotelinks. (>>>/img/1234)
    match = quotelink.href.match g.SITE.regexp.quotelink
    return unless match or (@isClone and quotelink.dataset.postID) # normal or resurrected quote

    @nodes.quotelinks.push quotelink

    return if @isClone

    # ES6 Set when?
    fullID = "#{match[1]}.#{match[3]}"
    @quotes.push fullID unless fullID in @quotes

  parseFiles: ->
    @files = []
    fileRoots = @fileRoots()
    index = 0
    for fileRoot, docIndex in fileRoots
      if (file = @parseFile fileRoot)
        file.index = (index++)
        file.docIndex = docIndex
        @files.push file
    if @files.length
      @file = @files[0]

  fileRoots: ->
    if g.SITE.selectors.multifile
      roots = $$(g.SITE.selectors.multifile, @nodes.root)
      return roots if roots.length
    [@nodes.root]

  parseFile: (fileRoot) ->
    file = {}
    for key, selector of g.SITE.selectors.file
      file[key] = $ selector, fileRoot
    file.thumbLink = file.thumb?.parentNode

    return if not (file.text and file.link)
    return if not g.SITE.parseFile @, file

    $.extend file,
      url:     file.link.href
      isImage: /\.(jpe?g|png|gif|bmp)$/i.test file.link.href
      isVideo: /\.(webm|mp4)$/i.test file.link.href
    size  = +file.size.match(/[\d.]+/)[0]
    unit  = ['B', 'KB', 'MB', 'GB'].indexOf file.size.match(/\w+$/)[0]
    size *= 1024 while unit-- > 0
    file.sizeInBytes = size

    file

  @deadMark =
    # \u00A0 is nbsp
    $.el 'span',
      textContent: '\u00A0(Dead)'
      className:   'qmark-dead'

  kill: (file, index=0) ->
    if file
      return if @isDead or @files[index].isDead
      @files[index].isDead = true
      $.addClass @nodes.root, 'deleted-file'
    else
      return if @isDead
      @isDead = true
      $.rmClass  @nodes.root, 'deleted-file'
      $.addClass @nodes.root, 'deleted-post'

    if not (strong = $ 'strong.warning', @nodes.info)
      strong = $.el 'strong',
        className: 'warning'
      $.after $('input', @nodes.info), strong
    strong.textContent = if file then '[File deleted]' else '[Deleted]'

    return if @isClone
    for clone in @clones
      clone.kill file, index

    return if file
    # Get quotelinks/backlinks to this post
    # and paint them (Dead).
    for quotelink in Get.allQuotelinksLinkingTo @ when not $.hasClass quotelink, 'deadlink'
      $.add quotelink, Post.deadMark.cloneNode(true)
      $.addClass quotelink, 'deadlink'
    return

  # XXX Workaround for 4chan's racing condition
  # giving us false-positive dead posts.
  resurrect: ->
    @isDead = false
    $.rmClass @nodes.root, 'deleted-post'
    strong = $ 'strong.warning', @nodes.info
    # no false-positive files
    if @files.some((file) -> file.isDead)
      $.addClass @nodes.root, 'deleted-file'
      strong.textContent = '[File deleted]'
    else
      $.rm strong

    return if @isClone
    for clone in @clones
      clone.resurrect()

    for quotelink in Get.allQuotelinksLinkingTo @ when $.hasClass quotelink, 'deadlink'
      $.rm $('.qmark-dead', quotelink)
      $.rmClass quotelink, 'deadlink'
    return

  collect: ->
    g.posts.rm @fullID
    @thread.posts.rm @
    @board.posts.rm @

  addClone: (context, contractThumb) ->
    # Callbacks may not have been run yet due to anti-browser-lock delay in Main.callbackNodesDB.
    Callbacks.Post.execute @
    new Post.Clone @, context, contractThumb

  rmClone: (index) ->
    @clones.splice index, 1
    for clone in @clones[index..]
      clone.nodes.root.dataset.clone = index++
    return

  setCatalogOP: (isCatalogOP) ->
    @nodes.root.classList.toggle 'catalog-container', isCatalogOP
    @nodes.root.classList.toggle 'opContainer', !isCatalogOP
    @nodes.post.classList.toggle 'catalog-post', isCatalogOP
    @nodes.post.classList.toggle 'op', !isCatalogOP
    @nodes.post.style.left = @nodes.post.style.right = null
