/**
 * Shared result destinations for Keyboard Layout Config procedures.
 *
 * Any Function / Macro Key that produces data can route it through `deliverActionResult` to the
 * clipboard, a result popover, back into the page, the Media Library, or (future) the Scrapbook.
 *
 * See KEY_ACTION_ARCHITECTURE.md, "Data Acquisition & Result Destinations", for the full design
 * writeup — this module is the `ResultDestination` half of that split (the "where does the data
 * go" side; "what data was captured" is `dataSource`/`dataKind` on `FunctionDef`, see
 * function-library.js).
 */
import { COLORS } from '../config/constants.js';
import { showProcedureResultPopover } from '../ui/procedure-result-popover.js';
import { addImageToMediaLibrary, addUrlToMediaLibrary } from './media-library-client.js';

/**
 * `modifyPage` and `both` (clipboard+popover) are historically/currently supported combinations;
 * `clipboardAndMediaLibrary` is clipboard plus the Media Library sink. `scrapbook` is reserved
 * for a sink that does not exist yet. `mediaLibrary` stores image blobs in IndexedDB.
 * @typedef {'clipboard'|'popover'|'both'|'modifyPage'|'mediaLibrary'|'clipboardAndMediaLibrary'|'scrapbook'} ActionResultDestination
 */

export const ACTION_RESULT_DESTINATIONS = Object.freeze({
  CLIPBOARD: 'clipboard',
  POPOVER: 'popover',
  BOTH: 'both',
  MODIFY_PAGE: 'modifyPage',
  MEDIA_LIBRARY: 'mediaLibrary',
  CLIPBOARD_AND_MEDIA_LIBRARY: 'clipboardAndMediaLibrary',
  SCRAPBOOK: 'scrapbook'
});

/** Display labels for every known destination id, keyed the same as {@link ACTION_RESULT_DESTINATIONS}. */
const DESTINATION_LABELS = Object.freeze({
  [ACTION_RESULT_DESTINATIONS.CLIPBOARD]: 'Clipboard',
  [ACTION_RESULT_DESTINATIONS.POPOVER]: 'Popover',
  [ACTION_RESULT_DESTINATIONS.BOTH]: 'Clipboard and popover',
  [ACTION_RESULT_DESTINATIONS.MODIFY_PAGE]: 'Replace in page',
  [ACTION_RESULT_DESTINATIONS.MEDIA_LIBRARY]: 'Media Library',
  [ACTION_RESULT_DESTINATIONS.CLIPBOARD_AND_MEDIA_LIBRARY]: 'Both',
  [ACTION_RESULT_DESTINATIONS.SCRAPBOOK]: 'Scrapbook (coming soon)'
});

/**
 * Build a `destination` parameter def scoped to only the destinations a given Function actually
 * supports (e.g. `TRANSLATE` offers `modifyPage`/`popover` but not `clipboard`; media-kind
 * Functions will offer `mediaLibrary` but not `modifyPage`). The first entry in
 * `applicableDestinations` is used as the default.
 *
 * Kept as a factory (rather than one shared frozen constant) because "which destinations make
 * sense" is a property of each Function, not universal — see KEY_ACTION_ARCHITECTURE.md.
 *
 * @param {ActionResultDestination[]} applicableDestinations
 * @returns {{ id: 'destination', label: string, type: 'enum', defaultValue: ActionResultDestination, options: Array<{ id: string, label: string }> }}
 */
export function buildResultDestinationParameter(applicableDestinations) {
  const ids = Array.isArray(applicableDestinations) && applicableDestinations.length
    ? applicableDestinations
    : [ACTION_RESULT_DESTINATIONS.CLIPBOARD];
  return Object.freeze({
    id: 'destination',
    label: 'Destination',
    type: 'enum',
    defaultValue: ids[0],
    options: Object.freeze(ids.map((id) => Object.freeze({ id, label: DESTINATION_LABELS[id] || id })))
  });
}

/**
 * @param {unknown} raw
 * @param {ActionResultDestination} [fallback='clipboard']
 * @returns {ActionResultDestination}
 */
export function normalizeActionResultDestination(raw, fallback = ACTION_RESULT_DESTINATIONS.CLIPBOARD) {
  const v = String(raw || '').trim();
  if (Object.prototype.hasOwnProperty.call(DESTINATION_LABELS, v)) {
    return /** @type {ActionResultDestination} */ (v);
  }
  return fallback;
}

/**
 * @param {ActionResultDestination} destination
 * @returns {{ clipboard: boolean, popover: boolean, modifyPage: boolean, mediaLibrary: boolean }}
 */
export function destinationFlags(destination) {
  const d = normalizeActionResultDestination(destination);
  const clipboardAndPopover = d === ACTION_RESULT_DESTINATIONS.BOTH;
  const clipboardAndMedia = d === ACTION_RESULT_DESTINATIONS.CLIPBOARD_AND_MEDIA_LIBRARY;
  return {
    clipboard: d === ACTION_RESULT_DESTINATIONS.CLIPBOARD || clipboardAndPopover || clipboardAndMedia,
    popover: d === ACTION_RESULT_DESTINATIONS.POPOVER || clipboardAndPopover,
    modifyPage: d === ACTION_RESULT_DESTINATIONS.MODIFY_PAGE,
    mediaLibrary: d === ACTION_RESULT_DESTINATIONS.MEDIA_LIBRARY || clipboardAndMedia
  };
}

/**
 * Deliver procedure output to the configured destination(s).
 *
 * `modifyPage` is the one destination that can't be implemented generically here: it needs to
 * know *where in the page* to write the result back to, which is caller-specific (a `Range`, a
 * specific element, etc.), not something this shared helper can infer from `text` alone. Callers
 * that support `modifyPage` (e.g. a future `TRANSLATE` Function) must pass `onModifyPage`; if the
 * resolved destination is `modifyPage` and no `onModifyPage` is given, this falls back to the
 * popover so the result is never silently dropped.
 *
 * @param {any} kp KeyPilot instance (needs copyToClipboard / showFlashNotification)
 * @param {{
 *   text: string,
 *   html?: string|null,
 *   title?: string,
 *   destination?: ActionResultDestination|string,
 *   successMessage?: string,
 *   onModifyPage?: (text: string) => (boolean|Promise<boolean>),
 *   blob?: Blob,
 *   mime?: string,
 *   sourceUrl?: string,
 *   pageUrl?: string
 * }} opts
 * @returns {Promise<{ clipboard: boolean, popover: boolean, modifyPage: boolean, mediaLibrary: boolean }>}
 */
export async function deliverActionResult(kp, opts = {}) {
  const flags = destinationFlags(opts.destination);
  const out = { clipboard: false, popover: false, modifyPage: false, mediaLibrary: false };

  if (flags.mediaLibrary) {
    const blob = opts.blob;
    if (blob instanceof Blob && blob.size > 0) {
      try {
        const result = await addImageToMediaLibrary({
          blob,
          mime: opts.mime || blob.type || '',
          sourceUrl: opts.sourceUrl || '',
          pageUrl: opts.pageUrl || (typeof location !== 'undefined' ? String(location.href || '') : '')
        });
        out.mediaLibrary = !!(result?.success || result?.duplicate);
        if (!flags.clipboard && typeof kp?.showFlashNotification === 'function') {
          if (result?.duplicate) {
            kp.showFlashNotification('Already in Media Library', COLORS.NOTIFICATION_INFO, blob);
          } else if (result?.success) {
            kp.showFlashNotification(
              opts.successMessage || 'Saved to Media Library',
              COLORS.NOTIFICATION_SUCCESS,
              blob
            );
          } else {
            kp.showFlashNotification(
              result?.error || 'Could not save to Media Library',
              COLORS.NOTIFICATION_ERROR
            );
          }
        }
      } catch {
        if (!flags.clipboard && typeof kp?.showFlashNotification === 'function') {
          kp.showFlashNotification('Could not save to Media Library', COLORS.NOTIFICATION_ERROR);
        }
      }
    } else {
      const href = String(opts.sourceUrl || opts.text || '').trim();
      try {
        const result = await addUrlToMediaLibrary({
          sourceUrl: href,
          pageUrl: opts.pageUrl || (typeof location !== 'undefined' ? String(location.href || '') : '')
        });
        out.mediaLibrary = !!(result?.success || result?.duplicate);
        if (!flags.clipboard && typeof kp?.showFlashNotification === 'function') {
          if (result?.duplicate) {
            kp.showFlashNotification('Already in Media Library', COLORS.NOTIFICATION_INFO);
          } else if (result?.success) {
            kp.showFlashNotification(
              opts.successMessage || 'URL saved to Media Library',
              COLORS.NOTIFICATION_SUCCESS
            );
          } else {
            kp.showFlashNotification(
              result?.error || 'Could not save URL to Media Library',
              COLORS.NOTIFICATION_ERROR
            );
          }
        }
      } catch {
        if (!flags.clipboard && typeof kp?.showFlashNotification === 'function') {
          kp.showFlashNotification('Could not save URL to Media Library', COLORS.NOTIFICATION_ERROR);
        }
      }
    }
  }

  const text = String(opts.text ?? '');

  if (flags.modifyPage) {
    if (typeof opts.onModifyPage === 'function') {
      try {
        out.modifyPage = !!(await opts.onModifyPage(text));
      } catch (e) {
        console.warn('[KeyPilot] onModifyPage handler failed:', e);
        out.modifyPage = false;
      }
    }
    if (!out.modifyPage) {
      // No page-write hook wired up (or it failed) — surface the result some other way rather
      // than losing it silently.
      flags.popover = true;
    }
  }

  if (flags.clipboard) {
    try {
      const payload = opts.html
        ? { plainText: text, htmlContent: String(opts.html), hasRichContent: true }
        : text;
      out.clipboard = !!(await kp?.copyToClipboard?.(payload));
    } catch {
      out.clipboard = false;
    }
    if (out.clipboard && typeof kp?.showFlashNotification === 'function') {
      try {
        const successMessage = opts.successMessage
          || (flags.mediaLibrary && out.mediaLibrary
            ? 'Copied and saved to Media Library'
            : 'Copied to clipboard');
        kp.showFlashNotification(
          successMessage,
          COLORS.NOTIFICATION_SUCCESS,
          flags.mediaLibrary ? opts.blob : undefined
        );
      } catch { /* ignore */ }
    } else if (flags.clipboard && !out.clipboard && typeof kp?.showFlashNotification === 'function') {
      try {
        kp.showFlashNotification('Could not copy to clipboard', COLORS.NOTIFICATION_ERROR);
      } catch { /* ignore */ }
    }
  }

  if (flags.popover) {
    try {
      showProcedureResultPopover({
        title: opts.title || 'Result',
        text,
        html: opts.html || null,
        onCopy: async () => {
          try {
            const payload = opts.html
              ? { plainText: text, htmlContent: String(opts.html), hasRichContent: true }
              : text;
            return !!(await kp?.copyToClipboard?.(payload));
          } catch {
            return false;
          }
        }
      });
      out.popover = true;
    } catch (e) {
      console.warn('[KeyPilot] Failed to show result popover:', e);
      out.popover = false;
    }
  }

  return out;
}
