import React from "react";

interface MarkdownBlock {
    type: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "code" | "blockquote" | "ul" | "ol" | "hr" | "p";
    content: string;
    language?: string;
    items?: string[];
}

interface ParseState {
    blocks: MarkdownBlock[];
    currentCodeBlock: { language: string; lines: string[] } | null;
    currentList: { type: "ul" | "ol"; items: string[] } | null;
    currentParagraphLines: string[];
}

interface InlineToken {
    type: "text" | "bold" | "italic" | "code" | "link";
    text: string;
    url?: string;
}

const flushParagraph = (state: ParseState): void => {
    if (state.currentParagraphLines.length > 0) {
        state.blocks.push({
            type: "p",
            content: state.currentParagraphLines.join(" ").trim(),
        });
        state.currentParagraphLines = [];
    }
};

const flushList = (state: ParseState): void => {
    if (state.currentList) {
        state.blocks.push({
            type: state.currentList.type,
            content: "",
            items: state.currentList.items,
        });
        state.currentList = null;
    }
};

const handleCodeBlock = (trimmed: string, state: ParseState): boolean => {
    if (trimmed.startsWith("```")) {
        if (state.currentCodeBlock) {
            state.blocks.push({
                type: "code",
                content: state.currentCodeBlock.lines.join("\n"),
                language: state.currentCodeBlock.language,
            });
            state.currentCodeBlock = null;
        } else {
            flushParagraph(state);
            flushList(state);
            const language = trimmed.slice(3).trim();
            state.currentCodeBlock = { language, lines: [] };
        }
        return true;
    }
    return false;
};

const handleHeading = (line: string, state: ParseState): boolean => {
    const match = line.match(/^(#{1,6})\s+(.*)$/);
    if (match) {
        flushParagraph(state);
        flushList(state);
        const level = match[1].length;
        state.blocks.push({
            type: `h${level}` as any,
            content: match[2].trim(),
        });
        return true;
    }
    return false;
};

const handleBlockquote = (trimmed: string, state: ParseState): boolean => {
    if (trimmed.startsWith(">")) {
        flushParagraph(state);
        flushList(state);
        state.blocks.push({
            type: "blockquote",
            content: trimmed.replace(/^>\s*/, ""),
        });
        return true;
    }
    return false;
};

const handleLists = (line: string, state: ParseState): boolean => {
    const ulMatch = line.match(/^(\s*)([-*+])\s+(.*)$/);
    if (ulMatch) {
        flushParagraph(state);
        const content = ulMatch[3].trim();
        if (state.currentList && state.currentList.type === "ul") {
            state.currentList.items.push(content);
        } else {
            flushList(state);
            state.currentList = { type: "ul", items: [content] };
        }
        return true;
    }

    const olMatch = line.match(/^(\s*)(\d+)\.\s+(.*)$/);
    if (olMatch) {
        flushParagraph(state);
        const content = olMatch[3].trim();
        if (state.currentList && state.currentList.type === "ol") {
            state.currentList.items.push(content);
        } else {
            flushList(state);
            state.currentList = { type: "ol", items: [content] };
        }
        return true;
    }

    return false;
};

const handleLine = (line: string, state: ParseState): void => {
    const trimmed = line.trim();

    if (handleCodeBlock(trimmed, state)) {
        return;
    }

    if (state.currentCodeBlock) {
        state.currentCodeBlock.lines.push(line);
        return;
    }

    if (trimmed === "---" || trimmed === "***" || trimmed === "___") {
        flushParagraph(state);
        flushList(state);
        state.blocks.push({ type: "hr", content: "" });
        return;
    }

    if (handleHeading(line, state) || handleBlockquote(trimmed, state)) {
        return;
    }

    if (handleLists(line, state)) {
        return;
    }

    if (trimmed === "") {
        flushParagraph(state);
        flushList(state);
        return;
    }

    flushList(state);
    state.currentParagraphLines.push(line);
};

export const parseMarkdown = (text: string): MarkdownBlock[] => {
    const lines = text.split("\n");
    const state: ParseState = {
        blocks: [],
        currentCodeBlock: null,
        currentList: null,
        currentParagraphLines: [],
    };

    for (let i = 0; i < lines.length; i++) {
        handleLine(lines[i], state);
    }

    flushParagraph(state);
    flushList(state);

    return state.blocks;
};

const parseInlineLinks = (tokens: InlineToken[]): InlineToken[] => {
    const updated: InlineToken[] = [];
    for (const part of tokens) {
        if (part.type === "text") {
            const regex = /\[([^\]]+)\]\(([^)]+)\)/g;
            let lastIndex = 0;
            let match;
            while ((match = regex.exec(part.text)) !== null) {
                const before = part.text.substring(lastIndex, match.index);
                if (before) updated.push({ type: "text", text: before });
                updated.push({ type: "link", text: match[1], url: match[2] });
                lastIndex = regex.lastIndex;
            }
            const after = part.text.substring(lastIndex);
            if (after) updated.push({ type: "text", text: after });
        } else {
            updated.push(part);
        }
    }
    return updated;
};

const parseInlineUrls = (tokens: InlineToken[]): InlineToken[] => {
    const updated: InlineToken[] = [];
    for (const part of tokens) {
        if (part.type === "text") {
            const regex = /(https?:\/\/[^\s)]+)/g;
            let lastIndex = 0;
            let match;
            while ((match = regex.exec(part.text)) !== null) {
                const before = part.text.substring(lastIndex, match.index);
                if (before) updated.push({ type: "text", text: before });
                updated.push({ type: "link", text: match[1], url: match[1] });
                lastIndex = regex.lastIndex;
            }
            const after = part.text.substring(lastIndex);
            if (after) updated.push({ type: "text", text: after });
        } else {
            updated.push(part);
        }
    }
    return updated;
};

const parseInlineBold = (tokens: InlineToken[]): InlineToken[] => {
    const updated: InlineToken[] = [];
    for (const part of tokens) {
        if (part.type === "text") {
            const regex = /\*\*([^*]+)\*\*/g;
            let lastIndex = 0;
            let match;
            while ((match = regex.exec(part.text)) !== null) {
                const before = part.text.substring(lastIndex, match.index);
                if (before) updated.push({ type: "text", text: before });
                updated.push({ type: "bold", text: match[1] });
                lastIndex = regex.lastIndex;
            }
            const after = part.text.substring(lastIndex);
            if (after) updated.push({ type: "text", text: after });
        } else {
            updated.push(part);
        }
    }
    return updated;
};

const parseInlineCode = (tokens: InlineToken[]): InlineToken[] => {
    const updated: InlineToken[] = [];
    for (const part of tokens) {
        if (part.type === "text") {
            const regex = /`([^`]+)`/g;
            let lastIndex = 0;
            let match;
            while ((match = regex.exec(part.text)) !== null) {
                const before = part.text.substring(lastIndex, match.index);
                if (before) updated.push({ type: "text", text: before });
                updated.push({ type: "code", text: match[1] });
                lastIndex = regex.lastIndex;
            }
            const after = part.text.substring(lastIndex);
            if (after) updated.push({ type: "text", text: after });
        } else {
            updated.push(part);
        }
    }
    return updated;
};

const parseInlineItalic = (tokens: InlineToken[]): InlineToken[] => {
    const updated: InlineToken[] = [];
    for (const part of tokens) {
        if (part.type === "text") {
            const regex = /\*([^*]+)\*/g;
            let lastIndex = 0;
            let match;
            while ((match = regex.exec(part.text)) !== null) {
                const before = part.text.substring(lastIndex, match.index);
                if (before) updated.push({ type: "text", text: before });
                updated.push({ type: "italic", text: match[1] });
                lastIndex = regex.lastIndex;
            }
            const after = part.text.substring(lastIndex);
            if (after) updated.push({ type: "text", text: after });
        } else {
            updated.push(part);
        }
    }
    return updated;
};

export const renderInlineText = (text: string): React.ReactNode[] => {
    if (!text) return [];

    let tokens: InlineToken[] = [{ type: "text", text }];
    tokens = parseInlineLinks(tokens);
    tokens = parseInlineUrls(tokens);
    tokens = parseInlineBold(tokens);
    tokens = parseInlineCode(tokens);
    tokens = parseInlineItalic(tokens);

    return tokens.map((part, idx) => {
        switch (part.type) {
            case "bold":
                return <strong key={idx} className="font-extrabold text-ink-900">{part.text}</strong>;
            case "italic":
                return <em key={idx} className="italic text-ink-800">{part.text}</em>;
            case "code":
                return <code key={idx} className="bg-ink-100 border border-ink-200 rounded px-1.5 py-0.5 text-xs font-mono text-emerald-700">{part.text}</code>;
            case "link":
                return (
                    <a
                        key={idx}
                        href={part.url}
                        target="_blank"
                        rel="noopener noreferrer"
                        className="text-primary-600 hover:text-primary-800 font-semibold underline break-all inline-flex items-center gap-0.5"
                    >
                        {part.text}
                    </a>
                );
            default:
                return <span key={idx}>{part.text}</span>;
        }
    });
};

const renderListBlock = (block: MarkdownBlock, key: string): React.ReactNode => {
    const Component = block.type === "ul" ? "ul" : "ol";
    const listClass = block.type === "ul" 
        ? "list-disc pl-6 space-y-1.5 my-2.5 text-sm text-ink-800" 
        : "list-decimal pl-6 space-y-1.5 my-2.5 text-sm text-ink-800";
    return (
        <Component key={key} className={listClass}>
            {block.items?.map((item, idx) => (
                <li key={idx}>{renderInlineText(item)}</li>
            ))}
        </Component>
    );
};

const renderHeadingBlock = (block: MarkdownBlock, key: string): React.ReactNode => {
    const level = block.type.slice(1);
    const classes: Record<string, string> = {
        "1": "text-2xl sm:text-3xl font-extrabold text-ink-900 mt-6 mb-3 border-b border-ink-150 pb-2",
        "2": "text-xl sm:text-2xl font-extrabold text-ink-900 mt-5 mb-2.5",
        "3": "text-lg sm:text-xl font-bold text-ink-900 mt-4 mb-2",
        "4": "text-base sm:text-lg font-bold text-ink-800 mt-3 mb-1.5",
        "5": "text-sm sm:text-base font-bold text-ink-800 mt-2.5 mb-1.5",
        "6": "text-xs sm:text-sm font-bold text-ink-700 mt-2 mb-1",
    };
    const Component = block.type as any;
    return (
        <Component key={key} className={classes[level] || ""}>
            {renderInlineText(block.content)}
        </Component>
    );
};

const renderBlock = (block: MarkdownBlock, index: number): React.ReactNode => {
    const key = `${block.type}-${index}`;
    if (block.type.startsWith("h") && block.type.length === 2 && block.type !== "hr") {
        return renderHeadingBlock(block, key);
    }
    switch (block.type) {
        case "blockquote":
            return (
                <blockquote key={key} className="border-l-4 border-primary-500 pl-4 py-1 italic bg-primary-50/20 text-ink-700 my-3 rounded-r-lg">
                    {renderInlineText(block.content)}
                </blockquote>
            );
        case "ul":
        case "ol":
            return renderListBlock(block, key);
        case "code":
            return (
                <div key={key} className="my-4 rounded-xl border border-ink-200 bg-ink-900 text-ink-100 p-4 overflow-x-auto shadow-inner relative group select-text">
                    {block.language && (
                        <div className="absolute right-3 top-3 text-[10px] uppercase font-bold text-ink-400 select-none">
                            {block.language}
                        </div>
                    )}
                    <pre className="font-mono text-xs sm:text-sm leading-relaxed overflow-x-auto">
                        {block.content}
                    </pre>
                </div>
            );
        case "hr":
            return <hr key={key} className="border-t border-ink-200 my-6" />;
        default:
            return <p key={key} className="text-sm text-ink-800 leading-relaxed my-2">{renderInlineText(block.content)}</p>;
    }
};

export interface MarkdownViewerProps {
    markdown: string;
}

export const MarkdownViewer: React.FC<MarkdownViewerProps> = ({ markdown }) => {
    const blocks = parseMarkdown(markdown);
    return (
        <div className="w-full text-left select-text font-sans leading-relaxed text-ink-800">
            {blocks.map((block, idx) => renderBlock(block, idx))}
        </div>
    );
};

export default MarkdownViewer;
