import * as v from 'valibot';

import { pick } from '../utils';

import { type Account, accountSchema } from './account';
import { customEmojiSchema } from './custom-emoji';
import { emojiReactionSchema } from './emoji-reaction';
import { filterResultSchema } from './filter-result';
import { groupSchema } from './group';
import { interactionPolicySchema } from './interaction-policy';
import { mediaAttachmentSchema } from './media-attachment';
import { mentionSchema } from './mention';
import { pollSchema } from './poll';
import { previewCardSchema } from './preview-card';
import { type Quote, quoteSchema, type ShallowQuote, shallowQuoteSchema } from './quote';
import { quoteApprovalSchema } from './quote-approval';
import { rssFeedSchema } from './rss-feed';
import { tagSchema } from './tag';
import { translationSchema } from './translation';
import { datetimeSchema, filteredArray } from './utils';

const locationSchema = v.object({
  name: v.fallback(v.string(), ''),
  url: v.fallback(v.pipe(v.string(), v.url()), ''),
  latitude: v.fallback(v.nullable(v.number()), null),
  longitude: v.fallback(v.nullable(v.number()), null),
  street: v.fallback(v.string(), ''),
  postal_code: v.fallback(v.string(), ''),
  locality: v.fallback(v.string(), ''),
  region: v.fallback(v.string(), ''),
  country: v.fallback(v.string(), ''),
});

const statusEventSchema = v.object({
  name: v.fallback(v.string(), ''),
  start_time: v.fallback(v.nullable(datetimeSchema), null),
  end_time: v.fallback(v.nullable(datetimeSchema), null),
  join_mode: v.fallback(v.nullable(v.picklist(['free', 'restricted', 'invite', 'external'])), null),
  participants_count: v.fallback(v.number(), 0),
  participation_request_count: v.fallback(v.number(), 0),
  location: v.fallback(v.nullable(locationSchema), null),
  join_state: v.fallback(v.nullable(v.picklist(['pending', 'reject', 'accept'])), null),
});

/** @see {@link https://docs.joinmastodon.org/entities/Status/} */
const baseStatusSchema = v.object({
  id: v.string(),
  uri: v.fallback(v.pipe(v.string(), v.url()), ''),
  created_at: v.fallback(datetimeSchema, new Date().toISOString()),
  account: v.pipe(
    v.unknown(),
    v.transform((account) => {
      if ((window as any).__PL_API_FALLBACK_ACCOUNT && JSON.stringify(account) === '{}')
        return (window as any).__PL_API_FALLBACK_ACCOUNT;
      return account;
    }),
    accountSchema,
  ),
  content: v.fallback(
    v.pipe(
      v.string(),
      v.transform((note) => (note === '<p></p>' ? '' : note)),
    ),
    '',
  ),
  visibility: v.fallback(v.string(), 'public'),
  sensitive: v.pipe(v.unknown(), v.transform(Boolean)),
  spoiler_text: v.fallback(v.string(), ''),
  media_attachments: filteredArray(mediaAttachmentSchema),
  application: v.fallback(
    v.nullable(
      v.object({
        name: v.string(),
        website: v.fallback(v.nullable(v.pipe(v.string(), v.url())), null),
      }),
    ),
    null,
  ),
  mentions: filteredArray(mentionSchema),
  tags: filteredArray(tagSchema),
  emojis: filteredArray(customEmojiSchema),
  reblogs_count: v.fallback(v.number(), 0),
  favourites_count: v.fallback(v.number(), 0),
  replies_count: v.fallback(v.number(), 0),
  url: v.fallback(v.pipe(v.string(), v.url()), ''),
  in_reply_to_id: v.fallback(v.nullable(v.string()), null),
  in_reply_to_account_id: v.fallback(v.nullable(v.string()), null),
  poll: v.fallback(v.nullable(pollSchema), null),
  card: v.fallback(v.nullable(previewCardSchema), null),
  language: v.fallback(v.nullable(v.string()), null),
  text: v.fallback(v.nullable(v.string()), null),
  edited_at: v.fallback(v.nullable(datetimeSchema), null),
  favourited: v.fallback(v.pipe(v.unknown(), v.transform(Boolean)), false),
  reblogged: v.fallback(v.pipe(v.unknown(), v.transform(Boolean)), false),
  muted: v.fallback(v.pipe(v.unknown(), v.transform(Boolean)), false),
  bookmarked: v.fallback(v.pipe(v.unknown(), v.transform(Boolean)), false),
  pinned: v.fallback(v.pipe(v.unknown(), v.transform(Boolean)), false),
  filtered: filteredArray(filterResultSchema),
  approval_status: v.fallback(v.nullable(v.picklist(['pending', 'approval', 'rejected'])), null),
  group: v.fallback(v.nullable(groupSchema), null),
  scheduled_at: v.fallback(v.null(), null),
  quote_approval: v.fallback(v.nullable(quoteApprovalSchema), null),

  quote_id: v.fallback(v.nullable(v.string()), null),
  local: v.fallback(v.optional(v.boolean()), undefined),
  conversation_id: v.fallback(v.optional(v.string()), undefined),
  direct_conversation_id: v.fallback(v.optional(v.string()), undefined),
  in_reply_to_account_acct: v.fallback(v.optional(v.string()), undefined),
  expires_at: v.fallback(v.optional(datetimeSchema), undefined),
  thread_muted: v.fallback(v.optional(v.boolean()), undefined),
  emoji_reactions: filteredArray(emojiReactionSchema),
  parent_visible: v.fallback(v.optional(v.boolean()), undefined),
  pinned_at: v.fallback(v.nullable(datetimeSchema), null),
  quote_visible: v.fallback(v.optional(v.boolean()), undefined),
  quote_url: v.fallback(v.optional(v.string()), undefined),
  quotes_count: v.fallback(v.number(), 0),
  bookmark_folder: v.fallback(v.nullable(v.string()), null),
  list_id: v.fallback(v.nullable(v.number()), null),

  event: v.fallback(v.nullable(statusEventSchema), null),
  translation: v.fallback(v.union([v.nullable(translationSchema), v.literal(false)]), null),
  rss_feed: v.fallback(v.nullable(rssFeedSchema), null),

  content_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null),
  text_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null),
  spoiler_text_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null),

  dislikes_count: v.fallback(v.number(), 0),
  disliked: v.fallback(v.pipe(v.unknown(), v.transform(Boolean)), false),

  local_only: v.fallback(v.optional(v.boolean()), undefined),
  interaction_policy: interactionPolicySchema,

  content_type: v.fallback(v.nullable(v.string()), null),
  location: v.fallback(v.nullable(locationSchema), null),
});

const preprocess = (status: any) => {
  if (!status) return null;

  let quote:
    | {
        state: string;
        quoted_status: any;
      }
    | {
        state: string;
        quoted_status_id: string;
      }
    | null = null;

  const quotedStatus = status.quote ?? status.pleroma?.quote;
  let quotedStatusId = quotedStatus?.id ?? status.quote_id ?? status.pleroma?.quote_id;
  if (quotedStatus?.state) {
    quote = quotedStatus;
    quotedStatusId = quotedStatus.quoted_status?.id || quotedStatus.quoted_status_id;
  } else if (quotedStatus) {
    quote = {
      state: 'accepted',
      quoted_status: quotedStatus,
    };
  } else {
    if (quotedStatusId) {
      quote = {
        state: 'accepted',
        quoted_status_id: quotedStatusId,
      };
    }
  }

  status = {
    // @ts-expect-error only overrides if present in pleroma object
    emoji_reactions: status.reactions,
    ...pick(status.pleroma || {}, [
      'local',
      'conversation_id',
      'direct_conversation_id',
      'in_reply_to_account_acct',
      'expires_at',
      'thread_muted',
      'emoji_reactions',
      'parent_visible',
      'pinned_at',
      'quote_url',
      'quote_visible',
      'quotes_count',
      'bookmark_folder',
      'list_id',

      'event',
      'translation',
      'rss_feed',
      'location',
    ]),
    ...pick(status.friendica || {}, ['dislikes_count', 'disliked']),
    ...status,
    quote,
    quote_id: quotedStatusId,
  };

  if (!status.interaction_policy && status.comments_disabled === true) {
    status.interaction_policy = {
      can_reply: {
        always: ['author'],
      },
    };
  }

  return status;
};

/**
 * @category Schemas
 */
const statusSchema: v.BaseSchema<any, Status, v.BaseIssue<unknown>> = v.pipe(
  v.any(),
  v.transform(preprocess),
  v.object({
    ...baseStatusSchema.entries,
    reblog: v.fallback(v.nullable(v.lazy(() => statusSchema)), null),

    quote: v.fallback(v.nullable(v.lazy(() => v.union([quoteSchema, shallowQuoteSchema]))), null),
  }),
) as any;

/**
 * @category Schemas
 */
const statusWithoutAccountSchema = v.pipe(
  v.any(),
  v.transform(preprocess),
  v.object({
    ...v.omit(baseStatusSchema, ['account']).entries,
    account: v.fallback(v.nullable(accountSchema), null),
    reblog: v.fallback(v.nullable(v.lazy(() => statusSchema)), null),

    quote: v.fallback(v.nullable(v.lazy(() => v.union([quoteSchema, shallowQuoteSchema]))), null),
  }),
);

const partialStatusSchema = v.partial(
  v.object({
    ...baseStatusSchema.entries,
    reblog: v.fallback(v.nullable(v.lazy(() => statusSchema)), null),
    quote: v.fallback(v.nullable(v.lazy(() => v.union([quoteSchema, shallowQuoteSchema]))), null),
  }),
);

/**
 * @category Entity types
 */
type StatusWithoutAccount = Omit<v.InferOutput<typeof baseStatusSchema>, 'account'> & {
  account: Account | null;
  reblog: Status | null;
  quote: Quote | ShallowQuote | null;
};

/**
 * @category Entity types
 */
type Status = v.InferOutput<typeof baseStatusSchema> & {
  reblog: Status | null;
  quote: Quote | ShallowQuote | null;
};

export {
  statusSchema,
  statusWithoutAccountSchema,
  partialStatusSchema,
  type Status,
  type StatusWithoutAccount,
};
