/* 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 https://mozilla.org/MPL/2.0/. */

// eslint-disable-next-line no-unused-vars
import React, { useCallback, useEffect, useState } from "react";
import { useSelector, batch } from "react-redux";
import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { useSizeSubmenu } from "../../../lib/utils";
import { WIDGET_REGISTRY, resolveWidgetSize } from "common/WidgetsRegistry.mjs";
import { MoveSubmenu } from "../MoveSubmenu";
import { useWidgetTelemetry } from "../useWidgetTelemetry";

const PICTURE_OF_THE_DAY_ENTRY = WIDGET_REGISTRY.find(
  w => w.id === "pictureOfTheDay"
);

// How long the confirmation checkmark shows after setting the wallpaper.
const JUST_SET_CHECKMARK_MS = 2000;

// "Set wallpaper" button icons: the canvas icon by default, swapped for a
// checkmark during the brief post-set confirmation.
const SET_WALLPAPER_ICON = "chrome://browser/skin/canvas.svg";
const SET_WALLPAPER_CHECK_ICON = "chrome://global/skin/icons/check.svg";

// The daily Merino picture (image, attribution, description, "Set wallpaper"),
// or a sunrise-gradient empty state with an eye button when it's hidden/absent.
const PictureOfTheDay = ({
  dispatch,
  handleUserInteraction,
  widgetsMayBeMaximized,
  widgetEnabledMap,
}) => {
  const prefs = useSelector(state => state.Prefs.values);
  const pictureData = useSelector(state => state.PictureOfTheDay);
  const widgetSize = resolveWidgetSize(PICTURE_OF_THE_DAY_ENTRY, prefs);

  const isSetAsWallpaper = Boolean(
    prefs["widgets.pictureOfTheDay.setAsWallpaper"]
  );

  // Only offer the "Set wallpaper" CTA when wallpapers are on and custom
  // wallpapers are allowed, since this action sets a custom wallpaper.
  const canSetWallpaper = Boolean(
    prefs["newtabWallpapers.enabled"] &&
    prefs["newtabWallpapers.customWallpaper.enabled"]
  );

  // Fall back to the empty state when the picture fails to load (e.g. a cached
  // URL opened offline, or a broken/404 image) instead of showing a broken
  // image. Reset when a new picture arrives so the next day's image is tried.
  const [imageFailed, setImageFailed] = useState(false);
  useEffect(() => {
    setImageFailed(false);
  }, [pictureData.imageUrl]);

  // Dismissal is keyed to the picture's date so a new day's picture shows again
  // automatically (Bug 2050972). Edge case: Merino may omit published_date, and
  // without a key "Hide today's picture" would silently no-op, so fall back to
  // the local day. Tradeoff: hide then tracks the user's local clock, so an
  // undated picture restores at local midnight even if Merino hasn't rotated it.
  const pictureDate = pictureData.publishedDate || new Date().toDateString();
  const dismissed =
    pictureDate === prefs["widgets.pictureOfTheDay.dismissedDate"];
  const hasPicture =
    Boolean(pictureData.imageUrl) && !dismissed && !imageFailed;

  // Show a brief checkmark right after the user sets the wallpaper, then settle
  // into the collapsed "already set" state.
  const [justSet, setJustSet] = useState(false);
  useEffect(() => {
    if (!justSet) {
      return undefined;
    }
    const timer = setTimeout(() => setJustSet(false), JUST_SET_CHECKMARK_MS);
    return () => clearTimeout(timer);
  }, [justSet]);

  // After setting, keep the button collapsed for the current hover/focus
  // session so it doesn't pop open to the pill the moment the checkmark clears;
  // a fresh hover (after leaving the widget) expands it again.
  const [suppressExpand, setSuppressExpand] = useState(false);

  const { impressionRef, recordUserAction, recordEnabled } = useWidgetTelemetry(
    { dispatch, widget: PICTURE_OF_THE_DAY_ENTRY, widgetSize }
  );

  // Flip widgets.pictureOfTheDay.interaction on the first user action; every
  // handler calls it so no action is missed (the flip is idempotent).
  const handleInteraction = useCallback(
    () => handleUserInteraction("pictureOfTheDay"),
    [handleUserInteraction]
  );

  // Alt text uses the (localized) description when present, else a localized
  // generic fallback (a11y decision, Bug 2050975; the raw image title was
  // rejected as unreliable). Resolved via the l10n value API (computed string).
  const [fallbackAlt, setFallbackAlt] = useState("");
  useEffect(() => {
    document.l10n
      ?.formatValues?.([{ id: "newtab-picture-image-alt" }])
      ?.then(([value]) => value && setFallbackAlt(value));
  }, []);
  const imageAlt = pictureData.description || fallbackAlt;

  const handleHide = () => {
    batch(() => {
      dispatch(
        ac.OnlyToMain({
          type: at.SET_PREF,
          data: { name: PICTURE_OF_THE_DAY_ENTRY.enabledPref, value: false },
        })
      );
      // Disabling the widget is not an interaction, so it does not flip the
      // interaction pref.
      recordEnabled(false, { source: "context_menu" });
    });
  };

  const handleChangeSize = useCallback(
    size => {
      batch(() => {
        dispatch(
          ac.OnlyToMain({
            type: at.SET_PREF,
            data: { name: PICTURE_OF_THE_DAY_ENTRY.sizePref, value: size },
          })
        );
        recordUserAction("change_size", {
          source: "context_menu",
          value: size,
          size,
        });
        handleInteraction();
      });
    },
    [dispatch, recordUserAction, handleInteraction]
  );

  const sizeSubmenuRef = useSizeSubmenu(handleChangeSize);

  const handleLearnMore = () => {
    batch(() => {
      dispatch(
        ac.OnlyToMain({
          type: at.OPEN_LINK,
          data: {
            url: "https://support.mozilla.org/kb/firefox-new-tab-widgets",
            where: "tab",
          },
        })
      );
      recordUserAction("learn_more", { source: "context_menu" });
      handleInteraction();
    });
  };

  const handleManageWallpaper = () => {
    batch(() => {
      dispatch({ type: at.SHOW_PERSONALIZE });
      recordUserAction("manage_wallpaper", { source: "context_menu" });
      handleInteraction();
    });
  };

  const setDismissedDate = value =>
    dispatch(
      ac.OnlyToMain({
        type: at.SET_PREF,
        data: { name: "widgets.pictureOfTheDay.dismissedDate", value },
      })
    );

  const handleHidePhoto = () => {
    batch(() => {
      setDismissedDate(pictureDate);
      recordUserAction("hide_photo", { source: "context_menu" });
      handleInteraction();
    });
  };

  const handleShow = (source = "widget") => {
    batch(() => {
      setDismissedDate("");
      recordUserAction("show_picture", { source });
      handleInteraction();
    });
  };

  // The Merino image host doesn't send CORS headers, so the picture bytes can
  // only be read in the privileged main process. The feed does the fetch,
  // derives the theme, and applies it as the custom wallpaper; show a checkmark
  // confirmation immediately.
  const handleSetWallpaper = () => {
    batch(() => {
      dispatch(ac.OnlyToMain({ type: at.WIDGETS_PICTURE_SET_WALLPAPER }));
      recordUserAction("set_wallpaper", { source: "widget" });
      handleInteraction();
    });
    setJustSet(true);
    setSuppressExpand(true);
  };

  // The image, source line, and description open the picture's source page
  // (Wikimedia Commons) in a new tab. Only wired when the feed supplies a source
  // URL; otherwise the elements render as their plain text/image equivalents.
  const canOpenSource = Boolean(pictureData.sourceUrl);
  const handleOpenSource = () => {
    if (!pictureData.sourceUrl) {
      return;
    }
    batch(() => {
      dispatch(
        ac.OnlyToMain({
          type: at.OPEN_LINK,
          data: { url: pictureData.sourceUrl, where: "tab" },
        })
      );
      recordUserAction("open_source", { source: "widget" });
      handleInteraction();
    });
  };

  // The license name links to the license terms (Creative Commons) in a new
  // tab, separate from the source page link above.
  const canOpenLicense = Boolean(pictureData.licenseUrl);
  const handleOpenLicense = () => {
    if (!pictureData.licenseUrl) {
      return;
    }
    batch(() => {
      dispatch(
        ac.OnlyToMain({
          type: at.OPEN_LINK,
          data: { url: pictureData.licenseUrl, where: "tab" },
        })
      );
      recordUserAction("open_license", { source: "widget" });
      handleInteraction();
    });
  };

  // Attribution line under the title: "© {author} / {source} / {license}".
  // Each part renders only when its field is present; the source and license
  // are links (when their URLs are supplied), the author is plain text.
  const renderAttribution = () => {
    const parts = [];
    if (pictureData.author) {
      parts.push(
        <span
          key="author"
          className="picture-of-the-day-attribution-author"
          data-l10n-id="newtab-picture-attribution-author"
          data-l10n-args={JSON.stringify({ author: pictureData.author })}
        ></span>
      );
    }
    if (canOpenSource) {
      parts.push(
        <button
          key="source"
          type="button"
          className="picture-of-the-day-attribution-link picture-of-the-day-source-link"
          data-l10n-id="newtab-picture-attribution-source-link"
          onClick={handleOpenSource}
        ></button>
      );
    }
    if (pictureData.licenseLabel) {
      parts.push(
        canOpenLicense ? (
          <button
            key="license"
            type="button"
            className="picture-of-the-day-attribution-link picture-of-the-day-source-link"
            data-l10n-id="newtab-picture-attribution-license"
            data-l10n-args={JSON.stringify({
              license: pictureData.licenseLabel,
            })}
            onClick={handleOpenLicense}
          >
            {pictureData.licenseLabel}
          </button>
        ) : (
          <span key="license" className="picture-of-the-day-attribution-item">
            {pictureData.licenseLabel}
          </span>
        )
      );
    }
    if (!parts.length) {
      return null;
    }
    return (
      <p className="picture-of-the-day-attribution">
        {parts.map((part, i) => (
          <React.Fragment key={part.key}>
            {i > 0 ? (
              <span
                className="picture-of-the-day-attribution-sep"
                aria-hidden="true"
              >
                {" / "}
              </span>
            ) : null}
            {part}
          </React.Fragment>
        ))}
      </p>
    );
  };

  const pictureImage = (
    <img
      className="picture-of-the-day-image"
      src={pictureData.imageUrl}
      alt={imageAlt}
      onError={() => setImageFailed(true)}
    />
  );

  return (
    <article
      className={`picture-of-the-day widget col-4 ${widgetSize}-widget${
        hasPicture ? " has-picture" : ""
      }`}
      ref={impressionRef}
      onMouseLeave={() => setSuppressExpand(false)}
      onBlur={e => {
        // Only reset when focus leaves the whole widget, not when it moves
        // between the menu and button inside it.
        if (!e.currentTarget.contains(e.relatedTarget)) {
          setSuppressExpand(false);
        }
      }}
    >
      <div className="picture-of-the-day-toolbar">
        {hasPicture ? (
          <div className="picture-of-the-day-heading">
            <p
              className="picture-of-the-day-source"
              data-l10n-id="newtab-picture-header-main"
            ></p>
            {renderAttribution()}
          </div>
        ) : null}
        <div className="picture-of-the-day-context-menu-wrapper">
          <moz-button
            className="picture-of-the-day-context-menu-button"
            data-l10n-id="newtab-picture-widget-menu-button"
            iconSrc="chrome://global/skin/icons/more.svg"
            menuId="picture-of-the-day-context-menu"
            type="ghost"
          />
          <panel-list id="picture-of-the-day-context-menu">
            <panel-item
              data-l10n-id="newtab-picture-menu-manage-wallpaper"
              onClick={handleManageWallpaper}
            />
            {dismissed ? (
              <panel-item
                data-l10n-id="newtab-picture-menu-show-photo"
                onClick={() => handleShow("context_menu")}
              />
            ) : (
              <panel-item
                data-l10n-id="newtab-picture-menu-hide-photo"
                onClick={handleHidePhoto}
              />
            )}
            <hr />
            {widgetsMayBeMaximized && (
              <panel-item submenu="picture-of-the-day-size-submenu">
                <span data-l10n-id="newtab-widget-menu-change-size"></span>
                <panel-list
                  ref={sizeSubmenuRef}
                  slot="submenu"
                  id="picture-of-the-day-size-submenu"
                >
                  {["medium", "large"].map(size => (
                    <panel-item
                      key={size}
                      type="checkbox"
                      checked={widgetSize === size || undefined}
                      data-size={size}
                      data-l10n-id={`newtab-widget-size-${size}`}
                    />
                  ))}
                </panel-list>
              </panel-item>
            )}

            <MoveSubmenu
              widgetId="pictureOfTheDay"
              widgetEnabledMap={widgetEnabledMap}
            />

            <panel-item
              data-l10n-id="newtab-widget-menu-hide"
              onClick={handleHide}
            />
            <panel-item
              data-l10n-id="newtab-picture-menu-learn-more"
              onClick={handleLearnMore}
            />
          </panel-list>
        </div>
      </div>

      {hasPicture ? (
        <div className="picture-of-the-day-populated">
          {canOpenSource ? (
            <button
              type="button"
              className="picture-of-the-day-image-link"
              onClick={handleOpenSource}
            >
              {pictureImage}
            </button>
          ) : (
            pictureImage
          )}
          <div className="picture-of-the-day-details">
            {pictureData.description ? (
              <p className="picture-of-the-day-description">
                {pictureData.description}
              </p>
            ) : null}
            {canSetWallpaper ? (
              <moz-button
                className={`picture-of-the-day-set-wallpaper${
                  justSet || isSetAsWallpaper ? " is-collapsed" : ""
                }${suppressExpand ? " no-expand" : ""}`}
                type="primary"
                iconSrc={
                  justSet ? SET_WALLPAPER_CHECK_ICON : SET_WALLPAPER_ICON
                }
                onClick={handleSetWallpaper}
                data-l10n-id="newtab-picture-set-wallpaper"
              ></moz-button>
            ) : null}
          </div>
        </div>
      ) : (
        <div className="picture-of-the-day-footer">
          <button
            type="button"
            className="picture-of-the-day-show-button"
            onClick={() => handleShow("widget")}
            data-l10n-id="newtab-picture-show-button"
          ></button>
          <p
            className="picture-of-the-day-message"
            data-l10n-id="newtab-picture-check-back"
          ></p>
        </div>
      )}
    </article>
  );
};

export { PictureOfTheDay };
