// site-app.js
// Copyright (C) 2022 DTP Technologies, LLC
// License: Apache-2.0

'use strict';

const DTP_COMPONENT = { name: 'Site App', slug: 'site-app' };
const dtp = window.dtp = window.dtp || { };

import DtpApp from 'dtp/dtp-app.js';

import UIkit from 'uikit';
import QRCode from 'qrcode';
import Cropper from 'cropperjs';

import SiteChat from './site-chat';
import SiteComments from './site-comments';

const GRID_COLOR = 'rgb(64, 64, 64)';
const GRID_TICK_COLOR = 'rgb(192,192,192)';

const AXIS_TICK_COLOR = 'rgb(192, 192, 192)';

const CHART_LINE_USER = 'rgb(0, 192, 0)';
const CHART_FILL_USER = 'rgb(0, 128, 0)';

export default class DtpSiteApp extends DtpApp {

  constructor (user) {
    super(DTP_COMPONENT, user);

    if (dtp.env === 'production') {
      const isSafari = !!navigator.userAgent.match(/Version\/[\d\.]+.*Safari/);
      const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
      this.isIOS = isSafari || isIOS;
    } else {
      this.isIOS = false;
    }

    this.log.debug('constructor', 'app instance created', {
      env: dtp.env,
      isIOS: this.isIOS,
    });

    this.chat = new SiteChat(this);
    this.comments = new SiteComments(this);

    this.charts = { /* will hold rendered charts */ };

    this.scrollToHash();
  }

  async scrollToHash ( ) {
    const { hash } = window.location;
    if (hash === '') {
      return;
    }
    const target = document.getElementById(hash.slice(1));
    if (target && target.scrollIntoView) {
      target.scrollIntoView({ behavior: 'smooth' });
    }
  }

  async connect ( ) {
    // can't use "super" because Webpack
    this.log.info('connect', 'connecting WebSocket layer');
    await DtpApp.prototype.connect.call(this, { withRetry: true, withError: false });
    if (this.user) {
      const { socket } = this.socket;
      socket.on('system-message', this.chat.appendSystemMessage.bind(this.chat));
      socket.on('user-chat', this.chat.appendUserChat.bind(this.chat));
      socket.on('user-react', this.chat.createEmojiReact.bind(this.chat));
    }
  }

  async goBack ( ) {
    if (document.referrer && (document.referrer.indexOf(`://${window.dtp.domain}`) >= 0)) {
      window.history.back();
    } else {
      window.location.href= '/';
    }
    return false;
  }

  async submitImageForm (event) {
    event.preventDefault();
    event.stopPropagation();

    const formElement = event.currentTarget || event.target;
    const form = new FormData(formElement);

    this.cropper.getCroppedCanvas().toBlob(async (imageData) => {
      try {
        form.append('imageFile', imageData, 'profile.png');
  
        this.log.info('submitImageForm', 'updating user settings', { event, action: formElement.action });
        const response = await fetch(formElement.action, {
          method: formElement.method,
          body: form,
        });
  
        if (!response.ok) {
          let json;
          try {
            json = await response.json();
          } catch (error) {
            throw new Error('Server error');
          }
          throw new Error(json.message || 'Server error');
        }
  
        await this.processResponse(response);
        window.location.reload();
      } catch (error) {
        UIkit.modal.alert(`Failed to update profile photo: ${error.message}`);
      }
    });

    return;
  }

  async closeCurrentDialog ( ) {
    if (!this.currentDialog) {
      return;
    }
    this.currentDialog.hide();
    delete this.currentDialog;
  }

  async copyHtmlToText (event, textContentId) {
    const content = this.editor.getContent({ format: 'text' });
    const text = document.getElementById(textContentId);
    text.value = content;
  }

  async selectImageFile (event) {
    event.preventDefault();

    const imageId = event.target.getAttribute('data-image-id');

    //z read the cropper options from the element on the page
    let cropperOptions = event.target.getAttribute('data-cropper-options');
    if (cropperOptions) {
      cropperOptions = JSON.parse(cropperOptions);
    }
    this.log.debug('selectImageFile', 'cropper options', { cropperOptions }); //z remove when done

    const fileSelectContainerId = event.target.getAttribute('data-file-select-container');
    if (!fileSelectContainerId) {
      UIkit.modal.alert('Missing file select container element ID information');
      return;
    }

    const fileSelectContainer = document.getElementById(fileSelectContainerId);
    if (!fileSelectContainer) {
      UIkit.modal.alert('Missing file select element');
      return;
    }

    const fileSelect = fileSelectContainer.querySelector('input[type="file"]');
    if (!fileSelect.files || (fileSelect.files.length === 0)) {
      return;
    }

    const selectedFile = fileSelect.files[0];
    if (!selectedFile) {
      return;
    }

    this.log.debug('selectImageFile', 'thumbnail file select', { event, selectedFile });

    const filter = /^(image\/jpg|image\/jpeg|image\/png)$/i;
    if (!filter.test(selectedFile.type)) {
      UIkit.modal.alert(`Unsupported image file type selected: ${selectedFile.type}`);
      return;
    }

    const fileSizeId = event.target.getAttribute('data-file-size-element');
    const FILE_MAX_SIZE = parseInt(fileSelect.getAttribute('data-file-max-size'), 10);
    const fileSize = document.getElementById(fileSizeId);
    fileSize.textContent = numeral(selectedFile.size).format('0,0.0b');
    if (selectedFile.size > (FILE_MAX_SIZE)) {
      UIkit.modal.alert(`File is too large: ${fileSize.textContent}. Custom thumbnail images may be up to ${numeral(FILE_MAX_SIZE).format('0,0.00b')} in size.`);
      return;
    }

    const reader = new FileReader();
    reader.onload = (e) => {
      const img = document.getElementById(imageId);
      img.onload = (e) => {
        console.log('image loaded', e, img.naturalWidth, img.naturalHeight);

        fileSelectContainer.querySelector('#file-name').textContent = selectedFile.name;
        fileSelectContainer.querySelector('#file-modified').textContent = moment(selectedFile.lastModifiedDate).fromNow();
        fileSelectContainer.querySelector('#image-resolution-w').textContent = img.naturalWidth.toString();
        fileSelectContainer.querySelector('#image-resolution-h').textContent = img.naturalHeight.toString();

        fileSelectContainer.querySelector('#file-select').setAttribute('hidden', true);
        fileSelectContainer.querySelector('#file-info').removeAttribute('hidden');

        fileSelectContainer.querySelector('#file-save-btn').removeAttribute('hidden');
      };

      // set the image as the "src" of the <img> in the DOM.
      img.src = e.target.result;

      //z create cropper and set options here
      this.createImageCropper(img, cropperOptions);
    };

    // read in the file, which will trigger everything else in the event handler above.
    reader.readAsDataURL(selectedFile);
  }

  async createImageCropper (img, options) {
    options = Object.assign({
      aspectRatio: 1,
      dragMode: 'move',
      autoCropArea: 0.85,
      restore: false,
      guides: false,
      center: false,
      highlight: false,
      cropBoxMovable: true,
      cropBoxResizable: true,
      toggleDragModeOnDblclick: false,
      modal: true,
    }, options);
    this.log.info("createImageCropper", "Creating image cropper", { img });
    this.cropper = new Cropper(img, options);
  }

  async attachTinyMCE (editor) {
    editor.on('KeyDown', async (e) => {
      if (dtp.autosaveTimeout) {
        window.clearTimeout(dtp.autosaveTimeout);
        delete dtp.autosaveTimeout;
      }
      dtp.autosaveTimeout = window.setTimeout(async ( ) => {
        console.log('document autosave');
      }, 1000);
      if ((e.keyCode === 8 || e.keyCode === 46) && editor.selection) {
        var selectedNode = editor.selection.getNode();
        if (selectedNode && selectedNode.nodeName === 'IMG') {
          console.log('removing image', selectedNode);
          await dtp.app.deleteImage(selectedNode.src.slice(-24));
        }
      }
    });
  }

  async deleteImage (imageId) {
    try {
      throw new Error(`would want to delete /image/${imageId}`);
    } catch (error) {
      UIkit.modal.alert(error.message);
    }
  }

  async openPaymentModal ( ) {
    await UIkit.modal('#donate-modal').show();
    await UIkit.slider('#payment-slider').show(0);
  }

  async generateQRCode (event) {
    const selectorQR = event.target.getAttribute('data-selector-qr');
    const selectorPrompt = event.target.getAttribute('data-selector-prompt');

    const targetAmount = event.target.getAttribute('data-amount');
    const amountLabel = numeral(targetAmount).format('$0,0.00');
    const currencyAmountLabel =  numeral(targetAmount * this.exchangeRates[this.paymentCurrency].conversionRateUSD).format('0,0.0000000000000000');
    const prompt = `Donate ${amountLabel} using ${this.paymentCurrencyLabel}.`;
    
    event.preventDefault();
    
    let targetUrl;
    switch (this.paymentCurrency) {
      case 'BTC':
        targetUrl = `bitcoin:${dtp.channel.wallet.btc}?amount=${targetAmount}&message=Donation to ${dtp.channel.name}`;
        break;
      case 'ETH':
        targetUrl = `ethereum:${dtp.channel.wallet.eth}?amount=${targetAmount}`;
        break;
      case 'LTC':
        targetUrl = `litecoin:${dtp.channel.wallet.ltc}?amount=${targetAmount}&message=Donation to ${dtp.channel.name}`;
        break;
    }

    try {
      let elements;

      const imageUrl = await QRCode.toDataURL(targetUrl);

      elements = document.querySelectorAll(selectorQR);
      elements.forEach((element) => element.setAttribute('src', imageUrl));

      elements = document.querySelectorAll(selectorPrompt);
      elements.forEach((e) => e.textContent = prompt);

      elements = document.querySelectorAll('span.prompt-donate-amount');
      elements.forEach((element) => {
        element.textContent = amountLabel;
      });

      elements = document.querySelectorAll('span.prompt-donate-amount-crypto');
      elements.forEach((element) => {
        element.textContent = currencyAmountLabel;
      });

      elements = document.querySelectorAll('a.payment-target-link');
      elements.forEach((element) => {
        element.setAttribute('href', targetUrl);
      });

      const e = document.getElementById('payment-link');
      e.setAttribute('href', targetUrl);

      UIkit.slider('#payment-slider').show(2);
    } catch (error) {
      this.log.error('failed to generate QR code to image', { error });
      UIkit.modal.alert(`Failed to generate QR code: ${error.message}`);
    }
    return true;
  }

  async setPaymentCurrency (currency) {
    this.paymentCurrency = currency;
    this.log.info('setPaymentCurrency', 'payment currency', { currency: this.paymentCurrency });

    await this.updateExchangeRates();
    switch (this.paymentCurrency) {
      case 'BTC':
        this.paymentCurrencyLabel = 'Bitcoin (BTC)';
        break;
      case 'ETH':
        this.paymentCurrencyLabel = 'Ethereum (ETH)';
        break;
      case 'LTC':
        this.paymentCurrencyLabel = 'Litecoin (LTC)';
        break;
    }

    let elements = document.querySelectorAll('span.prompt-donate-currency');
    elements.forEach((element) => {
      element.textContent = this.paymentCurrencyLabel;
    });

    UIkit.slider('#payment-slider').show(1);
  }

  async updateExchangeRates ( ) {
    const NOW = Date.now();
    try {
      let exchangeRates;
      if (!window.localStorage.exchangeRates) {
        exchangeRates = await this.loadExchangeRates();
        this.log.info('updateExchangeRates', 'current exchange rates received and cached', { exchangeRates });
        window.localStorage.exchangesRates = JSON.stringify(exchangeRates);
      } else {
        exchangeRates = JSON.parse(window.localStorage.exchangeRates);
        if (exchangeRates.timestamp < (NOW - 60000)) {
          exchangeRates = await this.loadExchangeRates();
          this.log.info('updateExchangeRates', 'current exchange rates received and cached', { exchangeRates });
          window.localStorage.exchangesRates = JSON.stringify(exchangeRates);
        }
      }
      this.exchangeRates = exchangeRates.symbols;
    } catch (error) {
      this.log.error('updateExchangeRates', 'failed to fetch currency exchange rates', { error });
      UIkit.modal.alert(`Failed to fetch current exchange rates: ${error.message}`);
    }
  }

  async loadExchangeRates ( ) {
    this.log.info('loadExchangeRates', 'fetching current exchange rates');
    const response = await fetch('/crypto-exchange/current-rates');
    if (!response.ok) {
      throw new Error('Server error');
    }
    let exchangeRates = await response.json();
    if (!exchangeRates.success) {
      throw new Error(exchangeRates.message);
    }
    exchangeRates.timestamp = Date.now();
    return exchangeRates;
  }

  async generateOtpQR (canvas, keyURI) {
    QRCode.toCanvas(canvas, keyURI);
  }

  async removeImageFile (event) {
    const imageType = (event.target || event.currentTarget).getAttribute('data-image-type');
    try {
      this.log.info('removeImageFile', 'request to remove image', event);

      let response;
      switch (imageType) {
        case 'profile-picture-file':
          response = await fetch(`/user/${this.user._id}/profile-photo`, { method: 'DELETE' });
          break;

        case 'post-image-file'://this is UNFINISHED, figure out the route in controller, and implement it
          response = await fetch(`/post/${this.user._id}/feature-image`, { method: 'DELETE' });
          break;

        default:
          throw new Error('Invalid image type');
      }

      if (!response.ok) {
        throw new Error('Server error');
      }

      await this.processResponse(response);
      window.location.reload();
    } catch (error) {
      UIkit.modal.alert(`Failed to remove image: ${error.message}`);
    }
  }

  async submitDialogForm (event, userAction) {
    await this.submitForm(event, userAction);
    await this.closeCurrentDialog();
  }

  async renderStatsGraph (selector, title, data) {
    try {
      const canvas = document.querySelector(selector);
      const ctx = canvas.getContext('2d');
      this.charts.profileStats = new Chart(ctx, {
        type: 'bar',
        data: {
          labels: data.map((item) => new Date(item.date)),
          datasets: [
            {
              label: title,
              data: data.map((item) => item.count),
              borderColor: CHART_LINE_USER,
              borderWidth: 1,
              backgroundColor: CHART_FILL_USER,
              tension: 0,
            },
          ],
        },
        options: {
          scales: {
            yAxis: {
              display: true,
              ticks: {
                color: AXIS_TICK_COLOR,
                callback: (value) => {
                  return numeral(value).format(value > 1000 ? '0,0.0a' : '0,0');
                },
              },
              grid: {
                color: GRID_COLOR,
                tickColor: GRID_TICK_COLOR,
              },
            },

            x: {
              type: 'time',
            },
            xAxis: {
              display: false,
              grid: {
                color: GRID_COLOR,
                tickColor: GRID_TICK_COLOR,
              },
            },
          },
          plugins: {
            title: { display: false },
            subtitle: { display: false },
            legend: { display: false },
          },
          maintainAspectRatio: true,
          aspectRatio: 16.0 / 9.0,
          onResize: (chart, event) => {
            if (event.width >= 960) {
              chart.config.options.aspectRatio = 16.0 / 5.0;
            }
            else if (event.width >= 640) {
              chart.config.options.aspectRatio = 16.0 / 9.0;
            } else if (event.width >= 480) {
              chart.config.options.aspectRatio = 16.0 / 12.0;
            } else {
              chart.config.options.aspectRatio = 16.0 / 16.0;
            }
          },
        },
      });
    } catch (error) {
      this.log.error('renderStatsGraph', 'failed to render stats graph', { title, error });
      UIkit.modal.alert(`Failed to render chart: ${error.message}`);
    }
  }

  async deletePost (event) {
    event.preventDefault();
    event.stopPropagation();

    const target = event.currentTarget || event.target;
    const postId = target.getAttribute('data-post-id');
    const postTitle = target.getAttribute('data-post-title');

    try {
      await UIkit.modal.confirm(`Are you sure you want to remove post "${postTitle}"?`);
    } catch (error) {
      // canceled
      return false;
    }

    try {
      const response = await fetch(`/post/${postId}`, { method: 'DELETE' });
      await this.processResponse(response);
    } catch (error) {
      UIkit.modal.alert(`Failed to remove post: ${error.message}`);
    }

    return false;
  }

  async deletePage (event) {
    event.preventDefault();
    event.stopPropagation();

    const target = event.currentTarget || event.target;
    const pageId = target.getAttribute('data-page-id');
    const pageTitle = target.getAttribute('data-page-title');

    try {
      await UIkit.modal.confirm(`Are you sure you want to remove page "${pageTitle}"?`);
    } catch (error) {
      // canceled
      return false;
    }

    try {
      const response = await fetch(`/page/${pageId}`, { method: 'DELETE' });
      await this.processResponse(response);
    } catch (error) {
      UIkit.modal.alert(`Failed to remove page: ${error.message}`);
    }

    return false;
  }

  updatePostTimestamps () {
    const timestamps = document.querySelectorAll('[data-dtp-timestamp]');

    // console.log(timestamps);
    timestamps.forEach((timestamp) => {
      const postTime = timestamp.getAttribute('data-dtp-timestamp');
      const format = timestamp.getAttribute('data-dtp-time-format');
      timestamp.textContent = moment(postTime).format(format || 'MMM DD, YYYY, hh:mm a');
    });
  }
}

dtp.DtpSiteApp = DtpSiteApp;