/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { useDispatch, useSelector } from "react-redux";
import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import {
  getNotificationIdsForUrl,
  originFromUrl,
} from "content-src/lib/web-notification-match.mjs";
import { proxiedIconUrl } from "content-src/lib/web-notification-icon.mjs";
import React from "react";

// Origins whose notification icon just repeats the site's own shortcut icon, so
// listing it is visual noise. Hand-curated; grown as needed.
const ICON_SUPPRESS_ORIGINS = new Set(["https://apnews.com"]);

// Biggest units first, so the loop returns the coarsest one that fits.
// Anything under a minute falls through to the "just now" string.
const RELATIVE_TIME_UNITS = [
  ["year", 365 * 24 * 60 * 60 * 1000],
  ["month", 30 * 24 * 60 * 60 * 1000],
  ["week", 7 * 24 * 60 * 60 * 1000],
  ["day", 24 * 60 * 60 * 1000],
  ["hour", 60 * 60 * 1000],
  ["minute", 60 * 1000],
];

/**
 * Picks the largest relative-time unit that fits ("2 hours ago", "5 days ago").
 * Returns null when the delta is under a minute, so the caller can show
 * "just now" instead.
 *
 * @param {number} timestamp ms epoch the notification was posted.
 * @param {string} [locale] BCP-47 locale; falls back to the runtime default.
 * @param {number} now ms epoch to measure against.
 * @returns {?string}
 */
function formatRelativeTime(timestamp, locale, now) {
  const delta = timestamp - now;
  const abs = Math.abs(delta);
  for (const [unit, ms] of RELATIVE_TIME_UNITS) {
    if (abs >= ms) {
      return new Intl.RelativeTimeFormat(locale || undefined, {
        numeric: "auto",
      }).format(Math.round(delta / ms), unit);
    }
  }
  return null;
}

function NotificationTime({ timestamp, locale, now }) {
  if (!timestamp) {
    return null;
  }
  const relative = formatRelativeTime(timestamp, locale, now);
  const dateTime = new Date(timestamp).toISOString();
  // A null relative string means it's under a minute, so show "just now".
  if (relative === null) {
    return (
      <time
        className="top-sites-hover-card-notification-time"
        dateTime={dateTime}
        data-l10n-id="newtab-topsites-hover-card-just-now"
      />
    );
  }
  return (
    <time
      className="top-sites-hover-card-notification-time"
      dateTime={dateTime}
    >
      {relative}
    </time>
  );
}

/**
 * A notification's icon, proxied. Renders nothing when the icon cannot be
 * proxied or the proxy fails to serve it — there is deliberately no fallback to
 * the origin URL, which is the load the proxy exists to avoid.
 */
function NotificationIcon({ notification }) {
  const [failed, setFailed] = React.useState(false);

  if (ICON_SUPPRESS_ORIGINS.has(notification.origin)) {
    return null;
  }
  const src = proxiedIconUrl(notification.icon);
  if (!src || failed) {
    return null;
  }
  return (
    <img
      src={src}
      alt=""
      className="top-sites-hover-card-notification-icon"
      onError={() => setFailed(true)}
    />
  );
}

function NotificationList({
  notifications,
  locale,
  now,
  onActivate,
  onDismiss,
}) {
  return (
    <ul className="top-sites-hover-card-notifications">
      {notifications.map(notification => {
        return (
          <li
            className="top-sites-hover-card-notification"
            key={notification.id}
            dir={notification.dir || "auto"}
          >
            <button
              type="button"
              className="top-sites-hover-card-notification-activate"
              onClick={() => onActivate(notification)}
            >
              <NotificationIcon notification={notification} />
              <div className="top-sites-hover-card-notification-text">
                <span className="top-sites-hover-card-notification-title">
                  {notification.title}
                </span>
                {notification.body ? (
                  <span className="top-sites-hover-card-notification-body">
                    {notification.body}
                  </span>
                ) : null}
                <NotificationTime
                  timestamp={notification.timestamp}
                  locale={locale}
                  now={now}
                />
              </div>
            </button>
            <button
              type="button"
              className="top-sites-hover-card-notification-dismiss"
              data-l10n-id="newtab-topsites-hover-card-dismiss"
              onClick={() => onDismiss(notification)}
            />
          </li>
        );
      })}
    </ul>
  );
}

/**
 * Web notifications variant of the top-sites hover card. Lists the hovered
 * site's web notifications, most recent first, and renders nothing when the
 * site has no notifications. The list scrolls in place past roughly three
 * entries rather than spilling into a separate surface.
 *
 * This is one discrete content card behind the TopSitesHoverCard shell, which
 * renders exactly one variant per tile (see hover-card-content.jsx). Clicking a
 * service-worker notification fires its origin and dismisses it; the dismiss and
 * mark-all controls remove entries durably via WebNotificationsFeed.
 *
 * @param link The top site link object for the hovered tile.
 */
function CardWebNotifications({ link }) {
  const dispatch = useDispatch();
  const locale = useSelector(state => state.App.locale);
  const byId = useSelector(state => state.WebNotifications.notifications);
  const ids = useSelector(state => getNotificationIdsForUrl(state, link?.url));

  const notifications = ids
    .map(id => byId[id])
    .filter(Boolean)
    .sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));

  if (!notifications.length) {
    return null;
  }

  const site = link?.label || link?.hostname || originFromUrl(link?.url) || "";
  const now = Date.now();

  const openSettings = () => {
    dispatch({ type: at.SHOW_PERSONALIZE });
    dispatch(ac.UserEvent({ event: "SHOW_PERSONALIZE" }));
  };

  const activate = notification =>
    dispatch(
      ac.AlsoToMain({
        type: at.WEB_NOTIFICATIONS_CLICK,
        data: { origin: notification.origin, id: notification.id },
      })
    );

  const dismiss = notification =>
    dispatch(
      ac.AlsoToMain({
        type: at.WEB_NOTIFICATIONS_DISMISS,
        data: { origin: notification.origin, id: notification.id },
      })
    );

  const dismissAll = () =>
    dispatch(
      ac.AlsoToMain({
        type: at.WEB_NOTIFICATIONS_DISMISS_ALL,
        data: { origin: notifications[0].origin },
      })
    );

  return (
    <div className="top-sites-hover-card" role="group">
      <div className="top-sites-hover-card-inner">
        <div className="top-sites-hover-card-header">
          <span
            className="top-sites-hover-card-header-title"
            data-l10n-id="newtab-topsites-hover-card-header"
            data-l10n-args={JSON.stringify({ site })}
          />
          <div className="top-sites-hover-card-header-actions">
            <button
              type="button"
              className="top-sites-hover-card-mark-read"
              data-l10n-id="newtab-topsites-hover-card-mark-all-read"
              onClick={dismissAll}
            />
            <button
              type="button"
              className="top-sites-hover-card-settings"
              data-l10n-id="newtab-topsites-hover-card-settings"
              onClick={openSettings}
            />
          </div>
        </div>
        <NotificationList
          notifications={notifications}
          locale={locale}
          now={now}
          onActivate={activate}
          onDismiss={dismiss}
        />
      </div>
    </div>
  );
}

export { CardWebNotifications };
