/* eslint-disable no-irregular-whitespace */
import i18next from 'i18next';
import { Constants } from '../constants';
import { getSocialTextIV } from '../helpers/socialproof';
import { sanitizeText } from '../helpers/utils';

enum AuthorActionType {
  Reply = 'Reply',
  Original = 'Original',
  FollowUp = 'FollowUp'
}

const populateUserLinks = (text: string): string => {
  /* TODO: Maybe we can add username splices to our API so only genuinely valid users are linked? */
  text.match(/@(\w{1,15})/g)?.forEach(match => {
    const username = match.replace('@', '');
    text = text.replace(
      match,
      `<a href="${Constants.TWITTER_ROOT}/${username}" target="_blank" rel="noopener noreferrer">${match}</a>`
    );
  });
  return text;
};

const generateStatusMedia = (status: APIStatus): string => {
  let media = '';
  if (status.media?.all?.length) {
    status.media.all.forEach(mediaItem => {
      switch (mediaItem.type) {
        case 'photo':
          // eslint-disable-next-line no-case-declarations
          const { altText } = mediaItem as APIPhoto;
          media += `<img src="{url}" {altText}/>`.format({
            altText: altText ? `alt="${altText}"` : '',
            url: mediaItem.url
          });
          break;
        case 'video':
          media += `<video src="${mediaItem.url}" alt="${i18next.t('videoAltTextUnavailable').format({ author: status.author.name })}"/>`;
          break;
        case 'gif':
          media += `<video src="${mediaItem.url}" alt="${i18next.t('gifAltTextUnavailable').format({ author: status.author.name })}"/>`;
          break;
      }
    });
  }
  return media;
};

// const formatDateTime = (date: Date): string => {
//   const yyyy = date.getFullYear();
//   const mm = String(date.getMonth() + 1).padStart(2, '0'); // Months are 0-indexed
//   const dd = String(date.getDate()).padStart(2, '0');
//   const hh = String(date.getHours()).padStart(2, '0');
//   const min = String(date.getMinutes()).padStart(2, '0');
//   return `${hh}:${min} - ${yyyy}/${mm}/${dd}`;
// }

const formatDate = (date: Date, language: string): string => {
  if (language.startsWith('en')) {
    language = 'en-CA'; // Use ISO dates for English to avoid problems with mm/dd vs. dd/mm
  }
  const formatter = new Intl.DateTimeFormat(language, {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit'
  });
  return formatter.format(date);
};

const htmlifyLinks = (input: string): string => {
  const urlPattern = /\bhttps?:\/\/[\w.-]+\.\w+[/\w.-]*\w/g;
  return input.replace(urlPattern, url => {
    return `<a href="${wrapForeignLinks(url)}">${url}</a>`;
  });
};

const htmlifyHashtags = (input: string): string => {
  const hashtagPattern = /#([a-zA-Z_]\w*)/g;
  return input.replace(hashtagPattern, (match, hashtag) => {
    const encodedHashtag = encodeURIComponent(hashtag);
    return `  <a href="${Constants.TWITTER_ROOT}/hashtag/${encodedHashtag}?src=hashtag_click">${match}</a>  `;
  });
};

function paragraphify(text: string, isQuote = false): string {
  const tag = isQuote ? 'blockquote' : 'p';
  return text
    .split('\n')
    .map(line => line.trim())
    .filter(line => line.length > 0)
    .map(line => `<${tag}>${line}</${tag}>`)
    .join('\n');
}

function getTranslatedText(status: APITwitterStatus, isQuote = false): string | null {
  if (!status.translation) {
    return null;
  }
  let text = paragraphify(sanitizeText(status.translation?.text), isQuote);
  text = htmlifyLinks(text);
  text = htmlifyHashtags(text);
  text = populateUserLinks(text);

  const formatText = `📑 {translation}`.format({
    translation: i18next.t('translatedFrom').format({
      language: i18next.t(`language_${status.translation.source_lang}`)
    })
  });

  return `<h4>${formatText}</h4>${text}<h4>${i18next.t('ivOriginalText')}</h4>`;
}

const notApplicableComment = '<!-- N/A -->';

const truncateSocialCount = (count: number, locale = 'en-US') => {
  const formatter = new Intl.NumberFormat(locale, {
    notation: 'compact',
    compactDisplay: 'short',
    maximumFractionDigits: 1
  });

  return formatter.format(count);
};

const generateInlineAuthorHeader = (
  status: APIStatus,
  author: APIUser,
  authorActionType: AuthorActionType | null
): string => {
  if (authorActionType === AuthorActionType.Original) {
    return `<h4><i>${i18next.t('ivAuthorActionOriginal', {
      statusUrl: status.url,
      authorName: author.name,
      authorUrl: author.url,
      authorScreenName: author.screen_name
    })}</i></h4>`;
  } else if (authorActionType === AuthorActionType.FollowUp) {
    return `<h4><i>${i18next.t('ivAuthorActionFollowUp', {
      statusUrl: status.url,
      authorName: author.name,
      authorUrl: author.url,
      authorScreenName: author.screen_name
    })}</i></h4>`;
  }
  // Reply / unknown
  return `<h4><i>${i18next.t('ivAuthorActionReply', {
    statusUrl: status.url,
    authorName: author.name,
    authorUrl: author.url,
    authorScreenName: author.screen_name
  })}</i></h4>`;
};

const wrapForeignLinks = (url: string) => {
  let unwrap = false;
  const whitelistedDomains = ['twitter.com', 'x.com', 't.me', 'telegram.me'];
  try {
    const urlObj = new URL(url);

    if (!whitelistedDomains.includes(urlObj.hostname)) {
      unwrap = true;
    }
  } catch (error) {
    unwrap = true;
  }

  return unwrap
    ? `https://${Constants.API_HOST_LIST[0]}/2/hit?url=${encodeURIComponent(url)}`
    : url;
};

const generateStatusFooter = (
  status: APIStatus,
  isQuote = false,
  author: APIUser,
  language: string
): string => {
  let description = author.description;
  description = htmlifyLinks(description);
  description = htmlifyHashtags(description);
  description = populateUserLinks(description);

  return `
    <p>{socialText}</p>
    <br>{viewOriginal}
    <!-- Embed profile picture, display name, and screen name in table -->
    {aboutSection}
    `.format({
    socialText: getSocialTextIV(status as APITwitterStatus) || '',
    viewOriginal: !isQuote
      ? `<a href="${status.url}">${i18next.t('ivViewOriginal')}</a>`
      : notApplicableComment,
    aboutSection: isQuote
      ? ''
      : `<h2>${i18next.t('ivAboutAuthor')}</h2>
        {pfp}
        <h2>${author.name}</h2>
        <p><a href="${author.url}">@${author.screen_name}</a></p>
        <p><b>${description}</b></p>
        <p>{location} {website} {joined}</p>
        <p>
          {following} <b>${i18next.t('ivProfileFollowing', { numFollowing: author.following })}</b> 
          {followers} <b>${i18next.t('ivProfileFollowers', { numFollowers: author.followers })}</b> 
          {statuses} <b>${i18next.t('ivProfileStatuses', { numStatuses: author.statuses })}</b>
        </p>`.format({
          pfp: `<img src="${author.avatar_url?.replace('_200x200', '_400x400')}" alt="${i18next.t('ivProfilePictureAlt', { author: author.name })}" />`,
          location: author.location ? `📌 ${author.location}` : '',
          website: author.website
            ? `🔗 <a rel="nofollow" href="${wrapForeignLinks(author.website.url)}">${author.website.display_url}</a>`
            : '',
          joined: author.joined ? `📆 ${formatDate(new Date(author.joined), language)}` : '',
          following: truncateSocialCount(author.following, language),
          followers: truncateSocialCount(author.followers, language),
          statuses: truncateSocialCount(author.statuses, language)
        })
  });
};

const generatePoll = (poll: APIPoll, language: string): string => {
  const intlFormat = Intl.NumberFormat(language ?? 'en');
  let str = '';

  const barLength = 20;

  poll.choices.forEach(choice => {
    const bar = '█'.repeat((choice.percentage / 100) * barLength);
    // eslint-disable-next-line no-irregular-whitespace
    str += `${bar}<br>${choice.label}<br>${i18next.t('ivPollChoice', { voteCount: intlFormat.format(choice.count), percentage: intlFormat.format(choice.percentage) })}<br>`;
  });
  /* Finally, add the footer of the poll with # of votes and time left */
  str += `<br>${i18next.t('pollVotes', { voteCount: intlFormat.format(poll.total_votes), timeLeft: poll.time_left_en })}`;

  return str;
};

const generateCommunityNote = (status: APITwitterStatus): string => {
  if (status.community_note) {
    console.log('community_note', status.community_note);
    const note = status.community_note;
    const entities = note.entities;
    entities.sort((a, b) => a.fromIndex - b.fromIndex); // sort entities by fromIndex

    let lastToIndex = 0;
    let result = '';

    entities.forEach(entity => {
      if (entity?.ref?.type !== 'TimelineUrl') {
        return;
      }
      const fromIndex = entity.fromIndex;
      const toIndex = entity.toIndex;
      const url = entity.ref.url;

      // Add the text before the link
      result += note.text.substring(lastToIndex, fromIndex);

      // Add the link
      result += `<a href="${url}">${note.text.substring(fromIndex, toIndex)}</a>`;

      lastToIndex = toIndex;
    });

    // Add the remaining text after the last link
    result = `<table>
      <thead>
        <th><b>${i18next.t('ivCommunityNoteHeader')}</b></th>
      </thead>
      <tbody>
        <th>${result.replace(/\n/g, '\n<br>')}</th>
      </tbody>
    </table>`;

    return result;
  }
  return '';
};

const generateStatus = (
  status: APIStatus,
  author: APIUser,
  language: string,
  isQuote = false,
  authorActionType: AuthorActionType | null
): string => {
  let text = paragraphify(sanitizeText(status.text), isQuote);
  text = htmlifyLinks(text);
  text = htmlifyHashtags(text);
  text = populateUserLinks(text);

  const translatedText = getTranslatedText(status as APITwitterStatus, isQuote);

  return `<!-- Telegram Instant View -->
  {quoteHeader}
  <!-- Embed media -->
  ${generateStatusMedia(status)} 
  <!-- Translated text (if applicable) -->
  ${translatedText ? translatedText : notApplicableComment}
  <!-- Inline author (if applicable) -->
  ${authorActionType ? generateInlineAuthorHeader(status, author, authorActionType) : ''}
  <!-- Embed Status text -->
  ${text}
  <!-- Embed Community Note -->
  ${generateCommunityNote(status as APITwitterStatus)}
  <!-- Embed poll -->
  ${status.poll ? generatePoll(status.poll, status.lang ?? 'en') : notApplicableComment}
  <!-- Embedded quote status -->
  ${!isQuote && status.quote ? generateStatus(status.quote, author, language, true, null) : notApplicableComment}`.format(
    {
      quoteHeader: isQuote
        ? '<h4>' +
          i18next.t('ivQuoteHeader').format({
            url: status.url,
            authorName: status.author.name,
            authorHandle: status.author.screen_name,
            authorURL: `${Constants.TWITTER_ROOT}/${status.author.screen_name}`
          }) +
          '</h4>'
        : ''
    }
  );
};

export const renderInstantView = (properties: RenderProperties): ResponseInstructions => {
  console.log('Generating Instant View...');
  const { status, thread, flags } = properties;
  const instructions: ResponseInstructions = { addHeaders: [] };

  let previousThreadPieceAuthor: string | null = null;
  let originalAuthor: string | null = null;

  const useThread = thread?.thread ?? [thread?.status];

  if (!status) {
    throw new Error('Status is undefined');
  }

  /* Use ISO date for Medium template */
  const statusDate = new Date(status.created_at).toISOString();

  /* Pretend to be Medium to allow Instant View to work.
     Thanks to https://nikstar.me/post/instant-view/ for the help!
    
     If you work for Telegram and want to let us build our own templates
     contact me https://t.me/dangeredwolf */
  instructions.addHeaders = [
    `<meta property="al:android:app_name" content="Medium"/>`,
    `<meta property="article:published_time" content="${statusDate}"/>`,
    flags?.archive
      ? `<style>img,video{width:100%;max-width:500px}html{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif}</style>`
      : ``
  ];

  instructions.text = `
    <section class="section-backgroundImage">
      <figure class="graf--layoutFillWidth"></figure>
    </section>
    <section class="section--first">${
      flags?.archive
        ? i18next.t('ivInternetArchiveText').format({ brandingName: Constants.BRANDING_NAME })
        : i18next.t('ivFallbackText')
    } <a href="${status.url}">${i18next.t('ivViewOriginal')}</a>
    </section>
    <article>
    <sub><a href="${status.url}">${i18next.t('ivViewOriginal')}</a></sub>
    <h1>${status.author.name} (@${status.author.screen_name})</h1>

    ${useThread
      .map(status => {
        console.log('previousThreadPieceAuthor', previousThreadPieceAuthor);
        if (!status) {
          return '';
        }
        if (originalAuthor === null) {
          originalAuthor = status.author?.id;
        }
        const differentAuthor =
          thread?.author?.id !== status.author?.id ||
          (previousThreadPieceAuthor !== null && previousThreadPieceAuthor !== status.author?.id);
        const isOriginal =
          thread?.author?.id !== status.author?.id && previousThreadPieceAuthor === null;
        const isFollowup =
          thread?.author?.id === status.author?.id &&
          previousThreadPieceAuthor !== null &&
          previousThreadPieceAuthor !== thread?.author?.id &&
          originalAuthor === status.author?.id;
        console.log('differentAuthor', differentAuthor);
        console.log('isOriginal', isOriginal);
        console.log('isFollowup', isFollowup);

        let authorAction = null;

        if (differentAuthor) {
          if (isFollowup) {
            authorAction = AuthorActionType.FollowUp;
          } else if (isOriginal) {
            authorAction = AuthorActionType.Original;
          } else if (previousThreadPieceAuthor !== status.author?.id) {
            authorAction = AuthorActionType.Reply;
          }
        }

        previousThreadPieceAuthor = status.author?.id;

        return generateStatus(
          status,
          status.author ?? thread?.author,
          properties?.targetLanguage ?? 'en',
          false,
          authorAction
        );
      })
      .join('')}
    ${generateStatusFooter(status, false, thread?.author ?? status.author, properties?.targetLanguage ?? 'en')}
    <br>${`<a href="${status.url}">${i18next.t('ivViewOriginal')}</a>`}
  </article>`;

  return instructions;
};
