package tech.easyflow.ai.node.filegeneration;

import org.apache.poi.xwpf.usermodel.*;
import org.commonmark.ext.gfm.tables.TableBlock;
import org.commonmark.ext.gfm.tables.TableBody;
import org.commonmark.ext.gfm.tables.TableCell;
import org.commonmark.ext.gfm.tables.TableHead;
import org.commonmark.ext.gfm.tables.TableRow;
import org.commonmark.node.*;
import org.springframework.stereotype.Component;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * DOCX 文件渲染器。
 *
 * @author Codex
 * @since 2026-04-18
 */
@Component
public class DocxFileRenderer implements DocumentRenderer {
    private final MarkdownSupport markdownSupport;

    public DocxFileRenderer(MarkdownSupport markdownSupport) {
        this.markdownSupport = markdownSupport;
    }

    @Override
    public TargetFormat getTargetFormat() {
        return TargetFormat.DOCX;
    }

    @Override
    public boolean supports(SourceFormat sourceFormat) {
        return sourceFormat == SourceFormat.PLAIN_TEXT || sourceFormat == SourceFormat.MARKDOWN;
    }

    @Override
    public FileGenerationResult render(FileGenerationRequest request) {
        try (XWPFDocument document = new XWPFDocument();
             ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            SourceFormat sourceFormat = SourceFormat.fromValue(request.getSourceFormat());
            if (sourceFormat == SourceFormat.PLAIN_TEXT) {
                renderPlainText(document, request.getContent());
            } else {
                renderMarkdown(document, request.getContent());
            }
            document.write(outputStream);
            return new FileGenerationResult(
                    request.getFileName(),
                    TargetFormat.DOCX.getValue(),
                    TargetFormat.DOCX.getContentType(),
                    outputStream.toByteArray()
            );
        } catch (IOException e) {
            throw new IllegalStateException("DOCX 导出失败: " + e.getMessage(), e);
        }
    }

    private void renderPlainText(XWPFDocument document, String content) {
        String normalized = content == null ? "" : content.replace("\r\n", "\n").replace('\r', '\n');
        String[] lines = normalized.split("\\n", -1);
        if (lines.length == 0) {
            lines = new String[]{""};
        }
        for (String line : lines) {
            XWPFParagraph paragraph = document.createParagraph();
            paragraph.setSpacingAfter(180);
            XWPFRun run = paragraph.createRun();
            run.setFontSize(11);
            run.setText(line);
        }
    }

    private void renderMarkdown(XWPFDocument document, String markdown) {
        Node root = markdownSupport.parse(markdown);
        for (Node child = root.getFirstChild(); child != null; child = child.getNext()) {
            renderBlock(child, document, 0, 0);
        }
    }

    private void renderBlock(Node node, XWPFDocument document, int listLevel, int quoteLevel) {
        if (node instanceof Heading heading) {
            renderHeading(heading, document, listLevel, quoteLevel);
            return;
        }
        if (node instanceof Paragraph paragraphNode) {
            renderParagraph(paragraphNode, document, listLevel, quoteLevel, null);
            return;
        }
        if (node instanceof BulletList bulletList) {
            renderBulletList(bulletList, document, listLevel + 1, quoteLevel);
            return;
        }
        if (node instanceof OrderedList orderedList) {
            renderOrderedList(orderedList, document, listLevel + 1, quoteLevel);
            return;
        }
        if (node instanceof BlockQuote blockQuote) {
            for (Node child = blockQuote.getFirstChild(); child != null; child = child.getNext()) {
                renderBlock(child, document, listLevel, quoteLevel + 1);
            }
            return;
        }
        if (node instanceof ThematicBreak) {
            XWPFParagraph paragraph = createParagraph(document, listLevel, quoteLevel);
            XWPFRun run = paragraph.createRun();
            run.setColor("94A3B8");
            run.setText("────────────────────────");
            return;
        }
        if (node instanceof TableBlock tableBlock) {
            renderTable(tableBlock, document);
            return;
        }
        if (node instanceof FencedCodeBlock fencedCodeBlock) {
            renderCodeParagraph(document, fencedCodeBlock.getLiteral(), listLevel, quoteLevel);
            return;
        }
        if (node instanceof IndentedCodeBlock indentedCodeBlock) {
            renderCodeParagraph(document, indentedCodeBlock.getLiteral(), listLevel, quoteLevel);
            return;
        }
        for (Node child = node.getFirstChild(); child != null; child = child.getNext()) {
            renderBlock(child, document, listLevel, quoteLevel);
        }
    }

    private void renderHeading(Heading heading, XWPFDocument document, int listLevel, int quoteLevel) {
        XWPFParagraph paragraph = createParagraph(document, listLevel, quoteLevel);
        paragraph.setSpacingBefore(240);
        paragraph.setSpacingAfter(180);
        XWPFRun run = paragraph.createRun();
        run.setBold(true);
        run.setFontSize(switch (heading.getLevel()) {
            case 1 -> 20;
            case 2 -> 18;
            case 3 -> 16;
            case 4 -> 15;
            case 5 -> 14;
            default -> 13;
        });
        renderInlineChildren(heading, paragraph, InlineStyle.defaults().withBold(true).withFontSize(run.getFontSize()));
    }

    private void renderBulletList(BulletList list, XWPFDocument document, int listLevel, int quoteLevel) {
        for (Node child = list.getFirstChild(); child != null; child = child.getNext()) {
            if (child instanceof ListItem listItem) {
                renderListItem(listItem, document, listLevel, quoteLevel, "- ");
            }
        }
    }

    private void renderOrderedList(OrderedList list, XWPFDocument document, int listLevel, int quoteLevel) {
        int index = list.getStartNumber();
        for (Node child = list.getFirstChild(); child != null; child = child.getNext()) {
            if (child instanceof ListItem listItem) {
                renderListItem(listItem, document, listLevel, quoteLevel, index + ". ");
                index++;
            }
        }
    }

    private void renderListItem(ListItem listItem, XWPFDocument document, int listLevel, int quoteLevel, String marker) {
        boolean markerRendered = false;
        for (Node child = listItem.getFirstChild(); child != null; child = child.getNext()) {
            if (child instanceof Paragraph paragraphNode) {
                renderParagraph(paragraphNode, document, listLevel, quoteLevel, markerRendered ? null : marker);
                markerRendered = true;
                continue;
            }
            if (child instanceof BulletList bulletList) {
                renderBulletList(bulletList, document, listLevel + 1, quoteLevel);
                continue;
            }
            if (child instanceof OrderedList orderedList) {
                renderOrderedList(orderedList, document, listLevel + 1, quoteLevel);
                continue;
            }
            renderBlock(child, document, listLevel, quoteLevel);
        }
        if (!markerRendered) {
            XWPFParagraph paragraph = createParagraph(document, listLevel, quoteLevel);
            appendPrefix(paragraph, quoteLevel, marker);
        }
    }

    private void renderParagraph(Node paragraphNode,
                                 XWPFDocument document,
                                 int listLevel,
                                 int quoteLevel,
                                 String prefix) {
        XWPFParagraph paragraph = createParagraph(document, listLevel, quoteLevel);
        appendPrefix(paragraph, quoteLevel, prefix);
        renderInlineChildren(paragraphNode, paragraph, InlineStyle.defaults());
    }

    private void renderCodeParagraph(XWPFDocument document, String literal, int listLevel, int quoteLevel) {
        XWPFParagraph paragraph = createParagraph(document, listLevel, quoteLevel);
        XWPFRun run = paragraph.createRun();
        run.setFontFamily("Consolas");
        run.setFontSize(10);
        run.setText(literal == null ? "" : literal);
    }

    private void renderInlineChildren(Node parent, XWPFParagraph paragraph, InlineStyle style) {
        for (Node child = parent.getFirstChild(); child != null; child = child.getNext()) {
            renderInlineNode(child, paragraph, style);
        }
    }

    private void renderInlineNode(Node node, XWPFParagraph paragraph, InlineStyle style) {
        if (node instanceof Text text) {
            createStyledRun(paragraph, style, text.getLiteral());
            return;
        }
        if (node instanceof SoftLineBreak || node instanceof HardLineBreak) {
            XWPFRun run = paragraph.createRun();
            applyStyle(run, style);
            run.addBreak();
            return;
        }
        if (node instanceof Emphasis emphasis) {
            renderInlineChildren(emphasis, paragraph, style.withItalic(true));
            return;
        }
        if (node instanceof StrongEmphasis strongEmphasis) {
            renderInlineChildren(strongEmphasis, paragraph, style.withBold(true));
            return;
        }
        if (node instanceof Link link) {
            String label = collectInlineText(link);
            if (label.isBlank()) {
                label = link.getDestination();
            }
            createHyperlinkRun(paragraph, style, label, link.getDestination());
            return;
        }
        if (node instanceof Code code) {
            XWPFRun run = paragraph.createRun();
            applyStyle(run, style);
            run.setFontFamily("Consolas");
            run.setText(code.getLiteral());
            return;
        }
        if (node instanceof HtmlInline htmlInline) {
            createStyledRun(paragraph, style, htmlInline.getLiteral());
            return;
        }
        if (node instanceof Image image) {
            String altText = collectInlineText(image);
            createStyledRun(paragraph, style, altText.isBlank() ? "[图片]" : altText);
            return;
        }
        renderInlineChildren(node, paragraph, style);
    }

    private void renderTable(TableBlock tableBlock, XWPFDocument document) {
        List<TableRowContent> rows = collectTableRows(tableBlock);
        if (rows.isEmpty()) {
            return;
        }
        int columnCount = rows.stream().mapToInt(item -> item.cells.size()).max().orElse(1);
        XWPFTable table = document.createTable(rows.size(), columnCount);
        for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) {
            TableRowContent rowContent = rows.get(rowIndex);
            XWPFTableRow tableRow = table.getRow(rowIndex);
            for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
                XWPFTableCell tableCell = tableRow.getCell(columnIndex);
                clearCell(tableCell);
                if (columnIndex < rowContent.cells.size()) {
                    XWPFParagraph paragraph = tableCell.addParagraph();
                    paragraph.setSpacingAfter(80);
                    renderInlineChildren(
                            rowContent.cells.get(columnIndex),
                            paragraph,
                            rowContent.header ? InlineStyle.defaults().withBold(true) : InlineStyle.defaults()
                    );
                }
            }
        }
    }

    private List<TableRowContent> collectTableRows(TableBlock tableBlock) {
        List<TableRowContent> rows = new ArrayList<>();
        for (Node section = tableBlock.getFirstChild(); section != null; section = section.getNext()) {
            boolean header = section instanceof TableHead;
            if (!(section instanceof TableHead) && !(section instanceof TableBody)) {
                continue;
            }
            for (Node rowNode = section.getFirstChild(); rowNode != null; rowNode = rowNode.getNext()) {
                if (!(rowNode instanceof TableRow)) {
                    continue;
                }
                List<TableCell> cells = new ArrayList<>();
                for (Node cellNode = rowNode.getFirstChild(); cellNode != null; cellNode = cellNode.getNext()) {
                    if (cellNode instanceof TableCell tableCell) {
                        cells.add(tableCell);
                    }
                }
                rows.add(new TableRowContent(header, cells));
            }
        }
        return rows;
    }

    private void clearCell(XWPFTableCell tableCell) {
        while (tableCell.getParagraphs().size() > 0) {
            tableCell.removeParagraph(0);
        }
    }

    private XWPFParagraph createParagraph(XWPFDocument document, int listLevel, int quoteLevel) {
        XWPFParagraph paragraph = document.createParagraph();
        paragraph.setSpacingAfter(140);
        paragraph.setIndentationLeft((listLevel + quoteLevel) * 320);
        return paragraph;
    }

    private void appendPrefix(XWPFParagraph paragraph, int quoteLevel, String prefix) {
        if (quoteLevel > 0) {
            createStyledRun(paragraph, InlineStyle.defaults().withColor("64748B"),
                    "> ".repeat(Math.max(1, quoteLevel)));
        }
        if (prefix != null && !prefix.isBlank()) {
            createStyledRun(paragraph, InlineStyle.defaults().withBold(true), prefix);
        }
    }

    private void createStyledRun(XWPFParagraph paragraph, InlineStyle style, String text) {
        XWPFRun run = paragraph.createRun();
        applyStyle(run, style);
        run.setText(text == null ? "" : text);
    }

    /**
     * 创建外部超链接；内部锚点与空链接首版退化为普通文本。
     *
     * @param paragraph 目标段落
     * @param style 当前内联样式
     * @param label 链接文案
     * @param destination 链接地址
     */
    private void createHyperlinkRun(XWPFParagraph paragraph, InlineStyle style, String label, String destination) {
        if (destination == null || destination.isBlank() || destination.startsWith("#")) {
            createStyledRun(paragraph, style.withUnderline(true).withColor("2563EB"), label);
            return;
        }
        XWPFHyperlinkRun hyperlinkRun = paragraph.createHyperlinkRun(destination);
        applyStyle(hyperlinkRun, style.withUnderline(true).withColor("2563EB"));
        hyperlinkRun.setText(label == null ? "" : label);
    }

    private void applyStyle(XWPFRun run, InlineStyle style) {
        run.setBold(style.bold);
        run.setItalic(style.italic);
        if (style.underline) {
            run.setUnderline(UnderlinePatterns.SINGLE);
        }
        if (style.color != null) {
            run.setColor(style.color);
        }
        if (style.fontSize != null) {
            run.setFontSize(style.fontSize);
        } else {
            run.setFontSize(11);
        }
    }

    private String collectInlineText(Node node) {
        StringBuilder builder = new StringBuilder();
        appendInlineText(node, builder);
        return builder.toString();
    }

    private void appendInlineText(Node node, StringBuilder builder) {
        if (node instanceof Text text) {
            builder.append(text.getLiteral());
            return;
        }
        if (node instanceof Code code) {
            builder.append(code.getLiteral());
            return;
        }
        if (node instanceof SoftLineBreak || node instanceof HardLineBreak) {
            builder.append('\n');
            return;
        }
        for (Node child = node.getFirstChild(); child != null; child = child.getNext()) {
            appendInlineText(child, builder);
        }
    }

    private static class TableRowContent {
        private final boolean header;
        private final List<TableCell> cells;

        private TableRowContent(boolean header, List<TableCell> cells) {
            this.header = header;
            this.cells = cells;
        }
    }

    private static class InlineStyle {
        private final boolean bold;
        private final boolean italic;
        private final boolean underline;
        private final String color;
        private final Integer fontSize;

        private InlineStyle(boolean bold, boolean italic, boolean underline, String color, Integer fontSize) {
            this.bold = bold;
            this.italic = italic;
            this.underline = underline;
            this.color = color;
            this.fontSize = fontSize;
        }

        private static InlineStyle defaults() {
            return new InlineStyle(false, false, false, null, null);
        }

        private InlineStyle withBold(boolean value) {
            return new InlineStyle(value, italic, underline, color, fontSize);
        }

        private InlineStyle withItalic(boolean value) {
            return new InlineStyle(bold, value, underline, color, fontSize);
        }

        private InlineStyle withUnderline(boolean value) {
            return new InlineStyle(bold, italic, value, color, fontSize);
        }

        private InlineStyle withColor(String value) {
            return new InlineStyle(bold, italic, underline, value, fontSize);
        }

        private InlineStyle withFontSize(Integer value) {
            return new InlineStyle(bold, italic, underline, color, value);
        }
    }
}
