import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeSanitize from 'rehype-sanitize';
import { CodeRenderer } from '../FileViewer/renderers';

// Markdown for anything the instance hands back — a README, an issue body, a release note.
//
// ─────────────────────────────────────────────────────────────────────────────────────────────────
// WHY THIS IS NOT `/api/v1/markdown`
//
// Gitea's own web UI renders markdown server-side and it does it better than this can: it resolves
// #123 issue references, @mentions and relative links against the repository, and it sanitises the
// result with bluemonday before returning it. Replicating the upstream's own rendering is the house
// rule, and the sidecar's allow-list carries `/markdown` and `/markup` so that door stays open.
//
// It is not used HERE because consuming it means putting instance-returned HTML through
// `dangerouslySetInnerHTML`, and this app has no second line of defence to put behind that: there is
// no DOMPurify, sanitize-html or xss in the tree, and installs are frozen, so adding one is a
// deliberate lockfile change and not something to slip into a feature. That would leave the owner's
// authenticated session trusting a remote server's sanitiser and nothing else — and Officer's whole
// perimeter is that one session.
//
// So: parse the markdown here instead, where `rehype-sanitize` runs on an AST that never contains raw
// HTML in the first place (note `rehype-raw` is deliberately ABSENT — with it, `<script>` would be
// parsed into the tree and we would be relying on the sanitiser to take it back out; without it, raw
// HTML is inert text and there is nothing to remove). What is lost is Gitea's cross-reference
// linking; relative links and images are resolved below instead.
// ─────────────────────────────────────────────────────────────────────────────────────────────────

type GiteaMarkdownProps = {
  content: string;
  /** `html_url` of the repository the text belongs to — the base for resolving relative links/images. */
  repoUrl?: string;
  /** Branch, tag or sha the text was read at. Defaults to the instance's own default-branch redirect. */
  refName?: string;
  /**
   * Directory the text was read from, repo-relative and without a trailing slash. A relative link in a
   * README resolves against the folder that README is in, not the repository root — so `docs/README.md`
   * linking to `install.md` means `docs/install.md`.
   */
  basePath?: string;
  className?: string;
};

const ABSOLUTE = /^[a-z][a-z0-9+.-]*:/i;

/**
 * Resolve a relative link the way Gitea would: images come from the raw endpoint (the bytes), everything
 * else from the source browser (a page). An anchor (`#heading`) and anything already absolute are left
 * alone.
 */
function resolveUrl(
  url: string,
  repoUrl: string | undefined,
  refName: string,
  kind: 'raw' | 'src',
  base: string,
): string {
  if (!url || url.startsWith('#') || url.startsWith('//') || ABSOLUTE.test(url)) return url;
  if (!repoUrl) return url;
  // A leading slash means repo-root-relative, which is how Gitea reads it too — so it escapes the base.
  const rooted = url.startsWith('/');
  const clean = url.replace(/^\.?\//, '');
  const prefix = rooted || !base ? '' : `${base.replace(/\/+$/, '')}/`;
  return `${repoUrl.replace(/\/+$/, '')}/${kind}/branch/${encodeURIComponent(refName)}/${prefix}${clean}`;
}

const getNodeText = (node: unknown): string => {
  const n = node as { value?: string; children?: unknown[] } | undefined;
  if (!n) return '';
  if (typeof n.value === 'string') return n.value;
  return (n.children ?? []).map(getNodeText).join('');
};

export const GiteaMarkdown = ({ content, repoUrl, refName = 'main', basePath = '', className }: GiteaMarkdownProps) => (
  <div className={`file-viewer-md ${className ?? ''}`}>
    <ReactMarkdown
      remarkPlugins={[remarkGfm]}
      rehypePlugins={[rehypeSanitize]}
      // react-markdown's own transform already drops dangerous protocols; this one additionally rebases
      // the repo-relative paths that make up most of a README's links.
      urlTransform={(url) => resolveUrl(url, repoUrl, refName, 'src', basePath)}
      components={{
        // Every link in remote content leaves the app, so it opens in a new tab and drops the referrer.
        a: ({ children, href }) => (
          <a href={href} target="_blank" rel="noreferrer noopener">
            {children}
          </a>
        ),
        img: ({ src, alt }) => (
          <img
            src={typeof src === 'string' ? resolveUrl(src, repoUrl, refName, 'raw', basePath) : src}
            alt={alt ?? ''}
            loading="lazy"
          />
        ),
        // Fenced blocks reuse the FileViewer's shiki renderer rather than a second highlighter.
        pre: ({ node, children }) => {
          const codeChild = node?.children[0];
          if (codeChild?.type === 'element' && codeChild.tagName === 'code') {
            const classes = (codeChild.properties?.className ?? []) as string[];
            const lang = classes.find((c) => c.startsWith('language-'))?.replace('language-', '') ?? '';
            return <CodeRenderer content={getNodeText(codeChild).replace(/\n$/, '')} lang={lang} />;
          }
          return <pre>{children}</pre>;
        },
      }}
    >
      {content}
    </ReactMarkdown>
  </div>
);
