import { verifyEvent, NostrEvent } from '@nostr/tools/pure'
import { decodeNostrURI, EventPointer, neventEncode, npubEncode, ProfilePointer } from '@nostr/tools/nip19'
import { normalizeURL } from '@nostr/tools/utils'
import { NostrUser } from '@nostr/gadgets/metadata'
import * as nip10 from '@nostr/tools/nip10'

import { fetchNostrUser, getOutboxRelaysFor, pool } from './nostr.js'
import { debounce, nostrLink, shorten, splitComma } from './utils.js'
import { renderText } from './text.js'

export default class NostrNote extends HTMLElement {
  static observedAttributes = ['ref', 'event', 'relays', 'template']

  root: HTMLDivElement

  constructor() {
    super()

    this.attachShadow({ mode: 'open' })
    const { shadowRoot } = this

    this.root = document.createElement('div')
    shadowRoot!.appendChild(this.root)
  }

  connectedCallback() {
    setTimeout(() => {
      let template = this.getAttribute('template')
        ? (document.getElementById(this.getAttribute('template')!) as HTMLTemplateElement)
        : null
      if (template) {
        this.root.appendChild(template.content.cloneNode(true))
      } else {
        this.root.innerHTML = `
          <div part="container">
            <header part="header">
              <a part="author-link">
                <span part="author-name"></span>
                <span part="author-npub-short"></span>
              </a>
              <a part="parent-link" style="display: none">
                <span part="parent">parent</span>
              </a>
              <a part="link">
                <time part="date"></time>
              </a>
            </header>
            <div part="content"></div>
          </div>
        `
      }
      this.set()
    }, 1)
  }

  *queryPart<E = HTMLElement>(name: string): Generator<E> {
    let slotted = this.querySelectorAll(`[part="${name}"]`)
    for (let i = 0; i < slotted.length; i++) {
      yield slotted[i] as E
    }
    let templated = this.root.querySelectorAll(`[part="${name}"]`)
    for (let i = 0; i < templated.length; i++) {
      yield templated[i] as E
    }
  }

  attributeChangedCallback() {
    this.set()
  }

  set: () => void = debounce(async () => {
    let evt: NostrEvent | null = null

    // if the raw event was provided as json, use that
    let eventj = this.getAttribute('event')
    if (eventj) {
      evt = JSON.parse(eventj)
      if (evt) {
        if (!verifyEvent(evt)) {
          return
        }
      }
    }

    if (!evt) {
      // otherwise try to fetch from ref
      let ref = this.getAttribute('ref')
      if (ref) {
        let { type, data } = decodeNostrURI(ref)
        if (type !== 'nevent') {
          return
        }
        let d = data as EventPointer
        let relays = (d.relays || []).concat(splitComma(this.getAttribute('relays')).map(normalizeURL))
        let filter = { ids: [d.id] }

        // try to fetch just from relay hints
        if (d.author && relays.length === 0) relays = await getOutboxRelaysFor(d.author)
        evt = await pool.get(relays, filter)

        // if it fails try to fetch from the author's outbox relays
        if (!evt && d.author) {
          relays = await getOutboxRelaysFor(d.author)
          evt = await pool.get(relays, filter)
        }
      }
    }

    if (!evt) {
      return
    }

    let npub = npubEncode(evt.pubkey)

    for (let el of this.queryPart<HTMLAnchorElement>('author-link')) {
      el.href = nostrLink(npub)
      el.target = '_blank'
    }
    for (let el of this.queryPart('author-npub')) {
      el.textContent = npub
    }

    let nu: NostrUser | null = null
    for (let el of this.queryPart('author-name')) {
      if (!nu) {
        nu = await fetchNostrUser(evt.pubkey, [])
      }
      el.textContent = nu.shortName
    }

    for (let el of this.queryPart('author-npub-short')) {
      el.textContent = shorten(npub)
    }

    let thread: {
      root: EventPointer | undefined
      reply: EventPointer | undefined
      mentions: EventPointer[]
      quotes: EventPointer[]
      profiles: ProfilePointer[]
    } | null = null
    for (let el of this.queryPart<HTMLAnchorElement>('parent-link')) {
      if (!thread) thread = nip10.parse(evt)
      if (thread.reply) {
        el.href = nostrLink(neventEncode(thread.reply))
        el.target = '_blank'
        el.style.display = ''
      } else {
        el.remove()
      }
    }
    for (let el of this.queryPart<HTMLAnchorElement>('root-link')) {
      if (!thread) thread = nip10.parse(evt)
      if (thread.root) {
        el.href = nostrLink(neventEncode(thread.root))
        el.target = '_blank'
      } else {
        el.remove()
      }
    }

    for (let el of this.queryPart<HTMLAnchorElement>('link')) {
      el.href = nostrLink(
        neventEncode({
          id: evt.id,
          relays: Array.from(pool.seenOn.get(evt.id) || [])
            .slice(0, 3)
            .map(r => r.url),
          author: evt.pubkey,
        }),
      )
      el.target = '_blank'
    }

    for (let el of this.queryPart('date')) {
      let date = new Date(evt.created_at * 1000)
      el.textContent = date.toLocaleString()
      if (el.tagName === 'TIME') {
        el.setAttribute('time', date.toISOString().substring(0, 23))
      }
    }

    let contentWrapper: HTMLDivElement | null = null
    for (let el of this.queryPart('content')) {
      if (!contentWrapper) {
        contentWrapper = document.createElement('div')
        el.appendChild(contentWrapper)
      }
      renderText(contentWrapper, evt.content)
    }
  }, 200)
}

window.customElements.define('nostr-note', NostrNote)
