import fs from 'node:fs/promises';
import path from 'node:path';
import { PUBLISH_ROOT_DIR, PUBLISH_KEY_UUID } from './user-publish.mjs';
import { workspaceThumbnailRelativePath } from './mindspace-workspace-thumbnails.mjs';

const URL_PATTERN =
  /https?:\/\/[^\s<>"')\]]+\/(?:MindSpace|temp)\/([0-9a-f-]{36}|[a-z0-9._-]+)\/([^\s<>"')\]]+\.html)/gi;

function decodePathSegment(segment) {
  try {
    return decodeURIComponent(segment);
  } catch {
    return segment;
  }
}

function encodeUrlPath(relativePath) {
  return String(relativePath ?? '')
    .split('/')
    .filter(Boolean)
    .map((part) => encodeURIComponent(part))
    .join('/');
}

export function normalizeStaticHtmlRelativePath(relativePath) {
  const parts = String(relativePath ?? '')
    .replace(/^\/+/, '')
    .split('/')
    .filter((part) => part && part !== '.' && part !== '..');
  if (parts.length === 0) return '';
  if (parts[0].toLowerCase() === 'public') return ['public', ...parts.slice(1)].join('/');
  if (parts.length === 1 && parts[0].toLowerCase().endsWith('.html')) return `public/${parts[0]}`;
  return parts.join('/');
}

function canonicalizeStaticPageUrl(publicUrl, originalRelativePath, canonicalRelativePath) {
  if (!canonicalRelativePath || canonicalRelativePath === String(originalRelativePath ?? '').replace(/^\/+/, '')) {
    return publicUrl;
  }
  const suffix = encodeUrlPath(originalRelativePath).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  return String(publicUrl).replace(new RegExp(`${suffix}$`), encodeUrlPath(canonicalRelativePath));
}

export function extractStaticPageLinks(content, { userId, username } = {}) {
  const text = String(content ?? '');
  const links = [];
  const seen = new Set();
  const normalizedUserId = userId ? String(userId).trim().toLowerCase() : null;
  const normalizedUsername = username ? String(username).trim().toLowerCase() : null;
  for (const match of text.matchAll(URL_PATTERN)) {
    const owner = decodePathSegment(match[1]).toLowerCase();
    const originalRelativePath = decodePathSegment(match[2]);
    const relativePath = normalizeStaticHtmlRelativePath(originalRelativePath);
    if (normalizedUserId) {
      if (owner !== normalizedUserId && owner !== normalizedUsername) continue;
    } else if (normalizedUsername && owner !== normalizedUsername) {
      continue;
    }
    const key = `${owner}/${relativePath}`;
    if (seen.has(key)) continue;
    seen.add(key);
    links.push({
      publicUrl: canonicalizeStaticPageUrl(match[0], originalRelativePath, relativePath),
      owner,
      relativePath,
      filename: path.basename(relativePath),
    });
  }
  return links;
}

export function buildWorkspaceAssetUrl(userId, relativePath) {
  const key = String(userId ?? '').trim();
  const clean = String(relativePath ?? '')
    .replace(/^\/+/, '')
    .split('/')
    .filter((part) => part && part !== '.' && part !== '..')
    .map((part) => encodeURIComponent(part))
    .join('/');
  if (!key || !clean) return null;
  return `/${PUBLISH_ROOT_DIR}/${encodeURIComponent(key)}/${clean}`;
}

export function buildWorkspaceThumbnailUrl(userId, htmlRelativePath) {
  const thumbRel = workspaceThumbnailRelativePath(htmlRelativePath);
  return buildWorkspaceAssetUrl(userId, thumbRel);
}

export function resolvePublishHtmlAbsolutePath(h5Root, userId, relativePath) {
  const key = String(userId ?? '').trim().toLowerCase();
  if (!PUBLISH_KEY_UUID.test(key)) {
    throw Object.assign(new Error('无效的用户 ID'), { code: 'invalid_page_path' });
  }
  const clean = String(relativePath ?? '')
    .replace(/^\/+/, '')
    .split('/')
    .filter((part) => part && part !== '.' && part !== '..')
    .join('/');
  if (!key || !clean || !clean.toLowerCase().endsWith('.html')) {
    throw Object.assign(new Error('无效的页面路径'), { code: 'invalid_page_path' });
  }
  const publishRoot = path.resolve(h5Root, PUBLISH_ROOT_DIR, key);
  const absolute = path.resolve(publishRoot, clean);
  if (absolute !== publishRoot && !absolute.startsWith(`${publishRoot}${path.sep}`)) {
    throw Object.assign(new Error('页面路径越界'), { code: 'invalid_page_path' });
  }
  return absolute;
}

export async function readPublishHtml(h5Root, userId, relativePath) {
  const absolute = resolvePublishHtmlAbsolutePath(h5Root, userId, relativePath);
  const content = await fs.readFile(absolute, 'utf8');
  if (!content.trim()) {
    throw Object.assign(new Error('页面内容为空'), { code: 'empty_page_content' });
  }
  return { absolute, content, relativePath, filename: path.basename(relativePath) };
}

async function walkPublishHtmlByBasename(publishRoot, basename, maxDepth = 6, depth = 0) {
  if (depth > maxDepth || !basename.toLowerCase().endsWith('.html')) return null;
  let entries;
  try {
    entries = await fs.readdir(publishRoot, { withFileTypes: true });
  } catch {
    return null;
  }
  for (const entry of entries) {
    if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
    const full = path.join(publishRoot, entry.name);
    if (entry.isFile() && entry.name === basename) return full;
  }
  for (const entry of entries) {
    if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'node_modules') continue;
    const found = await walkPublishHtmlByBasename(
      path.join(publishRoot, entry.name),
      basename,
      maxDepth,
      depth + 1,
    );
    if (found) return found;
  }
  return null;
}

async function collectPublishHtmlPaths(publishRoot, results, maxDepth = 6, depth = 0) {
  if (depth > maxDepth || results.length >= 200) return;
  let entries;
  try {
    entries = await fs.readdir(publishRoot, { withFileTypes: true });
  } catch {
    return;
  }
  for (const entry of entries) {
    if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
    const full = path.join(publishRoot, entry.name);
    if (entry.isFile() && entry.name.toLowerCase().endsWith('.html')) {
      results.push(full);
      if (results.length >= 200) return;
    }
  }
  for (const entry of entries) {
    if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'node_modules') continue;
    await collectPublishHtmlPaths(path.join(publishRoot, entry.name), results, maxDepth, depth + 1);
    if (results.length >= 200) return;
  }
}

function levenshteinDistance(left, right) {
  if (left === right) return 0;
  if (!left) return right.length;
  if (!right) return left.length;
  const prev = Array.from({ length: right.length + 1 }, (_, index) => index);
  const next = new Array(right.length + 1);
  for (let i = 0; i < left.length; i += 1) {
    next[0] = i + 1;
    for (let j = 0; j < right.length; j += 1) {
      const cost = left[i] === right[j] ? 0 : 1;
      next[j + 1] = Math.min(
        next[j] + 1,
        prev[j + 1] + 1,
        prev[j] + cost,
      );
    }
    for (let j = 0; j <= right.length; j += 1) {
      prev[j] = next[j];
    }
  }
  return prev[right.length];
}

function htmlNameForSimilarity(relativePath) {
  return path.basename(String(relativePath ?? ''), '.html').toLowerCase();
}

function isAcceptableSimilarHtmlMatch(requested, candidate, score, nextScore = Infinity) {
  if (!requested || !candidate || requested === candidate) return false;
  const longest = Math.max(requested.length, candidate.length);
  const allowedDistance = longest >= 18 ? 2 : 1;
  if (score > allowedDistance) return false;
  return nextScore > score;
}

export async function resolveClosestHtmlRelativePath(rootDir, relativePath) {
  const normalized = normalizeStaticHtmlRelativePath(relativePath);
  if (!normalized.toLowerCase().endsWith('.html')) return null;
  const requestedName = htmlNameForSimilarity(normalized);
  const candidates = [];
  await collectPublishHtmlPaths(rootDir, candidates);
  const ranked = candidates
    .map((absolute) => {
      const candidateRelative = path.relative(rootDir, absolute).split(path.sep).join('/');
      const candidateName = htmlNameForSimilarity(candidateRelative);
      return {
        relativePath: candidateRelative,
        score: levenshteinDistance(requestedName, candidateName),
      };
    })
    .sort((left, right) => left.score - right.score || left.relativePath.localeCompare(right.relativePath));
  if (ranked.length === 0) return null;
  const best = ranked[0];
  const nextBestScore = ranked[1]?.score ?? Infinity;
  if (!isAcceptableSimilarHtmlMatch(requestedName, htmlNameForSimilarity(best.relativePath), best.score, nextBestScore)) {
    return null;
  }
  return best.relativePath;
}

export async function findPublishHtml(h5Root, userId, relativePath) {
  const normalized = normalizeStaticHtmlRelativePath(relativePath);
  const basename = path.basename(normalized);
  const candidates = [
    normalized,
    path.posix.join('public', basename),
    basename,
  ].filter((value, index, list) => value && list.indexOf(value) === index);

  for (const candidate of candidates) {
    try {
      return await readPublishHtml(h5Root, userId, candidate);
    } catch {
      // try next candidate
    }
  }

  const publishRoot = path.resolve(
    h5Root,
    PUBLISH_ROOT_DIR,
    String(userId ?? '').trim().toLowerCase(),
  );
  const absolute = await walkPublishHtmlByBasename(publishRoot, basename);
  if (absolute) {
    const resolvedRelativePath = path.relative(publishRoot, absolute).split(path.sep).join('/');
    return readPublishHtml(h5Root, userId, resolvedRelativePath);
  }

  const similarRelativePath = await resolveClosestHtmlRelativePath(publishRoot, normalized);
  if (similarRelativePath) {
    return readPublishHtml(h5Root, userId, similarRelativePath);
  }

  throw Object.assign(new Error('无法读取链接页面内容'), { code: 'static_page_not_found' });
}

export function buildWorkspaceBaseHref(userId, htmlRelativePath) {
  const key = String(userId ?? '').trim();
  const dir = path.posix.dirname(String(htmlRelativePath ?? '').replace(/^\/+/, ''));
  const segments = dir === '.' ? [] : dir.split('/').filter(Boolean);
  const encoded = segments.map((part) => encodeURIComponent(part)).join('/');
  return `/${PUBLISH_ROOT_DIR}/${encodeURIComponent(key)}/${encoded ? `${encoded}/` : ''}`;
}

export function injectHtmlBaseHref(html, baseHref) {
  const safeBase = String(baseHref ?? '').replace(/"/g, '%22');
  if (!safeBase) return html;
  if (/<base\s/i.test(html)) {
    return html.replace(/<base\s[^>]*href="[^"]*"[^>]*>/i, `<base href="${safeBase}">`);
  }
  if (/<head[^>]*>/i.test(html)) {
    return html.replace(/<head[^>]*>/i, (match) => `${match}\n<base href="${safeBase}">`);
  }
  return `<!DOCTYPE html><html><head><base href="${safeBase}"></head><body>${html}</body></html>`;
}

export function buildChatSavePreviewQuery({
  sessionId,
  messageId,
  selectedLinkIndex = 0,
  previewTitle,
  previewSummary,
} = {}) {
  const params = new URLSearchParams({
    session_id: String(sessionId ?? ''),
    message_id: String(messageId ?? ''),
    selected_link_index: String(selectedLinkIndex),
  });
  const title = String(previewTitle ?? '').trim();
  const summary = String(previewSummary ?? '').trim();
  if (title) params.set('preview_title', title);
  if (summary) params.set('preview_summary', summary);
  return params;
}

export function buildChatSavePreviewFrameUrl(input) {
  return `/api/mindspace/v1/pages/chat-save-preview?${buildChatSavePreviewQuery(input).toString()}`;
}

export function buildChatSaveThumbnailUrl(input) {
  return `/api/mindspace/v1/pages/chat-save-thumbnail?${buildChatSavePreviewQuery(input).toString()}`;
}

function titleFromHtml(html) {
  const match = String(html).match(/<title[^>]*>([^<]+)<\/title>/i);
  return match?.[1]?.trim() ?? '';
}

function summaryFromHtml(html) {
  const stripped = String(html)
    .replace(/<script[\s\S]*?<\/script>/gi, ' ')
    .replace(/<style[\s\S]*?<\/style>/gi, ' ')
    .replace(/<[^>]+>/g, ' ')
    .replace(/\s+/g, ' ')
    .trim();
  return stripped.slice(0, 180);
}

function dedupeDuplicatedFilename(filename) {
  const match = String(filename ?? '').match(/^(.+)\.html$/i);
  if (!match) return filename;
  let base = match[1];
  const half = Math.floor(base.length / 2);
  if (half >= 2 && base.slice(0, half) === base.slice(half)) {
    base = base.slice(0, half);
  }
  base = base.replace(/(.+?)\1+/g, '$1');
  base = base.replace(/-v-v(\d+)/g, '-v$1');
  return `${base}.html`;
}

export function sanitizeMessageContentForSave(content) {
  let text = String(content ?? '');
  text = text.replace(/httpshttps:\/+/gi, 'https://');
  text = text.replace(/https:(?:\/\/)+/gi, 'https://');
  text = text.replace(/(https?:\/\/[^\s<>"')\]]+?)(?:\1)+/gi, '$1');
  text = text.replace(/([\u4e00-\u9fffA-Za-z0-9._-]+)\s+\1/g, '$1');
  text = text.replace(/(\.html)\.html\b/gi, '.html');
  text = text.replace(/((?:MindSpace|temp)\/[^/\s<>"')\]]+\/)(public\/)\2/gi, '$1$2');
  return text;
}

export function extractHtmlFilenameHints(content) {
  const hints = new Set();
  for (const match of String(content ?? '').matchAll(/([a-z0-9][a-z0-9._-]*\.html)/gi)) {
    hints.add(dedupeDuplicatedFilename(match[1].toLowerCase()));
  }
  return [...hints];
}

export function analyzeChatMessageForSave({
  content,
  userId,
  username,
  h5Root,
  selectedLinkIndex = 0,
}) {
  const sanitized = sanitizeMessageContentForSave(content);
  const links = extractStaticPageLinks(sanitized, { userId, username });
  const text = sanitized.replace(/\s+/g, ' ').trim();
  const suggestedTitleFromText = text
    .replace(/^#{1,6}\s*/, '')
    .replace(/[*_`~[\]]/g, '')
    .trim()
    .slice(0, 48);

  if (links.length === 0) {
    return {
      contentMode: 'markdown',
      links: [],
      selectedLink: null,
      suggestedTitle: suggestedTitleFromText || 'AI 创作页面',
      suggestedSummary: text.slice(0, 160),
      previewUrl: null,
      relativePath: null,
      filename: null,
    };
  }

  const index = Math.min(Math.max(0, selectedLinkIndex), links.length - 1);
  const selectedLink = links[index];
  return {
    contentMode: 'static_html',
    links,
    selectedLink,
    suggestedTitle:
      selectedLink.filename.replace(/\.html$/i, '').replace(/[-_]/g, ' ') ||
      suggestedTitleFromText ||
      'AI 生成页面',
    suggestedSummary: text.slice(0, 160),
    previewUrl: selectedLink.publicUrl,
    relativePath: selectedLink.relativePath,
    filename: selectedLink.filename,
    h5Root,
    userId,
    username,
  };
}

export async function resolveStaticHtmlContent(analysis) {
  if (analysis.contentMode !== 'static_html' || !analysis.selectedLink) {
    return null;
  }
  const loaded = await findPublishHtml(
    analysis.h5Root,
    analysis.userId,
    analysis.selectedLink.relativePath,
  );
  const title = titleFromHtml(loaded.content);
  const summary = summaryFromHtml(loaded.content);
  return {
    ...loaded,
    suggestedTitle: title || analysis.suggestedTitle,
    suggestedSummary: summary || analysis.suggestedSummary,
    publicUrl: analysis.selectedLink.publicUrl,
  };
}

export async function resolveChatSaveAnalysis({
  content,
  userId,
  username,
  h5Root,
  selectedLinkIndex = 0,
}) {
  const sanitized = sanitizeMessageContentForSave(content);
  let analysis = analyzeChatMessageForSave({
    content: sanitized,
    userId,
    username,
    h5Root,
    selectedLinkIndex,
  });

  if (analysis.contentMode === 'static_html' && analysis.selectedLink) {
    try {
      const resolvedHtml = await resolveStaticHtmlContent(analysis);
      return { analysis, resolvedHtml };
    } catch {
      // fall through to filename hints
    }
  }

  for (const hint of extractHtmlFilenameHints(sanitized)) {
    try {
      const loaded = await findPublishHtml(h5Root, userId, hint);
      const syntheticLink = {
        publicUrl:
          buildWorkspaceAssetUrl(userId, loaded.relativePath) ??
          `/${PUBLISH_ROOT_DIR}/${userId}/${loaded.relativePath}`,
        owner: String(userId ?? '').trim().toLowerCase(),
        relativePath: loaded.relativePath,
        filename: loaded.filename,
      };
      analysis = {
        contentMode: 'static_html',
        links: [syntheticLink],
        selectedLink: syntheticLink,
        suggestedTitle:
          titleFromHtml(loaded.content) ||
          syntheticLink.filename.replace(/\.html$/i, '').replace(/[-_]/g, ' ') ||
          'AI 生成页面',
        suggestedSummary: summaryFromHtml(loaded.content) || sanitized.slice(0, 160),
        previewUrl: syntheticLink.publicUrl,
        relativePath: loaded.relativePath,
        filename: loaded.filename,
        h5Root,
        userId,
        username,
      };
      return {
        analysis,
        resolvedHtml: {
          ...loaded,
          suggestedTitle: analysis.suggestedTitle,
          suggestedSummary: analysis.suggestedSummary,
          publicUrl: syntheticLink.publicUrl,
        },
      };
    } catch {
      // try next hint
    }
  }

  return { analysis, resolvedHtml: null };
}
