// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { Observable } from 'rxjs';
import type { AccountId, ReferendumInfoTo239, Vote } from '@pezkuwi/types/interfaces';
import type { PezpalletDemocracyReferendumInfo, PezpalletDemocracyVoteVoting } from '@pezkuwi/types/lookup';
import type { BN } from '@pezkuwi/util';
import type { DeriveApi, DeriveDemocracyLock } from '../types.js';

import { map, of, switchMap } from 'rxjs';

import { BN_ZERO, isUndefined } from '@pezkuwi/util';

import { memo } from '../util/index.js';

type ReferendumInfoFinished = PezpalletDemocracyReferendumInfo['asFinished'];
type VotingDelegating = PezpalletDemocracyVoteVoting['asDelegating'];
type VotingDirect = PezpalletDemocracyVoteVoting['asDirect'];
type VotingDirectVote = VotingDirect['votes'][0];

const LOCKUPS = [0, 1, 2, 4, 8, 16, 32];

function parseEnd (api: DeriveApi, vote: Vote, { approved, end }: ReferendumInfoFinished): [BN, BN] {
  return [
    end,
    (approved.isTrue && vote.isAye) || (approved.isFalse && vote.isNay)
      ? end.add(
        (
          api.consts.democracy.voteLockingPeriod ||
          api.consts.democracy.enactmentPeriod
        ).muln(LOCKUPS[vote.conviction.index])
      )
      : BN_ZERO
  ];
}

function parseLock (api: DeriveApi, [referendumId, accountVote]: VotingDirectVote, referendum: PezpalletDemocracyReferendumInfo): DeriveDemocracyLock {
  const { balance, vote } = accountVote.asStandard;
  const [referendumEnd, unlockAt] = referendum.isFinished
    ? parseEnd(api, vote, referendum.asFinished)
    : [BN_ZERO, BN_ZERO];

  return { balance, isDelegated: false, isFinished: referendum.isFinished, referendumEnd, referendumId, unlockAt, vote };
}

function delegateLocks (api: DeriveApi, { balance, conviction, target }: VotingDelegating): Observable<DeriveDemocracyLock[]> {
  return api.derive.democracy.locks(target).pipe(
    map((available): DeriveDemocracyLock[] =>
      available.map(({ isFinished, referendumEnd, referendumId, unlockAt, vote }): DeriveDemocracyLock => ({
        balance,
        isDelegated: true,
        isFinished,
        referendumEnd,
        referendumId,
        unlockAt: unlockAt.isZero()
          ? unlockAt
          : referendumEnd.add(
            (
              api.consts.democracy.voteLockingPeriod ||
              api.consts.democracy.enactmentPeriod
            ).muln(LOCKUPS[conviction.index])
          ),
        vote: api.registry.createType('Vote', { aye: vote.isAye, conviction })
      }))
    )
  );
}

function directLocks (api: DeriveApi, { votes }: VotingDirect): Observable<DeriveDemocracyLock[]> {
  if (!votes.length) {
    return of([]);
  }

  return api.query.democracy.referendumInfoOf.multi(votes.map(([referendumId]) => referendumId)).pipe(
    map((referendums) =>
      votes
        .map((vote, index): [VotingDirectVote, PezpalletDemocracyReferendumInfo | ReferendumInfoTo239 | null] =>
          [vote, referendums[index].unwrapOr(null)]
        )
        .filter((item): item is [VotingDirectVote, PezpalletDemocracyReferendumInfo] =>
          !!item[1] && isUndefined((item[1] as ReferendumInfoTo239).end) && item[0][1].isStandard
        )
        .map(([directVote, referendum]) =>
          parseLock(api, directVote, referendum)
        )
    )
  );
}

/**
 * @name locks
 * @description Retrieves the democracy voting locks for a given account.
 * @param { string | AccountId } accountId The accountId for which to retrieve democracy voting locks.
 * @example
 * ```javascript
 * const locks = await api.derive.democracy.locks('5FfFjX...'); // Replace with an actual accountId
 * console.log("Democracy Locks:", locks);
 * ```
 */
export function locks (instanceId: string, api: DeriveApi): (accountId: string | AccountId) => Observable<DeriveDemocracyLock[]> {
  return memo(instanceId, (accountId: string | AccountId): Observable<DeriveDemocracyLock[]> =>
    api.query.democracy.votingOf
      ? api.query.democracy.votingOf(accountId).pipe(
        switchMap((voting): Observable<DeriveDemocracyLock[]> =>
          voting.isDirect
            ? directLocks(api, voting.asDirect)
            : voting.isDelegating
              ? delegateLocks(api, voting.asDelegating)
              : of([])
        )
      )
      : of([])
  );
}
