/*
 * Copyright (c) 2024 Eclipse Foundation, Inc.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 */

import loadingTemplate from './templates/loading-icon.mustache';
import defaultTemplate from './templates/weighted-collaborations/default.mustache';
import renderTemplate from './templates/render-template';
import { getWorkingGroups } from '../api/eclipsefdn.working-groups';
//@ts-ignore Types do not exist yet
import { getInterestGroups } from '../api/eclipsefdn.interest-groups';
import { stringToBoolean, isWorkingGroup, getCollaborationType, repeatSelectUnique } from '../shared/utils';
import { InterestGroup, WorkingGroup } from '../types/models';

interface Options {
  templateId: string;
  count: number;
  types: string[];
  skeletonLoad: boolean;
  cache: boolean;
}

const defaultOptions = {
  templateId: 'default',
  count: 1,
  types: ['working-groups', 'interest-groups'],
  skeletonLoad: false,
  cache: false,
};

const templates = {
  default: defaultTemplate
};
const render = async (): Promise<void> => {
  const element: HTMLElement = document.querySelector('.eclipsefdn-weighted-collaborations');
  if (!element) return;

  element.innerHTML = loadingTemplate();

  const options: Options = { 
    ...defaultOptions, 
    ...element.dataset,
    types: element.dataset.types
      ? element.dataset.types.split(',').map(t => t.trim())
      : defaultOptions.types,
    cache: element.dataset.cache ? stringToBoolean(element.dataset.cache) : defaultOptions.cache,
    skeletonLoad: element.dataset.skeletonLoad ? stringToBoolean(element.dataset.skeletonLoad) : defaultOptions.skeletonLoad,
  };

  // Skeleton load the collaborations.
  if (options.skeletonLoad) {
    await renderTemplate({
      element,
      templates, 
      templateId: options.templateId, 
      data: {
        isFetching: true,
        collaborations: Array.from({ length: options.count }),
      }
    });
  } else {
    element.innerHTML = loadingTemplate();
  }

  // If the cache option is enabled, retrieve the cached collaborations.
  // Otherwise, set cached collaborations to null.
  let cachedCollaborations = options.cache ? getCachedCollaborations() : null;
  // In the case that no collaborations are cached, set collaborations to an
  // empty array.
  let collaborations = cachedCollaborations ?? [];
  let errorCount = 0;

  // If no cached collaborations were found and the "working groups" type
  // data-attribute was provided, fetch collaborations and select a
  // weighted-random count of them.
  if (!cachedCollaborations) {
    if (options.types.includes('working-groups')) {
      let [workingGroups, error] = await getWorkingGroups();
      // Only collect working groups who are active.
      workingGroups = workingGroups.filter((workingGroup: WorkingGroup) => workingGroup.status === 'active');

      if (!error) {
        collaborations = collaborations.concat(workingGroups);
        errorCount++;
      }
    }

    if (options.types.includes('interest-groups')) {
      const [interestGroups, error] = await getInterestGroups();

      if (!error) {
        collaborations = collaborations.concat(interestGroups);
        errorCount++;
      }
    }

    // Display an error message if both working groups and interest groups could
    // not be fetched.
    if (errorCount >= 2) {
      element.innerHTML = `
  <p class="alert alert-danger">
    Error when attempting to load working groups and interest groups.
  </p>
      `;
    }

    const buckets = collaborations
      .filter((collaboration: WorkingGroup | InterestGroup) => !!collaboration.logo)
      .reduce(assignCollaborationToBucket, new Map());

    collaborations = repeatSelectUnique(() => pickRandomCollaborationFromWeights(buckets), options.count);
    if (options.cache) persistCollaborations(collaborations);
  }

  await renderTemplate({
    element,
    templates, 
    templateId: options.templateId, 
    data: { 
      isFetching: false,
      collaborations: collaborations
        .map((collaboration: WorkingGroup | InterestGroup) => ({
          title: collaboration.title,
          logo: collaboration.logo,
          website: resolveCollaborationWebsite(collaboration), 
        }))
    }
  });
};

export default { render };

/** Resolves the website for a given industry collaboration */
const resolveCollaborationWebsite = (collaboration: WorkingGroup | InterestGroup): string => {
  if (collaboration.resources.website) {
    return collaboration.resources.website;
  }
  
  // Use the explore page as a fallback when the collaboration has no
  // website.
  let exploreUrl = 'https://www.eclipse.org/org/workinggroups/explore.php';

  switch (getCollaborationType(collaboration)) {
    case 'working-group':
      return `${exploreUrl}#wg-${collaboration.alias}`;
    case 'interest-group':
      return collaboration.shortProjectId ? `${exploreUrl}#${collaboration.shortProjectId}` : exploreUrl;
    default:
      throw new TypeError('Could not resolve website for the given industry collaboration type.');
  }
}

/**
  * Retrieves industry collaborations which were cached to the session storage.
  */
const getCachedCollaborations = () => {
  try {
    const cachedCollaborations = sessionStorage.getItem(`${window.location.pathname}-weighted-collaborations`);
    return JSON.parse(cachedCollaborations); 
  } catch (error) {
    return null;
  }
}

const persistCollaborations = (collaborations: Array<WorkingGroup | InterestGroup>): void => {
  try {
    window.sessionStorage.setItem(`${window.location.pathname}-weighted-collaborations`, JSON.stringify(collaborations));
    return;
  } catch (error) {
    return;
  }
}

const collaborationWeights: Record<string, number> = {
  default: -1,
  'jakarta-ee': 1,
  sdv: 20,
  'cloud-development-tools': 30,
  adoptium: 38,
  oniro: 38,
  'eclipse-ide': 44,
  'internet-things-iot': 44,
  aice: 50,
  asciidoc: 50,
  'edge-native': 50,
  microprofile: 50,
  openmobility: 50,
  osgi: 50,
  sparkplug: 50,
}

/**
  * Adds an industry collaboration to a weight bucket. This function can be
  * used in an `Array.prototype.reduce`.
  *
  * @param weights - A map with the weight as key and the array as the bucket.
  * @param collaboration - The collaboration to add to a bucket.
  *
  * @returns The map of weights and buckets with the added collaboration.
  */
const assignCollaborationToBucket = (weights: Map<number, WorkingGroup | InterestGroup>, collaboration: WorkingGroup | InterestGroup) => {
  // Only working groups have the `alias`.
  let weight: number = isWorkingGroup(collaboration) && collaborationWeights[collaboration.alias];
  
  // If no collaboration weight was found, assign it the default weight.
  if (!weight) {
    weight = collaborationWeights.default; 
  }

  // If the bucket does not yet exist, create the array for it.
  if (!weights.get(weight)) {
    weights.set(weight, new Array());
  }

  // Add the collaboration to the bucket.
  weights.get(weight).push(collaboration);

  return weights;
}

/**
  * Picks a single random collaboration from one of the provided buckets.
  *
  * @param buckets - The buckets of various weights containing collaborations.
  *
  * @returns An industry collaboration.
  */
const pickRandomCollaborationFromWeights = (buckets: Map<number, WorkingGroup | InterestGroup>): Array<WorkingGroup | InterestGroup> => {
  const highestWeight = Array.from(buckets.keys())
    .sort()
    .at(-1);
  const randomNumber = Math.floor(Math.random() * highestWeight);

  const nearestWeight = Array.from(buckets.keys()).reduce((previousWeight, currentWeight) => {
    return (Math.abs(currentWeight - randomNumber) < Math.abs(previousWeight - randomNumber)) ? currentWeight : previousWeight;
  });

  const nearestBucket = buckets.get(nearestWeight);
  const randomIndex = Math.floor(Math.random() * nearestBucket.length);
  
  return nearestBucket[randomIndex];
};

