<script setup>

import { ref, reactive, computed, onMounted, watch, useTemplateRef, nextTick } from 'vue';
import { Button, TextInput, MultiSelect, Popover, FormGroup, DateTimeInput } from '@cloudron/pankow';
import { useDebouncedRef, prettyEmailAddresses, prettyLongDate } from '@cloudron/pankow/utils';
import MailModel from '../models/MailModel.js';

const mailModel = MailModel.create();

const availableTypes = [
  { id: 'bounce', name: 'Bounce'  },
  { id: 'deferred', name: 'Deferred'  },
  { id: 'denied', name: 'Denied'  },
  { id: 'queued', name: 'Queued'  },
  { id: 'quota', name: 'Quota'  },
  { id: 'saved', name: 'Saved'  },
  { id: 'sent', name: 'Sent'  },
  { id: 'spam', name: 'Spam'  },
  { id: 'spam-learn', name: 'Spam Training'  },
];

const refreshBusy = ref(false);
const eventlogs = ref([]);
const page = ref(1);
const perPage = ref(100);
const eventlogContainer = useTemplateRef('eventlogContainer');
// eslint-disable-next-line prefer-const
let types = reactive([]);

const filterFrom = ref('');
const filterTo = ref('');
const dateFilterPopover = useTemplateRef('dateFilterPopover');
const dateFilterButton = useTemplateRef('dateFilterButton');

const highlight = useDebouncedRef('', 300);
const currentMatchPosition = ref(-1);
const searching = ref(false);
const SEARCH_LOOKAHEAD_PAGES = 5;

function isMatch(eventlog, term) {
  if (!term) return false;
  const t = term.toLowerCase();
  const fields = [
    prettyEmailAddresses(eventlog.mailFrom),
    prettyEmailAddresses(eventlog.rcptTo),
    eventlog.mailbox,
    eventlog.type,
    eventlog.message,
    eventlog.reason,
    JSON.stringify(eventlog),
  ];
  return fields.some(f => f && String(f).toLowerCase().includes(t));
}

const matchIndices = computed(() => {
  if (!highlight.value) return [];
  return eventlogs.value.reduce((acc, e, i) => {
    if (isMatch(e, highlight.value)) acc.push(i);
    return acc;
  }, []);
});

function scrollToIndex(idx) {
  const el = eventlogContainer.value?.querySelector(`[data-index="${idx}"]`);
  if (el) el.scrollIntoView({ behavior: 'instant', block: 'center' });
}

function goToPrevMatch() {
  if (currentMatchPosition.value > 0) {
    currentMatchPosition.value--;
    scrollToIndex(matchIndices.value[currentMatchPosition.value]);
  }
}

async function goToNextMatch() {
  if (!highlight.value || searching.value) return;

  if (currentMatchPosition.value + 1 < matchIndices.value.length) {
    currentMatchPosition.value++;
    scrollToIndex(matchIndices.value[currentMatchPosition.value]);
    return;
  }

  searching.value = true;
  let endOfLog = false;
  for (let i = 0; i < SEARCH_LOOKAHEAD_PAGES; i++) {
    const prevLength = eventlogs.value.length;
    await fetchMore();
    if (eventlogs.value.length === prevLength) { endOfLog = true; break; }

    if (currentMatchPosition.value + 1 < matchIndices.value.length) {
      currentMatchPosition.value++;
      scrollToIndex(matchIndices.value[currentMatchPosition.value]);
      searching.value = false;
      return;
    }
  }
  searching.value = false;
  if (endOfLog) window.pankow.notify({ text: `No more matches for "${highlight.value}".`, timeout: 3000 });
  else window.pankow.notify({ text: `No match found for "${highlight.value}" in ${eventlogs.value.length} entries. Click next to keep searching.`, timeout: 3000 });
}

function buildFromTo() {
  const from = filterFrom.value ? new Date(filterFrom.value + 'T00:00:00').toISOString() : undefined;
  const to = filterTo.value ? new Date(filterTo.value + 'T23:59:59.999').toISOString() : undefined;
  return { from, to };
}

async function onRefresh() {
  highlight.value = '';
  refreshBusy.value = true;
  page.value = 1;

  const { from, to } = buildFromTo();
  const [error, result] = await mailModel.eventlog(types.join(','), '', page.value, perPage.value, from, to);
  if (error) return console.error(error);

  eventlogs.value = result;

  await nextTick();

  while (eventlogContainer.value && eventlogContainer.value.scrollHeight <= eventlogContainer.value.offsetHeight) {
    await fetchMore();
  }

  refreshBusy.value = false;
}

async function fetchMore() {
  page.value++;

  const { from, to } = buildFromTo();
  const [error, result] = await mailModel.eventlog(types.join(','), '', page.value, perPage.value, from, to);
  if (error) return console.error(error);

  eventlogs.value = eventlogs.value.concat(result);
}

async function onScroll(event) {
  if (event.target.scrollTop + event.target.clientHeight >= event.target.scrollHeight) await fetchMore();
}

function onOpenDateFilter(event) {
  dateFilterPopover.value.open(event, dateFilterButton.value.$el || dateFilterButton.value);
}

watch(perPage, onRefresh);
watch(types, onRefresh);
watch(filterFrom, onRefresh);
watch(filterTo, onRefresh);
watch(highlight, async () => {
  if (matchIndices.value.length > 0) {
    currentMatchPosition.value = 0;
    await nextTick();
    scrollToIndex(matchIndices.value[0]);
  } else {
    currentMatchPosition.value = -1;
    if (highlight.value) goToNextMatch();
  }
});

onMounted(async () => {
  await onRefresh();

  while (eventlogContainer.value && eventlogContainer.value.scrollHeight <= eventlogContainer.value.offsetHeight) {
    await fetchMore();
  }
});

</script>

<template>

  <div class="content-large" style="height: 100%; overflow: hidden; display: flex; flex-direction: column;">
    <!-- cant use Section component as we need control over vertical scrolling -->
    <div class="section" style="overflow: hidden; display: flex; flex-direction: column;">
      <h2 class="section-header">
        {{ $t('emails.eventlog.title') }}
        <div>
          <TextInput placeholder="Highlight..." v-model="highlight" @keydown.enter="goToNextMatch()"/>
          <Button tool plain :disabled="!highlight || currentMatchPosition <= 0 || searching" @click="goToPrevMatch()" icon="fa-solid fa-chevron-up" />
          <Button tool plain :disabled="!highlight || searching" :loading="searching" @click="goToNextMatch()" icon="fa-solid fa-chevron-down" />
          <Button tool secondary ref="dateFilterButton" @click="onOpenDateFilter($event)" :icon="(filterFrom || filterTo) ? 'fa-solid fa-calendar-check' : 'fa-solid fa-calendar'" />
          <MultiSelect v-model="types" :options="availableTypes" option-key="id" option-label="name" :selected-label="types.length ? $t('main.multiselect.selected', { n: types.length }) : $t('emails.typeFilterHeader')"/>
          <Button tool secondary @click="onRefresh()" :loading="refreshBusy" icon="fa-solid fa-sync-alt" />
          <Button tool secondary href="/logs.html?id=mail" target="_blank">{{ $t('main.action.logs') }}</Button>
        </div>
      </h2>
      <Popover ref="dateFilterPopover" width="300px">
        <div style="padding: 15px; display: flex; flex-direction: column; gap: 10px;">
          <FormGroup>
            <label>From</label>
            <DateTimeInput date-only v-model="filterFrom" :max="filterTo || undefined" />
          </FormGroup>
          <FormGroup>
            <label>To</label>
            <DateTimeInput date-only v-model="filterTo" :min="filterFrom || undefined" />
          </FormGroup>
        </div>
      </Popover>
      <div class="section-body" ref="eventlogContainer" style="margin-top: 16px; overflow: auto; padding-top: 0" @scroll="onScroll">
        <table class="eventlog-table">
          <thead>
            <tr>
              <th style="width: 25px"><!-- Icon --></th>
              <th style="width:160px">{{ $t('emails.eventlog.time') }}</th>
              <th style="width: 20%">{{ $t('emails.eventlog.mailFrom') }}</th>
              <th style="width: 20%">{{ $t('emails.eventlog.rcptTo') }}</th>
              <th>{{ $t('emails.eventlog.details') }}</th>
            </tr>
          </thead>
          <tbody>
            <template v-for="(eventlog, index) in eventlogs" :key="eventlog._id">
              <tr :data-index="index" @click="eventlog.isOpen = !eventlog.isOpen" :class="{ 'active': eventlog.isOpen, 'eventlog-match': highlight && isMatch(eventlog, highlight), 'eventlog-match-current': matchIndices[currentMatchPosition] === index }" >
                <td>
                  <i class="fas fa-arrow-circle-left" v-if="eventlog.type === 'sent'" v-tooltip="$t('emails.eventlog.type.outgoing')"></i>
                  <i class="fas fa-history" v-if="eventlog.type === 'deferred'" v-tooltip="$t('emails.eventlog.type.deferred')"></i>
                  <i class="fas fa-arrow-circle-right" v-if="eventlog.type === 'saved'" v-tooltip="$t('emails.eventlog.type.incoming')"></i>
                  <i class="fas fa-align-justify" v-if="eventlog.type === 'queued' && !eventlog.spamStatus" v-tooltip="$t('emails.eventlog.type.queued')"></i>
                  <i class="fas fa-align-justify" v-if="eventlog.type === 'queued' && eventlog.spamStatus && eventlog.spamStatus.indexOf('Yes,') !== 0" v-tooltip="$t('emails.eventlog.type.queued')"></i>
                  <i class="fas fa-trash" v-if="eventlog.type === 'queued' && eventlog.spamStatus && eventlog.spamStatus.indexOf('Yes,') === 0" v-tooltip="$t('emails.eventlog.type.queued')"></i>
                  <i class="fas fa-minus-circle" v-if="eventlog.type === 'denied'" v-tooltip="$t('emails.eventlog.type.denied')"></i>
                  <i class="fas fa-hand-paper" v-if="eventlog.type === 'bounce'" v-tooltip="$t('emails.eventlog.type.bounce')"></i>
                  <i class="fas fa-filter" v-if="eventlog.type === 'spam-learn'" v-tooltip="$t('emails.eventlog.type.spamFilterTrained')"></i>
                  <i class="fas fa-fill-drip" v-if="eventlog.type === 'quota'" v-tooltip="$t('emails.eventlog.type.quota')"></i>
                </td>
                <td>{{ prettyLongDate(eventlog.ts) }}</td>
                <td class="elide-table-cell">{{ prettyEmailAddresses(eventlog.mailFrom) || '-' }}</td>
                <td class="elide-table-cell">{{ prettyEmailAddresses(eventlog.rcptTo) || eventlog.mailbox || '-' }}</td>
                <td>
                  <span v-if="eventlog.type === 'bounce'">{{ $t('emails.eventlog.type.bounceInfo') }}. {{ eventlog.message || eventlog.reason }}</span>
                  <span v-if="eventlog.type === 'deferred'">{{ $t('emails.eventlog.type.deferredInfo', { delay:eventlog.delay }) }} {{ eventlog.message || eventlog.reason }} </span>
                  <span v-if="eventlog.type === 'queued'">
                    <span v-if="eventlog.direction === 'inbound'">{{ $t('emails.eventlog.type.inboundInfo') }}</span>
                    <span v-if="eventlog.direction === 'outbound'">{{ $t('emails.eventlog.type.outboundInfo') }}</span>
                  </span>
                  <span v-if="eventlog.type === 'saved'">{{ $t('emails.eventlog.type.savedInfo') }}</span>
                  <span v-if="eventlog.type === 'sent'">{{ $t('emails.eventlog.type.sentInfo') }}</span>
                  <span v-if="eventlog.type === 'denied'">{{ $t('emails.eventlog.type.deniedInfo') }}. {{ eventlog.message || eventlog.reason }} </span>
                  <span v-if="eventlog.type === 'spam-learn'">{{ $t('emails.eventlog.type.spamFilterTrainedInfo') }}</span>
                  <span v-if="eventlog.type === 'quota'">
                    <span v-if="eventlog.quotaPercent > 0">{{ $t('emails.eventlog.type.overQuotaInfo', { mailbox: eventlog.mailbox, quotaPercent: eventlog.quotaPercent }) }}</span>
                    <span v-if="eventlog.quotaPercent < 0">{{ $t('emails.eventlog.type.underQuotaInfo', { mailbox: eventlog.mailbox, quotaPercent: -eventlog.quotaPercent }) }}</span>
                  </span>
                </td>
              </tr>
              <tr v-if="eventlog.isOpen">
                <td colspan="5" class="eventlog-details">
                  <pre>{{ JSON.stringify(eventlog, null, 4) }}</pre>
                </td>
              </tr>
            </template>
          </tbody>
        </table>
      </div>
    </div>
  </div>
</template>

<style scoped>
.eventlog-table {
  width: 100%;
  overflow: auto;
  border-spacing: 0px;
  table-layout: fixed;
}

.elide-table-cell {
  overflow: hidden;
  text-overflow: ellipsis;
}

.eventlog-table thead {
  background-color: var(--pankow-body-background-color);
  top: 0;
  position: sticky;
  z-index: 1;
}

.eventlog-table th {
  text-align: left;
}

.eventlog-table tbody tr {
  cursor: pointer;
}

.eventlog-table tbody tr.active,
.eventlog-table tbody tr:hover {
  background-color: var(--pankow-color-background-hover);
}

.eventlog-table th,
.eventlog-table td {
  padding: 6px;
}

.eventlog-details {
  background-color: color-mix(in oklab, var(--pankow-color-background-hover), black 5%);
  cursor: auto;
  position: relative;
}

.eventlog-details pre {
  white-space: pre-wrap;
  color: var(--pankow-text-color);
  font-size: 13px;
  padding-left: 10px;
  margin: 0;
  border: none;
  border-radius: var(--pankow-border-radius);
}

.eventlog-table tbody tr.eventlog-match {
  background-color: rgba(255, 193, 7, 0.15);
}

.eventlog-table tbody tr.eventlog-match-current {
  background-color: rgba(255, 193, 7, 0.35);
}
</style>
