// `<FederatedRepliesPill>` state-machine tests. The pill is a single-state-
// prop component (`PillState` discriminated union) so future P8b additions
// (`pending` / `complete` "Show N new replies" + "Updated · +N") land as
// state-construction changes in the post screen, not refactors here.
//
// The four-state contract under test:
//   idle      → renders null (no DOM, no padding, no border).
//   fetching  → spinner + "Fetching federated replies…", optional
//               "(N so far)" tail when `resultCount > 0`.
//   pending   → "N new replies" / "1 new reply" copy (no spinner).
//   complete  → "Updated · +N" copy when `delta > 0`, "Federated replies
//               up to date" otherwise.
//
// Tamagui primitives are stubbed to passthrough host primitives so the
// renderer can introspect text content without a TamaguiProvider tree.
// `useTheme` returns a stub of every token the pill reads (`borderColor`,
// `background`, `colorSecondary`).

import React from 'react'
import { act, create, type ReactTestRenderer } from 'react-test-renderer'

// Tamagui mock — every named primitive becomes a forwardRef'd host
// component that renders its children verbatim. The pill only consumes
// `Spinner`, `Text`, `View`, `XStack`, `useTheme`; stub each to the
// minimum shape the renderer needs.
jest.mock('tamagui', () => {
  const React = require('react') as typeof import('react')
  const passthrough =
    (tag: string) =>
    ({ children }: { children?: React.ReactNode }) =>
      React.createElement(tag, null, children)
  return {
    Spinner: () => React.createElement('Spinner', null),
    Text: ({ children }: { children?: React.ReactNode }) =>
      React.createElement('Text', null, children),
    View: passthrough('View'),
    XStack: passthrough('XStack'),
    YStack: passthrough('YStack'),
    useTheme: () => ({
      borderColor: { val: '#000' },
      background: { val: '#fff' },
      colorSecondary: { val: '#888' },
    }),
  }
})

// Lazy-require so the `jest.mock('tamagui', ...)` factory above is in
// effect before the component module loads.
type PillModule = typeof import('../src/components/comments/FederatedRepliesPill')
let FederatedRepliesPill: PillModule['FederatedRepliesPill']

beforeAll(() => {
  FederatedRepliesPill = (
    require('../src/components/comments/FederatedRepliesPill') as PillModule
  ).FederatedRepliesPill
})

type PillState = import('../src/components/comments/FederatedRepliesPill').PillState

function renderPill(state: PillState): ReactTestRenderer {
  let root: ReactTestRenderer = null as unknown as ReactTestRenderer
  act(() => {
    root = create(React.createElement(FederatedRepliesPill, { state }))
  })
  return root
}

// Shallow text-extraction walk. Walks the rendered tree and concatenates
// every string node into a single string for assertion. Sufficient for
// content-presence checks; doesn't try to preserve formatting.
function extractText(json: any): string {
  if (json == null) return ''
  if (typeof json === 'string') return json
  if (Array.isArray(json)) return json.map(extractText).join(' ')
  if (typeof json === 'object' && 'children' in json) {
    return extractText(json.children)
  }
  return ''
}

describe('FederatedRepliesPill state machine', () => {
  it('renders null in the `idle` state (no walk in flight)', () => {
    const root = renderPill({ kind: 'idle' })
    expect(root.toJSON()).toBeNull()
  })

  it('renders the spinner-only message when `fetching` without a count', () => {
    const root = renderPill({ kind: 'fetching' })
    const text = extractText(root.toJSON())
    expect(text).toContain('Fetching federated replies')
    // No "(N so far)" suffix when resultCount is missing or zero — avoids
    // flashing "(0 so far)" on the very first poll while the server is
    // still warming up.
    expect(text).not.toContain('so far')
  })

  it('appends "(N so far)" while `fetching` once `resultCount > 0`', () => {
    const root = renderPill({ kind: 'fetching', resultCount: 7 })
    const text = extractText(root.toJSON())
    expect(text).toContain('Fetching federated replies')
    expect(text).toContain('(7 so far)')
  })

  it('omits the suffix at `resultCount: 0` (server reports nothing yet)', () => {
    const root = renderPill({ kind: 'fetching', resultCount: 0 })
    const text = extractText(root.toJSON())
    expect(text).toContain('Fetching federated replies')
    expect(text).not.toContain('so far')
  })

  it('treats null `resultCount` as missing (defensive)', () => {
    // `useAsyncRefresh.spec.resultCount` is typed `number | null`. The
    // pill's read uses `state.resultCount && state.resultCount > 0` which
    // already short-circuits on null — pin that contract so a future
    // refactor doesn't leak `(null so far)`.
    const root = renderPill({ kind: 'fetching', resultCount: null })
    const text = extractText(root.toJSON())
    expect(text).toContain('Fetching federated replies')
    expect(text).not.toContain('so far')
  })

  it('renders pluralised pending copy when more than one reply is queued', () => {
    // Forward-looking smoke test: `pending` is constructed by P8b's
    // post-screen wiring, not yet today. The pill must already accept and
    // render the discriminated-union case so P8b is purely a state-
    // construction change.
    const root = renderPill({ kind: 'pending', pendingCount: 3 })
    const text = extractText(root.toJSON())
    expect(text).toContain('3 new replies')
  })

  it('renders singular pending copy at exactly one queued reply', () => {
    const root = renderPill({ kind: 'pending', pendingCount: 1 })
    const text = extractText(root.toJSON())
    expect(text).toContain('1 new reply')
    // Defensive: must NOT match the plural form. ("1 new replies" would
    // be a copy regression.)
    expect(text).not.toContain('1 new replies')
  })

  it('renders "Updated · +N" copy on `complete` with a positive delta', () => {
    const root = renderPill({ kind: 'complete', delta: 4 })
    const text = extractText(root.toJSON())
    expect(text).toContain('Updated')
    expect(text).toContain('+4')
  })

  it('renders the up-to-date copy on `complete` with zero delta', () => {
    // Server returned the polling completion signal but no new descendants
    // surfaced — the walk hit the federated cooldown or the actor had
    // nothing more to share. The pill should reassure rather than show a
    // misleading "+0" delta.
    const root = renderPill({ kind: 'complete', delta: 0 })
    const text = extractText(root.toJSON())
    expect(text).toContain('Federated replies up to date')
    expect(text).not.toContain('+0')
  })
})
