// `mergeOptimisticCredentials` — pure helper that maps an
// `UpdateCredentialsInput` patch onto a cached `Account` / `LoginUserResponse`
// blob. It backs the optimistic-update path of `useUpdateCredentialsMutation`,
// so a regression here would manifest as a toggle that flips and then
// snaps back, or an Edit Profile field that doesn't update until reload.
//
// Pins:
//   1. `display_name`, `note`, `website`, `locked` map directly.
//   2. `note` also writes through to `note_text` — the cached blob carries
//      both, and the privacy / profile screens read whichever is
//      convenient. Letting them drift is the bug we're guarding against.
//   3. Mastodon-only fields (`discoverable`, `hide_collections`,
//      `indexable`, `bot`) map directly.
//   4. `'source[privacy]'` lands inside the nested `source` object —
//      Mastodon's profile shape carries this under `source.privacy`.
//   5. Pixelfed-only privacy switches (`show_profile_*`, `is_suggestable`,
//      `public_dm`, `crawlable`, `disable_embeds`, `show_atom`) write
//      under the `settings` object — the privacy-settings page reads
//      from `profile.settings.<flag>`.
//   6. `undefined` fields are no-ops (so a partial input doesn't clobber
//      adjacent state).
//   7. The function is pure — the input objects are not mutated.
//   8. `null` / `undefined` input passes through unchanged (defensive
//      against the cold-cache case).

import { mergeOptimisticCredentials } from '../src/hooks/mutations/mergeOptimisticCredentials'

describe('mergeOptimisticCredentials', () => {
  it('returns the value unchanged when the cached profile is missing', () => {
    expect(mergeOptimisticCredentials(undefined, { locked: true })).toBeUndefined()
    // null is callable too — same defensive case.
    expect(
      mergeOptimisticCredentials(null as unknown as undefined, { locked: true })
    ).toBeNull()
  })

  it('maps display_name / website / locked directly', () => {
    const before = { display_name: 'Old', website: 'old.example', locked: false }
    const after = mergeOptimisticCredentials(before, {
      display_name: 'New',
      website: 'new.example',
      locked: true,
    })
    expect(after).toEqual({
      display_name: 'New',
      website: 'new.example',
      locked: true,
    })
  })

  it('mirrors note into note_text so both copies stay in lockstep', () => {
    const before = { note: '<p>old</p>', note_text: 'old' }
    const after = mergeOptimisticCredentials(before, { note: 'updated bio' })
    expect(after).toEqual({ note: 'updated bio', note_text: 'updated bio' })
  })

  it('writes Mastodon-only top-level booleans directly', () => {
    const before = { discoverable: false, hide_collections: false, indexable: false }
    const after = mergeOptimisticCredentials(before, {
      discoverable: true,
      hide_collections: true,
      indexable: true,
      bot: true,
    })
    expect(after).toEqual({
      discoverable: true,
      hide_collections: true,
      indexable: true,
      bot: true,
    })
  })

  it('writes source[privacy] into the nested source.privacy field', () => {
    const before = { source: { privacy: 'public' as const, language: 'en' } }
    const after = mergeOptimisticCredentials(before, {
      'source[privacy]': 'unlisted',
    })
    // language must survive — only privacy was patched.
    expect(after).toEqual({ source: { privacy: 'unlisted', language: 'en' } })
  })

  it('creates source.privacy when the cached blob does not carry source', () => {
    const before = { display_name: 'Alice' }
    const after = mergeOptimisticCredentials(before, {
      'source[privacy]': 'private',
    })
    expect((after as any).source).toEqual({ privacy: 'private' })
  })

  it('writes Pixelfed-only privacy switches under settings.*', () => {
    const before = {
      settings: {
        show_profile_follower_count: true,
        show_profile_following_count: true,
        is_suggestable: true,
        crawlable: true,
        disable_embeds: false,
        public_dm: true,
        show_atom: false,
      },
    }
    const after = mergeOptimisticCredentials(before, {
      show_profile_follower_count: false,
      crawlable: false,
      show_atom: true,
    })
    expect(after).toEqual({
      settings: {
        show_profile_follower_count: false,
        show_profile_following_count: true,
        is_suggestable: true,
        crawlable: false,
        disable_embeds: false,
        public_dm: true,
        show_atom: true,
      },
    })
  })

  it('treats undefined input fields as no-ops', () => {
    const before = { display_name: 'Alice', locked: false }
    const after = mergeOptimisticCredentials(before, {
      display_name: undefined,
      locked: true,
    })
    expect(after).toEqual({ display_name: 'Alice', locked: true })
  })

  it('does not mutate the cached blob in place', () => {
    const before = { display_name: 'Alice', settings: { is_suggestable: false } }
    const snapshot = JSON.parse(JSON.stringify(before))
    const after = mergeOptimisticCredentials(before, {
      display_name: 'Bob',
      is_suggestable: true,
    })
    expect(before).toEqual(snapshot)
    expect(after).not.toBe(before)
    // `settings` is also a fresh object — mutating the merged result
    // shouldn't leak back into the snapshot we just compared against.
    expect((after as any).settings).not.toBe((before as any).settings)
  })
})
