import {
    Component,
    EventEmitter,
    forwardRef,
    Input,
    OnDestroy,
    OnInit,
    Output,
    ViewEncapsulation,
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Destroyable } from '@shared/destroyable';
import { IUrlTimeRange } from '@typings/common/url';
import { countMinutesInUrlTime, minutesToTimeString } from '@utils/time';

type SliderRange = [number, number];
type TimeRange = {
    from: number; // total minutes
    to: number; // total minutes
};

@Component({
    selector: 'time-selector',
    templateUrl: './time-selector.component.html',
    styleUrls: ['./time-selector.component.scss'],
    encapsulation: ViewEncapsulation.None,
    providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: forwardRef(() => TimeSelectorComponent), // replace name as appropriate
            multi: true,
        },
    ],
})
export class TimeSelectorComponent
    extends Destroyable
    implements OnInit, OnDestroy, ControlValueAccessor
{
    @Input() step = 60;
    @Input() label: string;
    @Input() fullView: boolean;
    @Input() disabled = false;
    @Output() slideComplete = new EventEmitter();

    MINUTES_PER_DAY = 1440;
    DEFAULT_TIME_RANGE: TimeRange = { from: 0, to: this.MINUTES_PER_DAY };

    min = this.DEFAULT_TIME_RANGE.from;
    max = this.DEFAULT_TIME_RANGE.to;

    delta: number;
    change: 'from' | 'to' | 'none';
    value: SliderRange = [
        this.DEFAULT_TIME_RANGE.from,
        this.DEFAULT_TIME_RANGE.to,
    ];
    private previous: SliderRange = [
        this.DEFAULT_TIME_RANGE.from,
        this.DEFAULT_TIME_RANGE.to,
    ];

    onTouched: () => void;
    onChange: (timeRange: IUrlTimeRange) => void;

    ngOnInit(): void {
        this.min = this.min || 0;
        if (!this.max || this.max > this.MINUTES_PER_DAY) {
            this.max = this.MINUTES_PER_DAY;
        }
    }

    onModelChange(range: SliderRange) {
        this.detectChange(range);
        this.previous = [...range];
        const [from, to] = (this.value = this.normalize(range));

        this.onChange(
            this.timeRangeToUrlTimeRange({
                from,
                to,
            }),
        );
    }

    writeValue(urlTimeRange: IUrlTimeRange) {
        const from =
            countMinutesInUrlTime(urlTimeRange?.timeFrom) ||
            this.DEFAULT_TIME_RANGE.from;

        const to =
            countMinutesInUrlTime(urlTimeRange?.timeTo) ||
            this.DEFAULT_TIME_RANGE.to;

        this.value = this.normalize([from, to]);
    }

    registerOnChange(fn: (value: IUrlTimeRange) => void) {
        this.onChange = fn;
    }

    registerOnTouched(fn: () => void) {
        this.onTouched = fn;
    }

    get minTime() {
        const [from] = this.value;
        return this.toTime(from);
    }

    get maxTime() {
        const [, to] = this.value;
        return this.toTime(to);
    }

    toTime(minutes: number): string {
        const h = Math.floor(minutes / 60);
        const m = minutes % 60;

        return `${('0' + h).slice(-2)}:${('0' + m).slice(-2)}`;
    }

    private normalize(range: SliderRange): SliderRange {
        let [from, to] = range;

        from = Math.min(Math.max(this.min, from), this.max);
        to = Math.min(Math.max(this.min, to), this.max);

        const close = Math.abs(to - from) <= this.step;
        if (!close) return [from, to];

        if (this.change === 'to') {
            to = from + this.step;
        } else if (this.change === 'from') {
            from = to - this.step;
        }

        return [from, to];
    }

    private detectChange(range: SliderRange) {
        const [from, to] = range;
        const [prevFrom, prevTo] = this.previous;
        const changedFrom = prevFrom !== from;
        const changedTo = prevTo !== to;

        if (changedTo) this.change = 'to';
        if (changedFrom) this.change = 'from';
    }

    private timeRangeToUrlTimeRange(timeRange: TimeRange): IUrlTimeRange {
        if (timeRange.from === 0 && timeRange.to === this.MINUTES_PER_DAY) {
            return {};
        }

        return {
            timeFrom: minutesToTimeString(timeRange.from),
            timeTo: minutesToTimeString(timeRange.to),
        };
    }
}
