<template>
  <div>
    <section class="layout-section">
      <div class="heading">
        {{ fund.fundInfo.title }}
      </div>
      <div>
        <p v-html="fixNewlines(fund.fundInfo.description)"></p>
        <div>
          <ul class="fund-details">
            <li v-for="(detail, i) in fundDetails" :key="i" class="fund-detail">
              <div class="detail-icon">
                <svg-icon type="mdi" height="22" width="22" :path="detail.icon"/>
              </div>
              <div class="detail-text">
                {{ detail.val }}
              </div>
            </li>
          </ul>
        </div>
      </div>
      <div>
        <div class="heading">
          Tracker
        </div>
        <div v-if="fund.fundInfo.bonuses.length > 0">
          <div
            v-for="bonus in fund.fundInfo.bonuses.sort((v1, v2) => v1.goal - v2.goal)"
            v-bind:key="bonus.goal"
          >
            <div>
              <p

                :class="amountHeld >= bonus.goal
                ? 'has-text-success' : 'has-text-grey-dark'"
              >
                {{ bonus.goal.toLocaleString() }} XLM
              </p>
              <p

                :class="amountHeld >= bonus.goal
                ? 'has-text-success' : 'has-text-grey-dark'"
              >
                {{ bonus.percent }}%
              </p>
            </div>
          </div>
        </div>
      </div>
      <div>
        <div>
            <span class="total-label is-size-3 pr-2 has-text-weight-light">
              Raised
            </span>
          <span class="pl-3 has-text-weight-bold">
              {{ amountHeld.toLocaleString() }} XLM
            </span>
        </div>
        <div>
            <span class="remaining-label is-size-3 pr-2 has-text-weight-light">
              Remaining
            </span>
          <span class="pl-3 has-text-weight-bold">
              {{ amountAvailable.toLocaleString() }} XLM
            </span>
        </div>
      </div>
      <div class="title is-size-4 has-text-white-ter has-text-centered">
        Contribute
      </div>
      <ErrorDisplay :errors="errs" v-if="invalidContributionForm"/>
      <div class="control my-2">
        <input type="text"
               placeholder="Private Key" aria-label="Wallet" v-model="pk" @blur="queryAccount">
      </div>
      <div class="control my-2" :class="loading.balance ? 'is-loading' : null">
        <input

          type="number"
          placeholder="Amount"
          aria-label="Amount"
          v-model="amt"
          :max="acctBalance"
          :disabled="loading.balance"
        >
      </div>
      <div>
        <button
          :class="loading.contribution ? 'is-loading' : ''"
          :disabled="invalidContributionForm"
          @click="makeContribution"
        >Submit
        </button>
      </div>
      <section v-if="contributions.length > 0">
        <div class="title is-size-4 has-text-white-ter has-text-centered">
          Contributions
        </div>
        <div class="is-flex is-justify-content-space-between is-rounded my-2">
          <div>
            <select v-model="selectedDate" aria-label="Filter by date">
              <option v-for="date in selectableDates" v-bind:key="date" :value="date">
                {{ date ?? 'Cutoff Date' }}
              </option>
            </select>
          </div>
          <div class="consolidate-option has-background-white px-4 py-1 my-auto">
            <label for="consolidate" class="checkbox has-text-dark is-size-6">
            <span>
              Consolidate wallets
            </span>
              <input
                type="checkbox"
                class="ml-2"
                id="consolidate"
                aria-label="Consolidate wallets"
                v-model="enableConsolidation"
              >
            </label>
          </div>
        </div>
        <div id="contribution-container" @scroll="loadMoreIfNeeded">
          <table>
            <thead>
            <tr>
              <th>Wallet</th>
              <th>Amount</th>
              <th v-if="!enableConsolidation">Time</th>
              <th v-else>Tokens</th>
            </tr>
            </thead>
            <tbody>
            <tr v-for="(contribution, i) in contributions" v-bind:key="i">
              <td>{{ truncateWallet(contribution.wallet, 6, undefined) }}</td>
              <td>{{ contribution.amount }}</td>
              <td v-if="!enableConsolidation">
              <span :title="formatDate(contribution.createdAt, true)">
                {{ formatDate(contribution.createdAt, true) }}
              </span>
              </td>
              <td v-else>
                <span>{{ calculateReward(contribution.amount / fund.fundInfo.price) }}</span>
              </td>
            </tr>
            </tbody>
          </table>
        </div>
      </section>
      <section v-if="store.getters.getToken && hasPermission(Privileges.AdminPlus)">
        <div class="title is-size-4 has-text-white-ter has-text-centered">
          Close Group Fund
        </div>
        <div>
          <div>
            <label for="delete-confirm">
              <input type="checkbox" id="delete-confirm" v-model="allowDelete"> Close
            </label>
          </div>
          <div>
            <button

              :disabled="!allowDelete"
              @click="deleteFund"
            >
              Close
            </button>
          </div>
        </div>
      </section>
    </section>
  </div>
</template>

<script setup lang="ts">
import {
  useRoute,
  useRouter,
} from 'vue-router';
import {
  Bonus,
  Contribution,
  FundInfo,
  GetRewardFundResponse,
  Privileges,
} from '@/api/types';
import {
  computed,
  Ref,
  ref,
  watch,
} from 'vue';
import { useWebSocket } from '@vueuse/core';
import store from '@/store';
import {
  sanitize,
  truncateWallet,
} from '@/lib/helpers';
import * as luxon from 'luxon';
import hasPermission from '@/lib/auth';
import ErrorDisplay, { SignetError } from '@/components/ErrorDisplay.vue';
import {
  contribute,
  deleteRewardFund,
  getBalance,
  getContributions,
  getRewardFund,
} from '@/api/composed';
import SvgIcon from '@jamescoyle/vue-icon';
import {
  mdiCircleMultipleOutline,
  mdiGreaterThanOrEqual,
  mdiNoteTextOutline,
  mdiTarget,
} from '@mdi/js';

const route = useRoute();
const router = useRouter();
const { id } = route.params;

const identifier = parseInt(id as string, 10);

const formatDate = (time: string, includeTime = false) => {
  const s = luxon.DateTime.fromISO(time)
    .toUTC();
  const date = s.toFormat('yyyy-LLL-dd');
  return includeTime ? `${date} ${s.toFormat('HH:mm')} (UTC)` : date;
};

const pk = ref('');
const amt = ref(undefined as number | undefined);
const selectableDates = ref([undefined] as (string | undefined)[]);
const selectedDate = ref(undefined as string | undefined);

const allowDelete = ref(false);
const deleteFund = async () => {
  const deleted = await deleteRewardFund(identifier, allowDelete.value);
  if (deleted && deleted.success) {
    await router.push('/');
  }
};

const enableConsolidation = ref(false);
const fund = ref(
  {
    fundInfo: {} as FundInfo,
    contributions: {
      list: [],
      dates: [] as string[],
      total: 0,
    },
    total: { amountHeld: 0 },
  } as GetRewardFundResponse | null,
);
const fundDetails = ref([{
  icon: '',
  val: '',
}]);

fund.value = await getRewardFund(identifier, enableConsolidation.value);
if (!fund.value) {
  router.push('/');
  throw new Error('Fund not found');
}
fundDetails.value = [
  {
    icon: mdiCircleMultipleOutline,
    val: fund.value.fundInfo.asset,
  },
  {
    icon: mdiGreaterThanOrEqual,
    val: `${fund.value.fundInfo.minContribution.toLocaleString()}`,
  },
  {
    icon: mdiTarget,
    val: `${fund.value.fundInfo.amountAvailable.toLocaleString()}`,
  },
  {
    icon: mdiNoteTextOutline,
    val: `"${fund.value.fundInfo.memo}"`,
  },
];
if (fund.value.contributions.dates) {
  selectableDates.value = selectableDates.value.concat(
    fund.value.contributions.dates.map((d) => formatDate(d)),
  );
}

const reward = ref(0);
const maxBonus = ref(0);
const bonus = ref(undefined as Bonus | undefined);
const amountHeld = ref(fund.value.total.amountHeld);
const amountAvailable = ref(fund.value.fundInfo.amountAvailable);
const contributions: Ref<Contribution[]> = ref(fund.value.contributions.list ?? []);
const offset = ref(contributions.value.length);
const total = ref(fund.value.contributions.total);
const contributionsLoading = ref(false);
const acctBalance = ref(undefined as number | undefined);
const unknownAcct = ref(true);
const loading = ref({
  contribution: false,
  balance: false,
});

const hasInvalidValues = () => {
  if (!fund.value) throw new Error('Fund was not loaded!');
  return [pk, amt].every((v) => v.value !== undefined && v.value !== '')
    && (amt.value === 0
      || amt.value! > amountAvailable.value
      || amt.value! < fund.value.fundInfo.minContribution
      || (acctBalance.value && amt.value! > acctBalance.value)
      || unknownAcct.value);
};

const invalidContributionForm = computed(() => hasInvalidValues());

const getCurrentBonus = () => {
  if (!fund.value) throw new Error('Fund not found!');
  const achievedBonuses = fund.value.fundInfo.bonuses.filter(
    (b) => {
      if (!fund.value) throw new Error('Fund not found');
      return b.goal && fund.value.total.amountHeld >= b.goal;
    },
  );
  if (achievedBonuses.length > 0) {
    maxBonus.value = Math.max(...achievedBonuses.map((b) => b.goal ?? -1));
    bonus.value = achievedBonuses.find((b) => b.goal === maxBonus.value);
    if (!Object.entries(bonus).length) throw new Error('Something went wrong');
  }
};

const calculateReward = (bought: number) => {
  if (bonus.value) {
    if (!bonus.value.percent) throw new Error('Bonus did not have percent for some reason');
    reward.value = bought + bought * (bonus.value.percent / 100);
  } else {
    reward.value = bought;
  }
  return reward.value.toLocaleString();
};

const fixNewlines = (s: string) => s.replace('\n', '<br/>');

const errs: SignetError[] = [
  {
    text: 'Amount is required',
    condition: amt.value === undefined,
  },
  {
    text: 'Amount must be greater than 0',
    condition: amt.value && amt.value === 0 && !fund.value.fundInfo.minContribution,
  },
  {
    text: 'Amount is less than the minimum contribution',
    condition: amt.value && amt.value < fund.value.fundInfo.minContribution,
  },
  {
    text: `Not enough ${fund.value.fundInfo.asset} for sale in ICO`,
    condition: amt.value && amt.value > amountAvailable.value,
  },
  {
    text: `Not enough XLM to send (${amt.value?.toLocaleString()})`,
    condition: amt.value && acctBalance.value && amt.value > acctBalance.value,
  },
  {
    text: 'Could not find Stellar wallet',
    condition: unknownAcct,
  },
];

document.title = `Beignet - ${fund.value.fundInfo.title}`;

watch(selectedDate, async (newVal) => {
  offset.value = 0;
  const conts = await getContributions(identifier, offset.value, newVal, enableConsolidation.value);
  if (!fund.value) throw new Error('Fund not found');
  if (!conts) throw new Error('Contributions not found');
  contributions.value = conts.list;
  offset.value = contributions.value.length;
  total.value = fund.value.contributions.total;
});

const loadMoreIfNeeded = async (e: Event) => {
  const target = e.target as Element | null;
  const canLoadMore = () => target
    && (target.scrollTop + target.clientHeight) / target.scrollHeight > 0.8
    && offset.value < total.value && !contributionsLoading.value;

  if (canLoadMore()) {
    contributionsLoading.value = true;
    const moreContribs = await getContributions(
      identifier,
      offset.value,
      selectedDate.value,
      enableConsolidation.value,
    );
    if (!moreContribs) throw new Error('Contributions not found');
    offset.value += moreContribs.list.length;
    total.value = moreContribs.total;
    contributions.value = contributions.value.concat(moreContribs.list);
    contributionsLoading.value = false;
  }
};

const {
  status,
  data,
} = useWebSocket(
  'ws://127.0.0.1:7300/ContributorStream',
  {
    immediate: true,
    autoReconnect: true,
  },
);

const queryAccount = async () => {
  if (pk.value && pk.value.startsWith('S')) {
    loading.value.balance = true;
    const resp = await getBalance(pk.value);
    if (resp === null) {
      unknownAcct.value = true;
      acctBalance.value = undefined;
    } else {
      unknownAcct.value = false;
      if (resp?.balance) {
        acctBalance.value = resp?.balance;
        if (amt.value && amt.value > acctBalance.value) {
          amt.value = acctBalance.value;
        }
      }
    }
    loading.value.balance = false;
  }
};

const makeContribution = async () => {
  if (!fund.value) throw new Error('Fund not found');
  if (!amt.value) return;
  if (!/[^[0-9]+$/.test(amt.value.toString())) return;
  if (unknownAcct.value) return;
  if (!loading.value.contribution && pk.value && amt.value && amt.value <= amountAvailable.value) {
    loading.value.contribution = true;
    await contribute(sanitize(pk.value), amt.value, fund.value.fundInfo.id);
    loading.value.contribution = false;
    pk.value = '';
    amt.value = undefined;
  }
};

watch(enableConsolidation, async () => {
  offset.value = 0;
  const conts = await getContributions(
    identifier,
    offset.value,
    selectedDate.value,
    enableConsolidation.value,
  );
  if (!fund.value) throw new Error('Fund not found');
  if (!conts) throw new Error('Contributions not found');
  contributions.value = conts.list;
  offset.value = contributions.value.length;
  total.value = fund.value.contributions.total;
});

watch(data, (newVal) => {
  if (!fund.value) throw new Error('Fund not found');
  getCurrentBonus();
  if (status.value === 'OPEN') {
    const v = JSON.parse(newVal.trim()) as Contribution;
    v.createdAt = luxon.DateTime.now()
      .toISO();
    const formattedDate = formatDate(v.createdAt);
    if (!selectableDates.value.includes(formattedDate)) {
      selectableDates.value.push(formattedDate);
    }
    if (enableConsolidation.value && contributions.value
      && contributions.value.map((c: Contribution) => c.wallet)
        .includes(v.wallet)) {
      const hasContribution = contributions.value.find((c: Contribution) => c.wallet === v.wallet);
      if (!hasContribution) throw new Error('Something went wrong');
      hasContribution.amount += v.amount;
    } else {
      contributions.value.splice(0, 0, v);
      offset.value += 1;
    }
    amountHeld.value += v.amount;
    amountAvailable.value -= v.amount;
  }
});
</script>

<style scoped lang="stylus">
.transaction-date
  white-space nowrap
  overflow hidden
  max-width 20vw
  display block

.consolidate-label
  max-width: 24vw;
  white-space: nowrap;
  overflow: hidden;
  vertical-align: bottom;
  text-overflow: ellipsis;

.total-label, .remaining-label
  border-right 1px solid #8f8f8f
  font-variant all-petite-caps

.signet-asset-name
  cursor pointer

#contribution-container
  max-height 360px
  overflow-y auto

.consolidate-option
  border-radius 16px

.fund-description
  min-height 280px

.fund-details
  display flex
  flex-direction row
  justify-content space-around
  background-color darkorange
  border-radius 4px
  list-style-type none
  margin 0
  padding 8px 4px

.detail-icon
  display flex
  justify-content center
  color #fff
  margin-bottom 8px

.detail-text
  font-size 16px
  text-align center
  color #fff
  font-family "Passion One"

#consolidate
  vertical-align middle
</style>
