import {css, html, LitElement} from 'lit'
import {customElement, property} from 'lit/decorators.js'
import {FieldType, SelectOptions} from '../types'
import type {FieldDefinition} from '../types'
import {DataPlane} from '../DataPlane'
import {IBehaviorSubject, Unsubscribe} from '../../BehaviorSubject'
import {Ex} from '../../util'
import uuid = Ex.uuid
import {CodeChangeEvent} from './Code'
import {FormState} from './State'

export const inferFieldValue = (value: string, type: FieldType): unknown => {
    if ( type === FieldType.number ) {
        if ( !value ) {
            return undefined
        }

        return parseFloat(String(value))
    }

    if ( type === FieldType.integer ) {
        if ( !value ) {
            return undefined
        }

        return parseInt(String(value), 10)
    }

    if ( type === FieldType.date ) {
        const date = new Date(`${value} 00:00:00`)
        if ( isNaN(date.getTime()) ) {
            return undefined
        }
        return date
    }

    return value
}

@customElement('cobalt-form-field')
export class Field extends LitElement {
    static override styles = css`
        .field-wrapper {
            padding: 7px;
        }
    `

    @property({ attribute: false })
    plane?: DataPlane

    @property({ attribute: false })
    config?: FieldDefinition

    @property({ attribute: false })
    parentContext: Record<string, any> = {}

    private value?: IBehaviorSubject<unknown>
    private selectOptionMap?: Record<string, unknown>
    private dirty: boolean = false
    private formStateSub?: Unsubscribe
    private formState?: FormState

    private getSourceName(): string|undefined {
        return this.parentContext.sourceName
    }

    private getStateSourceName(): string|undefined {
        const sourceName = this.getSourceName()
        return sourceName ? `${sourceName}-state` : undefined
    }

    override render() {
        return html`
            <div class="field-wrapper">
                ${this.renderField()}
            </div>
        `
    }

    renderField() {
        const sourceName = this.getSourceName()
        const stateSourceName = this.getStateSourceName()

        if ( !this.config || !sourceName || !this.plane ) {
            return html`...`
        }

        if ( !this.value ) {
            this.value = this.plane.getField(sourceName, this.config.key)
        }

        if ( stateSourceName && !this.formStateSub ) {
            this.formStateSub = this.plane
                .getAll(stateSourceName)
                ?.subscribe(state => {
                    this.formState = state as FormState
                    this.requestUpdate()
                })
        }

        if ( this.isInputType(this.config.type) ) {
            return html`
                <wa-input
                    id="field-${sourceName}-${this.config.key}"
                    label=${this.config.display}
                    name=${this.config.key}
                    type="${this.mapInputType(this.config.type)}"
                    ?required=${this.config.required}
                    ?clearable=${!this.config.required}
                    ?disabled=${this.config.readonly}
                    placeholder=${this.config.placeholder}
                    help-text=${this.config.helpText}
                    @wa-change=${this.setValue}
                ></wa-input>
                ${this.renderErrors()}
            `
        }

        if ( this.config.type === FieldType.textarea ) {
            return html`
                <wa-textarea
                    id="field-${sourceName}-${this.config.key}"
                    label=${this.config.display}
                    name=${this.config.key}
                    resize="auto"
                    rows="2"
                    ?required=${this.config.required}
                    ?clearable=${!this.config.required}
                    ?disabled=${this.config.readonly}
                    placeholder=${this.config.placeholder}
                    help-text=${this.config.helpText}
                    @wa-change=${this.setValue}
                ></wa-textarea>
                ${this.renderErrors()}
            `
        }

        if ( this.config.type === FieldType.bool ) {
            return html`
                <wa-switch
                    id="field-${sourceName}-${this.config.key}"
                    name=${this.config.key}
                    help-text=${this.config.helpText}
                    ?required=${this.config.required}
                    ?disabled=${this.config.readonly}
                    @wa-change=${this.setValue}
                >
                    ${this.config.display}
                </wa-switch>
                ${this.renderErrors()}
            `
        }

        if ( this.config.type === FieldType.select && 'options' in this.config ) {
            if ( !this.selectOptionMap ) {
                this.buildSelectOptionMap()
            }

            return html`
                <wa-select
                    id="field-${sourceName}-${this.config.key}"
                    name=${this.config.key}
                    label=${this.config.display}
                    placeholder=${this.config.placeholder}
                    help-text=${this.config.helpText}
                    ?multiple=${this.config.multiple}
                    ?clearable=${!this.config.required}
                    ?required=${this.config.required}
                    ?disabled=${this.config.readonly}
                    @wa-change=${this.setValue}
                >
                    ${this.config.options.map(o => this.renderSelectOption(o))}
                </wa-select>
                ${this.renderErrors()}
            `
        }

        if ( this.config.type === FieldType.html ) {
            return html`
                <cobalt-code
                    id="field-${sourceName}-${this.config.key}"
                    syntax="html"
                    placeholder=${this.config.placeholder}
                    label=${this.config.display}
                    help-text=${this.config.helpText}
                    ?required=${this.config.required}
                    ?disabled=${this.config.readonly}
                    @code-change=${this.setValue}
                ></cobalt-code>
                ${this.renderErrors()}
            `
        }

        return html`
            Invalid field configuration: ${this.config.type}
        `
    }

    private buildSelectOptionMap() {
        if ( !this.config || this.config.type !== FieldType.select || !('options' in this.config) ) {
            return
        }

        this.selectOptionMap = {}
        for ( const opt of this.config.options ) {
            if ( !opt.uuid ) {
                opt.uuid = uuid()
            }

            this.selectOptionMap[opt.uuid] = opt.value
        }
    }

    private renderSelectOption(option: SelectOptions[0]) {
        return html`
            <wa-option
                .value=${option.uuid!}
            >
                ${option.display}
            </wa-option>
        `
    }

    private renderErrors() {
        if ( !this.formState?.errors?.[this.config!.key]?.length || !this.dirty ) {
            return html``
        }

        const errorDisplays = this.formState!.errors[this.config!.key]!
            .map(str => html`<small style="color: darkred;">${str}</small>`)

        return html`<div class="field-errors">${errorDisplays}</div>`
    }

    private mapInputType(type: FieldType): string {
        if ( type === FieldType.email ) {
            return 'email'
        }

        if ( type === FieldType.number || type === FieldType.integer ) {
            return 'number'
        }

        if ( type === FieldType.date ) {
            return 'date'
        }

        return 'text'
    }

    private isInputType(type: FieldType): boolean {
        return [
            FieldType.text,
            FieldType.email,
            FieldType.number,
            FieldType.integer,
            FieldType.date,
        ].includes(type)
    }

    private setValue(event: Event) {
        const newValue = this.getFieldValue(event)
        this.dirty = newValue !== this.value?.value()
        this.value?.next(newValue)
    }

    private getFieldValue(event: Event) {
        const input = (this.shadowRoot || this).querySelector(`#field-${this.getSourceName()!}-${this.config!.key}`)

        if ( this.config!.type === FieldType.bool ) {
            return !!(input as any)?.checked
        }

        if ( this.config!.type === FieldType.select ) {
            const uuid = (input as any)?.value
            return this.selectOptionMap?.[uuid]
        }

        if ( this.config!.type === FieldType.html && event instanceof CodeChangeEvent ) {
            return event.content
        }

        return inferFieldValue((input as any)?.value, this.config!.type)
    }
}
