<template>
  <div class="hold-inner">
    <div class="stake-inner">
      <template v-if="!statCollection.initialized">
        <div>
          <FLoader></FLoader>
        </div>
      </template>

      <template v-else>
        <div class="unaudited-notice" role="alert">
          ⚠️ This protocol is unaudited. Use at your own risk.
        </div>
        <div class="uniswap-link-banner">
          <span>Need $KRK?</span>
          <a 
            :href="`https://app.uniswap.org/swap?outputCurrency=${chainData?.harb}&chain=base`"
            target="_blank"
            rel="noopener noreferrer"
            class="uniswap-link"
          >
            Get $KRK on Uniswap →
          </a>
        </div>
        <form class="stake-form" @submit.prevent="handleSubmit" :aria-describedby="formStatusId" novalidate>
          <div class="form-group">
            <label :id="sliderLabelId" :for="sliderId" class="subheader2">Token Amount</label>
            <div class="input-range" :class="{ 'input-range--disabled': isSliderDisabled }">
              <input
                :id="sliderId"
                class="input-range__control"
                type="range"
                :min="sliderMin"
                :max="sliderMax"
                :step="sliderStep"
                :aria-labelledby="sliderLabelId"
                :aria-describedby="sliderHelpId"
                :aria-valuemin="sliderMin"
                :aria-valuemax="sliderMax"
                :aria-valuenow="currentStakeAmount"
                :aria-valuetext="stakeAmountAriaText"
                :disabled="isSliderDisabled"
                v-model.number="stake.stakingAmountNumber"
                :style="{ '--slider-percentage': sliderPercentage + '%' }"
              />
              <output class="input-range__value" :for="sliderId">{{ formattedStakeAmount }}</output>
            </div>
            <p :id="sliderHelpId" class="input-range__help">{{ sliderDescription }}</p>
            <div class="sr-only" aria-live="polite">{{ sliderAnnouncement }}</div>
          </div>

          <div class="formular">
            <div class="row row-1">
              <FInput
                label="Staking Amount"
                class="staking-amount"
                v-model="stake.stakingAmountNumber"
                type="number"
                inputmode="decimal"
                :aria-describedby="stakeAmountDescriptionId"
              >
                <template #details>
                  <div class="balance" :id="stakeAmountDescriptionId">Balance: {{ formattedBalance }} $KRK</div>
                  <button type="button" @click="setMaxAmount" class="staking-amount-max">
                    <b>Max</b>
                  </button>
                </template>
              </FInput>
              <Icon class="stake-arrow" icon="mdi:chevron-triple-right" aria-hidden="true"></Icon>
              <FInput
                label="Owner Slots"
                class="staking-amount"
                readonly
                :modelValue="`${stakeSlots} (${supplyFreeze?.toFixed(4) ?? '0.0000'})`"
                aria-live="polite"
              >
                <template #info>
                  Slots correspond to a percentage of ownership in the protocol.<br /><br />1,000 Slots = 1% Ownership<br /><br />When you
                  unstake you get the exact percentage of the current $KRK total supply. When the total supply increased since you staked
                  you get more tokens back than before.
                </template>
              </FInput>
            </div>

            <div class="row row-2">
              <div class="form-field tax-field">
                <div class="field-label">
                  <label :for="taxSelectId">Position Cost (Tax Rate)</label>
                  <IconInfo size="20px">
                    <template #text>
                      The yearly tax you have to pay to keep your slots open. The tax is paid when unstaking or manually in the dashboard.
                      If someone pays a higher tax they can buy you out.<br /><br />
                      <strong>Trade-off:</strong> Higher tax = your position is harder to snatch, but reduces your returns. Lower tax = higher returns, but anyone willing to pay more tax can take your position.
                    </template>
                  </IconInfo>
                </div>
                <div class="tax-helper-text">
                  💡 Higher tax = harder to snatch, but reduces your returns
                </div>
                <div class="tax-select-wrapper">
                  <select :id="taxSelectId" class="tax-select" v-model.number="taxRateIndex" :aria-describedby="taxHelpId">
                    <option v-for="option in taxOptions" :key="option.index" :value="option.index">
                      {{ option.label }}
                    </option>
                  </select>
                  <span class="tax-select__icon" aria-hidden="true">
                    <Icon icon="mdi:chevron-down"></Icon>
                  </span>
                </div>
                <p :id="taxHelpId" class="field-help">{{ taxRateDescription }}</p>
                <div class="sr-only" aria-live="polite">{{ taxRateAnnouncement }}</div>
              </div>

              <div class="form-field summary-field" :aria-labelledby="floorTaxLabelId" aria-live="polite">
                <div class="field-label" :id="floorTaxLabelId">
                  <span>Floor Tax</span>
                  <IconInfo size="20px">
                    <template #text> This is the current minimum tax you have to pay to claim owner slots from other owners. </template>
                  </IconInfo>
                </div>
                <p class="form-field__value">{{ floorTaxDisplay }}</p>
                <p class="field-help">{{ floorTaxHelpText }}</p>
              </div>

              <div class="form-field summary-field" :aria-labelledby="snatchLabelId" aria-live="polite">
                <div class="field-label" :id="snatchLabelId">
                  <span>Positions Buyout</span>
                  <IconInfo size="20px">
                    <template #text>
                      This shows you the number of staking positions you buy out from current owners by paying a higher tax. If you get
                      bought out yourself you receive the current market value of your position including your profits.
                    </template>
                  </IconInfo>
                </div>
                <p class="form-field__value">{{ positionsBuyoutDisplay }}</p>
                <p class="field-help">{{ snatchHelpText }}</p>
              </div>
            </div>
          </div>

          <section class="stake-summary" :aria-labelledby="stakeSummaryId" aria-live="polite">
            <h4 class="stake-summary__heading" :id="stakeSummaryId">Stake Summary</h4>
            <p>{{ stakeSummaryText }}</p>
            <p>{{ snatchSummaryText }}</p>
            <p>{{ walletSummaryText }}</p>
          </section>

          <div class="sr-only" aria-live="assertive">{{ assistiveSummary }}</div>

          <div class="form-status" :id="formStatusId" :role="actionState.tone === 'error' ? 'alert' : 'status'" aria-live="polite">
            {{ actionState.message }}
          </div>

          <FButton size="large" block type="submit" :disabled="actionState.disabled" :outlined="actionState.variant === 'outlined'">
            {{ actionState.label }}
          </FButton>
        </form>

        <div class="contract-addresses">
          <div class="contract-addresses__title">Contract Addresses (Sepolia Testnet)</div>
          <div class="contract-addresses__list">
            <div class="contract-address">
              <span class="contract-address__label">KRK Token:</span>
              <a 
                href="https://sepolia.basescan.org/address/0xff196f1e3a895404d073b8611252cf97388773a7" 
                target="_blank" 
                rel="noopener noreferrer"
                class="contract-address__link"
              >
                0xff196f1e3a895404d073b8611252cf97388773a7
              </a>
            </div>
            <div class="contract-address">
              <span class="contract-address__label">Stake Contract:</span>
              <a 
                href="https://sepolia.basescan.org/address/0xc36e784e1dff616bdae4eac7b310f0934faf04a4" 
                target="_blank" 
                rel="noopener noreferrer"
                class="contract-address__link"
              >
                0xc36e784e1dff616bdae4eac7b310f0934faf04a4
              </a>
            </div>
            <div class="contract-address">
              <span class="contract-address__label">LM Contract:</span>
              <a 
                href="https://sepolia.basescan.org/address/0x33d10f2449ffede92b43d4fba562f132ba6a766a" 
                target="_blank" 
                rel="noopener noreferrer"
                class="contract-address__link"
              >
                0x33d10f2449ffede92b43d4fba562f132ba6a766a
              </a>
            </div>
          </div>
        </div>
      </template>
    </div>
  </div>
</template>

<script setup lang="ts">
import FButton from '@/components/fcomponents/FButton.vue';
import FInput from '@/components/fcomponents/FInput.vue';
import FLoader from '@/components/fcomponents/FLoader.vue';
import IconInfo from '@/components/icons/IconInfo.vue';
import { Icon } from '@iconify/vue';
import { weiToNumber } from 'kraiken-lib/format';
import { loadPositions, usePositions, type Position } from '@/composables/usePositions';
import { useStake } from '@/composables/useStake';
import { useClaim } from '@/composables/useClaim';
import { useAdjustTaxRate } from '@/composables/useAdjustTaxRates';
import { useSnatchSelection } from '@/composables/useSnatchSelection';
import { assetsToShares } from '@/contracts/stake';
import { useWallet } from '@/composables/useWallet';
import { ref, onMounted, watch, computed, watchEffect, getCurrentInstance } from 'vue';
import { useStatCollection, loadStats } from '@/composables/useStatCollection';
import { DEFAULT_CHAIN_ID, getChain } from '@/config';

const demo = sessionStorage.getItem('demo') === 'true';

const adjustTaxRate = useAdjustTaxRate();
const defaultTaxRateIndex = adjustTaxRate.taxRates[0]?.index ?? 0;
const taxRateIndex = ref<number>(defaultTaxRateIndex);
const stake = useStake();
const _claim = useClaim();
const wallet = useWallet();
const initialChainId = wallet.account.chainId ?? DEFAULT_CHAIN_ID;
const statCollection = useStatCollection(initialChainId);
const { activePositions: _activePositions } = usePositions(initialChainId);
const currentChainId = computed(() => wallet.account.chainId ?? DEFAULT_CHAIN_ID);
const chainData = computed(() => getChain(currentChainId.value));

const instance = getCurrentInstance();
const uid = instance?.uid ?? Math.floor(Math.random() * 10000);
const sliderId = `stake-slider-${uid}`;
const sliderLabelId = `${sliderId}-label`;
const sliderHelpId = `${sliderId}-help`;
const stakeAmountDescriptionId = `stake-amount-description-${uid}`;
const taxSelectId = `stake-tax-select-${uid}`;
const taxHelpId = `${taxSelectId}-help`;
const floorTaxLabelId = `stake-floor-tax-${uid}`;
const snatchLabelId = `stake-snatch-${uid}`;
const stakeSummaryId = `stake-summary-${uid}`;
const formStatusId = `stake-status-${uid}`;

const minStake = computed(() => statCollection.minStake ?? 0n);
const stakeSlots = ref<string>('0.00');
const supplyFreeze = ref<number>(0);
let debounceTimer: ReturnType<typeof setTimeout>;

watchEffect(() => {
  if (!stake.stakingAmount) {
    supplyFreeze.value = 0;
    return;
  }

  clearTimeout(debounceTimer);
  debounceTimer = setTimeout(async () => {
    stake.stakingAmountShares = await assetsToShares(stake.stakingAmount);
    const stakingAmountSharesNumber = weiToNumber(stake.stakingAmountShares, 18);
    const stakeableSupplyNumber = weiToNumber(statCollection.stakeableSupply, 18);

    if (stakeableSupplyNumber === 0) {
      supplyFreeze.value = 0;
      return;
    }

    supplyFreeze.value = stakingAmountSharesNumber / stakeableSupplyNumber;
  }, 500);
});

watchEffect(() => {
  const slots = supplyFreeze.value * 1000;
  stakeSlots.value = Number.isFinite(slots) ? slots.toFixed(2) : '0.00';
});

const minStakeAmount = computed(() => weiToNumber(minStake.value, 18));

const maxStakeAmount = computed(() => {
  if (wallet.balance?.value) {
    return weiToNumber(wallet.balance.value, 18);
  }
  return 0;
});

const sliderMin = computed(() => {
  const value = Number(minStakeAmount.value || 0);
  return Number.isFinite(value) && value >= 0 ? value : 0;
});

const sliderMax = computed(() => {
  const value = Number(maxStakeAmount.value || 0);
  if (!Number.isFinite(value) || value <= sliderMin.value) {
    return sliderMin.value;
  }
  return value;
});

const isSliderDisabled = computed(() => sliderMax.value <= sliderMin.value);

const sliderStep = computed(() => {
  if (isSliderDisabled.value) {
    return 0.01;
  }
  const range = sliderMax.value - sliderMin.value;
  const step = range / 100;
  if (!Number.isFinite(step) || step <= 0) {
    return 0.01;
  }
  return Number(step.toFixed(4));
});

const currentStakeAmount = computed(() => {
  const value = Number(stake.stakingAmountNumber);
  if (!Number.isFinite(value)) {
    return sliderMin.value;
  }
  return Math.min(Math.max(value, sliderMin.value), sliderMax.value);
});

const sliderPercentage = computed(() => {
  const range = sliderMax.value - sliderMin.value;
  if (range <= 0) {
    return 0;
  }
  const percent = ((currentStakeAmount.value - sliderMin.value) / range) * 100;
  return Math.min(100, Math.max(0, Number(percent.toFixed(2))));
});

watch([sliderMin, sliderMax], ([min, max]) => {
  const value = Number(stake.stakingAmountNumber);
  if (!Number.isFinite(value)) {
    stake.stakingAmountNumber = min;
    return;
  }
  if (value < min) {
    stake.stakingAmountNumber = min;
  } else if (value > max) {
    stake.stakingAmountNumber = max;
  }
});

function formatNumber(value: number, maximumFractionDigits = 2) {
  if (!Number.isFinite(value)) {
    return '0';
  }
  return value.toLocaleString('en-US', {
    minimumFractionDigits: 0,
    maximumFractionDigits,
  });
}

const formattedStakeAmount = computed(() => `${formatNumber(currentStakeAmount.value, 4)} $KRK`);
const formattedBalance = computed(() => formatNumber(maxStakeAmount.value, 4));
const stakeAmountAriaText = computed(() => `Stake ${formatNumber(currentStakeAmount.value, 4)} Kraiken tokens`);

const sliderDescription = computed(() => {
  if (isSliderDisabled.value) {
    return 'Set a stake amount once you have tokens available in your wallet.';
  }
  return `Use arrow keys or type a value between ${formatNumber(sliderMin.value, 4)} and ${formatNumber(sliderMax.value, 4)} $KRK.`;
});

const sliderAnnouncement = computed(() => `Stake amount set to ${formatNumber(currentStakeAmount.value, 4)} $KRK.`);

const taxOptions = computed(() =>
  adjustTaxRate.taxRates.map(option => ({
    index: option.index,
    year: option.year,
    daily: option.daily,
    label: `${option.index} · ${formatNumber(option.year, 2)}% yearly (${option.daily.toFixed(4)}% daily)`,
  }))
);

const selectedTaxOption = computed(() => adjustTaxRate.taxRates.find(option => option.index === taxRateIndex.value) ?? null);

const taxRateDescription = computed(() => {
  if (!selectedTaxOption.value) {
    return 'Select a tax rate to determine your yearly obligation.';
  }
  return `Tax rate index ${selectedTaxOption.value.index} with ${formatNumber(selectedTaxOption.value.year, 2)}% yearly (${selectedTaxOption.value.daily.toFixed(4)}% daily).`;
});

const taxRateAnnouncement = computed(() => {
  if (!selectedTaxOption.value) {
    return 'No tax rate selected.';
  }
  return `Selected tax rate index ${selectedTaxOption.value.index}. You will pay ${formatNumber(selectedTaxOption.value.year, 2)} percent yearly.`;
});

const snatchSelection = useSnatchSelection(demo, taxRateIndex, initialChainId);

const floorTaxDisplay = computed(() => `${formatNumber(snatchSelection.floorTax.value ?? 0, 2)} %`);
const floorTaxHelpText = 'Your tax needs to exceed this value to displace an existing position.';
const snatchPositionsCount = computed(() => snatchSelection.snatchablePositions.value.length);
const positionsBuyoutDisplay = computed(() => formatNumber(snatchPositionsCount.value, 0));
const snatchHelpText = 'Increasing your tax may buy out existing slots. This count updates automatically as you adjust inputs.';

const stakeSummaryText = computed(() => {
  const amount = formatNumber(currentStakeAmount.value, 4);
  const taxText = selectedTaxOption.value
    ? `${formatNumber(selectedTaxOption.value.year, 2)}% yearly tax (index ${selectedTaxOption.value.index})`
    : 'no tax rate selected';
  return `Staking ${amount} $KRK with ${taxText}.`;
});

const snatchSummaryText = computed(() => {
  if (snatchPositionsCount.value > 0) {
    const count = snatchPositionsCount.value;
    const positions = count === 1 ? 'position' : 'positions';
    return `You will snatch ${count} ${positions} at a floor tax of ${floorTaxDisplay.value}.`;
  }
  if (!snatchSelection.openPositionsAvailable.value) {
    return 'No open positions are available at this tax rate. Increase the tax to claim slots from others.';
  }
  return 'No existing positions will be snatched.';
});

const walletSummaryText = computed(() => {
  const address = wallet.account.address;
  if (!address) {
    return 'Receiver wallet unavailable. Connect a wallet to continue.';
  }
  return `Receiver wallet: ${address}.`;
});

const assistiveSummary = computed(() => `${stakeSummaryText.value} ${snatchSummaryText.value} ${walletSummaryText.value}`);

const isStakeAmountTooLow = computed(() => currentStakeAmount.value < minStakeAmount.value);
const hasBalance = computed(() => maxStakeAmount.value > 0);

const actionState = computed(() => {
  if (!hasBalance.value || stake.state === 'NoBalance') {
    return {
      label: 'Insufficient Balance',
      disabled: true,
      variant: 'primary',
      message: 'You need more $KRK to cover the minimum stake amount.',
      tone: 'error' as const,
    };
  }

  if (isStakeAmountTooLow.value) {
    return {
      label: 'Stake Amount Too Low',
      disabled: true,
      variant: 'primary',
      message: `Minimum stake is ${formatNumber(minStakeAmount.value, 4)} $KRK.`,
      tone: 'error' as const,
    };
  }

  if (!snatchSelection.openPositionsAvailable.value && stake.state === 'StakeAble' && snatchPositionsCount.value === 0) {
    return {
      label: 'Tax Rate Too Low',
      disabled: true,
      variant: 'primary',
      message: 'Increase your tax rate to open staking slots.',
      tone: 'error' as const,
    };
  }

  if (stake.state === 'StakeAble' && snatchPositionsCount.value === 0) {
    return {
      label: 'Stake',
      disabled: false,
      variant: 'primary',
      message: 'Ready to stake without snatching other positions.',
      tone: 'status' as const,
    };
  }

  if (stake.state === 'StakeAble' && snatchPositionsCount.value > 0) {
    return {
      label: 'Snatch and Stake',
      disabled: false,
      variant: 'primary',
      message: `Ready to snatch ${snatchPositionsCount.value} position${snatchPositionsCount.value === 1 ? '' : 's'} while staking.`,
      tone: 'status' as const,
    };
  }

  if (stake.state === 'SignTransaction') {
    return {
      label: 'Sign Transaction ...',
      disabled: true,
      variant: 'outlined',
      message: 'Check your wallet to sign the staking transaction.',
      tone: 'status' as const,
    };
  }

  if (stake.state === 'Waiting') {
    return {
      label: 'Waiting ...',
      disabled: true,
      variant: 'outlined',
      message: 'Waiting for the transaction to confirm on-chain.',
      tone: 'status' as const,
    };
  }

  return {
    label: 'Stake',
    disabled: true,
    variant: 'primary',
    message: '',
    tone: 'status' as const,
  };
});

watch(
  minStakeAmount,
  newValue => {
    if (!Number.isFinite(newValue)) {
      return;
    }
    if (stake.stakingAmountNumber === 0 || stake.stakingAmountNumber < newValue) {
      stake.stakingAmountNumber = newValue;
    }
  },
  { immediate: true }
);

watch(maxStakeAmount, newValue => {
  if (!Number.isFinite(newValue)) {
    return;
  }
  if (stake.stakingAmountNumber > newValue) {
    stake.stakingAmountNumber = newValue;
  }
});

function setMaxAmount() {
  const max = sliderMax.value;
  if (Number.isFinite(max)) {
    stake.stakingAmountNumber = max;
  }
}

async function stakeSnatch() {
  if (snatchPositionsCount.value === 0) {
    await stake.snatch(stake.stakingAmount, taxRateIndex.value);
  } else {
    const snatchAblePositionsIds = snatchSelection.snatchablePositions.value.map((p: Position) => p.positionId);
    await stake.snatch(stake.stakingAmount, taxRateIndex.value, snatchAblePositionsIds);
  }
  await loadPositions(currentChainId.value);
  await loadStats(currentChainId.value);
}

async function handleSubmit() {
  if (actionState.value.disabled) {
    return;
  }
  await stakeSnatch();
}

onMounted(async () => {
  await wallet.loadBalance();
  stake.stakingAmountNumber = minStakeAmount.value;
});
</script>

<style lang="sass">
.hold-inner
    .stake-inner
        display: flex
        flex-direction: column
        gap: 24px

.unaudited-notice
    padding: 12px 16px
    background-color: rgba(255, 200, 0, 0.1)
    border-left: 3px solid #ffc800
    border-radius: 4px
    font-size: 14px
    color: #ffc800
    text-align: center

.uniswap-link-banner
    padding: 14px 18px
    background: linear-gradient(90deg, rgba(117, 80, 174, 0.15) 0%, rgba(117, 80, 174, 0.05) 100%)
    border-radius: 8px
    display: flex
    align-items: center
    justify-content: center
    gap: 12px
    font-size: 15px

    span
        color: #9A9898

    .uniswap-link
        color: #7550AE
        text-decoration: none
        font-weight: 600
        transition: color 0.2s ease

        &:hover
            color: #9370DB
            text-decoration: underline

.stake-form
    position: relative
    display: flex
    flex-direction: column
    gap: 24px

.form-group
    display: flex
    flex-direction: column
    gap: 8px

.input-range
    position: relative
    display: flex
    align-items: center
    gap: 16px
    margin-top: 8px
    &__control
        width: 100%
        height: 7px
        border-radius: 12px
        appearance: none
        background: linear-gradient(90deg, #7550AE var(--slider-percentage, 0%), #000000 var(--slider-percentage, 0%))
        outline: none
        accent-color: #7550AE
    &__control::-webkit-slider-thumb
        appearance: none
        width: 24px
        height: 24px
        border-radius: 50%
        background-color: #7550AE
        cursor: pointer
    &__control::-moz-range-thumb
        width: 24px
        height: 24px
        border-radius: 50%
        background-color: #7550AE
        cursor: pointer
    &__value
        min-width: 120px
        text-align: right
        font-weight: 600
    &--disabled
        opacity: .6

.input-range__help
    font-size: 14px
    color: #9A9898

.sr-only
    position: absolute
    width: 1px
    height: 1px
    padding: 0
    margin: -1px
    overflow: hidden
    clip: rect(0, 0, 0, 0)
    border: 0

.formular
    display: flex
    flex-direction: column
    gap: 16px
    .row
        display: flex
        gap: 12px
        flex-wrap: wrap
        >*
            flex: 1 1 30%
    .row-1
        align-items: flex-start
        >:nth-child(2)
            flex: 0 0 auto

.staking-amount
    .f-input--details
        display: flex
        gap: 8px
        justify-content: flex-end
        color: #9A9898
        font-size: 14px
        .staking-amount-max
            font-weight: 600
            background: none
            border: none
            color: inherit
            padding: 0
            cursor: pointer
            text-decoration: underline
            &:focus-visible
                outline: 2px solid #7550AE
                outline-offset: 2px

.stake-arrow
    align-self: center
    font-size: 30px

.form-field
    display: flex
    flex-direction: column
    gap: 8px

.field-label
    display: inline-flex
    align-items: center
    gap: 8px
    font-weight: 600

.tax-helper-text
    padding: 8px 12px
    background-color: rgba(117, 80, 174, 0.15)
    border-radius: 6px
    font-size: 13px
    color: #9A9898
    margin-bottom: 4px

.tax-select-wrapper
    position: relative
    display: flex
    align-items: center

.tax-select
    width: 100%
    padding: 12px
    border-radius: 12px
    border: 1px solid black
    background-color: #2D2D2D
    color: #FFFFFF
    appearance: none
    font-size: 16px

.tax-select__icon
    position: absolute
    right: 12px
    pointer-events: none
    color: #FFFFFF

.field-help
    font-size: 14px
    color: #9A9898

.summary-field
    .form-field__value
        font-size: 18px
        font-weight: 600

.stake-summary
    display: flex
    flex-direction: column
    gap: 8px
    padding: 16px
    border-radius: 12px
    background-color: rgba(117, 80, 174, 0.12)
    .stake-summary__heading
        margin: 0
        font-size: 16px

.form-status
    min-height: 24px
    font-size: 14px

@media (max-width: 767px)
    .formular .row
        flex-direction: column
        >*
            flex: 1 1 auto

.contract-addresses
    padding: 16px
    background-color: rgba(45, 45, 45, 0.3)
    border-radius: 8px
    font-size: 14px

    &__title
        font-weight: 600
        margin-bottom: 12px
        color: #FFFFFF

    &__list
        display: flex
        flex-direction: column
        gap: 8px

.contract-address
    display: flex
    flex-direction: column
    gap: 4px

    &__label
        color: #9A9898
        font-size: 12px

    &__link
        color: #7550AE
        text-decoration: none
        word-break: break-all
        font-family: monospace
        font-size: 13px

        &:hover
            text-decoration: underline
</style>
