// ==UserScript==
// @name        Qiita show followees iine
// @namespace   khsk
// @description 記事に自分(ユーザー)がフォローandいいねしているユーザーを表示
// @include     http://qiita.com/*/items/*
// @include     https://qiita.com/*/items/*
// @include     http://qiita.com/*/private/*
// @include     https://qiita.com/*/private/*
// @version     1
// @grant       none
// ==/UserScript==

console.time('Qiita show followees iine')

const userId = ''; // 主に自分
const token = ''; // 各自で取得してください
const displayFolloweesNum = 5; // 表示数 -1 で無制限

(async () => {

  // 表示場所作成
  const iine = document.getElementsByClassName('list-inline ArticleMainHeader__users')[0]
  const ciine = iine.cloneNode()
  iine.parentElement.insertBefore(ciine, null)
  // TODO いいね取得が長いから進捗を適当に書いているんだけれど、事前にheightを確保していないので、フォロイいねが無かったときなどにガタッとレイアウトが変わるかも。オプションめ。
  ciine.innerText = 'フォロイー取得中'


  let followees = sessionStorage.getItem('followees')
  if (!followees) {
    followees = await fetchAll('https://qiita.com/api/v2/users/' + userId + '/followees?per_page=100',user => {
        // 無いとは思うが、sessionStorage容量上限対策として削れるものは削る
        delete user.description
        delete user.facebook_id
        delete user.followees_count
        delete user.followers_count
        delete user.github_login_name
        delete user.items_count
        delete user.linkedin_id
        delete user.location
        delete user.prganization
        delete user.permanent_id
        delete user.twitter_screen_name
        delete user.website_url
        delete user.organization
        return user
      })
    // 保存
    followees = JSON.stringify(followees)
    sessionStorage.setItem('followees', followees)
  }

  followees = JSON.parse(followees)

  ciine.innerText = 'いいね取得中'

  // 記事のいいね取得
  const itemId = location.pathname.split('/').pop()
  // 1ページ取得→即アイコン表示の方が止まっている疑惑が出にくいが…
  let likes = await fetchAll('https://qiita.com/api/v2/items/' + itemId + '/likes?per_page=100', o => { return o.user.id })


  ciine.innerText = ''

  // 要素作成ループ
  // ここ100*1000程度はありそうでヤバめ
  // いいねしたフォロイーだけ抽出するとまたループが必要なのでDOMまで作るか…

  // breakしたいしsomeは嫌だからforにするか…
  // フォロイーといいねどちらを削除していけば性能良いかわからん…
  for (let i = 0, likesLength = likes.length, displayedNum = 0; i < likesLength; i++ ) {
    if (!followees.length || (displayFolloweesNum > 0 && displayFolloweesNum == displayedNum) ) {
      break
    }
    for (let j = 0, followeesLength = followees.length; j < followeesLength; j++) {
      if (likes[i] == followees[j].id) {
        let followee = followees[j]
        let id = followee.id
        let userTemplate = iine.firstChild.cloneNode(true)
        userTemplate.dataHovercardTargetName = id
        userTemplate.querySelector('a').href = '/' + id
        let img = userTemplate.querySelector('img') 
        img.alt = followee.name || id
        img.src = followee.profile_image_url

        // ツールチップ追加 フォロイーはアイコンでわかるだろうからオマケ程度
        setPopover(userTemplate, followee)

        ciine.insertBefore(userTemplate, null)
        displayedNum++

        // 表示し終わったフォロイーは配列から削除しようと思っていたが、影響が出るような数百単位のいいねの場合はいいね取得がボトルネックに見えるので保留にする
      }
    }
  }

})()


/////////// functions
// 今回は無名関数でなくfuntionを使ってみる。ライブラリコピペを下部に持ってくるので、自作関数も下にまとめたい→変数なら上に書かなければ行けないので関数で

// レスポンスがないとnextがわからないのでオーバーヘッドがだいぶある。
// いいねやフォロイー総数から事前にページ数を計算すれば非同期並列で取得して最後にPromise.allで合算、で高速化できそうなんだけれども、手間がかかる。
// 計算せずとも最初のlinkにラストページが表示されるのでその数までループ、なら総数取得は無しに出来るかも。初回1本+以降並列。
// TODO 進捗表示のためにいちループごとに何か出来るコールバック作るか
async function fetchAll(url, callback = (v) =>{return v}) {
  let next = url
  let result = []
  while (next) {
    let response = await fetch(next, {
      headers: new Headers({ Authorization: 'Bearer ' + token,}),
    })

    if (response.ok == false || response.status != 200) {
      return result
    }

    next = li().parse(response.headers.get('Link')).next

    let responseData = await response.json()
    // ここデフォルト引数付けずに無いならmapしない方が性能いいけど
    Array.prototype.push.apply(result, responseData.map(callback)) 
  }
  return result
}

// フィードのポップアップスクリプトの関数を持ってきて調整
function setPopover(elm, data) {
    // Qiitaのhovercardはhovercard.jsではないのかもで諦め
    // 最後に$(elm).tooltip() or popover()を実行するとタイトルがタグになる。謎。なので最初に設定
    // datasetでは効かないものがある
    $(elm).popover({
        trigger   : 'hover',
        placement : 'bottom', // いいねは下付き
        container : 'body',
        html      : true,
        // 画像サイズ固定化や左配置のために、本家で使っている.hovervardクラスをデフォルトテンプレートに追加
        template  : '<div class="popover" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-title"></h3><div class="popover-content hovercard"></div></div>'
    })
    elm.dataset.toggle = 'popover'
    elm.dataset.originalTitle = 'Followee Info'
    // hovercardっぽさのためにアイコン画像は今回は載せる
    elm.dataset.content = '<img src="' + data.profile_image_url + '">' + '<br>\n' + (data.name || data.id)
    // 改行対策にデフォルト値 + nowrap
    elm.dataset.viewport = "{ selector: 'body', padding: 0 , white-space: nowrap,}"
}


/////////// 外部ライブラリ(コピペ)
// MITライセンス表示はこれで大丈夫かしら
/**
* Author  : José F. Romaniello <jfromaniello@gmail.com> (http://joseoncode.com)
* License : MIT 2014 - JOSE F. ROMANIELLO https://opensource.org/licenses/mit-license.php
* URL     : https://github.com/jfromaniello/li
**/
function li () {
  // compile regular expressions ahead of time for efficiency
  var relsRegExp = /^;\s*([^"=]+)=(?:"([^"]+)"|([^";,]+)(?:[;,]|$))/;
  var keysRegExp = /([^\s]+)/g;
  var sourceRegExp = /^<([^>]*)>/;
  var delimiterRegExp = /^\s*,\s*/;

  return {
    parse: function (linksHeader, options) {
      var match;
      var source;
      var rels;
      var extended = options && options.extended || false;
      var links = [];

      while (linksHeader) {
        linksHeader = linksHeader.trim();

        // Parse `<link>`
        source = sourceRegExp.exec(linksHeader);
        if (!source) break;

        var current = {
          link: source[1]
        };

        // Move cursor
        linksHeader = linksHeader.slice(source[0].length);

        // Parse `; attr=relation` and `; attr="relation"`

        var nextDelimiter = linksHeader.match(delimiterRegExp);
        while(linksHeader && (!nextDelimiter || nextDelimiter.index > 0)) {
          match = relsRegExp.exec(linksHeader);
          if (!match) break;

          // Move cursor
          linksHeader = linksHeader.slice(match[0].length);
          nextDelimiter = linksHeader.match(delimiterRegExp);


          if (match[1] === 'rel' || match[1] === 'rev') {
            // Add either quoted rel or unquoted rel
            rels = (match[2] || match[3]).split(/\s+/);
            current[match[1]] = rels;
          } else {
            current[match[1]] = match[2] || match[3];
          }
        }

        links.push(current);
        // Move cursor
        linksHeader = linksHeader.replace(delimiterRegExp, '');
      }

      if (!extended) {
        return links.reduce(function(result, currentLink) {
          if (currentLink.rel) {
            currentLink.rel.forEach(function(rel) {
              result[rel] = currentLink.link;
            });
          }
          return result;
        }, {});
      }

      return links;
    },
    stringify: function (headerObject, callback) {
      var result = "";
      for (var x in headerObject) {
        result += '<' + headerObject[x] + '>; rel="' + x + '", ';
      }
      result = result.substring(0, result.length - 2);

      return result;
    }
  };
}

///////////////
console.timeEnd('Qiita show followees iine')