import { getPostBySlug, getPostSlugs } from '@/lib/mdx';
import { MDXRemote } from 'next-mdx-remote/rsc';
import fs from 'fs';
import NextImage, { type ImageProps } from 'next/image';
import Link from 'next/link';
import path from 'path';
import { notFound } from 'next/navigation';
import { isValidElement } from 'react';
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
import { TableOfContents } from '@/components/mdx/TableOfContents';
import { SideNote } from '@/components/mdx/SideNote';
import { Citation, Bibliography } from '@/components/mdx/Citation';
import { MobileTableOfContents } from '@/components/mdx/MobileTableOfContents';
import { formatPostDate } from '@/lib/format';

const slugify = (text: string): string => {
    return text
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, '-')
        .replace(/^-+|-+$/g, '') || 'section';
};

const publicDirectory = path.join(process.cwd(), 'public');
const imageDimensionCache = new Map<string, { width: number; height: number } | null>();
const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
const JPEG_START_OF_FRAME_MARKERS = new Set([
    0xc0, 0xc1, 0xc2, 0xc3, 0xc5, 0xc6, 0xc7, 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf,
]);

function normalizeHeadingText(text: string) {
    return text
        .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
        .replace(/[`*_~]/g, '')
        .replace(/<[^>]+>/g, '')
        .trim();
}

function createSlugger() {
    const counts = new Map<string, number>();

    return (text: string) => {
        const base = slugify(text);
        const count = counts.get(base) || 0;
        counts.set(base, count + 1);
        return count === 0 ? base : `${base}-${count}`;
    };
}

function toPlainText(node: ReactNode): string {
    if (typeof node === 'string' || typeof node === 'number') return String(node);
    if (Array.isArray(node)) return node.map(toPlainText).join('');
    if (isValidElement<{ children?: ReactNode }>(node)) return toPlainText(node.props.children);
    return '';
}

function extractHeadings(content: string) {
    const slugger = createSlugger();
    const headingLines = content.match(/^#{1,3}\s+(.+)$/gm) || [];

    return headingLines.flatMap((line) => {
        const level = line.match(/^#+/)?.[0].length || 2;
        const text = normalizeHeadingText(line.replace(/^#+\s+/, ''));
        const id = slugger(text);
        return level > 1 ? [{ id, text, level }] : [];
    });
}

function getPngDimensions(buffer: Buffer) {
    if (buffer.length < 24 || !buffer.subarray(0, 8).equals(PNG_SIGNATURE)) return null;
    return {
        width: buffer.readUInt32BE(16),
        height: buffer.readUInt32BE(20),
    };
}

function getJpegDimensions(buffer: Buffer) {
    if (buffer.length < 4 || buffer[0] !== 0xff || buffer[1] !== 0xd8) return null;

    let offset = 2;
    while (offset < buffer.length - 9) {
        if (buffer[offset] !== 0xff) {
            offset += 1;
            continue;
        }

        const marker = buffer[offset + 1];
        offset += 2;

        if (marker === 0xd8 || marker === 0xd9 || (marker >= 0xd0 && marker <= 0xd7)) {
            continue;
        }

        const length = buffer.readUInt16BE(offset);
        if (length < 2 || offset + length > buffer.length) break;

        if (JPEG_START_OF_FRAME_MARKERS.has(marker)) {
            return {
                height: buffer.readUInt16BE(offset + 3),
                width: buffer.readUInt16BE(offset + 5),
            };
        }

        offset += length;
    }

    return null;
}

function getLocalImageDimensions(src: string) {
    const pathname = src.split(/[?#]/, 1)[0];
    if (!pathname.startsWith('/') || pathname.includes('\0') || pathname.includes('..')) return null;

    const imagePath = path.join(publicDirectory, pathname.replace(/^\/+/, ''));
    if (!imagePath.startsWith(`${publicDirectory}${path.sep}`)) return null;

    if (imageDimensionCache.has(imagePath)) {
        return imageDimensionCache.get(imagePath) ?? null;
    }

    try {
        const buffer = fs.readFileSync(imagePath);
        const dimensions = getPngDimensions(buffer) ?? getJpegDimensions(buffer);
        imageDimensionCache.set(imagePath, dimensions);
        return dimensions;
    } catch {
        imageDimensionCache.set(imagePath, null);
        return null;
    }
}

function numericDimension(value: ImageProps['width'] | ImageProps['height']) {
    if (typeof value === 'number') return value;
    if (typeof value === 'string') {
        const parsed = Number(value);
        return Number.isFinite(parsed) ? parsed : undefined;
    }
    return undefined;
}

function MdxImage({ src, alt, width, height, sizes, ...props }: ImageProps) {
    if (typeof src !== 'string') {
        return <NextImage {...props} src={src} alt={alt} width={width} height={height} sizes={sizes} />;
    }

    const localDimensions = getLocalImageDimensions(src);
    const resolvedWidth = numericDimension(width) ?? localDimensions?.width;
    const resolvedHeight = numericDimension(height) ?? localDimensions?.height;
    const displayWidth = Math.min(resolvedWidth ?? 704, 704);
    const responsiveSizes = sizes ?? `(max-width: 768px) 100vw, ${displayWidth}px`;

    return (
        <NextImage
            {...props}
            src={src}
            alt={alt}
            width={resolvedWidth ?? 1200}
            height={resolvedHeight ?? 675}
            sizes={responsiveSizes}
        />
    );
}

function createMdxComponents() {
    const slugger = createSlugger();
    const getHeadingId = (children: ReactNode) => slugger(toPlainText(children));

    return {
        h1: ({ children, ...props }: ComponentPropsWithoutRef<'h1'>) => <h1 {...props} id={getHeadingId(children)} className="mt-12 scroll-mt-24 font-sans text-[2rem] font-medium leading-tight text-ink sm:text-[2.5rem]">{children}</h1>,
        h2: ({ children, ...props }: ComponentPropsWithoutRef<'h2'>) => <h2 {...props} id={getHeadingId(children)} className="mt-14 scroll-mt-24 font-sans text-[1.65rem] font-medium leading-tight text-ink sm:text-[2rem]">{children}</h2>,
        h3: ({ children, ...props }: ComponentPropsWithoutRef<'h3'>) => <h3 {...props} id={getHeadingId(children)} className="mt-10 scroll-mt-24 font-sans text-[1.22rem] font-medium text-ink sm:text-[1.42rem]">{children}</h3>,
        p: (props: ComponentPropsWithoutRef<'p'>) => <p {...props} className="mb-6 text-[1.02rem] leading-[1.85] text-ink-soft" />,
        ul: (props: ComponentPropsWithoutRef<'ul'>) => <ul {...props} className="mb-6 list-disc space-y-2 pl-5 text-[0.98rem] leading-8 text-ink-soft marker:text-accent" />,
        ol: (props: ComponentPropsWithoutRef<'ol'>) => <ol {...props} className="mb-6 list-decimal space-y-2 pl-5 text-[0.98rem] leading-8 text-ink-soft marker:text-accent" />,
        blockquote: (props: ComponentPropsWithoutRef<'blockquote'>) => <blockquote {...props} className="my-10 border-l border-line-strong pl-5 text-[1.05rem] italic leading-8 text-ink-soft" />,
        code: (props: ComponentPropsWithoutRef<'code'>) => <code {...props} className="rounded bg-paper-strong px-1.5 py-0.5 font-mono text-[0.9em] text-ink" />,
        pre: (props: ComponentPropsWithoutRef<'pre'>) => <pre {...props} className="my-8 overflow-x-auto rounded-[1rem] border border-line bg-[#161412] p-5 text-sm text-paper shadow-[0_16px_40px_rgba(17,16,15,0.12)]" />,
        a: (props: ComponentPropsWithoutRef<'a'>) => <a {...props} className="font-medium text-ink underline decoration-line-strong underline-offset-4 transition-colors hover:text-accent hover:decoration-accent" />,
        Image: MdxImage,
        SideNote,
        Citation,
        Bibliography,
    };
}

type Props = {
    params: Promise<{ slug: string }>;
};

export function generateStaticParams() {
    return getPostSlugs()
        .filter((slug) => slug.endsWith('.mdx'))
        .map((slug) => ({ slug: slug.replace(/\.mdx$/, '') }));
}

export async function generateMetadata({ params }: Props) {
    const { slug } = await params;
    try {
        const { metadata } = getPostBySlug(slug);
        return {
            title: metadata.title,
            description: metadata.description,
            openGraph: {
                title: metadata.title,
                description: metadata.description,
                type: 'article' as const,
                publishedTime: metadata.date,
            },
            twitter: {
                card: 'summary' as const,
                title: metadata.title,
                description: metadata.description,
            },
        };
    } catch {
        return {
            title: 'Post Not Found',
        };
    }
}

export default async function BlogPost({ params }: Props) {
    const { slug } = await params;

    let post;
    try {
        post = getPostBySlug(slug);
    } catch {
        notFound();
    }

    const headings = extractHeadings(post.content);
    const mdxComponents = createMdxComponents();
    const tags = Array.isArray(post.metadata.tags) ? post.metadata.tags : [];

    return (
        <div className="page-frame py-20 sm:py-24">
            <div className="mx-auto max-w-[78rem] xl:grid xl:grid-cols-[11rem_minmax(0,44rem)] xl:gap-x-10">
                <aside className="hidden xl:block">
                    <div className="sticky top-24">
                        <TableOfContents headings={headings} />
                    </div>
                </aside>

                <article className="w-full">
                    <header className="mb-8 space-y-4 border-b border-line pb-8">
                        <div className="flex flex-wrap items-center gap-x-4 gap-y-2 text-[0.72rem] font-mono uppercase text-muted-strong">
                            <time dateTime={post.metadata.date}>
                                {formatPostDate(post.metadata.date)}
                            </time>
                            {tags.map((tag: string) => (
                                <span key={tag}>{tag}</span>
                            ))}
                        </div>

                        <h1 className="max-w-[38rem] text-balance font-sans text-4xl font-medium leading-[0.98] text-ink sm:text-5xl lg:text-6xl">
                            {post.metadata.title}
                        </h1>

                        <p className="max-w-[34rem] text-[1.04rem] leading-8 text-muted">
                            {post.metadata.description}
                        </p>
                    </header>

                    <MobileTableOfContents headings={headings} />

                    <div className="essay-prose relative mt-8 max-w-none xl:max-w-[44rem]">
                        <MDXRemote
                            source={post.content}
                            components={mdxComponents}
                        />
                    </div>

                    <div className="mt-14 flex items-center justify-between border-t border-line pt-6 text-sm text-muted">
                        <Link href="/blog" className="transition-colors hover:text-ink">
                            &larr; Back to all posts
                        </Link>
                        <a href="#main-content" className="transition-colors hover:text-ink">
                            Scroll to top &uarr;
                        </a>
                    </div>
                </article>
            </div>
        </div>
    );
}
