/*
 * Copyright (c) 2025 Rostock University Library
 * Licensed under the MIT License. See LICENSE file in the project root.
 */
package de.uni.rostock.ub.wsrb.edition.webapp.service;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import javax.xml.transform.stream.StreamSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import de.uni.rostock.ub.wsrb.edition.webapp.util.WSRBUtil;
import jakarta.annotation.PostConstruct;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.XdmAtomicValue;
import net.sf.saxon.s9api.XdmValue;
import net.sf.saxon.s9api.Xslt30Transformer;
import net.sf.saxon.s9api.XsltCompiler;
import net.sf.saxon.s9api.XsltExecutable;

@Service
public class XSLTDocumentationService {

    private static final Logger LOGGER = LoggerFactory.getLogger(XSLTDocumentationService.class);

    private static final QName QNAME_ELEM_TEI_DIV = new QName(WSRBUtil.XML_NS_TEI, "div");
    private static final QName QNAME_ATTR_XML_ID = new QName(XMLConstants.XML_NS_URI, "id");
    private static final QName QNAME_ATTR_TEI_N = new QName("n");
    private static final QName QNAME_ATTR_TEI_TYPE = new QName("type");

    @Value("${wsrb.folder.teixml}")
    private String teixmlFolder;

    @Value("${wsrb.folder.xslt}")
    private String xsltFolder;

    @Value("${wsrb.dokumentation.id}")
    private String dokumentationId;

    @Autowired
    DataService dataService;

    XsltExecutable xslDocumentation;

    XsltExecutable xslBiblPopups;

    Processor processor;

    Map<String, String> htmlBody = new ConcurrentHashMap<>();
    Map<String, String> pageLabels = new ConcurrentHashMap<>();
    Map<String, List<String>> subPages = new ConcurrentHashMap<>();

    String htmlBiblPopups = "";

    String webseiteResourcePath;

    @PostConstruct
    private void init() {
        webseiteResourcePath = teixmlFolder + "dokumentation/" + dokumentationId + WSRBUtil.TEI_FILE_EXTENSION;
        processor = new Processor(false);
        try (InputStream instrWeb = getClass().getResourceAsStream(xsltFolder + "dokumentation/html4webseite.xsl");
            InputStream instrBibl = getClass().getResourceAsStream(xsltFolder + "dokumentation/html4biblpopups.xsl")) {
            XsltCompiler compiler = processor.newXsltCompiler();

            xslDocumentation = compiler.compile(new StreamSource(instrWeb));
            List<String> textIds = initDocumentation();
            processTexts(textIds);

            xslBiblPopups = compiler.compile(new StreamSource(instrBibl));
            initBiblPopups();
        } catch (SaxonApiException | XMLStreamException | IOException e) {
            LOGGER.error("Initalization error", e);
        }
    }

    private void initBiblPopups() throws SaxonApiException, IOException {
        Xslt30Transformer transformer = xslBiblPopups.load30();
        try (InputStream is = getClass().getResourceAsStream(webseiteResourcePath)) {
            StreamSource in = new StreamSource(is);
            StringWriter sw = new StringWriter();
            transformer.transform(in, WSRBUtil.createSerializer(processor, sw));
            htmlBiblPopups = sw.toString();
        }
    }

    private List<String> initDocumentation() throws XMLStreamException, IOException {
        List<String> textIds = new ArrayList<>();
        XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
        try (InputStream instr = getClass().getResourceAsStream(webseiteResourcePath)) {
            XMLEventReader reader = xmlInputFactory.createXMLEventReader(instr);
            while (reader.hasNext()) {
                XMLEvent nextEvent = reader.nextEvent();
                if (nextEvent.isStartElement()) {
                    StartElement startElement = nextEvent.asStartElement();
                    if (startElement.getName().equals(QNAME_ELEM_TEI_DIV)
                        && startElement.getAttributeByName(QNAME_ATTR_TEI_TYPE) != null
                        && "webseite".equals(startElement.getAttributeByName(QNAME_ATTR_TEI_TYPE).getValue())
                        && startElement.getAttributeByName(QNAME_ATTR_XML_ID) != null) {
                        String id = startElement.getAttributeByName(QNAME_ATTR_XML_ID).getValue();
                        textIds.add(id);
                        pageLabels.put(id, startElement.getAttributeByName(QNAME_ATTR_TEI_N) != null
                            ? startElement.getAttributeByName(QNAME_ATTR_TEI_N).getValue() : "");

                        processSubpages(id);
                    }
                }
            }
        }
        return textIds;

    }

    private void processTexts(List<String> textIds) throws SaxonApiException, IOException {
        for (String id : textIds) {
            Xslt30Transformer transformer = xslDocumentation.load30();
            try (InputStream is = getClass().getResourceAsStream(webseiteResourcePath)) {
                StreamSource in = new StreamSource(is);
                Map<net.sf.saxon.s9api.QName, XdmValue> params = Map.ofEntries(
                    Map.entry(new net.sf.saxon.s9api.QName("text_id"), new XdmAtomicValue(id)));
                transformer.setStylesheetParameters(params);
                StringWriter sw = new StringWriter();
                transformer.transform(in, WSRBUtil.createSerializer(processor, sw));

                htmlBody.put(id, sw.toString());
            }
        }
    }

    private void processSubpages(String id) {
        //subpages
        if (!id.contains("__")) {
            if (!subPages.containsKey(id)) {
                subPages.put(id, new ArrayList<>());
            }
        } else {
            String page = id.substring(0, id.indexOf("__"));
            if (!subPages.containsKey(page)) {
                subPages.put(page, new ArrayList<>());
            }
            subPages.get(page).add(id);
        }
    }

    public Map<String, String> getHtmlBody() {
        return htmlBody;
    }

    public String getHtmlBiblPopups(String relPath) {
        String r = relPath == null ? "." : relPath;
        return htmlBiblPopups.replace("%%REL_URL%%", r);
    }

    public Map<String, String> getPageLabels() {
        return pageLabels;
    }

    public Map<String, List<String>> getSubPages() {
        return subPages;
    }

}
