import React, { useMemo } from 'react';
import { marked } from 'marked';
import DOMPurify from 'dompurify';
import { escapeHtml } from '../lib/utils.js';
import { getToken } from '../api/client.js';
import LockBlock from './LockBlock.jsx';

/**
 * 锁块附件鉴权地址：后端 /api/upload/locked 按 (ref_type, ref_id, block, file) + token 校验，
 * 只允许取「该帖该块」已解锁的附件。缺 refType/refId/token 返回 null → 调用方回退原直链。
 */
function lockedAttachmentUrl(refType, refId, blockIndex, filename, token) {
  if (!refType || refId == null || !token) return null;
  return `/api/upload/locked?ref_type=${encodeURIComponent(refType)}&ref_id=${refId}&block=${encodeURIComponent(blockIndex)}&file=${encodeURIComponent(filename)}&token=${encodeURIComponent(token)}`;
}

/** [image:文件名] → 图片标签；lockedUrl 存在则 src 走鉴权接口，否则 /uploads/ 直链 */
function imageTag(filename, lockedUrl) {
  if (!filename) return '';
  const src = lockedUrl || `/uploads/${encodeURIComponent(filename)}`;
  return `<img src="${src}" alt="" loading="lazy" style="max-width:100%;border-radius:8px;margin:8px 0">`;
}

/** [file:文件名] → 下载链接；lockedUrl 存在则 href 走鉴权接口（保留 target=_blank 与样式），否则原下载逻辑 */
function fileTag(filename, lockedUrl) {
  if (!filename) return '';
  const token = getToken() || '';
  const href = lockedUrl || `/api/upload/download/${encodeURIComponent(filename)}?token=${encodeURIComponent(token)}`;
  return `<a href="${href}" target="_blank" class="file-link" style="display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:var(--md-ref-surface-container);border-radius:8px;margin:4px 0;text-decoration:none;color:var(--md-ref-primary);font-size:14px">
    <span class="material-icons" style="font-size:18px">attachment</span> ${escapeHtml(filename)}</a>`;
}

/**
 * 剥离内联 [lock:] 成对标签，保留块内内容（只去标签不去内容）。
 * 保留导出供外部/预览使用；renderContent 不再调用——组件层已用切段方案接管（保留锁块容器）。
 */
export function stripLockTags(content) {
  return String(content).replace(/\[lock(?::[^\]]*)?\]([\s\S]*?)\[\/lock\]/g, '$1');
}

/** 从整块标签提取 lock 类型参数：'login' / 'reply' / 'password'（无参数返回 ''） */
function extractLockType(block) {
  const m = String(block).match(/^\[lock(?::([^\]]*))?\]/);
  const param = (m && m[1]) || '';
  return param.split(':')[0] || '';
}

/**
 * 按内联 [lock:] 成对标签切段（split 带捕获组）：
 * 交替产出 [文本, 整块(含标签), 块内内容, 文本, …]（i%3：0=文本 / 1=整块 / 2=块内内容）
 */
const INLINE_LOCK_RE = /(\[lock(?::[^\]]*)?\]([\s\S]*?)\[\/lock\])/g;

/** 统计文本段内内联 [lock:] 成对标签数量（全文锁块索引游标推进用） */
function countInlineLocks(text) {
  const m = String(text).match(INLINE_LOCK_RE);
  return m ? m.length : 0;
}

/**
 * 渲染一段文本：文本段走 renderContent；内联 [lock:] 段渲染「已解锁内容」容器。
 * 已解锁块（admin/作者/登录或评论后可见）后端在 content 里原样保留 [lock:...]...[/lock] 标签，
 * 这里按标签切成锁块容器（同款背景+边框 + 🔓 已解锁头部），内容递归 MarkdownRenderer 渲染。
 * rest.inlineStartIdx：本段起始内联块在全文中的索引（buildNodes 用锁块游标给出），
 * 用于查 locks 数组拿该块的附件 token（块内 [image:]/[file:] 走鉴权接口）。
 */
function renderSegments(text, useMarkdown, rest) {
  const { locks, refType, refId, blockCtx: parentCtx } = rest;
  const parts = String(text).split(INLINE_LOCK_RE);
  const nodes = [];
  let textIdx = 0;
  let lockIdx = 0;
  let tagIdx = rest.inlineStartIdx || 0; // 内联块在全文中的索引游标
  for (let i = 0; i < parts.length; i += 1) {
    const p = parts[i];
    if (i % 3 === 1) {
      // 整块（含标签），紧随其后 i+1 是对应块内内容
      const inner = parts[i + 1] || '';
      const type = extractLockType(p);
      const idx = tagIdx++;
      // 内联块附件上下文：已处锁块内（parentCtx）时沿用父块 token（嵌套块文件同属父块）；
      // 顶层则按本段索引查 locks 数组拿该块的 token（无 token 保持直链向后兼容）
      let childCtx = null;
      if (parentCtx) {
        childCtx = parentCtx;
      } else {
        const meta = (Array.isArray(locks) && locks.find((l) => l.index === idx)) || {};
        if (meta.token) childCtx = { index: idx, token: meta.token };
      }
      nodes.push(
        <div key={'reveal' + lockIdx++} className="lock-reveal">
          <div className="lock-placeholder lock-revealed">
            <div className="lock-revealed-head">
              <span className="material-icons" style={{ fontSize: 16 }} aria-hidden="true">lock_open</span>
              已解锁内容
              {type ? <span className="lock-revealed-type">{type}</span> : null}
            </div>
            <div className="lock-revealed-body">
              <MarkdownRenderer
                content={inner}
                useMarkdown={useMarkdown}
                locks={locks}
                unlocked={rest.unlocked}
                lockContent={rest.lockContent}
                onUnlock={rest.onUnlock}
                onGoComment={rest.onGoComment}
                refType={refType}
                refId={refId}
                blockCtx={childCtx}
              />
            </div>
          </div>
        </div>
      );
      i += 1; // 跳过内容段（已作为 inner 渲染）
    } else if (p) {
      // 文本段：仅当外层处于已解锁锁块内（parentCtx）时附件才改走鉴权接口
      const attach = parentCtx ? { refType, refId, blockIndex: parentCtx.index, token: parentCtx.token || '' } : null;
      nodes.push(<RawBlock key={'text' + textIdx++} html={renderContent(p, useMarkdown, attach)} />);
    }
  }
  return nodes;
}

/**
 * 内容渲染：useMarkdown=false 走纯文本（转义 + 标签替换 + <br>）；
 * useMarkdown=true 先抽取 [image:]/[file:] 标签，marked 渲染后再还原（照搬 render.js）。
 * attach（可选）= { refType, refId, blockIndex, token }：处于已解锁锁块内时的附件鉴权上下文；
 * 缺省则 [image:]/[file:] 照旧 /uploads/ 直链与下载逻辑。本函数只处理单段文本，lock 分段在组件层完成。
 */
export function renderContent(content, useMarkdown, attach) {
  if (content == null) return '';

  // 锁块附件地址生成器：无 attach 上下文或无 token 时返回 null → 走原直链/下载逻辑
  const lockedUrl = (filename) => (attach
    ? lockedAttachmentUrl(attach.refType, attach.refId, attach.blockIndex, filename, attach.token)
    : null);

  if (!useMarkdown) {
    let html = escapeHtml(content);
    html = html.replace(/\[image:([^\]]+)\]/g, (m, f) => imageTag(f, lockedUrl(f)));
    html = html.replace(/\[file:([^\]]+)\]/g, (m, f) => fileTag(f, lockedUrl(f)));
    html = html.replace(/\n/g, '<br>');
    return DOMPurify.sanitize(html);
  }

  // Markdown 模式：先抽取自定义标签，避免被 marked 转义
  const images = [];
  const files = [];
  let html = content.replace(/\[image:([^\]]+)\]/g, (m, f) => { images.push(f); return `\x00IMG${images.length - 1}\x00`; });
  html = html.replace(/\[file:([^\]]+)\]/g, (m, f) => { files.push(f); return `\x00FILE${files.length - 1}\x00`; });

  html = marked.parse(html, { breaks: true, gfm: true });

  html = html.replace(/\x00IMG(\d+)\x00/g, (m, i) => imageTag(images[parseInt(i)], lockedUrl(images[parseInt(i)])));
  html = html.replace(/\x00FILE(\d+)\x00/g, (m, i) => fileTag(files[parseInt(i)], lockedUrl(files[parseInt(i)])));
  html = DOMPurify.sanitize(html);

  // 给 h2 注入稳定锚点 id（在 HTML 字符串内生成，重渲一致，供目录锚点定位；
  // 不在渲染后 DOM 上赋 id——React 重渲可能替换 DOM 导致 id 丢失）
  let h2Index = 0;
  html = html.replace(/<h2(?![^>]*\bid=)/gi, () => `<h2 id="toc-${h2Index++}"`);
  return html;
}

/** 单段文本块：dangerouslySetInnerHTML 包装（renderContent 已 DOMPurify 净化） */
function RawBlock({ html }) {
  return <div dangerouslySetInnerHTML={{ __html: html }} />;
}

/**
 * 构建渲染节点（两层分段）：
 * 1. @@LOCK<n>@@ 占位分段（详情页 locks 非空）——lock 段：已解锁+有内容 → 递归渲染（.lock-reveal 淡入），
 *    否则 LockBlock 锁定卡片；文本段 → 再按内联 [lock:] 切段（已解锁容器 / 文本）。
 *    文本段内联块索引用全文游标（nextIdx）衔接，保证与后端 parseLocks 的 index 一致。
 * 2. locks 为空（含 previewMode 编辑预览）：整段按内联 [lock:] 切段，作者视角同样显示已解锁容器
 */
function buildNodes({ content, useMarkdown, locks, unlocked, lockContent, onUnlock, onGoComment, previewMode, refType, refId, blockCtx }) {
  if (content == null) return null;
  const str = String(content);
  const hasLocks = Array.isArray(locks) && locks.length > 0;

  if (hasLocks) {
    // 服务端已把未解锁块替换为 @@LOCK<n>@@；split 交替产出 [文本, 索引, 文本, 索引…]
    const parts = str.split(/@@LOCK(\d+)@@/);
    const nodes = [];
    let nextIdx = 0; // 全文锁块索引游标：内联块与 @@LOCK 占位按文档顺序递增
    for (let i = 0; i < parts.length; i += 1) {
      const p = parts[i];
      if (i % 2 === 1) {
        const n = parseInt(p, 10);
        nextIdx = n + 1; // 占位符自带索引，同步游标
        const meta = locks.find((l) => l.index === n) || {};
        const type = meta.type || 'password';
        const lockEntry = unlocked && unlocked.has(n) && lockContent && lockContent.get(n);
        if (lockEntry) {
          // lockContent 兼容纯字符串或 { content, token }；token 优先取 locks 数组项（后端按块签发）
          const raw = typeof lockEntry === 'object' ? lockEntry.content : lockEntry;
          const entryToken = typeof lockEntry === 'object' && lockEntry.token ? lockEntry.token : '';
          const token = (meta.token || entryToken) || '';
          nodes.push(
            <div key={'lock' + n} className="lock-reveal">
              <MarkdownRenderer
                content={raw}
                useMarkdown={useMarkdown}
                locks={locks}
                unlocked={unlocked}
                lockContent={lockContent}
                onUnlock={onUnlock}
                onGoComment={onGoComment}
                refType={refType}
                refId={refId}
                blockCtx={{ index: n, token }}
              />
            </div>
          );
        } else {
          nodes.push(<LockBlock key={'lock' + n} index={n} type={type} onUnlock={onUnlock} onGoComment={onGoComment} />);
        }
      } else if (p) {
        // 文本段：可能仍含内联 [lock:]（已解锁块）→ 切段渲染已解锁容器
        nodes.push(...renderSegments(p, useMarkdown, {
          locks, unlocked, lockContent, onUnlock, onGoComment, refType, refId, blockCtx,
          inlineStartIdx: nextIdx,
        }));
        nextIdx += countInlineLocks(p);
      }
    }
    return nodes;
  }

  // locks 为空：整段按内联 [lock:] 切段（previewMode 作者预览同样显示「已解锁内容」容器）
  return renderSegments(str, useMarkdown, {
    locks, unlocked, lockContent, onUnlock, onGoComment, refType, refId, blockCtx,
    inlineStartIdx: 0,
  });
}

/**
 * Markdown 渲染器（前台统一入口）：
 * props:
 *   content / useMarkdown — 同原有
 *   locks        — [{index, type}]（新后端带 {index, type, token}）详情接口返回的锁定块元信息
 *   unlocked     — Set<number> 已解锁索引（login/reply 由服务端判定后内容直接给原文；
 *                  password 类解锁后内容存 lockContent）
 *   lockContent  — Map<number, string | {content, token}> password 块解锁拿到的 markdown（仅内存态）
 *   onUnlock     — async (index, password?) → 解锁；失败 throw
 *   onGoComment  — reply 块「去评论」滚动回调
 *   previewMode  — 编辑器作者预览：锁定标签直接展开
 *   refType      — 'blog' | 'forum'（可选）：提供后锁块内 [image:]/[file:] 附件改走鉴权接口
 *   refId        — 详情 id（可选，配合 refType）
 *   blockCtx     — {index, token}（可选）：当前渲染上下文处于已解锁锁块内，附件用该块的 token 鉴权
 */
export default function MarkdownRenderer({
  content = '',
  useMarkdown = true,
  locks,
  unlocked,
  lockContent,
  onUnlock,
  onGoComment,
  previewMode = false,
  refType,
  refId,
  blockCtx,
}) {
  const nodes = useMemo(
    () => buildNodes({ content, useMarkdown, locks, unlocked, lockContent, onUnlock, onGoComment, previewMode, refType, refId, blockCtx }),
    [content, useMarkdown, locks, unlocked, lockContent, onUnlock, onGoComment, previewMode, refType, refId, blockCtx]
  );
  return <div className="md-body">{nodes}</div>;
}
