import { Component, h, Host, Prop, Event, EventEmitter, Watch, Method, State } from '@stencil/core'
import Tagify from '@yaireo/tagify'
import Filter from 'bad-words'

const filter = new Filter()

export type Size = 'sm' | 'lg'
export type Tags = string | string[]
export type Variant = 'pills'

@Component({
    tag: 'gd-tag-input',
    styleUrl: 'gd-tag-input.scss',
    shadow: true,
})
export class GdTagInput {
    /**
     * Specifies how much of the input is shown.
     */
    @Prop() htmlSize: number

    /**
     * change the style of the tags
     */
    @Prop() variant: Variant

    /**
     * Used to change the size of inputs and labels respectively.
     */
    @Prop() size: Size

    /**
     * The value of the input is not editable.
     */
    @Prop() readOnly: boolean

    /**
     * Disables the input / select / textarea from edit or copy.
     */
    @Prop() disabled: boolean

    /**
     * The initial value of the input
     */
    @Prop() defaultValue: Tags

    /**
     * The element id
     */
    @Prop() controlId: string

    /**
     * The name applied to the input
     */
    @Prop() name: string

    /**
     * The placeholder text for the input
     */
    @Prop() placeholder: string

    /**
     * Used for making form control required
     */
    @Prop() required: boolean = false

    /**
     * The event is emitted when the tags change
     */
    @Event() tagsChange: EventEmitter<Tags>

    /**
     * the internal tag state, for deciding if we should reinitialize tagify
     */
    @State() currentTags: Tags = []

    private inputEl: HTMLElement
    private tagify: Tagify

    componentDidLoad() {
        this.initializeTagify()
    }

    @Watch('defaultValue')
    defaultValueChanged() {
        // TODO: this seems to bug out the tag input (causes an infinite change loop). Need to review so that tag input can actually reinitialize properly
        /*
        if (JSON.stringify(this.defaultValue) !== JSON.stringify(this.currentTags)) {
            // the application modified the tags, so lets reinitialize tagify
            this.initializeTagify()
        }
        */
    }

    @Watch('placeholder')
    initializeTagify() {
        // already have a tagify instance, reset it's tags
        if (this.tagify) {
            this.tagify.removeAllTags()
            this.tagify.addTags(this.defaultValue)
            this.currentTags = this.defaultValue

            return
        }

        this.tagify = new Tagify(this.inputEl, {
            transformTag: this.filterBadWords,
        })

        this.currentTags = this.defaultValue

        // on change, trigger the tagsChange event
        this.tagify.on('change', e => {
            let tags = e.detail.value || '[]' // default to no tags (tagify uses JSON, hence the empty string array)

            if (this.isJson(tags)) {
                tags = JSON.parse(tags)
            } else if (typeof tags === 'string') {
                tags = tags.split(',')
            }

            this.currentTags = tags.map(tag => tag.value || tag)

            this.tagsChange.emit(this.currentTags)
        })
    }

    @Method()
    async addTags(tags: Tags) {
        this.tagify && this.tagify.addTags(tags)
    }

    /**
     * renders the tag component
     */
    render() {
        return (
            <Host
                class={{
                    pills: this.variant == 'pills',
                }}
            >
                <input
                    type="text"
                    name={this.name}
                    id={this.controlId}
                    size={this.htmlSize}
                    read-only={this.readOnly}
                    disabled={this.disabled}
                    value={this.defaultValue}
                    placeholder={this.placeholder}
                    required={this.required}
                    ref={el => (this.inputEl = el)}
                />
            </Host>
        )
    }

    private filterBadWords(tag) {
        if (!filter.isProfane(tag.value)) {
            // tag isn't profane, just return
            return
        }

        // no bad words! 🤬
        tag.value = filter.clean(tag.value)
    }

    private isJson(item) {
        item = typeof item !== 'string' ? JSON.stringify(item) : item

        try {
            item = JSON.parse(item)
        } catch (e) {
            return false
        }

        if (typeof item === 'object' && item !== null) {
            return true
        }

        return false
    }
}
