/**
 * Copyright Aquera Inc 2025
 *
 * This source code is licensed under the BSD-3-Clause license found in the
 * LICENSE file in the root directory of this source tree.
 */

import { styles } from './nile-slider.css';
import NileElement from '../internal/nile-element';
import { classMap } from 'lit/directives/class-map.js';
import { html, CSSResultArray, TemplateResult } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { TooltipPosition } from './types/nile-slider.types';
import { NileSliderEvents } from './types/nile-slider.enums';
import { rangeSlider, singleSlider, lableContaier } from './nile-slider.template';
import { 
  handleSingleSlider, 
  handleRangeOne, 
  handleRangeTwo, 
  getHtmlElements, 
  removeMoveListeners,
  valueToPercent,
  handleTwoThumbClick,
  addMoveListeners
} from './utils/nile-slider.utils'

/** 
 * Nile slider component.
 *
 * @tag nile-slider
 */
@customElement('nile-slider')
export class NileSlider extends NileElement {
  public static get styles(): CSSResultArray {
    return [styles];
  }

  @property({ type: Number }) minValue: number = 0;
  @property({ type: Number }) value: number = this.minValue;
  @property({ type: Number }) maxValue: number = 100;
  @property({ type: Number }) rangeOneValue: number = this.minValue;
  @property({ type: Number }) rangeTwoValue: number = this.maxValue;
  @property({ type: Boolean}) showLabel: boolean = false;
  @property({ type: String }) labelStart: string = "";
  @property({ type: String }) labelEnd: string = "";
  @property({ type: Boolean}) rangeSlider: boolean = false;
  @property({ type: String }) labelPosition: string = "top";
  @property({ type: String }) tooltipPosition: TooltipPosition = "top";

  public buttonOne!: HTMLElement;
  public buttonTwo!: HTMLElement;
  public range!: HTMLElement;
  public completed!: HTMLElement;
  public activeThumb: 'one' | 'two' | null = null;

  connectedCallback(): void {
    super.connectedCallback();
    this.emit(NileSliderEvents.NILE_INIT); 

    if(this.rangeSlider) {
      this.checkRangeValidity();
    }
  }

  firstUpdated(changedProps: Map<string, any>): void {
    getHtmlElements(this);

    if (!this.rangeSlider && changedProps.has('value')) {
      this.value = this.checkValueValidity(this.value, this.minValue, this.maxValue);
      const percent = valueToPercent(this.value, this);
      
      this.buttonOne.style.left = `${percent}%`;
      this.completed.style.width = `${percent}%`;
    } else if (this.rangeSlider) {
      
      const percentOne = valueToPercent(this.rangeOneValue, this);
      const percentTwo = valueToPercent(this.rangeTwoValue, this);
      
      if (this.buttonOne) this.buttonOne.style.left = `${percentOne}%`;
      if (this.buttonTwo) this.buttonTwo.style.left = `${percentTwo}%`;
      
      const left = Math.min(percentOne, percentTwo);
      const width = Math.abs(percentTwo - percentOne);
      
      this.completed.style.left = `${left}%`;
      this.completed.style.width = `${width}%`;
    }
  }

  checkValueValidity(value: number, min: number, max: number): number {
    if(max - min <= 1) { 
      return Math.max(min, Math.min(value, max));
    }
    return Math.floor(Math.max(min, Math.min(value, max)));
  }

  checkRangeValidity(): void {
    if(this.rangeTwoValue > this.maxValue) {
      this.rangeTwoValue = this.maxValue;
    } else if(this.rangeTwoValue < this.rangeOneValue) {
      this.rangeTwoValue = this.rangeOneValue;
    }

    if(this.rangeOneValue < this.minValue) {
      this.rangeOneValue = this.minValue;
    } else if(this.rangeOneValue > this.rangeTwoValue) {
      this.rangeOneValue = this.rangeTwoValue;
    }
  }  

  public onMouseMove = (e: MouseEvent): void => {
    const rect = this.range.getBoundingClientRect();

    if (!this.rangeSlider) {
      handleSingleSlider(e, rect, this);
    } else {
      if (this.activeThumb === 'one') {
        handleRangeOne(e, rect, this);
      } else if (this.activeThumb === 'two') {
        handleRangeTwo(e, rect, this);
      }

      const start = valueToPercent(this.rangeOneValue, this);
      const end = valueToPercent(this.rangeTwoValue, this);
      const left = Math.min(start, end);
      const width = Math.abs(end - start);

      if(this.rangeTwoValue >= this.rangeOneValue) {
        this.completed.style.left = `${left}%`;
        this.completed.style.width = `${width}%`;
      }
    }
  };

  public onMouseUp = (): void => {
    if(!this.rangeSlider) this.emit(NileSliderEvents.NILE_CHANGE_END, { value: this.value });
    else {
      this.emit(NileSliderEvents.NILE_BUTTON_FIRST_CHANGE_END, { value: this.rangeOneValue });
      this.emit(NileSliderEvents.NILE_BUTTON_LAST_CHANGE_END, { value: this.rangeTwoValue });
    }
    this.activeThumb = null;
    removeMoveListeners(this);
  };

  public onMouseDown = (e: MouseEvent): void => {
    const rect = this.range.getBoundingClientRect();
    const clickX = e.clientX - rect.left;
    const percent = (clickX / rect.width) * 100;
    const value = this.minValue + (percent / 100) * (this.maxValue - this.minValue);
  
    if (!this.rangeSlider) {
      handleSingleSlider(e, rect, this);
      this.activeThumb = 'one';
      addMoveListeners(this);
    } else {
      handleTwoThumbClick(value, this);
      const distToOne = Math.abs(value - this.rangeOneValue);
      const distToTwo = Math.abs(value - this.rangeTwoValue);
      this.activeThumb = distToOne <= distToTwo ? 'one' : 'two';
      addMoveListeners(this);
    }
  };

  public render(): TemplateResult {
    const hasLabels = this.showLabel;
    const ariaLabelledby = hasLabels ? 'label-start label-end' : undefined;
  
    return html`
      <div
        part="base"
        class=${classMap({
          'container': true,
          'align-item-center': !hasLabels
        })}
      >
        <slot class="span" name="prefix"></slot>
  
        <div
          part="range-container"
          class=${classMap({
            'range-container': true,
            'column-reverse': this.labelPosition === 'bottom'
          })}
        >
          ${hasLabels ? lableContaier(this) : html``}
  
          <div
            class="range"
            part="range"
            role="group"
            aria-labelledby=${ariaLabelledby}
          >
            <span class="range-completed" part="range-completed"></span>
            ${this.rangeSlider ? rangeSlider(this) : singleSlider(this)}
          </div>
        </div>
  
        <slot class="span" name="suffix"></slot>
      </div>
    `;
  }  

  disconnectedCallback(): void {
    super.disconnectedCallback();
    this.emit(NileSliderEvents.NILE_DESTROY);
  }
}

export default NileSlider;

declare global {
  interface HTMLElementTagNameMap {
    'nile-slider': NileSlider;
  }
}
