/* global L -- eslint */

/**
 * @file Main code block that renders the portal details in the sidebar and
 * methods that highlight the portal in the map view.
 * @module portal_detail_display
 */

/**
 * Resets the scroll position of the sidebar when a new portal is selected.
 *
 * @function resetScrollOnNewPortal
 */
window.resetScrollOnNewPortal = function () {
  if (window.selectedPortal !== window.renderPortalDetails.lastVisible) {
    // another portal selected so scroll position become irrelevant to new portal details
    $('#sidebar').scrollTop(0); // NB: this works ONLY when #sidebar:visible
  }
};

/**
 * Generates and displays URLs related to the portal.
 * This includes a permalink for the portal, a link for Ingress Prime, and links to alternative maps.
 * Function is overwritten in `app.js`
 *
 * @function renderPortalUrl
 * @param {number} lat - The latitude of the portal.
 * @param {number} lng - The longitude of the portal.
 * @param {string} title - The title of the portal.
 * @param {string} guid - The GUID of the portal.
 */
window.renderPortalUrl = function (lat, lng, title, guid) {
  var linkDetails = $('.linkdetails');

  // a permalink for the portal
  var permaHtml = $('<a>')
    .attr({
      href: window.makePermalink([lat, lng]),
      title: 'Create a URL link to this portal',
    })
    .text('Portal link');
  linkDetails.append($('<aside>').append(permaHtml));

  var scannerLink = $('<a>')
    .attr({
      href: window.makePrimeLink(guid, lat, lng),
      title: 'Copy link to this portal for Ingress Prime',
    })
    .click(function (event) {
      navigator.clipboard.writeText(event.target.href).then();
      event.stopPropagation();
      return false;
    })
    .text('Copy scanner link');
  linkDetails.append($('<aside>').append(scannerLink));

  // and a map link popup dialog
  var mapHtml = $('<a>')
    .attr({
      title: 'Link to alternative maps (Google, etc)',
    })
    .text('Map links')
    .click(window.showPortalPosLinks.bind(this, lat, lng, title));
  linkDetails.append($('<aside>').append(mapHtml));
};

/**
 * Renders the details of a portal in the sidebar.
 *
 * @function renderPortalDetails
 * @param {string|null} guid - The globally unique identifier of the portal to display details for.
 */
window.renderPortalDetails = function (guid) {
  window.selectPortal(window.portals[guid] ? guid : null);
  if ($('#sidebar').is(':visible')) {
    window.resetScrollOnNewPortal();
    window.renderPortalDetails.lastVisible = guid;
  }

  if (guid && !window.portalDetail.isFresh(guid)) {
    window.portalDetail.request(guid);
  }

  // TODO? handle the case where we request data for a particular portal GUID, but it *isn't* in
  // window.portals....

  if (!window.portals[guid]) {
    window.urlPortal = guid;
    $('#portaldetails').html('');
    if (window.isSmartphone()) {
      $('.fullimg').remove();
      $('#mobileinfo').html('<div style="text-align: center"><b>tap here for info screen</b></div>');
    }
    return;
  }

  var portal = window.portals[guid];
  var data = portal.options.data;
  var details = window.portalDetail.get(guid);
  var historyDetails = window.getPortalHistoryDetails(data);

  // details and data can get out of sync. if we have details, construct a matching 'data'
  if (details) {
    data = window.getPortalSummaryData(details);
  }

  var modDetails = details ? '<div class="mods">' + window.getModDetails(details) + '</div>' : '';
  var miscDetails = details ? window.getPortalMiscDetails(guid, details) : '';
  var resoDetails = details ? window.getResonatorDetails(details) : '';

  // TODO? other status details...
  var statusDetails = details ? '' : '<div id="portalStatus">Loading details...</div>';

  var img = window.fixPortalImageUrl(details ? details.image : data.image);
  var title = (details && details.title) || (data && data.title) || 'null';

  var lat = data.latE6 / 1e6;
  var lng = data.lngE6 / 1e6;

  var imgTitle = title + '\n\nClick to show full image.';

  // portal level. start with basic data - then extend with fractional info in tooltip if available
  var levelInt = window.teamStringToId(data.team) === window.TEAM_NONE ? 0 : data.level;
  var levelDetails = levelInt;
  if (details) {
    levelDetails = window.getPortalLevel(details);
    if (levelDetails !== 8) {
      if (levelDetails === Math.ceil(levelDetails)) levelDetails += '\n8';
      else levelDetails += '\n' + (Math.ceil(levelDetails) - levelDetails) * 8;
      levelDetails += ' resonator level(s) needed for next portal level';
    } else {
      levelDetails += '\nfully upgraded';
    }
  }
  levelDetails = 'Level ' + levelDetails;

  $('#portaldetails')
    .html('') // to ensure it's clear
    .attr('class', window.TEAM_TO_CSS[window.teamStringToId(data.team)])
    .append(
      $('<h3>', { class: 'title' })
        .text(title)
        .prepend(
          $('<svg><use xlink:href="#ic_place_24px"/><title>Click to move to portal</title></svg>')
            .attr({
              class: 'material-icons icon-button',
              style: 'float: left',
            })
            .click(function () {
              window.zoomToAndShowPortal(guid, [data.latE6 / 1e6, data.lngE6 / 1e6]);
              if (window.isSmartphone()) {
                window.show('map');
              }
            })
        ),

      $('<span>')
        .attr({
          class: 'close',
          title: 'Close [w]',
          accesskey: 'w',
        })
        .text('X')
        .click(function () {
          window.renderPortalDetails(null);
          if (window.isSmartphone()) {
            window.show('map');
          }
        }),

      // help cursor via ".imgpreview img"
      $('<div>')
        .attr({
          class: 'imgpreview',
          title: imgTitle,
          style: 'background-image: url("' + img + '")',
        })
        .append($('<span>', { id: 'level', title: levelDetails }).text(levelInt), $('<img>', { class: 'hide', src: img })),

      modDetails,
      miscDetails,
      resoDetails,
      statusDetails,
      $('<div>', { class: 'linkdetails' }),
      historyDetails
    );

  window.renderPortalUrl(lat, lng, title, guid);

  // only run the hooks when we have a portalDetails object - most plugins rely on the extended data
  // TODO? another hook to call always, for any plugins that can work with less data?
  if (details) {
    window.runHooks('portalDetailsUpdated', { guid: guid, portal: portal, portalDetails: details, portalData: data });
  }
};

/**
 * Gets miscellaneous details for a specified portal.
 *
 * @function getPortalMiscDetails
 * @param {string} guid - The GUID of the portal.
 * @param {Object} d - The portal detail object containing various properties of the portal.
 * @returns {string} HTML string representing the miscellaneous details of the portal.
 */
window.getPortalMiscDetails = function (guid, d) {
  var randDetails;

  if (d) {
    // collect some random data that’s not worth to put in an own method
    var linkInfo = window.getPortalLinks(guid);
    var maxOutgoing = window.getMaxOutgoingLinks(d);
    var linkCount = linkInfo.in.length + linkInfo.out.length;
    var links = { incoming: linkInfo.in.length, outgoing: linkInfo.out.length };

    var title =
      `at most ${maxOutgoing} outgoing links\n` +
      `${links.outgoing} links out\n` +
      `${links.incoming} links in\n` +
      `(${links.outgoing + links.incoming} total)`;
    var linksText = ['links', links.outgoing + ' out / ' + links.incoming + ' in', title];

    var player = d.owner ? '<span class="nickname">' + d.owner + '</span>' : '-';
    var playerText = ['owner', player];

    var fieldCount = window.getPortalFieldsCount(guid);

    var fieldsText = ['fields', fieldCount];

    var apGainText = window.getAttackApGainText(d, fieldCount, linkCount);

    var attackValues = window.getPortalAttackValues(d);

    // collect and html-ify random data

    var randDetailsData = [
      // these pieces of data are only relevant when the portal is captured
      // maybe check if portal is captured and remove?
      // But this makes the info panel look rather empty for unclaimed portals
      playerText,
      window.getRangeText(d),
      linksText,
      fieldsText,
      window.getMitigationText(d, linkCount),
      window.getEnergyText(d),
      // and these have some use, even for uncaptured portals
      apGainText,
      window.getHackDetailsText(d),
    ];

    if (attackValues.attack_frequency !== 0) {
      randDetailsData.push(['<span title="attack frequency" class="text-overflow-ellipsis">attack frequency</span>', '×' + attackValues.attack_frequency]);
    }
    if (attackValues.hit_bonus !== 0) {
      randDetailsData.push(['hit bonus', attackValues.hit_bonus + '%']);
    }
    if (attackValues.force_amplifier !== 0) {
      randDetailsData.push(['<span title="force amplifier" class="text-overflow-ellipsis">force amplifier</span>', '×' + attackValues.force_amplifier]);
    }

    randDetails = '<table id="randdetails">' + window.genFourColumnTable(randDetailsData) + '</table>';

    // artifacts - tacked on after (but not as part of) the 'randdetails' table
    // instead of using the existing columns....

    if (d.artifactBrief && d.artifactBrief.target && Object.keys(d.artifactBrief.target).length > 0) {
      var targets = Object.keys(d.artifactBrief.target);
      // currently (2015-07-10) we no longer know the team each target portal is for - so we'll just show the artifact type(s)
      randDetails +=
        '<div id="artifact_target">Target portal: ' +
        targets
          .map(function (x) {
            return x.capitalize();
          })
          .join(', ') +
        '</div>';
    }

    // shards - taken directly from the portal details
    if (d.artifactDetail) {
      randDetails += '<div id="artifact_fragments">Shards: ' + d.artifactDetail.displayName + ' #' + d.artifactDetail.fragments.join(', ') + '</div>';
    }
  }

  return randDetails;
};

/**
 * The function adds circles indicating the hack range and link range of the portal.
 * If the portal object are null, the indicators are removed.
 *
 * @function setPortalIndicators
 * @param {Object} p - The portal object for which to set the indicators.
 */
window.setPortalIndicators = function (p) {
  if (window.portalRangeIndicator) window.map.removeLayer(window.portalRangeIndicator);
  window.portalRangeIndicator = null;
  if (window.portalAccessIndicator) window.map.removeLayer(window.portalAccessIndicator);
  window.portalAccessIndicator = null;

  // if we have a portal...

  if (p) {
    var coord = p.getLatLng();

    // range is only known for sure if we have portal details
    // TODO? render a min range guess until details are loaded..?

    var d = window.portalDetail.get(p.options.guid);
    if (d) {
      var range = window.getPortalRange(d);
      window.portalRangeIndicator = (
        range.range > 0
          ? L.geodesicCircle(coord, range.range, {
              fill: false,
              color: window.RANGE_INDICATOR_COLOR,
              weight: 3,
              dashArray: range.isLinkable ? undefined : '10,10',
              interactive: false,
            })
          : L.circle(coord, range.range, { fill: false, stroke: false, interactive: false })
      ).addTo(window.map);
    }

    window.portalAccessIndicator = L.circle(coord, window.HACK_RANGE, {
      fill: false,
      color: window.ACCESS_INDICATOR_COLOR,
      weight: 2,
      interactive: false,
    }).addTo(window.map);
  }
};

/**
 * Highlights the selected portal on the map and clears the highlight from the previously selected portal.
 *
 * @function selectPortal
 * @param {string} guid - The GUID of the portal to select.
 * @returns {boolean} True if the same portal is re-selected (just an update), false if a different portal is selected.
 */
window.selectPortal = function (guid) {
  var update = window.selectedPortal === guid;
  var oldPortalGuid = window.selectedPortal;
  window.selectedPortal = guid;

  var oldPortal = window.portals[oldPortalGuid];
  var newPortal = window.portals[guid];

  // Restore style of unselected portal
  if (!update && oldPortal) window.setMarkerStyle(oldPortal, false);

  // Change style of selected portal
  if (newPortal) {
    window.setMarkerStyle(newPortal, true);

    if (window.map.hasLayer(newPortal)) {
      newPortal.bringToFront();
    }
  }

  window.setPortalIndicators(newPortal);

  window.runHooks('portalSelected', { selectedPortalGuid: guid, unselectedPortalGuid: oldPortalGuid });
  return update;
};

/**
 * Changes the coordinates and map scale to show the range for portal links.
 *
 * @function rangeLinkClick
 */
window.rangeLinkClick = function () {
  if (window.portalRangeIndicator) window.map.fitBounds(window.portalRangeIndicator.getBounds());
  if (window.isSmartphone()) window.show('map');
};

/**
 * Creates a link to open a specific portal in Ingress Prime.
 *
 * @function makePrimeLink
 * @param {string} guid - The globally unique identifier of the portal.
 * @param {number} lat - The latitude of the portal.
 * @param {number} lng - The longitude of the portal.
 * @returns {string} The Ingress Prime link for the portal
 */
window.makePrimeLink = function (guid, lat, lng) {
  return `https://link.ingress.com/?link=https%3A%2F%2Fintel.ingress.com%2Fportal%2F${guid}&apn=com.nianticproject.ingress&isi=576505181&ibi=com.google.ingress&ifl=https%3A%2F%2Fapps.apple.com%2Fapp%2Fingress%2Fid576505181&ofl=https%3A%2F%2Fintel.ingress.com%2Fintel%3Fpll%3D${lat}%2C${lng}`;
};

/**
 * Generates a permalink URL based on the specified latitude and longitude and additional options.
 *
 * @param {L.LatLng|number[]} [latlng] - The latitude and longitude for the permalink.
 *                              Can be omitted to create mapview-only permalink.
 * @param {Object} [options] - Additional options for permalink generation.
 * @param {boolean} [options.includeMapView] - Include current map view in the permalink.
 * @param {boolean} [options.fullURL] - Generate a fully qualified URL (default: relative link).
 * @returns {string} The generated permalink URL.
 */
window.makePermalink = function (latlng, options) {
  options = options || {};

  function round(l) {
    // ensures that lat,lng are with same precision as in stock intel permalinks
    return Math.floor(l * 1e6) / 1e6;
  }
  var args = [];
  if (!latlng || options.includeMapView) {
    var c = window.map.getCenter();
    args.push('ll=' + [round(c.lat), round(c.lng)].join(','), 'z=' + window.map.getZoom());
  }
  if (latlng) {
    if ('lat' in latlng) {
      latlng = [latlng.lat, latlng.lng];
    }
    args.push('pll=' + latlng.join(','));
  }
  var url = '';
  if (options.fullURL) {
    url += new URL(document.baseURI).origin;
  }
  url += '/';
  return url + '?' + args.join('&');
};