// ==UserScript==
// @name         FuRecs
// @namespace    https://twitter.com/DeltaNoises
// @version      1.4
// @description  FurAffinity User Recommender
// @author       https://twitter.com/DeltaNoises
// @match        https://www.furaffinity.net/user/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=furaffinity.net
// @grant        GM_xmlhttpRequest
// @grant        GM.getValue
// @grant        GM.setValue
// @connect      refurrer.com
// @connect      localhost:3001
// @connect      localhost
// @updateURL    https://refurrer.com/us/furecs.user.js
// @downloadURL  https://refurrer.com/us/furecs.user.js
// ==/UserScript==

'use strict';

interface SimilarUserList {
  name: string;
  url_name: string;
  profile_thumb_url: string;
  external_url: string;
  refurrer_url: string;
}

type SimilarUsersErrorType =
  | 'recs_not_computed'
  | 'user_not_found'
  | 'exclude_user_not_found'
  | 'exclude_user_not_scanned';
type SimilarUsersResponse =
  | {
      all: SimilarUserList[];
      not_followed: SimilarUserList[] | null;
    }
  | {
      error_type: SimilarUsersErrorType;
      error: string;
    };

function urlNameFromUserHref(href: string) {
  const url = new URL(href);
  if (url.host != 'www.furaffinity.net') {
    return null;
  }

  const userPageRegex = /^\/(user|gallery|scraps|favorites|journals)\/.+/;
  const match = url.pathname.match(userPageRegex);
  if (!match) {
    return null;
  }
  return url.pathname.split('/')[2];
}

async function faUserRecs(): Promise<void> {
  const HOST = 'https://refurrer.com';

  if (!window.location.pathname.startsWith('/user/')) {
    return null;
  }

  const urlName = urlNameFromUserHref(window.location.href);
  if (!urlName) {
    return;
  }

  // if the user is logged in, get their user account, so we can
  // also provide a list of recommendations that don't overlap with their
  // current watchlist
  let loggedInUrlName: string | null = null;
  const loggedInLink = document.querySelector(
    '.mobile-nav-content-container h2 a',
  ) as HTMLAnchorElement | null;
  if (loggedInLink) {
    loggedInUrlName = urlNameFromUserHref(loggedInLink.href);
  }

  function buildRecommendedContainer(): HTMLElement | null {
    let leftColumn = document.querySelector(
      '.userpage-layout-left-col-content',
    ) as HTMLElement | null;
    if (!leftColumn) {
      console.error("didn't find left column container, bailing");
      return null;
    }

    let section = document.createElement('section');
    section.classList.add('userpage-left-column');
    section.classList.add('gallery_container');
    section.innerHTML = `
      <div class="userpage-section-left">
        <div class="section-header" style='display:flex;align-items:center'>
          <h2 style='flex-grow:1'>Similar Users</h2>
        </div>
        <div class="section-body">
        </div>
      </div>`;
    leftColumn.prepend(section);
    return section.querySelector('.section-body');
  }

  const container = buildRecommendedContainer();
  if (!container) {
    console.error('failed to build recommended container, bailing');
    return;
  }

  function setContainerError(error: string) {
    container.innerHTML = `Error: <pre>${error}</pre>`;
  }

  async function requestSimilarUsers(): Promise<SimilarUsersResponse> {
    return new Promise((resolve, reject) => {
      container.innerHTML = `<h2 class='aligncenter'>(Loading...)</h2>`;
      let url = `${HOST}/api/fa/similar_users`;
      url += `?url_name=${encodeURIComponent(urlName)}`;
      if (loggedInUrlName) {
        url += `&exclude_url_name=${encodeURIComponent(loggedInUrlName)}`;
      }

      GM_xmlhttpRequest({
        url,
        method: 'GET',
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/json',
        },
        onabort: () => {
          reject(new Error('request aborted'));
        },
        ontimeout: () => {
          reject(new Error('request timed out'));
        },
        onerror: (response) => {
          reject(new Error('server error: ' + response.responseText));
        },
        onload: (response) => {
          const json_error_codes = [403, 404, 500];
          if (response.status == 200) {
            let json = JSON.parse(response.responseText);
            resolve(json);
          } else if (json_error_codes.includes(response.status)) {
            let json = JSON.parse(response.responseText);
            reject(new Error(json.error));
          } else {
            reject(new Error(response.responseText));
          }
        },
      });
    });
  }

  function populateRecommendations(recommendations: SimilarUsersResponse) {
    if ('error' in recommendations) {
      setContainerError(recommendations.error);
    } else {
      container.innerHTML = buildTwoColumns({
        all: recommendations.all,
        not_followed: recommendations.not_followed,
      });
    }
  }

  function buildTwoColumns({
    all,
    not_followed,
  }: {
    all: SimilarUserList[];
    not_followed: SimilarUserList[] | null;
  }) {
    const width = not_followed ? '50%' : '100%';

    let content = ``;
    content += `<table>`;
    content += `  <tr>`;
    content += `    <th>Similar to ${urlName}</th>`;
    if (not_followed) {
      content += `    <th>Similar users you don't follow</th>`;
    }
    content += `  </tr>`;
    content += `  <tr>`;
    content += `    <td style='width:${width}'>`;
    content += `      ` + buildOneColumn(all);
    content += `    </td>`;

    if (not_followed) {
      content += `    <td style='width:${width}'>`;
      content += `      ` + buildOneColumn(not_followed);
      content += `    </td>`;
    }
    content += `  </tr>`;
    content += `</table>`;
    return content;
  }

  function buildOneColumn(userList: SimilarUserList[]) {
    let content = ``;
    content += '<ol class="user-submitted-links">';
    userList.forEach(({ name, external_url, profile_thumb_url }) => {
      content += `<li>`;
      content += `<a
        href="${external_url}"
        target="_blank"
        style="display: flex; align-items: center; border: dotted 1px rgba(255,255,255,0.5); padding: 4px"
      >`;
      if (profile_thumb_url) {
        content += `<img
          alt="${name} thumbnail"
          title="${name}"
          src="${profile_thumb_url}"
          style="max-height: 40px; padding-right: 10px"
        />`;
      }
      content += `<span>${name}</span>`;
      content += `</a>`;
      content += '</li>';
    });
    content += '</ol>';
    return content;
  }

  await requestSimilarUsers()
    .then(populateRecommendations)
    .catch((error) => {
      setContainerError(error.message);
    });
}

(async function () {
  if (window.location.hostname == 'www.furaffinity.net') {
    await faUserRecs();
  } else {
    console.log('unhandled domain ', window.location.hostname);
  }
})();
