import { Term } from '@rdfjs/types'
import { ShaclPropertyTemplate } from "../property-template"
import { Editor, InputListEntry, Theme } from "../theme"
import { PREFIX_SHACL, PREFIX_XSD, XSD_DATATYPE_STRING } from '../constants'
import { Literal, NamedNode } from 'n3'
import { Term as N3Term }  from 'n3'
import css from './default.css?raw'
import { RokitInput, RokitSelect, RokitTextArea } from '@ro-kit/ui-widgets'

export class DefaultTheme extends Theme {
    idCtr = 0

    constructor(overiddenCss?: string) {
        super(overiddenCss ? overiddenCss : css)
    }

    createDefaultTemplate(label: string, value: Term | null, required: boolean, editor: Editor, template?: ShaclPropertyTemplate): HTMLElement {
        editor.id = `e${this.idCtr++}`
        editor.classList.add('editor')
        if (template?.datatype) {
            // store datatype on editor, this is used for RDF serialization
            editor.shaclDatatype = template.datatype
        } else if (value instanceof Literal) {
            editor.shaclDatatype = value.datatype
        }
        if (template?.minCount !== undefined) {
            editor.dataset.minCount = String(template.minCount)
        }
        if (template?.class) {
            editor.dataset.class = template.class.value
        }
        if (template?.nodeKind) {
            editor.dataset.nodeKind = template.nodeKind.value
        } else if (value instanceof NamedNode) {
            editor.dataset.nodeKind = PREFIX_SHACL + 'IRI'
        }
        if (template?.hasValue || template?.readonly) {
            editor.disabled = true
        }
        editor.value = value?.value || template?.defaultValue?.value || ''
    
        const labelElem = document.createElement('label')
        labelElem.htmlFor = editor.id
        labelElem.innerText = label
        if (template?.description) {
            labelElem.setAttribute('title', template.description.value)
        }
    
        const placeholder = template?.description ? template.description.value : template?.pattern ? template.pattern : null
        if (placeholder) {
            editor.setAttribute('placeholder', placeholder)
        }
        if (required) {
            editor.setAttribute('required', 'true')
            labelElem.classList.add('required')
        }
    
        const result = document.createElement('div')
        result.appendChild(labelElem)
        result.appendChild(editor)
        return result
    }

    createDateEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
        const editor = new RokitInput()
        if (template.datatype?.value  === PREFIX_XSD + 'dateTime') {
            editor.type = 'datetime-local'
            // this enables seconds in dateTime input
            editor.setAttribute('step', '1')
        }
        else {
            editor.type = 'date'
        }
        editor.clearable = true
        editor.dense = true
        editor.classList.add('pr-0')
        const result = this.createDefaultTemplate(label, null, required, editor, template)
        if (value) {
            try {
                let isoDate = new Date(value.value).toISOString()
                if (template.datatype?.value  === PREFIX_XSD + 'dateTime') {
                    isoDate = isoDate.slice(0, 19)
                } else {
                    isoDate = isoDate.slice(0, 10)
                }
                editor.value = isoDate
            } catch(ex) {
                console.error(ex, value)
            }
        }
        return result
    }

    createTextEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
        let editor
        if (template.singleLine === false) {
            editor = new RokitTextArea()
            editor.resize = 'auto'
        }
        else {
            editor = new RokitInput()
        }
        editor.dense = true
        if (template.pattern) {
            editor.pattern = template.pattern
        }
        if (template.minLength) {
            editor.minLength = template.minLength
        }
        if (template.maxLength) {
            editor.maxLength = template.maxLength
        }
        return this.createDefaultTemplate(label, value, required, editor, template)
    }

    createLangStringEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
        const result = this.createTextEditor(label, value, required, template)
        const editor = result.querySelector(':scope .editor') as Editor
        let langChooser: HTMLSelectElement | HTMLInputElement
        if (template.languageIn?.length) {
            langChooser = document.createElement('select')
            for (const lang of template.languageIn) {
                const option = document.createElement('option')
                option.innerText = lang.value
                langChooser.appendChild(option)
            }
        } else {
            langChooser = document.createElement('input')
            langChooser.maxLength = 5 // e.g. en-US
            langChooser.size = 5
            langChooser.placeholder = 'lang?'
        }
        langChooser.title = 'Language of the text'
        langChooser.classList.add('lang-chooser')
        langChooser.slot = 'suffix'
        // if lang chooser changes, fire a change event on the text input instead. this is for shacl validation handling.
        langChooser.addEventListener('change', (ev) => {
            ev.stopPropagation();
            if (editor) {
                editor.dataset.lang = langChooser.value
                editor.dispatchEvent(new Event('change', { bubbles: true }))
            }
        })
        if (value instanceof Literal) {
            langChooser.value = value.language
        }
        editor.dataset.lang = langChooser.value
        editor.appendChild(langChooser)
        return result
    }

    createBooleanEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
        const editor = document.createElement('input')
        editor.type = 'checkbox'
        editor.classList.add('ml-0')
    
        const result = this.createDefaultTemplate(label, null, required, editor, template)
    
        // 'required' on checkboxes forces the user to tick the checkbox, which is not what we want here
        editor.removeAttribute('required')
        result.querySelector(':scope label')?.classList.remove('required')
        if (value instanceof Literal) {
            editor.checked = value.value === 'true'
        }
        return result
    }

    createFileEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
        const editor = document.createElement('input')
        editor.type = 'file'
        editor.addEventListener('change', (e) => {
            if (editor.files?.length) {
                e.stopPropagation()
                const reader = new FileReader()
                reader.readAsDataURL(editor.files[0])
                reader.onload = () => {
                    (editor as Editor)['binaryData'] = btoa(reader.result as string)
                    editor.parentElement?.dispatchEvent(new Event('change', { bubbles: true }))
                }
            } else {
                (editor as Editor)['binaryData'] = undefined               
            }
        })
        return this.createDefaultTemplate(label, value, required, editor, template)
    }

    createNumberEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
        const editor = new RokitInput()
        editor.type = 'number'
        editor.clearable = true
        editor.dense = true
        editor.classList.add('pr-0')
        const min = template.minInclusive !== undefined ? template.minInclusive : template.minExclusive !== undefined ? template.minExclusive + 1 : undefined
        const max = template.maxInclusive !== undefined ? template.maxInclusive : template.maxExclusive !== undefined ? template.maxExclusive - 1 : undefined
        if (min !== undefined) {
            editor.min = String(min)
        }
        if (max !== undefined) {
            editor.max = String(max)
        }
        if (template.datatype?.value !== PREFIX_XSD + 'integer') {
            editor.step = '0.1'
        }
        return this.createDefaultTemplate(label, value, required, editor, template)
    }

    createListEditor(label: string, value: Term | null, required: boolean, listEntries: InputListEntry[], template?: ShaclPropertyTemplate): HTMLElement {
        const editor = new RokitSelect()
        editor.clearable = true
        editor.dense = true
        const result = this.createDefaultTemplate(label, null, required, editor, template)
        const ul = document.createElement('ul')
        let isFlatList = true

        const appendListEntry = (entry: InputListEntry, parent: HTMLUListElement) => {
            const li = document.createElement('li')
            if (typeof entry.value === 'string') {
                li.dataset.value = entry.value
                li.innerText = entry.label ? entry.label : entry.value
            } else {
                if (entry.value instanceof Literal && entry.value.datatype.equals(XSD_DATATYPE_STRING)) {
                    li.dataset.value = entry.value.value
                } else {
                    // this is needed for typed rdf literals
                    li.dataset.value = (entry.value as N3Term).id
                }
                li.innerText = entry.label ? entry.label : entry.value.value
            }
            parent.appendChild(li)
            if (entry.children?.length) {
                isFlatList = false
                const ul = document.createElement('ul')
                li.appendChild(ul)
                for (const child of entry.children) {
                    appendListEntry(child, ul)
                }
            }
        }

        for (const item of listEntries) {
            appendListEntry(item, ul)
        }
        if (!isFlatList) {
            editor.collapse = true
        }

        editor.appendChild(ul)
        if (value) {
            editor.value = value.value
        }
        return result
    }

    createButton(label: string, _: boolean): HTMLElement {
        const button = document.createElement('button')
        button.type = 'button'
        button.innerHTML = label
        return button
    }
}
