/**
 * Tlon Story Format - Rich text converter
 *
 * Converts markdown-like text to Tlon's story format.
 */

// Inline content types
type StoryInline =
  | string
  | { bold: StoryInline[] }
  | { italics: StoryInline[] }
  | { strike: StoryInline[] }
  | { blockquote: StoryInline[] }
  | { "inline-code": string }
  | { code: string }
  | { ship: string }
  | { link: { href: string; content: string } }
  | { break: null }
  | { tag: string };

// Block content types
type StoryBlock =
  | { header: { tag: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; content: StoryInline[] } }
  | { code: { code: string; lang: string } }
  | { image: { src: string; height: number; width: number; alt: string } }
  | { rule: null }
  | { listing: StoryListing };

type StoryListing =
  | {
      list: {
        type: "ordered" | "unordered" | "tasklist";
        items: StoryListing[];
        contents: StoryInline[];
      };
    }
  | { item: StoryInline[] };

// A verse is either a block or inline content
type StoryVerse = { block: StoryBlock } | { inline: StoryInline[] };

// A story is a list of verses
export type Story = StoryVerse[];

/**
 * Parse inline markdown formatting (bold, italic, code, links, mentions)
 */
function parseInlineMarkdown(text: string): StoryInline[] {
  const result: StoryInline[] = [];
  let remaining = text;

  while (remaining.length > 0) {
    // Ship mentions: ~sampel-palnet
    const shipMatch = remaining.match(/^(~[a-z][-a-z0-9]*)/);
    if (shipMatch) {
      result.push({ ship: shipMatch[1] });
      remaining = remaining.slice(shipMatch[0].length);
      continue;
    }

    // Bold: **text** or __text__
    const boldMatch = remaining.match(/^\*\*(.+?)\*\*|^__(.+?)__/);
    if (boldMatch) {
      const content = boldMatch[1] || boldMatch[2];
      result.push({ bold: parseInlineMarkdown(content) });
      remaining = remaining.slice(boldMatch[0].length);
      continue;
    }

    // Italics: *text* or _text_ (but not inside words for _)
    const italicsMatch = remaining.match(/^\*([^*]+?)\*|^_([^_]+?)_(?![a-zA-Z0-9])/);
    if (italicsMatch) {
      const content = italicsMatch[1] || italicsMatch[2];
      result.push({ italics: parseInlineMarkdown(content) });
      remaining = remaining.slice(italicsMatch[0].length);
      continue;
    }

    // Strikethrough: ~~text~~
    const strikeMatch = remaining.match(/^~~(.+?)~~/);
    if (strikeMatch) {
      result.push({ strike: parseInlineMarkdown(strikeMatch[1]) });
      remaining = remaining.slice(strikeMatch[0].length);
      continue;
    }

    // Inline code: `code`
    const codeMatch = remaining.match(/^`([^`]+)`/);
    if (codeMatch) {
      result.push({ "inline-code": codeMatch[1] });
      remaining = remaining.slice(codeMatch[0].length);
      continue;
    }

    // Links: [text](url)
    const linkMatch = remaining.match(/^\[([^\]]+)\]\(([^)]+)\)/);
    if (linkMatch) {
      result.push({ link: { href: linkMatch[2], content: linkMatch[1] } });
      remaining = remaining.slice(linkMatch[0].length);
      continue;
    }

    // Markdown images: ![alt](url)
    const imageMatch = remaining.match(/^!\[([^\]]*)\]\(([^)]+)\)/);
    if (imageMatch) {
      // Return a special marker that will be hoisted to a block
      result.push({
        __image: { src: imageMatch[2], alt: imageMatch[1] },
      } as unknown as StoryInline);
      remaining = remaining.slice(imageMatch[0].length);
      continue;
    }

    // Plain URL detection
    const urlMatch = remaining.match(/^(https?:\/\/[^\s<>"\]]+)/);
    if (urlMatch) {
      result.push({ link: { href: urlMatch[1], content: urlMatch[1] } });
      remaining = remaining.slice(urlMatch[0].length);
      continue;
    }

    // Hashtags: #tag - disabled, chat UI doesn't render them
    // const tagMatch = remaining.match(/^#([a-zA-Z][a-zA-Z0-9_-]*)/);
    // if (tagMatch) {
    //   result.push({ tag: tagMatch[1] });
    //   remaining = remaining.slice(tagMatch[0].length);
    //   continue;
    // }

    // Plain text: consume until next special character or URL start
    // Exclude : and / to allow URL detection to work (stops before https://)
    const plainMatch = remaining.match(/^[^*_`~[#~\n:/]+/);
    if (plainMatch) {
      result.push(plainMatch[0]);
      remaining = remaining.slice(plainMatch[0].length);
      continue;
    }

    // Single special char that didn't match a pattern
    result.push(remaining[0]);
    remaining = remaining.slice(1);
  }

  // Merge adjacent strings
  return mergeAdjacentStrings(result);
}

/**
 * Merge adjacent string elements in an inline array
 */
function mergeAdjacentStrings(inlines: StoryInline[]): StoryInline[] {
  const result: StoryInline[] = [];
  for (const item of inlines) {
    if (typeof item === "string" && typeof result[result.length - 1] === "string") {
      result[result.length - 1] = (result[result.length - 1] as string) + item;
    } else {
      result.push(item);
    }
  }
  return result;
}

/**
 * Create an image block
 */
export function createImageBlock(src: string, alt = "", height = 0, width = 0): StoryVerse {
  return {
    block: {
      image: { src, height, width, alt },
    },
  };
}

/**
 * Check if URL looks like an image
 */
export function isImageUrl(url: string): boolean {
  const imageExtensions = /\.(jpg|jpeg|png|gif|webp|svg|bmp|ico)(\?.*)?$/i;
  return imageExtensions.test(url);
}

/**
 * Process inlines and extract any image markers into blocks
 */
function processInlinesForImages(inlines: StoryInline[]): {
  inlines: StoryInline[];
  imageBlocks: StoryVerse[];
} {
  const cleanInlines: StoryInline[] = [];
  const imageBlocks: StoryVerse[] = [];

  for (const inline of inlines) {
    if (typeof inline === "object" && "__image" in inline) {
      const img = (inline as unknown as { __image: { src: string; alt: string } })["__image"];
      imageBlocks.push(createImageBlock(img.src, img.alt));
    } else {
      cleanInlines.push(inline);
    }
  }

  return { inlines: cleanInlines, imageBlocks };
}

/**
 * Convert markdown text to Tlon story format
 */
export function markdownToStory(markdown: string): Story {
  const story: Story = [];
  const lines = markdown.split("\n");
  let i = 0;

  while (i < lines.length) {
    const line = lines[i];

    // Code block: ```lang\ncode\n```
    if (line.startsWith("```")) {
      const lang = line.slice(3).trim() || "plaintext";
      const codeLines: string[] = [];
      i++;
      while (i < lines.length && !lines[i].startsWith("```")) {
        codeLines.push(lines[i]);
        i++;
      }
      story.push({
        block: {
          code: {
            code: codeLines.join("\n"),
            lang,
          },
        },
      });
      i++; // skip closing ```
      continue;
    }

    // Headers: # H1, ## H2, etc.
    const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
    if (headerMatch) {
      const level = headerMatch[1].length as 1 | 2 | 3 | 4 | 5 | 6;
      const tag = `h${level}` as const;
      story.push({
        block: {
          header: {
            tag,
            content: parseInlineMarkdown(headerMatch[2]),
          },
        },
      });
      i++;
      continue;
    }

    // Horizontal rule: --- or ***
    if (/^(-{3,}|\*{3,})$/.test(line.trim())) {
      story.push({ block: { rule: null } });
      i++;
      continue;
    }

    // Blockquote: > text
    if (line.startsWith("> ")) {
      const quoteLines: string[] = [];
      while (i < lines.length && lines[i].startsWith("> ")) {
        quoteLines.push(lines[i].slice(2));
        i++;
      }
      const quoteText = quoteLines.join("\n");
      story.push({
        inline: [{ blockquote: parseInlineMarkdown(quoteText) }],
      });
      continue;
    }

    // Empty line - skip
    if (line.trim() === "") {
      i++;
      continue;
    }

    // Regular paragraph - collect consecutive non-empty lines
    const paragraphLines: string[] = [];
    while (
      i < lines.length &&
      lines[i].trim() !== "" &&
      !lines[i].startsWith("#") &&
      !lines[i].startsWith("```") &&
      !lines[i].startsWith("> ") &&
      !/^(-{3,}|\*{3,})$/.test(lines[i].trim())
    ) {
      paragraphLines.push(lines[i]);
      i++;
    }

    if (paragraphLines.length > 0) {
      const paragraphText = paragraphLines.join("\n");
      // Convert newlines within paragraph to break elements
      const inlines = parseInlineMarkdown(paragraphText);
      // Replace \n in strings with break elements
      const withBreaks: StoryInline[] = [];
      for (const inline of inlines) {
        if (typeof inline === "string" && inline.includes("\n")) {
          const parts = inline.split("\n");
          for (let j = 0; j < parts.length; j++) {
            if (parts[j]) {
              withBreaks.push(parts[j]);
            }
            if (j < parts.length - 1) {
              withBreaks.push({ break: null });
            }
          }
        } else {
          withBreaks.push(inline);
        }
      }

      // Extract any images from inlines and add as separate blocks
      const { inlines: cleanInlines, imageBlocks } = processInlinesForImages(withBreaks);

      if (cleanInlines.length > 0) {
        story.push({ inline: cleanInlines });
      }
      story.push(...imageBlocks);
    }
  }

  return story;
}
