"use client";

import Link from "next/link";
import * as React from "react";

type LinkOptions = {
  /** Turns a link's address into a page of this portal, or null when it is not one the panel opens (shown as plain text). */
  resolveLink?: (url: string) => string | null;
  /** Called when a link is followed (the panel closes itself to show the page). */
  onLinkClick?: () => void;
};

/**
 * Small markdown renderer for the Portfolio Agent: headings (#–####), "-" / "*" / "•" bullets (indented ones too),
 * numbered lists, tables, rules, and inline **bold**, _italic_, `code` and [links](address). No dependency, and no
 * HTML is ever rendered from the text (it only becomes React text nodes). A link is only a link when `resolveLink`
 * accepts its address; otherwise only its text is shown, so an answer can never send the user somewhere unexpected.
 */
export function SoftMarkdown({
  text,
  className,
  resolveLink,
  onLinkClick,
}: { text: string; className?: string } & LinkOptions) {
  const link: LinkOptions = { resolveLink, onLinkClick };
  const lines = text.split("\n");
  const out: React.ReactNode[] = [];

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    const trimmed = line.trim();

    if (trimmed === "") {
      out.push(<div key={i} className="h-2" />);
      continue;
    }

    if (/^(-{3,}|\*{3,}|_{3,})$/.test(trimmed)) {
      out.push(<hr key={i} className="my-2 border-border" />);
      continue;
    }

    const heading = /^(#{1,6})\s+(.*)$/.exec(trimmed);
    if (heading) {
      const level = heading[1].length;
      out.push(
        <p
          key={i}
          className={level <= 2 ? "mt-1 text-base font-semibold" : "mt-1 text-[15px] font-semibold"}
        >
          {renderInline(heading[2].replace(/\s*#+\s*$/, ""), link)}
        </p>,
      );
      continue;
    }

    // A table: a row of cells, then a separator row (|---|---|), then more rows.
    if (isTableRow(trimmed) && i + 1 < lines.length && isTableSeparator(lines[i + 1].trim())) {
      const header = splitRow(trimmed);
      const rows: string[][] = [];
      let j = i + 2;
      while (j < lines.length && isTableRow(lines[j].trim())) {
        rows.push(splitRow(lines[j].trim()));
        j++;
      }
      out.push(
        <div key={i} className="my-1 overflow-x-auto">
          <table className="w-full border-collapse text-sm">
            <thead>
              <tr>
                {header.map((cell, c) => (
                  <th key={c} className="border-b px-2 py-1 text-left font-semibold">
                    {renderInline(cell, link)}
                  </th>
                ))}
              </tr>
            </thead>
            <tbody>
              {rows.map((row, r) => (
                <tr key={r}>
                  {header.map((_, c) => (
                    <td key={c} className="border-b px-2 py-1 align-top">
                      {renderInline(row[c] ?? "", link)}
                    </td>
                  ))}
                </tr>
              ))}
            </tbody>
          </table>
        </div>,
      );
      i = j - 1;
      continue;
    }

    const bullet = /^(\s*)([-*•])\s+(.*)$/.exec(line);
    if (bullet) {
      out.push(
        <div key={i} className="flex gap-2" style={{ paddingLeft: indentOf(bullet[1]) }}>
          <span className="shrink-0 text-muted-foreground">•</span>
          <span>{renderInline(bullet[3], link)}</span>
        </div>,
      );
      continue;
    }

    const numbered = /^(\s*)(\d+)[.)]\s+(.*)$/.exec(line);
    if (numbered) {
      out.push(
        <div key={i} className="flex gap-2" style={{ paddingLeft: indentOf(numbered[1]) }}>
          <span className="shrink-0 text-muted-foreground">{numbered[2]}.</span>
          <span>{renderInline(numbered[3], link)}</span>
        </div>,
      );
      continue;
    }

    out.push(
      <p key={i} className="leading-relaxed">
        {renderInline(line, link)}
      </p>,
    );
  }

  return <div className={className}>{out}</div>;
}

/** Two spaces of indent = one level (1rem). */
function indentOf(spaces: string): string {
  return `${Math.min(Math.floor(spaces.replace(/\t/g, "  ").length / 2), 4)}rem`;
}

function isTableRow(line: string): boolean {
  return line.startsWith("|") && line.endsWith("|") && line.length > 1;
}

function isTableSeparator(line: string): boolean {
  return isTableRow(line) && /^[|\s:-]+$/.test(line) && line.includes("-");
}

function splitRow(line: string): string[] {
  return line
    .slice(1, -1)
    .split("|")
    .map((cell) => cell.trim());
}

function renderInline(line: string, { resolveLink, onLinkClick }: LinkOptions = {}): React.ReactNode[] {
  const nodes: React.ReactNode[] = [];
  const pattern = /(\[[^\]]+\]\([^)\s]+\)|\*\*[^*]+\*\*|_[^_]+_|`[^`]+`)/g;
  let last = 0;
  let match: RegExpExecArray | null;
  let key = 0;

  while ((match = pattern.exec(line)) !== null) {
    if (match.index > last) {
      nodes.push(line.slice(last, match.index));
    }
    const token = match[0];
    const linkMatch = token.startsWith("[") ? /^\[([^\]]+)\]\(([^)\s]+)\)$/.exec(token) : null;
    if (linkMatch) {
      const href = resolveLink?.(linkMatch[2]) ?? null;
      nodes.push(
        href ? (
          <Link
            key={key++}
            href={href}
            onClick={onLinkClick}
            className="font-medium text-primary underline underline-offset-2 hover:opacity-80"
          >
            {linkMatch[1]}
          </Link>
        ) : (
          linkMatch[1]
        ),
      );
    } else if (token.startsWith("**") && token.endsWith("**")) {
      nodes.push(
        <strong key={key++} className="font-semibold">
          {token.slice(2, -2)}
        </strong>,
      );
    } else if (token.startsWith("_") && token.endsWith("_")) {
      nodes.push(
        <em key={key++} className="text-muted-foreground italic">
          {token.slice(1, -1)}
        </em>,
      );
    } else if (token.startsWith("`") && token.endsWith("`")) {
      nodes.push(
        <code key={key++} className="rounded bg-muted px-1 py-0.5 font-mono text-[0.85em]">
          {token.slice(1, -1)}
        </code>,
      );
    } else {
      nodes.push(token);
    }
    last = match.index + token.length;
  }

  if (last < line.length) {
    nodes.push(line.slice(last));
  }

  return nodes;
}
