import gpt from './poem-tome.js';

const NBSP = '\x0a';

const chat_window = document.getElementById('chat-window');
const textarea = chat_window.querySelector('textarea');
const messages = chat_window.querySelector('.messages');
const scroller = chat_window.querySelector('.messages-scroller');

textarea.message_history = JSON.parse(window.sessionStorage.getItem('message_history')) || [''];
textarea.history_index = 0;

const message = window.location.search;
if (message) {
  handle_message(message.replace(/^\?/, ''));
}

function resize_textarea() {
  textarea.style.height = 0;
  textarea.style.height = (textarea.scrollHeight - 4) + 'px';
}

function remove_placeholder() {
  textarea.placeholder = ' ';
  textarea.removeEventListener('click', remove_placeholder);
}
textarea.addEventListener('click', remove_placeholder);
textarea.addEventListener('input', resize_textarea);
textarea.addEventListener('keydown', e => {
  if (e.key === 'Enter') {
    if (textarea.value) {
      handle_message(textarea.value);
      textarea.history_index = 0;
      if (textarea.value !== textarea.message_history[1]) {
        textarea.message_history[0] = textarea.value;
        textarea.message_history.unshift('');
        window.sessionStorage.setItem('message_history', JSON.stringify(textarea.message_history));
      }
      textarea.value = '';
      resize_textarea();
      scroller.scrollTo(0, scroller.scrollHeight);
    }
    e.preventDefault();
  } else if (e.key === 'ArrowUp' || e.key === 'Up') {
    if (
      (textarea.selectionStart === textarea.value.length || textarea.selectionStart === 0) &&
      textarea.history_index < textarea.message_history.length - 1
    ) {
      if (textarea.history_index === 0) {
        textarea.message_history[0] = textarea.value;
      }
      textarea.history_index++;
      textarea.value = textarea.message_history[textarea.history_index];
      resize_textarea();
    }
  } else if (e.key === 'ArrowDown' || e.key === 'Down') {
    if (
      (textarea.selectionStart === textarea.value.length || textarea.selectionStart === 0) &&
      textarea.history_index > 0
    ) {
      textarea.history_index--;
      textarea.value = textarea.message_history[textarea.history_index];
      resize_textarea();
    }
  } else if (e.key === 'c' && e.ctrlKey && textarea.selectionEnd - textarea.selectionStart <= 0) {
    textarea.history_index = 0;
    textarea.value = '';
    resize_textarea();
  }
});
async function handle_message(message) {
  add_message_block('user', message);
  await wait(300);
  const response_div = add_message_block('gpt');
  const ellipsis_p = add_text(response_div);
  const { cancel: cancel_typeout } = typeout('... ... ...', ellipsis_p, 150, false);
  const atLeastOneEllipsis = wait(525);
  const response = await gpt(message).catch(error => ({error}));
  await atLeastOneEllipsis;
  cancel_typeout();
  set_response(response_div, response);
}
function add_message_block(type, message) {
  const div = document.createElement('div');
  div.className = `message ${type}`;
  if (message) {
    add_text(div, message);
  }
  messages.appendChild(div);
  return div;
}
function add_text(node, text) {
  const p = document.createElement('p');
  if (text) {
    p.innerText = text;
  }
  node.appendChild(p);
  return p;
}
function add_error(node) {
  const p = document.createElement('p');
  p.className = 'error';
  node.appendChild(p);
  return p;
}
async function set_response(div, response) {
  if (typeof response === 'string') {
    typeout(response, div.firstChild);
    if (response.includes('poems')) {
      div.firstChild.addEventListener('click', prompt_helper_handler(`Show me the poems`));
      div.firstChild.classList += 'prompt-help';
    } else if (response.includes('“help”')) {
      div.firstChild.addEventListener('click', prompt_helper_handler(`Help me out`));
      div.firstChild.classList += 'prompt-help';
    }
  } else if (typeof response === 'object') {
    div.innerText = '';
    if (response.error) {
      const error_p = add_error(div);
      typeout(response.error, error_p);
    } else if (response.poem) {
      const poem_div = document.createElement('div');
      poem_div.className = 'poem';
      div.appendChild(poem_div);

      const title = document.createElement('h3');
      poem_div.appendChild(title);
      await typeout(response.poem.title, title, 100).promise;

      const byline = document.createElement('div');
      byline.className = 'byline';
      poem_div.appendChild(byline);
      await typeout('by '+response.poem.author, byline, 100).promise;

      typeout_lines(response.poem.lines, poem_div);
    } else if (response.poem_list) {
      const title = document.createElement('h3');
      div.appendChild(title);
      await typeout('Available poems', title, 50).promise;

      const list_ul = document.createElement('ul');
      list_ul.className = 'poem-list';
      div.appendChild(list_ul);

      list_ul.append(...response.poem_list.map(({title, author}) => {
        const poem_li = document.createElement('li');
        poem_li.className = 'poem-list-entry prompt-help';
        poem_li.innerText = NBSP;
        poem_li.addEventListener('click', prompt_helper_handler(`Show me “${title}”`));
        wait(random(250))
        .then(() => typeout(`“${title}” by ${author}`, poem_li, 25));
        return poem_li;
      }));
    }
  }
}
function wait(delay) {
  return new Promise(resolve => window.setTimeout(() => resolve(), delay));
}
const NON_WS = /\S/;
function typeout(message, node, interval = 10, speedup = true) {
  let cancel = false;
  const promise = new Promise(resolve => {
    function type_letter(index) {
      if (!cancel) {
        const substring = message.substring(0, index);
        if (node instanceof Text) {
          node.textContent = substring;
        } else {
          node.innerText = substring;
        }
        if (index + 1 <= message.length) {
          const extra_speed = speedup ? Math.min(Math.round(index / 128), 9) : 0;
          window.setTimeout(() => type_letter(index + 1 + extra_speed), interval);
        } else {
          window.setTimeout(resolve, interval);
        }
      }
    }
    type_letter(message.search(NON_WS) + 1);
  });
  return { cancel: () => { node.innerText = NBSP; cancel = true; }, promise };
}
async function parsed_typeout(line, node, interval = 10, speedup = true) {
  const match = line.match(/(?:<sc>(.+?)<\/sc>)|(?:<i>(.+?)<\/i>)/);
  const append = child => {
    if (node instanceof Text) {
      node.parentElement.appendChild(child);
    } else {
      node.appendChild(child);
    }
  }
  if (!match) {
    return typeout(line, node, interval, speedup).promise;
  } else {
    await typeout(line.substring(0, match.index), node, interval, speedup).promise;

    if (match[1] !== undefined) {
      const smallCapSpan = document.createElement('span');
      smallCapSpan.className = 'small-caps';
      append(smallCapSpan);
      await typeout(match[1], smallCapSpan, interval, speedup).promise;
    } else if (match[2] !== undefined) {
      const italicSpan = document.createElement('i');
      append(italicSpan);
      await typeout(match[2], italicSpan, interval, speedup).promise;
    }

    const afterText = document.createTextNode(' ');
    append(afterText);
    return parsed_typeout(line.substring(match.index + match[0].length), afterText, interval, speedup).promise;
  }
}
async function typeout_lines(lines, node, interval = 50, speedup = true) {
  const promises = [];
  for (const line of lines) {
    if (line === '') {
      await Promise.all(promises);
      const line_div = document.createElement('div');
      line_div.className = 'poem-line';
      node.append(line_div);
    } else {
      const line_div = document.createElement('div');
      line_div.className = 'poem-line';
      line_div.innerText = NBSP;
      node.append(line_div);
      promises.push(
        wait(random(500))
        .then(() => parsed_typeout(line, line_div, interval, speedup))
      );
    }
  }
  return Promise.all(promises);
}

function random(max) {
  return Math.floor((Math.random() * max));
}

function prompt_helper_handler(message) {
  return () => {
    textarea.value = message;
    resize_textarea();
    textarea.focus();
  };
}
