/*******************************************************************************
 * Danbooru Diffusion Prompt Builder
 * Copyright (C) 2022  Jabasukuriputo Wang
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 ******************************************************************************/

// @ts-nocheck not really a typescript file

import { renderToString } from 'vue/server-renderer'
import { createApp } from './main'

function isPathSeparator(code) {
    return code === '/' || code === '\\'
}

function isWindowsDeviceRoot(code) {
    return (code >= 'A' && code <= 'Z') || (code >= 'a' && code <= 'z')
}

function validateString(value, name) {
    if (typeof value !== 'string') throw new Error(name, 'string', value)
}

/**
 * @param {string} path
 * @param {string} [suffix]
 * @returns {string}
 */
function basename(path, suffix) {
    if (suffix !== undefined) validateString(suffix, 'ext')
    validateString(path, 'path')
    let start = 0
    let end = -1
    let matchedSlash = true

    // Check for a drive letter prefix so as not to mistake the following
    // path separator as an extra separator at the end of the path that can be
    // disregarded
    if (
        path.length >= 2 &&
        isWindowsDeviceRoot(path.charCodeAt(0)) &&
        path.charCodeAt(1) === ':'
    ) {
        start = 2
    }

    if (
        suffix !== undefined &&
        suffix.length > 0 &&
        suffix.length <= path.length
    ) {
        if (suffix === path) return ''
        let extIdx = suffix.length - 1
        let firstNonSlashEnd = -1
        for (let i = path.length - 1; i >= start; --i) {
            const code = path.charCodeAt(i)
            if (isPathSeparator(code)) {
                // If we reached a path separator that was not part of a set of path
                // separators at the end of the string, stop now
                if (!matchedSlash) {
                    start = i + 1
                    break
                }
            } else {
                if (firstNonSlashEnd === -1) {
                    // We saw the first non-path separator, remember this index in case
                    // we need it if the extension ends up not matching
                    matchedSlash = false
                    firstNonSlashEnd = i + 1
                }
                if (extIdx >= 0) {
                    // Try to match the explicit extension
                    if (code === suffix.charCodeAt(extIdx)) {
                        if (--extIdx === -1) {
                            // We matched the extension, so mark this as the end of our path
                            // component
                            end = i
                        }
                    } else {
                        // Extension does not match, so our result is the entire path
                        // component
                        extIdx = -1
                        end = firstNonSlashEnd
                    }
                }
            }
        }

        if (start === end) end = firstNonSlashEnd
        else if (end === -1) end = path.length
        return path.slice(start, end)
    }
    for (let i = path.length - 1; i >= start; --i) {
        if (isPathSeparator(path.charCodeAt(i))) {
            // If we reached a path separator that was not part of a set of path
            // separators at the end of the string, stop now
            if (!matchedSlash) {
                start = i + 1
                break
            }
        } else if (end === -1) {
            // We saw the first non-path separator, mark this as the end of our
            // path component
            matchedSlash = false
            end = i + 1
        }
    }

    if (end === -1) return ''
    return path.slice(start, end)
}

export async function render(manifest) {
    const app = createApp()

    // passing SSR context object which will be available via useSSRContext()
    // @vitejs/plugin-vue injects code into a component's setup() that registers
    // itself on ctx.modules. After the render, ctx.modules would contain all the
    // components that have been instantiated during this render call.
    const ctx = {}
    const html = await renderToString(app, ctx)

    // the SSR manifest generated by Vite contains module -> chunk/asset mapping
    // which we can then use to determine what files need to be preloaded for this
    // request.
    const preloadLinks = renderPreloadLinks(ctx.modules, manifest)
    return [html, preloadLinks]
}

function renderPreloadLinks(modules, manifest) {
    let links = ''
    const seen = new Set()
    modules.forEach((id) => {
        const files = manifest[id]
        if (files) {
            files.forEach((file) => {
                if (!seen.has(file)) {
                    seen.add(file)
                    const filename = basename(file)
                    if (manifest[filename]) {
                        for (const depFile of manifest[filename]) {
                            links += renderPreloadLink(depFile)
                            seen.add(depFile)
                        }
                    }
                    links += renderPreloadLink(file)
                }
            })
        }
    })
    return links
}

function renderPreloadLink(file) {
    if (file.endsWith('.js')) {
        return `<link rel="modulepreload" crossorigin href="${file}">`
    } else if (file.endsWith('.css')) {
        return `<link rel="stylesheet" href="${file}">`
    } else if (file.endsWith('.woff')) {
        return ` <link rel="preload" href="${file}" as="font" type="font/woff" crossorigin>`
    } else if (file.endsWith('.woff2')) {
        return ` <link rel="preload" href="${file}" as="font" type="font/woff2" crossorigin>`
    } else if (file.endsWith('.gif')) {
        return ` <link rel="preload" href="${file}" as="image" type="image/gif">`
    } else if (file.endsWith('.jpg') || file.endsWith('.jpeg')) {
        return ` <link rel="preload" href="${file}" as="image" type="image/jpeg">`
    } else if (file.endsWith('.png')) {
        return ` <link rel="preload" href="${file}" as="image" type="image/png">`
    } else if (file.endsWith('.webp')) {
        return ` <link rel="preload" href="${file}" as="image" type="image/webp">`
    } else {
        // TODO
        return ''
    }
}
