/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */

import { CSSProperties, Suspense, useEffect, useState } from 'react';
import { $getSelection, $isTextNode } from 'lexical';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';

import { Button } from '../../button';
import { useTr } from '../i18n';

type MetaTagBaseShared = {
  description?: string;
  image?: string;
  title?: string;
};

type MetaTags = {
  facebook: MetaTagBaseShared;
  google: MetaTagBaseShared;
  linkedin: MetaTagBaseShared;
  pinterest: MetaTagBaseShared;
  slack: MetaTagBaseShared;
  twitter: MetaTagBaseShared;
  metatags: MetaTagBaseShared;
  favicon?: string;
};

type MetaTagsRequestResponse =
  | (MetaTags & {
      success: boolean;
    })
  | null;

// Cached responses or running request promises
const PREVIEW_CACHE: Record<string, Promise<void | MetaTagsRequestResponse> | { preview: MetaTagsRequestResponse }> =
  {};

const URL_MATCHER =
  /((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;

function useSuspenseRequest(url: string) {
  let cached = PREVIEW_CACHE[url];

  if (!url.match(URL_MATCHER)) {
    return { preview: null };
  }

  if (!cached) {
    cached = PREVIEW_CACHE[url] = fetch(`https://service-metatags.crystallize.workers.dev/?url=${encodeURI(url)}`)
      .then(response => response.json() as Promise<MetaTagsRequestResponse>)
      .then(preview => {
        PREVIEW_CACHE[url] = { preview };
        return preview;
      })
      .catch(() => {
        PREVIEW_CACHE[url] = { preview: null };
      });
  }

  if (cached instanceof Promise) {
    throw cached;
  }

  return cached;
}

function LinkPreviewContent({
  url,
}: Readonly<{
  url: string;
}>): JSX.Element | null {
  const [textContent, setTextContent] = useState('');
  const { preview } = useSuspenseRequest(url);
  const [editor] = useLexicalComposerContext();
  const tr = useTr();

  const hasPreview = preview !== null && preview.google?.title;

  // Get the textContent from the link node (if the link is a single node)
  useEffect(() => {
    editor.update(() => {
      const sel = $getSelection();
      const nodes = sel?.getNodes();
      if (hasPreview && nodes?.length === 1) {
        const [firstNode] = nodes;
        if ($isTextNode(firstNode)) {
          setTextContent(firstNode.getTextContent());
        }
      }
    });
  }, [editor, preview]);

  /**
   * Replace the current
   */
  function useTitleForText() {
    editor.update(() => {
      const sel = $getSelection();
      const nodes = sel?.getNodes();
      if (hasPreview && nodes?.length === 1) {
        const [firstNode] = nodes;
        if ($isTextNode(firstNode)) {
          // @ts-expect-error
          firstNode.setTextContent(preview.google.title);
          // @ts-expect-error
          setTextContent(preview.google.title);
        }
      }
    });
  }

  if (!hasPreview) {
    return null;
  }

  return (
    <div className="c-rte-link-preview">
      {preview.google.image && (
        <div className="c-rte-link-preview-image-wrapper">
          <img src={preview.google.image} alt={preview.google.title} className="c-rte-link-preview-image" />
        </div>
      )}
      {preview.google.title && <div className="c-rte-link-preview-title">{preview.google.title}</div>}
      {preview.google.description && <div className="c-rte-link-preview-description">{preview.google.description}</div>}
      {textContent && textContent !== preview.google.title ? (
        <Button className="c-rte-link-preview__replace-text-btn" onClick={useTitleForText}>
          {tr('linkPreviewReplaceTextWithTitle')}
        </Button>
      ) : null}
    </div>
  );
}

function Glimmer(props: { style: CSSProperties; index: number }): JSX.Element {
  return (
    <div
      className="c-rte-link-preview-glimmer"
      {...props}
      style={{
        animationDelay: String((props.index || 0) * 300),
        ...(props.style || {}),
      }}
    />
  );
}

export default function LinkPreview({
  url,
}: Readonly<{
  url: string;
}>): JSX.Element {
  return (
    <Suspense
      fallback={
        <>
          <Glimmer style={{ height: '80px' }} index={0} />
          <Glimmer style={{ width: '60%' }} index={1} />
          <Glimmer style={{ width: '80%' }} index={2} />
        </>
      }
    >
      <LinkPreviewContent url={url} />
    </Suspense>
  );
}
