import * as v from 'valibot';

import {
  accountSchema,
  antennaSchema,
  circleSchema,
  familiarFollowersSchema,
  featuredTagSchema,
  listSchema,
  relationshipSchema,
  reportSchema,
  scrobbleSchema,
  statusSchema,
} from '../entities';
import { filteredArray } from '../entities/utils';
import { ICESHRIMP_NET, PIXELFED, PLEROMA } from '../features';
import { type RequestMeta } from '../request';

import type { PlApiBaseClient } from '../client-base';
import type {
  CreateScrobbleParams,
  FollowAccountParams,
  GetAccountEndorsementsParams,
  GetAccountFavouritesParams,
  GetAccountFollowersParams,
  GetAccountFollowingParams,
  GetAccountParams,
  GetAccountStatusesParams,
  GetAccountSubscribersParams,
  GetRelationshipsParams,
  GetScrobblesParams,
  ReportAccountParams,
  SearchAccountParams,
} from '../params/accounts';

type EmptyObject = Record<string, never>;

const accounts = (client: PlApiBaseClient) => ({
  /**
   * Get account
   * View information about a profile.
   * @see {@link https://docs.joinmastodon.org/methods/accounts/#get}
   */
  getAccount: async (accountId: string, params?: GetAccountParams) => {
    const response = await client.request(`/api/v1/accounts/${accountId}`, { params });

    return v.parse(accountSchema, response.json);
  },

  /**
   * Get multiple accounts
   * View information about multiple profiles.
   *
   * Requires features{@link Features.getAccounts}.
   * @see {@link https://docs.joinmastodon.org/methods/accounts/#index}
   */
  getAccounts: async (accountId: string[]) => {
    const response = await client.request('/api/v1/accounts', { params: { id: accountId } });

    return v.parse(filteredArray(accountSchema), response.json);
  },

  /**
   * Get account’s statuses
   * Statuses posted to the given account.
   * @see {@link https://docs.joinmastodon.org/methods/accounts/#statuses}
   */
  getAccountStatuses: (accountId: string, params?: GetAccountStatusesParams) =>
    client.paginatedGet(`/api/v1/accounts/${accountId}/statuses`, { params }, statusSchema),

  /**
   * Get account’s followers
   * Accounts which follow the given account, if network is not hidden by the account owner.
   * @see {@link https://docs.joinmastodon.org/methods/accounts/#followers}
   */
  getAccountFollowers: (accountId: string, params?: GetAccountFollowersParams) =>
    client.paginatedGet(`/api/v1/accounts/${accountId}/followers`, { params }, accountSchema),

  /**
   * Get account’s following
   * Accounts which the given account is following, if network is not hidden by the account owner.
   * @see {@link https://docs.joinmastodon.org/methods/accounts/#following}
   */
  getAccountFollowing: (accountId: string, params?: GetAccountFollowingParams) =>
    client.paginatedGet(`/api/v1/accounts/${accountId}/following`, { params }, accountSchema),

  /**
   * Subscriptions to the given user.
   *
   * Requires features{@link Features.subscriptions}.
   */
  getAccountSubscribers: (accountId: string, params?: GetAccountSubscribersParams) =>
    client.paginatedGet(`/api/v1/accounts/${accountId}/subscribers`, { params }, accountSchema),

  /**
   * Get account’s featured tags
   * Tags featured by this account.
   *
   * Requires features{@link Features.featuredTags}.
   * @see {@link https://docs.joinmastodon.org/methods/accounts/#featured_tags}
   */
  getAccountFeaturedTags: async (accountId: string) => {
    const response = await client.request(`/api/v1/accounts/${accountId}/featured_tags`);

    return v.parse(filteredArray(featuredTagSchema), response.json);
  },

  /**
   * Get lists containing this account
   * User lists that you have added this account to.
   * @see {@link https://docs.joinmastodon.org/methods/accounts/#lists}
   */
  getAccountLists: async (accountId: string) => {
    const response = await client.request(`/api/v1/accounts/${accountId}/lists`);

    return v.parse(filteredArray(listSchema), response.json);
  },

  /**
   * Get antennas containing this account
   * User antennas that you have added this account to.
   * Requires features{@link Features.antennas}.
   */
  getAccountAntennas: async (accountId: string) => {
    const response = await client.request(`/api/v1/accounts/${accountId}/antennas`);

    return v.parse(filteredArray(antennaSchema), response.json);
  },

  /**
   * Get antennas excluding this account
   * Requires features{@link Features.antennas}.
   */
  getAccountExcludeAntennas: async (accountId: string) => {
    const response = await client.request(`/api/v1/accounts/${accountId}/exclude_antennas`);

    return v.parse(filteredArray(circleSchema), response.json);
  },

  /**
   * Get circles including this account
   * User circles that you have added this account to.
   * Requires features{@link Features.circles}.
   */
  getAccountCircles: async (accountId: string) => {
    const response = await client.request(`/api/v1/accounts/${accountId}/circles`);

    return v.parse(filteredArray(antennaSchema), response.json);
  },

  /**
   * Follow account
   * Follow the given account. Can also be used to update whether to show reblogs or enable notifications.
   * @see {@link https://docs.joinmastodon.org/methods/accounts/#follow}
   */
  followAccount: async (accountId: string, params?: FollowAccountParams) => {
    const response = await client.request(`/api/v1/accounts/${accountId}/follow`, {
      method: 'POST',
      body: params,
    });

    return v.parse(relationshipSchema, response.json);
  },

  /**
   * Unfollow account
   * Unfollow the given account.
   * @see {@link https://docs.joinmastodon.org/methods/accounts/#unfollow}
   */
  unfollowAccount: async (accountId: string) => {
    const response = await client.request(`/api/v1/accounts/${accountId}/unfollow`, {
      method: 'POST',
    });

    return v.parse(relationshipSchema, response.json);
  },

  /**
   * Remove account from followers
   * Remove the given account from your followers.
   * @see {@link https://docs.joinmastodon.org/methods/accounts/#remove_from_followers}
   */
  removeAccountFromFollowers: async (accountId: string) => {
    const response = await client.request(`/api/v1/accounts/${accountId}/remove_from_followers`, {
      method: 'POST',
    });

    return v.parse(relationshipSchema, response.json);
  },

  /**
   * Feature account on your profile
   * Add the given account to the user’s featured profiles.
   * @see {@link https://docs.joinmastodon.org/methods/accounts/#pin}
   */
  pinAccount: async (accountId: string) => {
    const response = await client.request(`/api/v1/accounts/${accountId}/pin`, { method: 'POST' });

    return v.parse(relationshipSchema, response.json);
  },

  /**
   * Unfeature account from profile
   * Remove the given account from the user’s featured profiles.
   * @see {@link https://docs.joinmastodon.org/methods/accounts/#unpin}
   */
  unpinAccount: async (accountId: string) => {
    const response = await client.request(`/api/v1/accounts/${accountId}/unpin`, {
      method: 'POST',
    });

    return v.parse(relationshipSchema, response.json);
  },

  /**
   * Set private note on profile
   * Sets a private note on a user.
   * @see {@link https://docs.joinmastodon.org/methods/accounts/#note}
   */
  updateAccountNote: async (accountId: string, comment: string) => {
    const response = await client.request(`/api/v1/accounts/${accountId}/note`, {
      method: 'POST',
      body: { comment },
    });

    return v.parse(relationshipSchema, response.json);
  },

  /**
   * Check relationships to other accounts
   * Find out whether a given account is followed, blocked, muted, etc.
   * @see {@link https://docs.joinmastodon.org/methods/accounts/#relationships}
   */
  getRelationships: async (accountIds: string[], params?: GetRelationshipsParams) => {
    const response = await client.request('/api/v1/accounts/relationships', {
      params: { ...params, id: accountIds },
    });

    return v.parse(filteredArray(relationshipSchema), response.json);
  },

  /**
   * Find familiar followers
   * Obtain a list of all accounts that follow a given account, filtered for accounts you follow.
   *
   * Requires features{@link Features.familiarFollowers}.
   * @see {@link https://docs.joinmastodon.org/methods/accounts/#familiar_followers}
   */
  getFamiliarFollowers: async (accountIds: string[]) => {
    let response: any;

    if (client.features.version.software === PIXELFED) {
      const settledResponse = await Promise.allSettled(
        accountIds.map(async (accountId) => {
          const accounts = (await client.request(`/api/v1.1/accounts/mutuals/${accountId}`)).json;

          return {
            id: accountId,
            accounts,
          };
        }),
      );

      response = settledResponse.map((result, index) =>
        result.status === 'fulfilled'
          ? result.value
          : {
              id: accountIds[index],
              accounts: [],
            },
      );
    } else {
      response = (
        await client.request('/api/v1/accounts/familiar_followers', { params: { id: accountIds } })
      ).json;
    }

    return v.parse(filteredArray(familiarFollowersSchema), response);
  },

  /**
   * Search for matching accounts
   * Search for matching accounts by username or display name.
   * @see {@link https://docs.joinmastodon.org/methods/accounts/#search}
   */
  searchAccounts: async (q: string, params?: SearchAccountParams, meta?: RequestMeta) => {
    const response = await client.request('/api/v1/accounts/search', {
      ...meta,
      params: { ...params, q },
    });

    return v.parse(filteredArray(accountSchema), response.json);
  },

  /**
   * Lookup account ID from Webfinger address
   * Quickly lookup a username to see if it is available, skipping WebFinger resolution.

   * Requires features{@link Features.accountLookup}.
   * @see {@link https://docs.joinmastodon.org/methods/accounts/#lookup}
   */
  lookupAccount: async (acct: string, meta?: RequestMeta) => {
    const response = await client.request('/api/v1/accounts/lookup', { ...meta, params: { acct } });

    return v.parse(accountSchema, response.json);
  },

  /**
   * File a report
   * @see {@link https://docs.joinmastodon.org/methods/reports/#post}
   */
  reportAccount: async (accountId: string, params: ReportAccountParams) => {
    const response = await client.request('/api/v1/reports', {
      method: 'POST',
      body: { ...params, account_id: accountId },
    });

    return v.parse(reportSchema, response.json);
  },

  /**
   * Endorsements
   * Returns endorsed accounts
   *
   * Requires features{@link Features.accountEndorsements}.
   * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#apiv1pleromaaccountsidendorsements}
   * @see {@link https://docs.joinmastodon.org/methods/accounts/endorsements}
   */
  getAccountEndorsements: (accountId: string, params?: GetAccountEndorsementsParams) =>
    client.paginatedGet(
      `/api/v1/${[PLEROMA].includes(client.features.version.software as string) ? 'pleroma/' : ''}accounts/${accountId}/endorsements`,
      { params },
      accountSchema,
    ),

  /**
   * Birthday reminders
   * Birthday reminders about users you follow.
   *
   * Requires features{@link Features.birthdays}.
   */
  getBirthdays: async (day: number, month: number) => {
    const response = await client.request('/api/v1/pleroma/birthdays', { params: { day, month } });

    return v.parse(filteredArray(accountSchema), response.json);
  },

  /**
   * Returns favorites timeline of any user
   *
   * Requires features{@link Features.publicFavourites}.
   * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#apiv1pleromaaccountsidfavourites}
   */
  getAccountFavourites: (accountId: string, params?: GetAccountFavouritesParams) =>
    client.paginatedGet(
      `/api/v1/pleroma/accounts/${accountId}/favourites`,
      { params },
      statusSchema,
    ),

  /**
   * Interact with profile or status from remote account
   *
   * Requires features{@link Features.remoteInteractions}.
   * @param ap_id - Profile or status ActivityPub ID
   * @param profile - Remote profile webfinger
   * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#apiv1pleromaremote_interaction}
   */
  remoteInteraction: async (ap_id: string, profile: string) => {
    const response = await client.request('/api/v1/pleroma/remote_interaction', {
      method: 'POST',
      body: { ap_id, profile },
    });

    if (response.json?.error) throw response.json.error;

    return v.parse(
      v.object({
        url: v.string(),
      }),
      response.json,
    );
  },

  /**
   * Bite the given user.
   *
   * Requires features{@link Features.bites}.
   * @see {@link https://github.com/purifetchi/Toki/blob/master/Toki/Controllers/MastodonApi/Bite/BiteController.cs}
   */
  biteAccount: async (accountId: string) => {
    let response;
    switch (client.features.version.software) {
      case ICESHRIMP_NET:
        response = await client.request<EmptyObject>('/api/v1/bite', {
          method: 'POST',
          body: accountId,
        });
        break;
      default:
        response = await client.request<EmptyObject>('/api/v1/bite', {
          method: 'POST',
          params: { id: accountId },
        });
        break;
    }

    return response.json;
  },

  /**
   * Requests a list of current and recent Listen activities for an account
   *
   * Requires features{@link Features.scrobbles}
   * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#get-apiv1pleromaaccountsidscrobbles}
   */
  getScrobbles: (accountId: string, params?: GetScrobblesParams) =>
    client.paginatedGet(
      `/api/v1/pleroma/accounts/${accountId}/scrobbles`,
      { params },
      scrobbleSchema,
    ),

  /**
   * Creates a new Listen activity for an account
   *
   * Requires features{@link Features.scrobbles}
   * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#post-apiv1pleromascrobble}
   */
  createScrobble: async (params: CreateScrobbleParams) => {
    if (params.external_link) (params as any).externalLink = params.external_link;

    const response = await client.request('/api/v1/pleroma/scrobble', { body: params });

    return v.parse(scrobbleSchema, response.json);
  },

  /**
   * Load latest activities from outbox
   *
   * Requires features{@link Features.loadActivities}
   */
  loadActivities: async (accountId: string) => {
    const response = await client.request<EmptyObject>(
      `/api/v1/accounts/${accountId}/load_activities`,
      { method: 'POST' },
    );

    return response.json;
  },
});

export { accounts };
