import { last, pluck, identity } from "ramda";
import { nip19 } from "nostr-tools";

export const NEWLINE = "newline";
export const TEXT = "text";
export const TOPIC = "topic";
export const LINK = "link";
export const INVOICE = "invoice";
export const NOSTR_NOTE = "nostr:note";
export const NOSTR_NEVENT = "nostr:nevent";
export const NOSTR_NPUB = "nostr:npub";
export const NOSTR_NPROFILE = "nostr:nprofile";
export const NOSTR_NADDR = "nostr:naddr";

function first(list: any) {
  return list ? list[0] : undefined;
}

export const fromNostrURI = (s: string) => s.replace(/^[\w\+]+:\/?\/?/, "");

export const urlIsMedia = (url: string) =>
  !url.match(/\.(apk|docx|xlsx|csv|dmg)/) &&
  last(url.split("://"))?.includes("/");

export const parseContent = ({
  content,
  tags = [],
}: {
  content: string;
  tags: any[];
}): { type: string; value: string }[] => {
  const result: { type: string; value: string }[] = [];

  let text = content.trim();
  let buffer = "";

  const parseNewline = () => {
    const newline = first(text.match(/^\n+/));

    if (newline) {
      return [NEWLINE, newline, newline];
    }
  };

  const parseMention = () => {
    // Convert legacy mentions to bech32 entities
    const mentionMatch = text.match(/^#\[(\d+)\]/i);

    if (mentionMatch && mentionMatch[1]) {
      const i = parseInt(mentionMatch[1]);

      if (tags[i]) {
        const [tag, value, url] = tags[i];
        const relays = [url].filter(identity);

        let type, data, entity;
        if (tag === "p") {
          type = "nprofile";
          data = { pubkey: value, relays };
          entity = nip19.nprofileEncode(data);
        } else {
          type = "nevent";
          data = { id: value, relays };
          entity = nip19.neventEncode(data);
        }

        return [`nostr:${type}`, mentionMatch[0], { ...data, entity }];
      }
    }
  };

  const parseTopic = () => {
    const topic = first(text.match(/^#\w+/i));

    // Skip numeric topics
    if (topic && !topic.match(/^#\d+$/)) {
      return [TOPIC, topic, topic.slice(1)];
    }
  };

  const parseBech32 = () => {
    const bech32 = first(
      text.match(
        /^(web\+)?(nostr:)?\/?\/?n(event|ote|profile|pub|addr)1[\d\w]+/i,
      ),
    );

    if (bech32) {
      try {
        const entity = fromNostrURI(bech32);
        const { type, data } = nip19.decode(entity) as {
          type: string;
          data: object;
        };

        let value = data;
        if (type === "note") {
          value = { id: data };
        } else if (type === "npub") {
          value = { pubkey: data };
        }

        return [`nostr:${type}`, bech32, { ...value, entity }];
      } catch (e) {
        console.log(e);
        // pass
      }
    }
  };

  const parseInvoice = () => {
    const invoice = first(text.match(/^ln(bc|url)[\d\w]{50,1000}/i));

    if (invoice) {
      return [INVOICE, invoice, invoice];
    }
  };

  const parseUrl = () => {
    const raw = first(
      text.match(
        /^([a-z\+:]{2,30}:\/\/)?[^\s]+\.[a-z]{2,6}[^\s]*[^\.!?,:\s]/gi,
      ),
    );

    // Skip url if it's just the end of a filepath
    if (raw) {
      const prev: any = last(result);

      if (prev?.type === "text" && prev.value.endsWith("/")) {
        return;
      }

      let url = raw;

      // Skip ellipses and very short non-urls
      if (url.match(/\.\./)) {
        return;
      }

      if (!url.match("://")) {
        url = "https://" + url;
      }

      return [LINK, raw, { url, isMedia: urlIsMedia(url) }];
    }
  };

  while (text) {
    const part =
      parseNewline() ||
      parseMention() ||
      parseTopic() ||
      parseBech32() ||
      parseUrl() ||
      parseInvoice();

    if (part) {
      if (buffer) {
        result.push({ type: "text", value: buffer });
        buffer = "";
      }

      const [type, raw, value] = part;

      result.push({ type, value });
      text = text.slice(raw.length);
    } else {
      // Instead of going character by character and re-running all the above regular expressions
      // a million times, try to match the next word and add it to the buffer
      const match = first(text.match(/^[\w\d]+ ?/i)) || text[0];

      buffer += match;
      text = text.slice(match.length);
    }
  }

  if (buffer) {
    result.push({ type: TEXT, value: buffer });
  }

  return result;
};

export const truncateContent = (
  content: { value: string; type: string; isMedia: boolean }[],
  {
    showEntire,
    maxLength,
    showMedia = false,
  }: { showEntire: boolean; maxLength: number; showMedia: boolean },
) => {
  if (showEntire) {
    return content;
  }

  let length = 0;
  const result: any[] = [];
  const truncateAt = maxLength * 0.6;

  content.every((part, i) => {
    const isText =
      [TOPIC, TEXT].includes(part.type) ||
      (part.type === LINK && !part.isMedia);
    const isMedia =
      part.type === INVOICE || part.type.startsWith("nostr:") || part.isMedia;

    if (isText) {
      length += part.value.length;
    }

    if (isMedia) {
      length += showMedia ? maxLength / 3 : part.value.length;
    }

    result.push(part);

    if (length > truncateAt && i < content.length - 1) {
      if (isText || (isMedia && !showMedia)) {
        result.push({ type: TEXT, value: "..." });
      }

      return false;
    }

    return true;
  });

  return result;
};

export const getLinks = (
  parts: { value: string; type: string; isMedia: boolean }[],
) =>
  pluck(
    "value",
    parts.filter((x) => x.type === LINK && x.isMedia),
  );