/**
 * 読み上げ付きtextarea
 *
 * @author ao-system, Inc.
 * @date 2024-03-02
 *
 * e.g.
 * <textarea-speak></textarea-speak>
 * <script defer src="ce_textarea_speak.js"></script>
 *
 */
(() => {
	'use strict';
	class TextareaSpeak {
		#constProperties = [
			{	prop: 'style',
				value: `
					:host > div { /*wrapper*/
						--color-bar-text1: #fff;
						--color-bar-text2: #000;
						width: 100%;
						position: relative;
						> div.first {	/*bar first*/
							width: 100%;
							padding: 2px 5px;
							box-sizing: border-box;
							> select {
								font-size: 13px;
								color: #000;
								background-color: transparent;
								border: solid 1px var(--color-bar-text2);
							}
						}
						> div.second {	/*bar second*/
							width: 100%;
							padding: 2px 5px;
							box-sizing: border-box;
							display: flex;
							align-items: center;
							column-gap: 10px;
							> div {
								user-select: none;
								font-size: 14px;
								color: var(--color-bar-text1);
								cursor: pointer;
								opacity: 1;
								&.off {
									opacity: 0.5;
								}
							}
							> span {
								user-select: none;
								font-size: 14px;
								color: var(--color-bar-text2);
							}
						}
						> textarea {
							box-sizing: border-box;
						}
					}
				`,
			},
			{	prop: 'styleElm',
				value: document.createElement('style'),
			},
			{	prop: 'wrapElm',
				value: document.createElement('div'),
			},
			{	prop: 'barFirstElm',
				value: document.createElement('div'),
			},
			{	prop: 'barSecondElm',
				value: document.createElement('div'),
			},
			{	prop: 'selectElm',
				value: document.createElement('select'),
			},
			{	prop: 'playElm',
				value: document.createElement('div'),
			},
			{	prop: 'pauseElm',
				value: document.createElement('div'),
			},
			{	prop: 'resumeElm',
				value: document.createElement('div'),
			},
			{	prop: 'cancelElm',
				value: document.createElement('div'),
			},
			{	prop: 'statusElm',
				value: document.createElement('span'),
			},
			{	prop: 'textareaElm',
				value: document.createElement('textarea'),
			},
		];
		constructor() {
			//class内でwritable:falseの定数を作成する方法
			this.#constProperties.forEach((obj) => Object.defineProperty(this, obj.prop, {value:obj.value, writable:false}));
			this.#setVoiceSelect();
			speechSynthesis.addEventListener('voiceschanged',() => {
				this.#setVoiceSelect();
				this.#cancel();
			});
		}
		#setVoiceSelect() {
			if (this.selectElm.innerHTML != '') {
				return;
			}
			const voices = speechSynthesis.getVoices();
			const optionVoices = [];
			for (let i = 0; i < voices.length; i++) {
				const lang = voices[i]['lang'];
				const nm = voices[i]['name'];
				const df = voices[i]['default'];
				const lo = voices[i]['localService'];
				const elm = document.createElement('option');
				elm.value = i;
				if (df) {
					elm.setAttribute('selected','selected');
				}
				elm.innerHTML = lang + ':' + nm;
				elm.setAttribute('data-local',lo);
				optionVoices.push(elm);
			}
			for (let i = 0; i < optionVoices.length; i++) {
				this.selectElm.appendChild(optionVoices[i]);
			}
		}
		#speak() {
			if (speechSynthesis.speaking) {
				return;
			}
			const ssu = new SpeechSynthesisUtterance();
			const voices = speechSynthesis.getVoices();
			const num = this.selectElm.value;
			ssu.voice = voices[num];
			ssu.volume = 1;
			ssu.rate = 1;
			ssu.pitch = 1;
			ssu.text = this.textareaElm.value;
			speechSynthesis.speak(ssu);
			ssu.addEventListener('start',  () => { this.#setSpeakStatus(); });
			ssu.addEventListener('end',    () => { this.#setSpeakStatus(); });
			ssu.addEventListener('pause',  () => { this.#setSpeakStatus(); });
			ssu.addEventListener('resume', () => { this.#setSpeakStatus(); });
			ssu.addEventListener('error',  () => { this.#setSpeakStatus(); });
		}
		#setSpeakStatus() {
			const selectedOption = this.selectElm.options[this.selectElm.selectedIndex];
			const dataLocal = selectedOption.getAttribute('data-local');
			if (dataLocal == 'true') {
				if (speechSynthesis.speaking == true && speechSynthesis.paused == false && speechSynthesis.pending == false) {
					this.statusElm.textContent = 'start';
					this.playElm.classList.add('off');
					this.pauseElm.classList.remove('off');
					this.resumeElm.classList.add('off');
					this.cancelElm.classList.remove('off');
				} else if (speechSynthesis.speaking == true && speechSynthesis.paused == true && speechSynthesis.pending == false) {
					this.statusElm.textContent = 'pause';
					this.playElm.classList.add('off');
					this.pauseElm.classList.add('off');
					this.resumeElm.classList.remove('off');
					this.cancelElm.classList.remove('off');
				} else {
					this.statusElm.textContent = 'ready';
					this.playElm.classList.remove('off');
					this.pauseElm.classList.add('off');
					this.resumeElm.classList.add('off');
					this.cancelElm.classList.add('off');
				}
			} else {
				if (speechSynthesis.speaking == true && speechSynthesis.paused == false && speechSynthesis.pending == false) {
					this.statusElm.textContent = 'start';
					this.playElm.classList.add('off');
					this.pauseElm.classList.remove('off');
					this.resumeElm.classList.remove('off');
					this.cancelElm.classList.remove('off');
				} else {
					this.statusElm.textContent = 'ready';
					this.playElm.classList.remove('off');
					this.pauseElm.classList.add('off');
					this.resumeElm.classList.add('off');
					this.cancelElm.classList.add('off');
				}
			}
		}
		#pause() {
			speechSynthesis.pause();
		}
		#resume() {
			speechSynthesis.resume();
		}
		#cancel() {
			speechSynthesis.cancel();
		}
		//styleを作成
		#styleElement() {
			this.styleElm.textContent = this.style;
		}
		//barを作成
		#barElement(elementRef) {
			this.barFirstElm.classList.add('first');
			this.barSecondElm.classList.add('second');
			//
			this.barFirstElm.appendChild(this.selectElm);
			//
			this.playElm.textContent = 'play';
			this.playElm.addEventListener('click',() => {this.#speak();});
			this.barSecondElm.appendChild(this.playElm);
			//
			this.pauseElm.textContent = 'pause';
			this.pauseElm.addEventListener('click',() => {this.#pause();});
			this.pauseElm.classList.add('off');
			this.barSecondElm.appendChild(this.pauseElm);
			//
			this.resumeElm.textContent = 'resume';
			this.resumeElm.addEventListener('click',() => {this.#resume();});
			this.resumeElm.classList.add('off');
			this.barSecondElm.appendChild(this.resumeElm);
			//
			this.cancelElm.textContent = 'cancel';
			this.cancelElm.addEventListener('click',() => {this.#cancel();});
			this.cancelElm.classList.add('off');
			this.barSecondElm.appendChild(this.cancelElm);
			//
			this.barSecondElm.appendChild(this.statusElm);
			//
			this.wrapElm.appendChild(this.barFirstElm);
			this.wrapElm.appendChild(this.barSecondElm);
		}
		//textareaを作成
		#textareaElement(elementRef) {
			this.textareaElm.value = elementRef.innerHTML;
			this.wrapElm.appendChild(this.textareaElm);
		}
		//shadowDOMにappend
		#attachShadow(elementRef,styleElm,wrapElm) {
			const shadowClosed = elementRef.attachShadow({mode:'closed'});
			shadowClosed.innerHTML = '';
			shadowClosed.appendChild(this.styleElm);
			shadowClosed.appendChild(this.wrapElm);
		}
		//textareaのスタイル変更を監視
		#observeTextarea() {
			const observer = new MutationObserver((mutationsList, observer) => {
				for(const mutation of mutationsList) {
					if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
						//textareaのwidthが変化したらwrapperのwidthに適用
						this.wrapElm.style.width = this.textareaElm.style.width;
					}
				}
			});
			observer.observe(this.textareaElm, { attributes:true, attributeFilter:['style'] });
		}
		//textarea-speakのスタイルを取得してshadowDOM内のエレメントに適用する
		#applyStyle(elementRef) {
			const computedStyle = window.getComputedStyle(elementRef);
			for (let i = 0; i < computedStyle.length; i++) {
			    const propertyName = computedStyle[i];
			    const propertyValue = computedStyle.getPropertyValue(propertyName);
				if (['box-sizing','resize','white-space-collapse'].includes(propertyName) == false) {	//これらを除く
				    this.textareaElm.style.setProperty(propertyName, propertyValue);
				}
			}
			//textarea-speakのborderTopColorをshadow内barのbackgroundColorに適用
			this.barFirstElm.style.backgroundColor = computedStyle.borderTopColor;
			this.barSecondElm.style.backgroundColor = computedStyle.borderTopColor;
			//textarea-speakのborderTopLeftRadius,borderTopRightRadiusをbarFirstElmのborderTopLeftRadius,borderTopRightRadiusに適用
			this.barFirstElm.style.setProperty('border-top-left-radius',computedStyle.borderTopLeftRadius);
			this.barFirstElm.style.setProperty('border-top-right-radius',computedStyle.borderTopRightRadius);
			this.textareaElm.style.borderTopLeftRadius = 0;
			this.textareaElm.style.borderTopRightRadius = 0;
			//textareaのwidthをwrapperのwidthに適用
			this.wrapElm.style.width = this.textareaElm.clientWidth + 'px';
			//textarea-speakのスタイルを削除。<textarea-sort/>を非表示にする為
			elementRef.style.border = 'none';
			elementRef.style.padding = 0;
		}
		render(elementRef) {
			this.#styleElement();
			this.#barElement(elementRef);
			this.#textareaElement(elementRef);
			this.#attachShadow(elementRef);
			this.#observeTextarea();
			this.#applyStyle(elementRef);
		}
	}
	customElements.define('textarea-speak',
		class extends HTMLElement {
			constructor() {
				super();
				(new TextareaSpeak()).render(this);
			}
		}
	);
})();