export type TaskAssignmentData = {
    id: string,
    name: string,
    points: number | null,
    description: string,
    titleHtml: string
}

type TaskLocation = {
    /** Relative location of HTML file containing this task */
    url: string
    /** id of the element where the specific task begins */
    startElement: string
}

function fixAllLinks(e: any) {
    if (typeof e.src == "string") {
        e.src = e.src
    }
    if (typeof e.href == "string") {
        e.href = e.href
    }
    let c = (e as HTMLElement).firstElementChild
    while (c) {
        fixAllLinks(c)
        c = c.nextElementSibling
    }
}

export type ParsedTaskId = {
    rocnik: string
    z: boolean
    serie: string
    uloha: string
}

export function parseTaskId(id: string): ParsedTaskId | null {
    const m = /^(\d+)-(Z?)(\d)-(\d)$/.exec(id)
    if (!m) return null
    const [_, rocnik, z, serie, uloha] = m
    return { rocnik, z: !!z, serie, uloha }
}

function getLocation(id: string, solution: boolean): TaskLocation {
    const parsedId = parseTaskId(id)
    if (!parsedId) {
        throw new Error("Can not parse " + id)
    }
    const { rocnik, z, serie, uloha } = parsedId
    const urlX = solution ? "reseni" : "zadani"
    if (z) {
        return {
            url: `/z/ulohy/${rocnik}/${urlX}${serie}.html`,
            startElement: `task-${id}`
        }
    } else {
        return {
            url: `/h/ulohy/${rocnik}/${urlX}${serie}.html`,
            startElement: `task-${id}`
        }
    }
}

function htmlEncode(text: string): string {
    const p = document.createElement("p")
    p.textContent = text
    return p.innerHTML
}

function parseTask(startElementId: string, doc: HTMLDocument): TaskAssignmentData {
    const titleElement = doc.getElementById(startElementId)
    if (!titleElement)
        throw new Error(`Document does not contain ${startElementId}`)
    fixAllLinks(titleElement)

    let e = titleElement

    const titleMatch = /^(\d+-Z?\d+-\d+) (.*?)( \((\d+) bod.*\))?$/.exec(e.textContent!.trim())
    if (!titleMatch) {
        var [_, id, name, __, points] = ["", startElementId, "Neznámé jméno úlohy", "", ""]
    } else {
        var [_, id, name, __, points] = titleMatch
    }

    e = e.nextElementSibling as HTMLElement

    // skip first <hr>
    while (e.nextElementSibling &&
           e.tagName.toLowerCase() == "hr")
        e = e.nextElementSibling as HTMLElement


    // hack: remove img tag that shows this task is a practical one. Some tasks have it, some don't, so we remove it for consistency
    const intoImgTag = e.firstElementChild
    if (intoImgTag && intoImgTag.tagName.toLowerCase() == "img" && intoImgTag.classList.contains("leftfloat")) {
        intoImgTag.remove()
    }

    let r = ""

    copyElements: while (!e.classList.contains("story") &&
        //    !e.classList.contains("clearfloat") &&
           e.tagName.toLowerCase() != "h3" &&
           e.textContent!.trim() != "Řešení"
        ) {

        processElement: {
            // hack: remove the paragraph with the matching text. Occurs in KSP-H, but is useless in this context.
            if (e.textContent!.trim().replace(/\s+/g, " ") == "Toto je praktická open-data úloha. V odevzdávacím systému si necháte vygenerovat vstupy a odevzdáte příslušné výstupy. Záleží jen na vás, jak výstupy vyrobíte.") {
                break processElement
            }

            fixAllLinks(e)

            r += e.outerHTML + "\n"
        }

        let n = e.nextSibling
        copyNodes: while(true) {
            if (!n) {
                break copyElements
            }
            if (n.nodeType == Node.ELEMENT_NODE) {
                e = n as HTMLElement
                break copyNodes
            } else if (n.nodeType == Node.TEXT_NODE && n.textContent!.trim() != "") {
                r += htmlEncode(n.textContent!)
            }
            n = n.nextSibling
        }
    }

    return {
        description: r,
        id: id.trim(),
        name: name.trim(),
        points: points ? +points : null,
        titleHtml: titleElement.outerHTML
    }
}

export async function fetchHtml(url: string) {
    const r = await fetch(url, { headers: { "Accept": "text/html,application/xhtml+xml" } })
    if (r.status >= 400) {
        throw Error(r.statusText)
    }
    const html = await r.text()
    const parser = new DOMParser()
    const doc = parser.parseFromString(html, "text/html")
    if (!doc.head.querySelector("base")) {
        let baseEl = doc.createElement('base');
        baseEl.setAttribute('href', new URL(url, location.href).href);
        doc.head.append(baseEl);
    }
    return doc
}

async function loadTask({ url, startElement }: TaskLocation): Promise<TaskAssignmentData> {
    const html = await fetchHtml(url)
    return parseTask(startElement, html)
}

export function isLoggedIn(): boolean {
    return !!document.head.querySelector("meta[name=x-ksp-uid]")
}

export async function grabAssignment(id: string): Promise<TaskAssignmentData> {
    return await loadTask(getLocation(id, false))
}

export async function grabSolution(id: string): Promise<TaskAssignmentData> {
    return await loadTask(getLocation(id, true))
}