import { property, state } from 'lit/decorators.js'
import { html } from 'lit'
import componentStyles from '../../styles/component.styles.js'
import TerraElement from '../../internal/terra-element.js'
import styles from './data-rods.styles.js'
import type { CSSResultGroup } from 'lit'
import TerraVariableCombobox from '../variable-combobox/variable-combobox.component.js'
import TerraSpatialPicker from '../spatial-picker/spatial-picker.component.js'
import TerraDateRangeSlider from '../date-range-slider/date-range-slider.component.js'
import type { Variable } from '../browse-variables/browse-variables.types.js'
import { getUTCDate } from '../../utilities/date.js'
import type { TerraDateRangeChangeEvent } from '../../events/terra-date-range-change.js'
import type { TerraComboboxChangeEvent } from '../../events/terra-combobox-change.js'
import TerraTimeSeries from '../time-series/time-series.component.js'
import type { TerraMapChangeEvent } from '../../events/terra-map-change.js'
import { MapEventType } from '../map/type.js'
import { getFetchVariableTask } from '../../metadata-catalog/tasks.js'
import { getVariableEntryId } from '../../metadata-catalog/utilities.js'

/**
 * @summary A component for visualizing Hydrology Data Rods time series using the GES DISC Giovanni API
 * @documentation https://terra-ui.netlify.app/components/data-rods
 * @status stable
 * @since 1.0
 *
 * @event terra-date-range-change - Emitted whenever the date range of the date slider is updated
 */

type LastChanged = 'variable' | 'location' | undefined

export default class TerraDataRods extends TerraElement {
    static styles: CSSResultGroup = [componentStyles, styles]
    static dependencies = {
        'terra-time-series': TerraTimeSeries,
        'terra-date-range-slider': TerraDateRangeSlider,
        'terra-spatial-picker': TerraSpatialPicker,
        'terra-variable-combobox': TerraVariableCombobox,
    }

    /**
     * a variable entry ID (ex: GPM_3IMERGHH_06_precipitationCal)
     */
    @property({ attribute: 'variable-entry-id', reflect: true })
    variableEntryId?: string

    /**
     * a collection entry id (ex: GPM_3IMERGHH_06)
     * only required if you don't include a variableEntryId
     */
    @property({ reflect: true })
    collection?: string

    /**
     * a variable short name to plot (ex: precipitationCal)
     * only required if you don't include a variableEntryId
     */
    @property({ reflect: true })
    variable?: string // TODO: support multiple variables (non-MVP feature)

    /**
     * The start date for the time series plot. (ex: 2021-01-01)
     */
    @property({
        attribute: 'start-date',
        reflect: true,
    })
    startDate?: string

    /**
     * The end date for the time series plot. (ex: 2021-01-01)
     */
    @property({
        attribute: 'end-date',
        reflect: true,
    })
    endDate?: string

    /**
     * The point location in "lat,lon" format.
     */
    @property({
        reflect: true,
    })
    location?: string

    /**
     * The token to be used for authentication with remote servers.
     * The component provides the header "Authorization: Bearer" (the request header and authentication scheme).
     * The property's value will be inserted after "Bearer" (the authentication scheme).
     */
    @property({ attribute: 'bearer-token', reflect: false })
    bearerToken: string

    @state() catalogVariable: Variable

    /**
     * anytime user selects invalid dates
     */
    @state() private dateErrorMessage?: string

    @state() private lastChanged?: LastChanged

    /**
     * add a warning state
     */
    @state() private spatialWarningMessage?: string

    _fetchVariableTask = getFetchVariableTask(this)

    get variableBoundingBox() {
        const variable = this.catalogVariable
        if (!variable) return undefined

        const {
            dataProductWest,
            dataProductSouth,
            dataProductEast,
            dataProductNorth,
        } = variable

        if (
            dataProductWest == null ||
            dataProductSouth == null ||
            dataProductEast == null ||
            dataProductNorth == null
        ) {
            return undefined
        }

        return `${dataProductWest}, ${dataProductSouth}, ${dataProductEast}, ${dataProductNorth}`
    }

    private compatibilityWarning() {
        this.spatialWarningMessage = undefined

        if (!this.catalogVariable || !this.location) return

        const {
            dataProductWest,
            dataProductSouth,
            dataProductEast,
            dataProductNorth,
        } = this.catalogVariable

        if (
            dataProductWest == null ||
            dataProductSouth == null ||
            dataProductEast == null ||
            dataProductNorth == null
        ) {
            return
        }

        const [latStr, lonStr] = this.location.split(',')
        const lat = Number(latStr)
        const lon = Number(lonStr)

        const inside =
            lon >= dataProductWest &&
            lon <= dataProductEast &&
            lat >= dataProductSouth &&
            lat <= dataProductNorth

        if (!inside) {
            this.spatialWarningMessage =
                this.lastChanged === 'variable'
                    ? 'This variable has no data at the selected location.'
                    : 'The selected location is outside the coverage of this variable.'
        }
    }

    render() {
        const minDate = this.catalogVariable
            ? getUTCDate(this.catalogVariable.dataProductBeginDateTime)
            : undefined
        const maxDate = this.catalogVariable
            ? getUTCDate(this.catalogVariable.dataProductEndDateTime)
            : undefined

        return html`
            ${this.spatialWarningMessage && this.lastChanged === 'location'
                ? html`<div class="warning">⚠️ ${this.spatialWarningMessage}</div>`
                : null}
            <terra-variable-combobox
                exportparts="base:variable-combobox__base, combobox:variable-combobox__combobox, button:variable-combobox__button, listbox:variable-combobox__listbox"
                .value=${getVariableEntryId(this)}
                .bearerToken=${this.bearerToken ?? null}
                .useTags=${true}
                @terra-combobox-change="${this.#handleVariableChange}"
            ></terra-variable-combobox>

            ${this.spatialWarningMessage && this.lastChanged === 'variable'
                ? html`<div class="warning">⚠️ ${this.spatialWarningMessage}</div>`
                : null}
            <terra-spatial-picker
                initial-value=${this.location}
                .spatialConstraints=${this.variableBoundingBox}
                exportparts="map:spatial-picker__map, leaflet-bbox:spatial-picker__leaflet-bbox, leaflet-point:spatial-picker__leaflet-point"
                label="Select Point"
                @terra-map-change=${this.#handleMapChange}
            ></terra-spatial-picker>

            <terra-time-series
                variable-entry-id=${getVariableEntryId(this)}
                start-date=${this.startDate}
                end-date=${this.endDate}
                .location=${this.location ?? undefined}
                bearer-token=${this.bearerToken}
                show-citation=${true}
                @terra-date-range-change=${this.#handleTimeSeriesDateRangeChange}
            >
                <li slot="help-links">
                    <a
                        href="https://disc.gsfc.nasa.gov/information/tools?title=Hydrology%20Time%20Series"
                        >User Guide</a
                    >
                </li>
            </terra-time-series>

            <terra-date-range-slider
                exportparts="slider:date-range-slider__slider"
                min-date=${minDate}
                max-date=${maxDate}
                start-date=${this.startDate}
                end-date=${this.endDate}
                @terra-date-range-change="${this.#handleDateRangeSliderChangeEvent}"
                @terra-date-selection-invalid="${this.#handleInvalidDateSelection}"
            ></terra-date-range-slider>
            ${this.dateErrorMessage
                ? html`<div class="date-error" style="color: red;">
                      ${this.dateErrorMessage}
                  </div>`
                : null}
        `
    }

    /**
     * anytime the date range slider changes, update the start and end date
     */
    #handleDateRangeSliderChangeEvent(event: TerraDateRangeChangeEvent) {
        this.startDate = event.detail.startDate
        this.endDate = event.detail.endDate
    }

    /**
     * anytime user selects invalid dates outside variable date range
     */
    #handleInvalidDateSelection(event: CustomEvent) {
        this.dateErrorMessage = event.detail.message
    }

    #handleVariableChange(event: TerraComboboxChangeEvent) {
        const newEntryId = event.detail.entryId

        if (!this.variableEntryId || newEntryId === this.variableEntryId) {
            return
        }

        this.variableEntryId = newEntryId
        this.location = undefined
        this.lastChanged = 'variable'

        this.compatibilityWarning()
    }

    #handleMapChange(event: TerraMapChangeEvent) {
        if (event.detail.type === MapEventType.POINT) {
            // TODO: we may want to pick a `toFixed()` length in the spatial picker and stick with it.
            this.location = `${event.detail.latLng.lat.toFixed(4)},${event.detail.latLng.lng.toFixed(4)}`
            this.lastChanged = 'location'

            this.compatibilityWarning()
        }
    }

    #handleTimeSeriesDateRangeChange(event: CustomEvent) {
        this.startDate = event.detail.startDate
        this.endDate = event.detail.endDate
    }
}
