import { Host, h } from '@stencil/core';
import { dateRange, daysInMonth, getBeginningOfMonth, getStartEndDateForInterval, validateTimezone, } from './helpers';
import { differenceInHours, differenceInMinutes, differenceInMonths, format, } from 'date-fns';
/**
 * @part playhead - The timeline's playhead
 * @part time-region-container - The container for time regions. Use this part to set a maximum height and enable vertical scrolling.
 */
export class RuxTimeline {
  constructor() {
    this.slots = 'empty';
    this.playheadPositionInPixels = 200;
    this.columnWidth = 120;
    this.playheadHeight = 0;
    this.showPlayhead = true;
    this.start = '';
    this.end = '';
    this.zoom = 1;
    this.playhead = undefined;
    this.hasPlayedIndicator = false;
    this.interval = 'hour';
    this.showGrid = false;
    this.timezone = 'UTC';
    this.rulerPosition = 'both';
    this.hideJDay = false;
    this.showSecondaryRuler = false;
  }
  syncPlayhead() {
    if (this.playhead) {
      const time = this._calculatePlayheadFromTime(this.playhead);
      if (time) {
        this.playheadPositionInPixels = time;
      }
      this._updateRegions();
    }
  }
  handleZoomChange() {
    this._setZoom();
    this.syncPlayhead();
    this._updateRegions();
  }
  handleChange() {
    this.syncPlayhead();
    this._updateRegions();
    this.syncPlayhead();
  }
  handleRulerPosChange() {
    this._updateRegions();
  }
  handleGridChange() {
    //because we're using the :host selector, we need to set the attribute on the host element. otherwise,
    //`show-grid="false"` will register as show-grid still being there, thus applying the grid styles.
    if (!this.showGrid) {
      this.el.removeAttribute('show-grid');
    }
  }
  connectedCallback() {
    this._handleSlotChange = this._handleSlotChange.bind(this);
    this._handleMouse = this._handleMouse.bind(this);
    this.syncPlayhead = this.syncPlayhead.bind(this);
    this._updateRegions = this._updateRegions.bind(this);
    if (!this.showGrid) {
      this.el.removeAttribute('show-grid');
    }
  }
  componentWillLoad() {
    this._setZoom();
    this.syncPlayhead();
  }
  get width() {
    let unitOfTime = 60;
    if (this.interval === 'day') {
      unitOfTime = 24;
    }
    // widths will be the same as for days
    if (this.interval === 'week') {
      unitOfTime = 24;
    }
    if (this.interval === 'month') {
      unitOfTime = 24;
    }
    return this.columnWidth / unitOfTime;
  }
  get columns() {
    let unitOfTime = 60;
    if (this.interval === 'day') {
      unitOfTime = 24;
    }
    // columns will be the same as for days
    if (this.interval === 'week') {
      unitOfTime = 24;
    }
    if (this.interval === 'month') {
      unitOfTime = 24;
    }
    return this.totalColumns * unitOfTime;
  }
  get totalColumns() {
    if (!this.start && !this.end) {
      return 0;
    }
    const range = dateRange(this.start, this.end, this.interval);
    return range.length;
  }
  /**
   * The relationship between 1px and the datetime it represents.
   * We need a way to map individual pixels to a particular time, so that
   * if the playhead or an event is positioned at 120px visually, we can determine
   * what exact time that represents.
   */
  get pxToTimeRatio() {
    if (this.interval === 'hour') {
      return this.columnWidth / 60; // for hours.
    }
    if (this.interval === 'minute') {
      return this.columnWidth / 1; // for minutes.
    }
    if (this.interval === 'day') {
      return this.columnWidth / 24; //tbd
    }
    // same as for days
    if (this.interval === 'week') {
      return this.columnWidth / 24; //seems ok
    }
    if (this.interval === 'month') {
      return this.columnWidth / 24; //seems ok
    }
    return 2;
  }
  get formattedCurrentTime() {
    if (this.playhead) {
      return format(new Date(this.playhead), 'MM/dd/Y HH:mm:ss');
    }
    else {
      return null;
    }
  }
  initializeTracks() {
    const tracks = [...this.el.children].filter((el) => el.tagName.toLowerCase() === 'rux-track');
    // handle start/end timeline for boundaries for month/week
    let useStartEndDates = getStartEndDateForInterval(this.start, this.end, this.interval);
    tracks.map((el) => {
      el.width = this.width;
      el.columns = this.columns;
      if (this.hasPlayedIndicator) {
        el.playhead = this.playhead;
      }
      else {
        el.playhead = null;
      }
      el.interval = this.interval;
      el.start = useStartEndDates.timelineStart.toISOString();
      el.end = useStartEndDates.timelineEnd.toISOString();
    });
  }
  /**
   * Give it a position (in pixels) and get the time that represents
   */
  // private _calculateTimeFromPlayhead(position: any) {
  //     // this.playheadPositionInPixels = position - 2
  //     const time = position - 200
  //     const min = time / this.pxToTimeRatio
  //     let newTime = new Date()
  //     if (this.interval === 'hour') {
  //         newTime = addMinutes(new Date(this.start), min)
  //     }
  //     if (this.interval === 'day') {
  //         /**
  //          * If the interval is day, we need to round the start/end times to the start of the day
  //          * Ie you passing 01/01/2020 06:00 as the start, the timeline needs to start at 00
  //          */
  //         const start = startOfDay(new Date(this.start))
  //         newTime = addHours(start, min)
  //     }
  //     return newTime
  // }
  /**
   * Give it a time, get where it should be positioned visually (in pixels)
   */
  _calculatePlayheadFromTime(time) {
    if (!time)
      return;
    // handle start/end timeline for boundaries for month/week
    let useStartEndDates = getStartEndDateForInterval(this.start, this.end, this.interval);
    if (new Date(time) < useStartEndDates.timelineStart ||
      new Date(time) > useStartEndDates.timelineEnd) {
      console.warn(`Playhead date must be between ${useStartEndDates.timelineStart.toISOString()} - ${useStartEndDates.timelineEnd.toISOString()}`);
    }
    const timeAsDate = new Date(time);
    let newTime = Math.abs(differenceInMinutes(useStartEndDates.timelineStart, timeAsDate));
    if (this.interval === 'day') {
      newTime = Math.abs(differenceInHours(useStartEndDates.timelineStart, timeAsDate));
    }
    if (this.interval === 'week') {
      newTime =
        Math.abs(differenceInHours(useStartEndDates.timelineStart, timeAsDate)) / 7;
    }
    if (this.interval === 'month') {
      // For a month, the timeline starts on the first of the month
      // This code allows us to take into account the varying length of each month.
      const monthStart = getBeginningOfMonth(useStartEndDates.timelineStart, 0);
      // number of months from start + 1/nth of the current month
      const numMonths = Math.abs(differenceInMonths(monthStart, timeAsDate));
      const extraDays = timeAsDate.getDate() - 1;
      const daysInCurrentMonth = daysInMonth(timeAsDate);
      const extraHours = timeAsDate.getHours();
      newTime =
        (numMonths +
          (extraDays + extraHours / 24) / daysInCurrentMonth) *
          this.columnWidth +
          200;
      return newTime;
    }
    const result = newTime * this.pxToTimeRatio + 200;
    return result;
  }
  /**
   * For debugging
   */
  _handleMouse(e) {
    var _a;
    const rect = this.el.getBoundingClientRect();
    const scrollOffset = this.timelineContainer
      ? (_a = this.timelineContainer) === null || _a === void 0 ? void 0 : _a.scrollLeft
      : 0;
    const position = e.clientX - rect.left + scrollOffset;
    if (position >= 200) {
      // this.playheadPositionInPixels = position - scrollOffset
      // const time = this._calculateTimeFromPlayhead(position)
      // this.playhead = time.toISOString()
    }
  }
  _handleSlotChange() {
    this._updateRegions();
  }
  /**
   * Syncs the Timeline's current interval and pxToTimeRatio to it's children and grandchildren
   * We're taking a props down, events up approach to data flow here.
   */
  _updateRegions() {
    var _a, _b;
    const slot = (_a = this.slotContainer) === null || _a === void 0 ? void 0 : _a.querySelectorAll('slot')[0];
    if (slot) {
      const tracks = [
        ...slot === null || slot === void 0 ? void 0 : slot.assignedElements({ flatten: true }).filter((node) => node.tagName.toLowerCase() === 'rux-track'),
      ];
      let useStartEndDates = getStartEndDateForInterval(this.start, this.end, this.interval);
      tracks.map((el) => {
        el.width = this.width;
        el.columns = this.columns;
        if (this.hasPlayedIndicator) {
          el.playhead = this.playhead;
        }
        else {
          el.playhead = null;
        }
        el.interval = this.interval;
        el.start = useStartEndDates.timelineStart.toISOString();
        el.end = useStartEndDates.timelineEnd.toISOString();
        el.timezone = this.timezone;
      });
    }
    const rulerSlot = (_b = this.rulerContainer) === null || _b === void 0 ? void 0 : _b.querySelector('slot');
    const rulerTrack = rulerSlot === null || rulerSlot === void 0 ? void 0 : rulerSlot.assignedElements({ flatten: true }).find((el) => el.tagName.toLowerCase() === 'rux-track');
    if (rulerTrack) {
      let useStartEndDates = getStartEndDateForInterval(this.start, this.end, this.interval);
      rulerTrack.width = this.width;
      rulerTrack.columns = this.columns;
      rulerTrack.interval = this.interval;
      rulerTrack.start = useStartEndDates.timelineStart.toISOString();
      rulerTrack.end = useStartEndDates.timelineEnd.toISOString();
      const rulerEl = [...rulerTrack.children].find((el) => el.tagName.toLowerCase() === 'rux-ruler');
      if (rulerEl) {
        validateTimezone(this.timezone).then(() => {
          rulerEl.timezone = this.timezone;
        });
        // pass any relative props to rux-ruler element
        rulerEl.start = useStartEndDates.timelineStart.toISOString();
        rulerEl.end = useStartEndDates.timelineEnd.toISOString();
        rulerEl.interval = this.interval;
        rulerEl.rulerPosition = this.rulerPosition;
        rulerEl.showSecondaryRuler = this.showSecondaryRuler;
        rulerEl.hideJDay = this.hideJDay;
      }
    }
  }
  _setZoom() {
    let unitOfTime = 60;
    if (this.interval === 'day') {
      unitOfTime = 120;
    }
    if (this.interval === 'month') {
      unitOfTime = 120;
    }
    if (this.interval === 'week') {
      unitOfTime = 120;
    }
    if (isNaN(this.zoom)) {
      this.zoom = 1;
    }
    // this change allows the developers to zoom more tightly if desired.
    if (this.zoom > 0) {
      this.columnWidth = Math.floor(this.zoom * unitOfTime);
    }
  }
  _handleScroll() {
    var _a, _b;
    // #TODO Maybe throttle this event w/ requestAnimationFrame?
    const scrollOffset = this.timelineContainer
      ? (_a = this.timelineContainer) === null || _a === void 0 ? void 0 : _a.scrollTop
      : 0;
    this.playheadHeight = scrollOffset;
    const leftOffset = this.timelineContainer
      ? (_b = this.timelineContainer) === null || _b === void 0 ? void 0 : _b.scrollLeft
      : 0;
    this.showPlayhead = leftOffset + 200 <= this.playheadPositionInPixels;
  }
  render() {
    return (h(Host, null, h("div", { class: {
        'rux-timeline': true,
        'ruler-position__top': this.rulerPosition === 'top',
        'ruler-position__bottom': this.rulerPosition === 'bottom',
        'ruler-position__both': this.rulerPosition === 'both',
      }, onMouseMove: (ev) => this._handleMouse(ev), onScroll: () => this._handleScroll(), ref: (el) => (this.timelineContainer = el), part: "time-region-container" }, this.playhead && (h("div", { class: {
        'rux-playhead': true,
        hidden: !this.showPlayhead,
      }, part: "playhead", style: {
        top: `${this.playheadHeight}px`,
        left: `${this.playheadPositionInPixels}px`,
      } })), 
    //* If we need a second ruler, create one here.
    this.rulerPosition === 'both' ? (h("rux-track", { slot: "ruler", interval: this.interval, timezone: this.timezone, start: this.start, end: this.end, width: this.width, columns: this.columns, playhead: this.playhead }, h("rux-ruler", { interval: this.interval, start: this.start, end: this.end, timezone: this.timezone, rulerPosition: this.rulerPosition, showSecondaryRuler: this.showSecondaryRuler, isSecondary: true, hideJDay: this.hideJDay }))) : null, h("div", { class: "events", ref: (el) => (this.slotContainer = el) }, h("slot", { onSlotchange: this._handleSlotChange })), h("div", { class: "ruler", ref: (el) => (this.rulerContainer = el) }, h("slot", { name: "ruler" })))));
  }
  static get is() { return "rux-timeline"; }
  static get encapsulation() { return "shadow"; }
  static get originalStyleUrls() {
    return {
      "$": ["rux-timeline.scss"]
    };
  }
  static get styleUrls() {
    return {
      "$": ["rux-timeline.css"]
    };
  }
  static get properties() {
    return {
      "start": {
        "type": "string",
        "mutable": false,
        "complexType": {
          "original": "string",
          "resolved": "string",
          "references": {}
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "The timeline's start date. Must be an ISO string \"2021-02-02T05:00:00Z\""
        },
        "attribute": "start",
        "reflect": false,
        "defaultValue": "''"
      },
      "end": {
        "type": "string",
        "mutable": false,
        "complexType": {
          "original": "string",
          "resolved": "string",
          "references": {}
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "The timeline's end date. Must be an ISO string \"2021-02-02T05:00:00Z\""
        },
        "attribute": "end",
        "reflect": false,
        "defaultValue": "''"
      },
      "zoom": {
        "type": "number",
        "mutable": false,
        "complexType": {
          "original": "number",
          "resolved": "number",
          "references": {}
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "The timeline's zoom level."
        },
        "attribute": "zoom",
        "reflect": true,
        "defaultValue": "1"
      },
      "playhead": {
        "type": "string",
        "mutable": true,
        "complexType": {
          "original": "string",
          "resolved": "string | undefined",
          "references": {}
        },
        "required": false,
        "optional": true,
        "docs": {
          "tags": [],
          "text": "The timeline's playhead date time. Must be an ISO string \"2021-02-02T05:00:00Z\""
        },
        "attribute": "playhead",
        "reflect": true
      },
      "hasPlayedIndicator": {
        "type": "boolean",
        "mutable": false,
        "complexType": {
          "original": "boolean",
          "resolved": "boolean",
          "references": {}
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "Visually marks past time as played in each track"
        },
        "attribute": "has-played-indicator",
        "reflect": true,
        "defaultValue": "false"
      },
      "interval": {
        "type": "string",
        "mutable": false,
        "complexType": {
          "original": "'month' | 'week' | 'hour' | 'day' | 'minute'",
          "resolved": "\"day\" | \"hour\" | \"minute\" | \"month\" | \"week\"",
          "references": {}
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "The timeline's date time interval"
        },
        "attribute": "interval",
        "reflect": false,
        "defaultValue": "'hour'"
      },
      "showGrid": {
        "type": "boolean",
        "mutable": false,
        "complexType": {
          "original": "boolean",
          "resolved": "boolean",
          "references": {}
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "Controls the display of grid lines"
        },
        "attribute": "show-grid",
        "reflect": false,
        "defaultValue": "false"
      },
      "timezone": {
        "type": "string",
        "mutable": false,
        "complexType": {
          "original": "string",
          "resolved": "string",
          "references": {}
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "Controls the timezone that the timeline is localized to. Must be an IANA time zone name (\"America/New_York\") or an offset string."
        },
        "attribute": "timezone",
        "reflect": false,
        "defaultValue": "'UTC'"
      },
      "rulerPosition": {
        "type": "string",
        "mutable": false,
        "complexType": {
          "original": "| 'top'\n        | 'bottom'\n        | 'both'",
          "resolved": "\"both\" | \"bottom\" | \"top\"",
          "references": {}
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "Controls the position of the ruler. Either top, bottom or both."
        },
        "attribute": "ruler-position",
        "reflect": true,
        "defaultValue": "'both'"
      },
      "hideJDay": {
        "type": "boolean",
        "mutable": false,
        "complexType": {
          "original": "boolean",
          "resolved": "boolean",
          "references": {}
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "Hides the J-Day display when show-secondary-ruler is true."
        },
        "attribute": "hide-j-day",
        "reflect": false,
        "defaultValue": "false"
      },
      "showSecondaryRuler": {
        "type": "boolean",
        "mutable": false,
        "complexType": {
          "original": "boolean",
          "resolved": "boolean",
          "references": {}
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "Controls whether or not the attached rux-ruler displays the secondary date portion."
        },
        "attribute": "show-secondary-ruler",
        "reflect": false,
        "defaultValue": "false"
      }
    };
  }
  static get states() {
    return {
      "playheadPositionInPixels": {},
      "columnWidth": {},
      "playheadHeight": {},
      "showPlayhead": {}
    };
  }
  static get elementRef() { return "el"; }
  static get watchers() {
    return [{
        "propName": "hasPlayedIndicator",
        "methodName": "syncPlayhead"
      }, {
        "propName": "playhead",
        "methodName": "syncPlayhead"
      }, {
        "propName": "zoom",
        "methodName": "handleZoomChange"
      }, {
        "propName": "start",
        "methodName": "handleChange"
      }, {
        "propName": "end",
        "methodName": "handleChange"
      }, {
        "propName": "interval",
        "methodName": "handleChange"
      }, {
        "propName": "timezone",
        "methodName": "handleChange"
      }, {
        "propName": "rulerPosition",
        "methodName": "handleRulerPosChange"
      }, {
        "propName": "showSecondaryRuler",
        "methodName": "handleRulerPosChange"
      }, {
        "propName": "hideJDay",
        "methodName": "handleRulerPosChange"
      }, {
        "propName": "showGrid",
        "methodName": "handleGridChange"
      }];
  }
}
