import { promises as fs } from 'fs';
import * as path from 'path';
import { HttpError } from './http-helpers';

const MIME_TYPES_BY_EXTENSION: Record<string, string> = {
  '.css': 'text/css; charset=utf-8',
  '.gif': 'image/gif',
  '.html': 'text/html; charset=utf-8',
  '.ico': 'image/x-icon',
  '.jpeg': 'image/jpeg',
  '.jpg': 'image/jpeg',
  '.js': 'application/javascript; charset=utf-8',
  '.json': 'application/json; charset=utf-8',
  '.map': 'application/json; charset=utf-8',
  '.png': 'image/png',
  '.svg': 'image/svg+xml; charset=utf-8',
  '.txt': 'text/plain; charset=utf-8',
  '.webp': 'image/webp',
  '.woff': 'font/woff',
  '.woff2': 'font/woff2'
};

function getDocsRootCandidates(): string[] {
  const processWithResources = process as NodeJS.Process & { resourcesPath?: string };
  const candidates: string[] = [];

  if (processWithResources.resourcesPath) {
    candidates.push(path.join(processWithResources.resourcesPath, 'docusaurus'));
  }

  candidates.push(path.join(process.cwd(), 'docs-site', 'build'));

  return candidates;
}

async function getDocusaurusBuildRoot(): Promise<string | null> {
  for (const candidate of getDocsRootCandidates()) {
    try {
      const stat = await fs.stat(candidate);

      if (stat.isDirectory()) {
        return candidate;
      }
    } catch {
      // try next candidate
    }
  }

  return null;
}

function isPathInside(parent: string, child: string): boolean {
  const relative = path.relative(parent, child);

  return relative === '' || (!!relative && !relative.startsWith('..') && !path.isAbsolute(relative));
}

function resolveAssetPath(root: string, pathname: string): string {
  const withoutPrefix = pathname.replace(/^\/docusaurus\/?/u, '');
  const decoded = decodeURIComponent(withoutPrefix);
  const normalized = decoded.endsWith('/') || decoded === '' ? path.join(decoded, 'index.html') : decoded;
  const absolutePath = path.resolve(root, normalized);

  if (!isPathInside(root, absolutePath)) {
    throw new HttpError(400, 'Invalid Docusaurus asset path', 'INVALID_DOCS_PATH');
  }

  return absolutePath;
}

export async function resolveDocusaurusRoute(pathname: string): Promise<{ filePath: string; contentType: string }> {
  const root = await getDocusaurusBuildRoot();

  if (!root) {
    throw new HttpError(
      503,
      'Docusaurus build is not available. Run npm run build:docs before opening the docs endpoint.',
      'DOCUSAURUS_BUILD_MISSING'
    );
  }

  let filePath = resolveAssetPath(root, pathname);

  try {
    const stat = await fs.stat(filePath);

    if (stat.isDirectory()) {
      filePath = path.join(filePath, 'index.html');
    }
  } catch {
    const directoryIndexPath = path.join(filePath, 'index.html');

    try {
      await fs.access(directoryIndexPath);
      filePath = directoryIndexPath;
    } catch {
      filePath = path.join(root, '404.html');
    }
  }

  if (!isPathInside(root, filePath)) {
    throw new HttpError(400, 'Invalid Docusaurus asset path', 'INVALID_DOCS_PATH');
  }

  const contentType = MIME_TYPES_BY_EXTENSION[path.extname(filePath).toLowerCase()] ?? 'application/octet-stream';

  return { filePath, contentType };
}
