import type { ComposeSettings, InstanceV1, Visibility } from 'src/lib/api-types'
import type { InstanceCapabilities } from 'src/lib/capabilities'

export type MediaLimits = {
  maxImageBytes: number
  maxVideoBytes: number
  maxAttachments: number
  maxAltTextLength: number
  maxCharacters: number
  defaultScope: Visibility
  quotePolicy: string
  // Mastodon Pass §9.3.17: when set, the composer's character counter
  // reserves this many characters per URL (typically 23 on Mastodon)
  // regardless of actual URL length. Null on Pixelfed (raw counting).
  charactersReservedPerUrl: number | null
  // Mastodon Pass §4.1: text-only posts are valid on Mastodon; Pixelfed's
  // composer has always required at least one media attachment. The hook
  // surfaces this so the post-button gating and the empty-rail tap-to-add-media
  // UI both branch from the same source of truth.
  mediaRequired: boolean
  // Compose Redesign Phase 1: spoiler/CW text cap. Hardcoded server-side at
  // 140 chars on Pixelfed (`ApiV1Controller.php:3735`); Mastodon has no hard
  // cap on `spoiler_text` and the composer's CW input drops the counter
  // there. Carried as a number rather than `number | null` because the
  // Pixelfed cap is universal on that server type — Mastodon paths simply
  // don't render the counter.
  maxSpoilerLength: number
  // Compose Redesign Phase 1: per-hashtag length cap before the server's
  // lexer silently skips the tag (post still publishes; tag is not indexed).
  // Hardcoded at 124 chars on Pixelfed (`StatusEntityLexer.php:123`); not
  // advertised by either server. Mirrored to Mastodon at the same value
  // because Mastodon's `Tag` model uses a near-identical regex and the cost
  // of a false-positive warning is lower than a silent-skip surprise.
  maxHashtagLength: number
}

const FIFTEEN_MB = 15 * 1024 * 1024
const DEFAULT_MAX_IMAGE_BYTES = 15 * 1024 * 1024
const DEFAULT_MAX_ATTACHMENTS = 4
// Compose Redesign Phase 1: Pixelfed's actual default is 1000
// (`config/pixelfed.php:286`, `PF_MEDIA_MAX_ALTTEXT_LENGTH`); the resolver
// reads `compose?.max_altext_length` first so the live server value wins
// when advertised, and this fallback only fires for cold caches or
// mis-configured instances. Was 1500 historically, which over-promised
// against the 422-rejecting server cap.
const DEFAULT_MAX_ALT = 1000
const DEFAULT_MAX_CHARS = 500
const DEFAULT_MAX_SPOILER = 140
const DEFAULT_MAX_HASHTAG = 124

// Mastodon Pass §9.3.17: typical `characters_reserved_per_url`. Used as the
// Mastodon fallback when the capability cache is cold and the live instance
// has not yet been probed.
const MASTODON_DEFAULT_URL_RESERVATION = 23

export function getMediaLimits(
  instance: InstanceV1 | null | undefined,
  compose: ComposeSettings | null | undefined
): MediaLimits {
  const cfg = instance?.configuration
  const imageLimit = cfg?.media_attachments?.image_size_limit
  const videoLimit = cfg?.media_attachments?.video_size_limit
  return {
    maxImageBytes:
      typeof imageLimit === 'number' && imageLimit > 0
        ? imageLimit
        : DEFAULT_MAX_IMAGE_BYTES,
    maxVideoBytes:
      typeof videoLimit === 'number' && videoLimit > 0 ? videoLimit : FIFTEEN_MB,
    maxAttachments:
      cfg?.statuses?.max_media_attachments ??
      compose?.max_media_attachments ??
      DEFAULT_MAX_ATTACHMENTS,
    maxAltTextLength:
      Number(compose?.max_altext_length ?? DEFAULT_MAX_ALT) || DEFAULT_MAX_ALT,
    maxCharacters: cfg?.statuses?.max_characters ?? DEFAULT_MAX_CHARS,
    defaultScope: (compose?.default_scope ?? 'public') as Visibility,
    quotePolicy:
      typeof compose?.quote_policy === 'string' && compose.quote_policy
        ? compose.quote_policy
        : 'public',
    charactersReservedPerUrl: null,
    mediaRequired: true,
    maxSpoilerLength: DEFAULT_MAX_SPOILER,
    maxHashtagLength: DEFAULT_MAX_HASHTAG,
  }
}

/**
 * Mastodon Pass §4.1: Mastodon's composer pulls limits from the
 * `/api/v2/instance.configuration` capability cache (Phase 2). The cache is
 * never blocking — `defaultCapabilities('mastodon')` is the spec-default
 * fallback, which is fine to ship to the composer until the live values
 * land. Image/video byte limits are not advertised reliably across Mastodon
 * deployments so we keep the same client-side defaults the Pixelfed path
 * uses (the server will reject oversized uploads regardless).
 *
 * `defaultScope` is the spec default; the hook layer may override it from
 * the active account's `source.privacy` per plan §9.3.18.
 */
export function getMediaLimitsForMastodon(
  capabilities: InstanceCapabilities
): MediaLimits {
  return {
    maxImageBytes: DEFAULT_MAX_IMAGE_BYTES,
    maxVideoBytes: FIFTEEN_MB,
    maxAttachments: capabilities.maxMediaAttachments,
    maxAltTextLength: capabilities.mediaDescriptionLimit ?? DEFAULT_MAX_ALT,
    maxCharacters: capabilities.maxStatusCharacters,
    defaultScope: 'public',
    // Mastodon 4.5+ accepts `quote_approval_policy` on `POST /api/v1/statuses`.
    // The composer only sends a policy when it's also sending a
    // `quoted_status_id`; until quote-authoring UI lands on Mastodon
    // (plan §4.5 — out of scope unless free) the value is moot. Keep
    // 'public' as the safe default; matches Pixelfed's fallback.
    quotePolicy: 'public',
    charactersReservedPerUrl:
      capabilities.charactersReservedPerUrl ?? MASTODON_DEFAULT_URL_RESERVATION,
    mediaRequired: false,
    maxSpoilerLength: DEFAULT_MAX_SPOILER,
    maxHashtagLength: DEFAULT_MAX_HASHTAG,
  }
}
