import { classMap } from 'lit/directives/class-map.js'
import { defaultValue } from '../../internal/default-value.js'
import { FormControlController } from '../../internal/form.js'
import { HasSlotController } from '../../internal/slot.js'
import { html } from 'lit'
import { ifDefined } from 'lit/directives/if-defined.js'
import { live } from 'lit/directives/live.js'
import { property, query, state } from 'lit/decorators.js'
import { watch } from '../../internal/watch.js'
import componentStyles from '../../styles/component.styles.js'
import formControlStyles from '../../styles/form-control.styles.js'
import styles from './toggle.styles.js'
import type { CSSResultGroup } from 'lit'
import type { TerraFormControl } from '../../internal/terra-element.js'
import TerraElement from '../../internal/terra-element.js'

/**
 * @summary Switches allow the user to toggle an option on or off.
 * @documentation https://terra-ui.netlify.app/components/toggle
 * @status stable
 * @since 1.0
 *
 * @slot - The switch's label.
 * @slot help-text - Text that describes how to use the switch. Alternatively, you can use the `help-text` attribute.
 *
 * @event terra-blur - Emitted when the control loses focus.
 * @event terra-change - Emitted when the control's checked state changes.
 * @event terra-input - Emitted when the control receives input.
 * @event terra-focus - Emitted when the control gains focus.
 * @event terra-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.
 *
 * @csspart base - The component's base wrapper.
 * @csspart control - The control that houses the switch's thumb.
 * @csspart thumb - The switch's thumb.
 * @csspart label - The switch's label.
 * @csspart form-control-help-text - The help text's wrapper.
 *
 * @cssproperty --width - The width of the switch.
 * @cssproperty --height - The height of the switch.
 * @cssproperty --thumb-size - The size of the thumb.
 */
export default class TerraToggle extends TerraElement implements TerraFormControl {
    static styles: CSSResultGroup = [componentStyles, formControlStyles, styles]

    private readonly formControlController = new FormControlController(this, {
        value: (control: TerraToggle) =>
            control.checked ? control.value || 'on' : undefined,
        defaultValue: (control: TerraToggle) => control.defaultChecked,
        setValue: (control: TerraToggle, checked: boolean) =>
            (control.checked = checked),
    })
    private readonly hasSlotController = new HasSlotController(this, 'help-text')

    @query('input[type="checkbox"]') input: HTMLInputElement

    @state() private hasFocus = false
    @property() title = '' // make reactive to pass through

    /** The name of the switch, submitted as a name/value pair with form data. */
    @property() name = ''

    /** The current value of the switch, submitted as a name/value pair with form data. */
    @property() value: string

    /** The switch's size. */
    @property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium'

    /** Disables the switch. */
    @property({ type: Boolean, reflect: true }) disabled = false

    /** Draws the switch in a checked state. */
    @property({ type: Boolean, reflect: true }) checked = false

    /** The default value of the form control. Primarily used for resetting the form control. */
    @defaultValue('checked') defaultChecked = false

    /**
     * By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you
     * to place the form control outside of a form and associate it with the form that has this `id`. The form must be in
     * the same document or shadow root for this to work.
     */
    @property({ reflect: true }) form = ''

    /** Makes the switch a required field. */
    @property({ type: Boolean, reflect: true }) required = false

    /** The switch's help text. If you need to display HTML, use the `help-text` slot instead. */
    @property({ attribute: 'help-text' }) helpText = ''

    /** Gets the validity state object */
    get validity() {
        return this.input.validity
    }

    /** Gets the validation message */
    get validationMessage() {
        return this.input.validationMessage
    }

    firstUpdated() {
        this.formControlController.updateValidity()
    }

    private handleBlur() {
        this.hasFocus = false
        this.emit('terra-blur')
    }

    private handleInput() {
        this.emit('terra-input')
    }

    private handleInvalid(event: Event) {
        this.formControlController.setValidity(false)
        this.formControlController.emitInvalidEvent(event)
    }

    private handleClick() {
        this.checked = !this.checked
        this.emit('terra-change')
    }

    private handleFocus() {
        this.hasFocus = true
        this.emit('terra-focus')
    }

    private handleKeyDown(event: KeyboardEvent) {
        if (event.key === 'ArrowLeft') {
            event.preventDefault()
            this.checked = false
            this.emit('terra-change')
            this.emit('terra-input')
        }

        if (event.key === 'ArrowRight') {
            event.preventDefault()
            this.checked = true
            this.emit('terra-change')
            this.emit('terra-input')
        }
    }

    @watch('checked', { waitUntilFirstUpdate: true })
    handleCheckedChange() {
        this.input.checked = this.checked // force a sync update
        this.formControlController.updateValidity()
    }

    @watch('disabled', { waitUntilFirstUpdate: true })
    handleDisabledChange() {
        // Disabled form controls are always valid
        this.formControlController.setValidity(true)
    }

    /** Simulates a click on the switch. */
    click() {
        this.input.click()
    }

    /** Sets focus on the switch. */
    focus(options?: FocusOptions) {
        this.input.focus(options)
    }

    /** Removes focus from the switch. */
    blur() {
        this.input.blur()
    }

    /** Checks for validity but does not show a validation message. Returns `true` when valid and `false` when invalid. */
    checkValidity() {
        return this.input.checkValidity()
    }

    /** Gets the associated form, if one exists. */
    getForm(): HTMLFormElement | null {
        return this.formControlController.getForm()
    }

    /** Checks for validity and shows the browser's validation message if the control is invalid. */
    reportValidity() {
        return this.input.reportValidity()
    }

    /** Sets a custom validation message. Pass an empty string to restore validity. */
    setCustomValidity(message: string) {
        this.input.setCustomValidity(message)
        this.formControlController.updateValidity()
    }

    render() {
        const hasHelpTextSlot = this.hasSlotController.test('help-text')
        const hasHelpText = this.helpText ? true : !!hasHelpTextSlot

        return html`
            <div
                class=${classMap({
                    'form-control': true,
                    'form-control--small': this.size === 'small',
                    'form-control--medium': this.size === 'medium',
                    'form-control--large': this.size === 'large',
                    'form-control--has-help-text': hasHelpText,
                })}
            >
                <label
                    part="base"
                    class=${classMap({
                        toggle: true,
                        'toggle--checked': this.checked,
                        'toggle--disabled': this.disabled,
                        'toggle--focused': this.hasFocus,
                        'toggle--small': this.size === 'small',
                        'toggle--medium': this.size === 'medium',
                        'toggle--large': this.size === 'large',
                    })}
                >
                    <input
                        class="toggle__input"
                        type="checkbox"
                        title=${
                            this
                                .title /* An empty title prevents browser validation tooltips from appearing on hover */
                        }
                        name=${this.name}
                        value=${ifDefined(this.value)}
                        .checked=${live(this.checked)}
                        .disabled=${this.disabled}
                        .required=${this.required}
                        role="switch"
                        aria-checked=${this.checked ? 'true' : 'false'}
                        aria-describedby="help-text"
                        @click=${this.handleClick}
                        @input=${this.handleInput}
                        @invalid=${this.handleInvalid}
                        @blur=${this.handleBlur}
                        @focus=${this.handleFocus}
                        @keydown=${this.handleKeyDown}
                    />

                    <span part="control" class="toggle__control">
                        <span part="thumb" class="toggle__thumb"></span>
                    </span>

                    <div part="label" class="toggle__label">
                        <slot></slot>
                    </div>
                </label>

                <div
                    aria-hidden=${hasHelpText ? 'false' : 'true'}
                    class="form-control__help-text"
                    id="help-text"
                    part="form-control-help-text"
                >
                    <slot name="help-text">${this.helpText}</slot>
                </div>
            </div>
        `
    }
}

declare global {
    interface HTMLElementTagNameMap {
        'terra-toggle': TerraToggle
    }
}
