import { Fragment, type ReactNode } from "react";

type Props = {
  text: string;
  className?: string;
};

/**
 * 聊天泡泡用輕量 markdown（不引入 react-markdown）。
 * 一般回覆：每個實際換行都獨立成行，不會擠成一團。
 * 另支援：標題、粗斜體、刪除線、行內/區塊 code、清單、引用、連結。
 * HTML 一律當純文字，連結僅允許 http(s)/mailto。
 */
export function MarkdownText({ text, className = "" }: Props) {
  const source = normalizeNewlines(text ?? "");
  if (!source.trim()) {
    return <div className={`hb-md ${className}`.trim()} />;
  }

  const blocks = parseBlocks(source);
  return (
    <div className={`hb-md ${className}`.trim()}>
      {blocks.map((block, i) => (
        <Fragment key={i}>{renderBlock(block)}</Fragment>
      ))}
    </div>
  );
}

type Block =
  | { type: "line"; text: string }
  | { type: "gap" }
  | { type: "h"; level: 1 | 2 | 3; text: string }
  | { type: "ul"; items: string[] }
  | { type: "ol"; items: string[] }
  | { type: "quote"; lines: string[] }
  | { type: "code"; lang: string; code: string }
  | { type: "hr" };

function normalizeNewlines(source: string): string {
  return source
    .replace(/\r\n/g, "\n")
    .replace(/\r/g, "\n")
    .replace(/\u2028/g, "\n")
    .replace(/\u2029/g, "\n\n");
}

function parseBlocks(source: string): Block[] {
  const lines = source.split("\n");
  const blocks: Block[] = [];
  let i = 0;
  /** 連續空行只算一段落間距一次，開頭不插 gap */
  let pendingGap = false;
  let sawContent = false;

  const flushGap = () => {
    if (pendingGap && sawContent) {
      blocks.push({ type: "gap" });
    }
    pendingGap = false;
  };

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

    // fenced code
    const fence = line.match(/^```([\w-]*)\s*$/);
    if (fence) {
      flushGap();
      const lang = fence[1] || "";
      const body: string[] = [];
      i += 1;
      while (i < lines.length && !/^```\s*$/.test(lines[i] ?? "")) {
        body.push(lines[i] ?? "");
        i += 1;
      }
      if (i < lines.length) i += 1; // closing ```
      blocks.push({ type: "code", lang, code: body.join("\n") });
      sawContent = true;
      continue;
    }

    if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(line)) {
      flushGap();
      blocks.push({ type: "hr" });
      sawContent = true;
      i += 1;
      continue;
    }

    const heading = line.match(/^(#{1,3})\s+(.+)$/);
    if (heading) {
      flushGap();
      const level = heading[1]!.length as 1 | 2 | 3;
      blocks.push({ type: "h", level, text: heading[2]!.trim() });
      sawContent = true;
      i += 1;
      continue;
    }

    if (/^>\s?/.test(line)) {
      flushGap();
      const quoteLines: string[] = [];
      while (i < lines.length && /^>\s?/.test(lines[i] ?? "")) {
        quoteLines.push((lines[i] ?? "").replace(/^>\s?/, ""));
        i += 1;
      }
      blocks.push({ type: "quote", lines: quoteLines });
      sawContent = true;
      continue;
    }

    if (/^\s*[-*+]\s+/.test(line)) {
      flushGap();
      const items: string[] = [];
      while (i < lines.length && /^\s*[-*+]\s+/.test(lines[i] ?? "")) {
        items.push((lines[i] ?? "").replace(/^\s*[-*+]\s+/, ""));
        i += 1;
      }
      blocks.push({ type: "ul", items });
      sawContent = true;
      continue;
    }

    if (/^\s*\d+\.\s+/.test(line)) {
      flushGap();
      const items: string[] = [];
      while (i < lines.length && /^\s*\d+\.\s+/.test(lines[i] ?? "")) {
        items.push((lines[i] ?? "").replace(/^\s*\d+\.\s+/, ""));
        i += 1;
      }
      blocks.push({ type: "ol", items });
      sawContent = true;
      continue;
    }

    // 空行 → 段落間距（普通回覆的「空一行」）
    if (!line.trim()) {
      pendingGap = true;
      i += 1;
      continue;
    }

    // 一般文字：每一行獨立成行（聊天最重要）
    flushGap();
    blocks.push({ type: "line", text: line });
    sawContent = true;
    i += 1;
  }

  return blocks;
}

function renderBlock(block: Block): ReactNode {
  switch (block.type) {
    case "line":
      return (
        <div className="hb-md__line">
          {block.text ? renderInline(block.text) : "\u00a0"}
        </div>
      );
    case "gap":
      return <div className="hb-md__gap" aria-hidden />;
    case "h": {
      const Tag = `h${block.level}` as "h1" | "h2" | "h3";
      return (
        <Tag className={`hb-md__h hb-md__h--${block.level}`}>
          {renderInline(block.text)}
        </Tag>
      );
    }
    case "ul":
      return (
        <ul className="hb-md__ul">
          {block.items.map((item, j) => (
            <li key={j}>{renderInline(item)}</li>
          ))}
        </ul>
      );
    case "ol":
      return (
        <ol className="hb-md__ol">
          {block.items.map((item, j) => (
            <li key={j}>{renderInline(item)}</li>
          ))}
        </ol>
      );
    case "quote":
      return (
        <blockquote className="hb-md__quote">
          {block.lines.map((line, j) => (
            <div key={j} className="hb-md__line">
              {line ? renderInline(line) : "\u00a0"}
            </div>
          ))}
        </blockquote>
      );
    case "code":
      return (
        <pre className="hb-md__pre" data-lang={block.lang || undefined}>
          <code>{block.code}</code>
        </pre>
      );
    case "hr":
      return <hr className="hb-md__hr" />;
    default:
      return null;
  }
}

/** 行內：code → bold/italic/strike → links → plain */
function renderInline(text: string): ReactNode[] {
  const nodes: ReactNode[] = [];
  const codeSplit = text.split(/(`[^`\n]+`)/g);
  codeSplit.forEach((chunk, ci) => {
    if (!chunk) return;
    if (chunk.startsWith("`") && chunk.endsWith("`") && chunk.length >= 2) {
      nodes.push(
        <code key={`c${ci}`} className="hb-md__code">
          {chunk.slice(1, -1)}
        </code>,
      );
      return;
    }
    nodes.push(...renderInlineDecorated(chunk, `t${ci}`));
  });
  return nodes;
}

function renderInlineDecorated(text: string, keyPrefix: string): ReactNode[] {
  const nodes: ReactNode[] = [];
  const re =
    /(\*\*[^*]+\*\*|\*[^*\n]+\*|~~[^~]+~~|\[([^\]]+)\]\(([^)\s]+)\))/g;
  let last = 0;
  let m: RegExpExecArray | null;
  let idx = 0;
  while ((m = re.exec(text)) !== null) {
    if (m.index > last) {
      nodes.push(
        <Fragment key={`${keyPrefix}-p${idx++}`}>{text.slice(last, m.index)}</Fragment>,
      );
    }
    const token = m[0];
    if (token.startsWith("**") && token.endsWith("**")) {
      nodes.push(
        <strong key={`${keyPrefix}-b${idx++}`}>{token.slice(2, -2)}</strong>,
      );
    } else if (token.startsWith("~~") && token.endsWith("~~")) {
      nodes.push(
        <del key={`${keyPrefix}-d${idx++}`}>{token.slice(2, -2)}</del>,
      );
    } else if (token.startsWith("*") && token.endsWith("*")) {
      nodes.push(
        <em key={`${keyPrefix}-i${idx++}`}>{token.slice(1, -1)}</em>,
      );
    } else if (m[2] != null && m[3] != null) {
      const href = sanitizeHref(m[3]);
      if (href) {
        nodes.push(
          <a
            key={`${keyPrefix}-a${idx++}`}
            href={href}
            target="_blank"
            rel="noopener noreferrer"
            className="hb-md__a"
          >
            {m[2]}
          </a>,
        );
      } else {
        nodes.push(
          <Fragment key={`${keyPrefix}-al${idx++}`}>{m[2]}</Fragment>,
        );
      }
    }
    last = m.index + token.length;
  }
  if (last < text.length) {
    nodes.push(<Fragment key={`${keyPrefix}-e`}>{text.slice(last)}</Fragment>);
  }
  return nodes;
}

function sanitizeHref(raw: string): string | null {
  const href = raw.trim();
  if (/^https?:\/\//i.test(href)) return href;
  if (/^mailto:/i.test(href)) return href;
  return null;
}
