<script setup>
import { ref, computed } from 'vue'
import MDIcon from '../MDIcon/MDIcon.vue'

const props = defineProps({
  /**
   * 選択肢
   * hideFlagがtrueの時optionで選択できない
   * @example p_Options: [
   *{label: "test", value: 'test1', hideFlag: false},
   *{label: "test", value: 'test2', hideFlag: false},
   *{label: "test", value: 'test3', hideFlag: false},
   *]
   */
  p_Options: { type: Array, default: () => [] },
  /**
   * v-modelの値
   * @example modelValue: ''
   */
  modelValue: { type: [String, Number, Object], default: '' | 0 },
  /**
   * タイトル
   * @example p_Title: 'メールアドレス'
   */
  p_Title: { type: String, default: '' },
  /**
   * 制御
   * @example disabled: true
   */
  disabled: { type: Boolean, default: false },
  /**
   * エラー
   * @example p_Error: true
   */
  p_Error: { type: Boolean, default: false },
  /**
   * 外側の要素名
   * @example p_ParentElement: #id
   */
  p_ParentElement: { type: String, default: '._card' },
  /**
   * スモールモード
   * @example p_SmallMode: true
   */
  p_SmallMode: { type: Boolean, default: false },
})
// optionの表示フラグ
const arrowFlg = ref(false)
// trueの時はbottom側、falseはtop側に表示となる
const optionVertical = ref(true)
// optionVerticalが変更されたか判断する際に使う比較用の保持値
const beforeVertical = ref(false)
// trueの時はbottom側、falseはtop側に表示となる
const optionBeside = ref(true)
// // optionを改行するかのフラグ
const wordBreak = ref(false)
// 直近でイベントを起こしたdropdownを保持する
const targetElement = ref()
// イベントを起こした要素が何番目であるかを保持する
let targetIndex = 0
// optionをhover時に背景色のflgの変更
const optionBg = ref(true)
// optionをhover時に背景色のflgの変更
const topMargin = ref()
// optionをhover時に背景色のflgの変更
const bottomMargin = ref()
// dropdown用のref
const c_dropdown = ref()
const option = ref()
const optionArea = ref()
const optionLabel = ref()
// v-modelの処理
const emit = defineEmits(['update:modelValue', 'click', 'onFocus', 'onBlur'])
const model = computed({
  get() {
    return props.modelValue
  },
  set(val) {
    emit('update:modelValue', val)
  },
})

const selectLabel = computed(() => {
  //選択されたvalueと同じvalueをfindで探して、そのlabelを取得する
  let getLabel = props.p_Options.find((el) => el.value === model.value)
  // 初期値valueが選択肢に含まれない場合は初期値と同じ値をlabelとして表示する
  getLabel = getLabel === undefined ? model.value : getLabel.label
  return getLabel
})

// スクロールした際にoptionが表示されていた場合、その要素を取得してoptionの表示位置を調整する処理
window.addEventListener('scroll', () => {
  if (arrowFlg.value && targetElement.value) {
    // 取得した要素のクライアントでの高さを取得する
    const Yh = targetElement.value.getBoundingClientRect()
    // 上下どちらの高さが大きいかを取得（下の空間のほうが大きい場合がtrue）
    optionVertical.value = Yh.top < window.innerHeight - Yh.bottom
    // 過去の保持値がoptionVerticalと異なった値であった場合、適切なmaxHeightを設定する
    if (beforeVertical.value !== optionVertical.value) {
      document.querySelectorAll('._select_box ._options_area')[targetIndex].style.maxHeight =
        (optionVertical.value ? bottomMargin.value * 0.95 : topMargin.value * 0.95) + 'px'
    }
  }
  // 保持値の更新
  beforeVertical.value = optionVertical.value
})

// 矢印アイコンをクリック時の処理
const clickArrow = (event) => {
  // エンターで展開させた場合、処理を終了させる
  // if (event.detail === 0) {
  //   return
  // }
  // if (event.target.closest('._options_area')) return
  emit('click')

  let leftMargin
  let rightMargin
  // イベントを起こした要素を取得
  targetElement.value = event.currentTarget.closest('._select_area')
  // 取得した要素のクライアントでの高さを取得する
  const Yh = targetElement.value.getBoundingClientRect()
  // イベントを起こした要素の横幅
  const elementHeight = Math.floor(40)
  // イベントを起こした要素の横幅
  const elementWidth = Math.floor((Yh.right - Yh.left) * 0.9)

  // 指定した親要素が存在した場合
  if (targetElement.value?.closest(props.p_ParentElement)) {
    const parentXY = targetElement.value.closest(props.p_ParentElement).getBoundingClientRect()

    topMargin.value = Yh.top - parentXY.top
    bottomMargin.value = parentXY.bottom - Yh.bottom - 105

    // 左右どちらの余白が大きいかを取得（右の空間のほうが大きい場合がtrue）
    leftMargin = Yh.left - parentXY.left + elementWidth
    rightMargin = parentXY.right - Yh.right + elementWidth
    // optionBeside.value = leftMargin < rightMargin
  } else {
    topMargin.value = Yh.top
    bottomMargin.value = window.innerHeight - Yh.bottom

    leftMargin = Yh.left + elementWidth
    rightMargin = window.innerWidth - Yh.right + elementWidth
  }
  // 上下どちらの高さが大きいかを取得（下の空間のほうが大きい場合がtrue）
  optionVertical.value = topMargin.value < bottomMargin.value
  // optionVerticalの過去の値を保持する
  beforeVertical.value = optionVertical.value
  // 左右どちらの幅が大きいかを取得（右の空間のほうが大きい場合がtrue）
  optionBeside.value = leftMargin < rightMargin - 32 //スクロールバー回避

  // ページ内のクラス名：_select_boxを持つ要素を配列として取得
  const AllSelect = Array.from(document.querySelectorAll('._select_box'))
  // イベントを発火させた要素がクラス名：_select_boxの何番目か特定
  targetIndex = AllSelect.findIndex(
    (selected) => selected === event.currentTarget.closest('._select_box')
  )
  // イベントが起こったドロップダウンを取得
  const TargetDropdown = document.querySelectorAll('._select_box ._options_area')[targetIndex]
  // 特定した要素のmaxHeightを設定
  TargetDropdown.style.maxHeight =
    (optionVertical.value ? bottomMargin.value * 0.95 : topMargin.value * 0.95) + 'px'
  // 特定した要素のmaxWidthを設定
  TargetDropdown.style.maxWidth = (optionBeside.value ? rightMargin - 32 : leftMargin) + 'px'
  const maxWidth = optionBeside.value ? rightMargin - 32 : leftMargin

  // optionの横幅最大値を取得
  let max_content = 0
  for (const option of targetElement.value.getElementsByClassName('_option_label')) {
    max_content = option.scrollWidth > max_content + 10 ? option.scrollWidth : max_content + 10
  }
  // option_areaの横幅を最大値に設定
  TargetDropdown.style.width = max_content + 'px'
  // option_areaの横幅が限界値以上の場合は改行用のクラスを適応する（32はスクロールバー分）
  wordBreak.value = max_content + 32 - maxWidth >= 0

  //選択されたvalueと同じvalueをfindで探して、そのlabelを取得する
  optionBg.value = props.p_Options.some((el) => el.value === model.value)
  // optionの表示切替
  arrowFlg.value = !arrowFlg.value

  // スクロールの開始位置を選択された選択肢からに指定する処理
  //valueと一致する値のindexを取得(選択された値が見つからないときは一番上)
  const targetOption = props.p_Options
    .filter((op) => !op.hideFlag)
    .findIndex((e) => e.value === model.value)
  // 表示位置を指定する
  document
    .querySelectorAll('._select_box ._options_area')
    [targetIndex].scrollTo(0, 45 * targetOption)
}
// buttonからfoucusが外れた際にoptionの表示offにする
const blurArrow = () => {
  arrowFlg.value = false
  emit('onBlur')
}
// オプションをクリックした際に対応した値をmodelに格納する
const clickOption = (e, returnValue) => {
  // if (!navigator.userAgent.match(/iPhone|iPad|iPod|Android.+Mobile/)) {
  model.value = returnValue
  // 親要素のイベント発火伝播防止
  e.stopPropagation()
  arrowFlg.value = false
  // }
}
//! optionを指でスクロール時にスクロールせずにoption表示が消える不具合のためコメントアウト（2023/03/29）
// オプションをクリックした際に対応した値をmodelに格納する
// const touchOption = (e, returnValue) => {
//   if (navigator.userAgent.match(/iPhone|iPad|iPod|Android.+Mobile/)) {
//     // モバイル端末の場合
//     model.value = returnValue
//     // 親要素のイベント発火伝播防止
//     e.stopPropagation()
//     arrowFlg.value = false
//   }
// }

// optionにホバーした際に水色の背景を無効にする
const hovorOption = () => {
  optionBg.value = false
}
const onFocus = () => {
  emit('onFocus')
}
//! optionを指でスクロール時にスクロールせずにoption表示が消える不具合のためコメントアウト（2023/03/29）
/**
 * タッチイベント touchend
 * タッチ終了時に発生するイベント(指が画面から離れるとき)
 */
// window.addEventListener('touchend', () => {
//   // アクティブになっている要素を取得
//   const el = document.activeElement
//   // アクティブになっている要素がc_dropdown,optionArea,option,optionLabelでない場合
//   if (
//     el !== c_dropdown.value &&
//     el !== optionArea.value &&
//     el !== option.value &&
//     el !== optionLabel.value
//   ) {
//     // メニューを閉じる
//     arrowFlg.value = false
//   }
// })
</script>

<template>
  <!-- インプットボックス -->
  <div class="_select_box">
    <!-- タイトル -->
    <div v-if="p_Title" class="_title">{{ p_Title }}</div>
    <!-- インプット -->
    <select ref="c_dropdown" v-model="model" class="_invisible" @change="changeSelect"></select>
    <!-- selectエリア -->
    <button
      type="button"
      :disabled="disabled"
      class="_select_area"
      :class="{
        _disable_style: disabled,
        _error_style: p_Error,
        _smallmode_height_on: p_SmallMode,
        _smallmode_height_off: !p_SmallMode,
      }"
      @click="clickArrow"
      @blur="blurArrow"
      @focus="onFocus"
    >
      <div
        class="_select_label"
        :class="{
          _select_label_smallmode_on: p_SmallMode,
          _select_label_smallmode_off: !p_SmallMode,
        }"
      >
        {{ selectLabel }}
      </div>
      <!-- option格納エリア -->
      <div
        ref="optionArea"
        class="_options_area"
        :class="[
          optionVertical ? '_position_bottom' : '_position_top',
          { _position_right: !optionBeside, _option_visible: !arrowFlg },
        ]"
        @mouseover="hovorOption"
      >
        <!-- optionにあたる要素 -->
        <div
          ref="option"
          v-for="option in p_Options.filter((op) => !op.hideFlag)"
          :key="option"
          class="_option"
          :class="{
            _selectedColor: option.value === modelValue,
            _selectedBg: option.value === modelValue && optionBg,
            _word_break: wordBreak,
          }"
          @click="clickOption($event, option.value)"
        >
          <div ref="optionLabel" class="_option_label">{{ option.label }}</div>
        </div>
      </div>
      <!-- アイコン -->
      <div>
        <MDIcon
          v-if="!disabled"
          name="arrow_drop_down"
          class="_c_material _arrow"
          :class="{
            _reverse: arrowFlg,
            _arrow_position_smallmode_on: p_SmallMode,
            _arrow_position_smallmode_off: !p_SmallMode,
          }"
        />
      </div>
    </button>
  </div>
</template>

<style lang="sass" scoped>
._option_visible
  visibility: collapse
  width: 100%

// インプットボックス
._select_box
  font-family: "Noto Sans JP", sans-serif
  position: relative

// タイトル
._title
  font-size: 12px
  color: #707070
  margin-bottom: 5px

// インプット
._invisible
  display: none

._select_area
  position: relative
  width: 100%
  outline: none
  border: none
  box-shadow: 0 0 5px 0 rgb(0 0 0 / 16%)
  border-radius: 4px
  appearance: none
  cursor: pointer
  padding: 0
  background: #ffffff
  text-align: left
  font-size: 13.4px
  font-family: inherit
  color: #000000
  font-weight: 400
._smallmode_height_on
  height: 30px
._smallmode_height_off
  height: 40px

._select_label
  line-height: 1.8
  overflow: hidden
  text-overflow: ellipsis
  white-space: nowrap
._select_label_smallmode_on
  padding: 0px 16px 0px 5px
._select_label_smallmode_off
  padding: 6px 36px 6px 20px

._options_area
  z-index: 10
  border-radius: 4px
  min-width: max(calc( 100% - 10px - 20px ), calc( 100% - 10% - 20% ))
  background: #fff
  position: absolute
  left: 20px
  box-shadow: 0 1px 5px rgb(0 0 0 / 20%), 0 2px 2px rgb(0 0 0 / 14%), 0 3px 1px -2px rgb(0 0 0 / 12%)
  overflow-y: auto
  width: fit-content
  max-height: 0

._position_top
  bottom: 40px
._position_bottom
  top: 40px
._position_right
  right: 20px
  left: auto

._option
  outline: 0
  min-height: 45px
  display: flex
  align-items: center
  width: 100%
  white-space: nowrap

._selectedBg
  background: rgb(224,235,246)

._selectedColor
  color: rgb(45,129,211)

._option_label
  padding: 0 min(20px, 20%) 0 min(20px, 20%)
  font-size: 1.3em
  // overflow-wrap: break-word

._option._word_break
  word-break: break-all
  white-space: normal

._option:hover
  background: #DCDCDC

._selected:hover
  background: #aad9d9

// disablesカラー
._disable_style
  background: #EFEFEF
  cursor: not-allowed
  color: #707070

// errorカラー
._error_style
  background: var(--q-warning, #FF6D6D)

// searchスタイル
._search_style
  padding-left: 40px

// マテリアルアイコン
._c_material
  position: absolute
  font-size: 30px
  color: #707070
._arrow_position_smallmode_on
  right: 0px
  top: 0px
._arrow_position_smallmode_off
  right: 6px
  top: 6px
// サーチアイコン
._search
  left: 6px
// ドロップダウンアイコン
._arrow
  transition: transform 0.3s
._reverse
  transform: rotate(180deg)
</style>
