// Per-row stripe + bounded indent. Threading visualization is
// `f(depth, accent)`; no parent-child coordination, no central walker.
// The hook (`useThreadRowStyle`) reads the active accent token from the
// Tamagui theme; the pure variant (`threadRowStyleFor`) is what these
// tests assert on, since the hook constraint isn't worth fighting inside
// a unit test.

import {
  BASE_LEFT_PADDING,
  FADE,
  INDENT_CAP,
  INDENT_STEP,
  resolvedLeftInset,
  STRIPE_GAP,
  STRIPE_WIDTH,
  threadRowStyleFor,
} from 'src/components/comments/threadStyle'

// Use an unambiguous primary so the rgba conversion is easy to read in
// failure messages. Real accents come from `src/lib/accentVariants.ts`.
const ACCENT = '#FF0000'

function rgba(alpha: number): string {
  return `rgba(255,0,0,${alpha})`
}

describe('threadRowStyleFor', () => {
  it('returns an empty object at depth 0 (ancestors / direct-replies)', () => {
    // Direct replies to the anchor start at depth 0 so the stripe palette
    // anchors at depth 1 — the first level of *nesting* is the first to
    // grow a stripe.
    expect(threadRowStyleFor(0, ACCENT)).toEqual({})
  })

  it('returns an empty object for negative depths (defensive)', () => {
    // `flattenWithDepth` only emits non-negative depths, but the helper
    // is exported and reachable from future callers — keep the contract
    // total to make the failure mode obvious.
    expect(threadRowStyleFor(-1, ACCENT)).toEqual({})
  })

  it('applies the brightest fade + base indent at depth 1', () => {
    expect(threadRowStyleFor(1, ACCENT)).toEqual({
      borderLeftWidth: STRIPE_WIDTH,
      borderLeftColor: rgba(FADE[0]),
      paddingLeft: STRIPE_GAP + 1 * INDENT_STEP,
    })
  })

  it('applies max indent at the cap', () => {
    expect(threadRowStyleFor(INDENT_CAP, ACCENT)).toEqual({
      borderLeftWidth: STRIPE_WIDTH,
      borderLeftColor: rgba(FADE[INDENT_CAP - 1]),
      paddingLeft: STRIPE_GAP + INDENT_CAP * INDENT_STEP,
    })
  })

  it('keeps the indent pinned past the cap and continues fading alpha', () => {
    // Past the indent cap the horizontal position plateaus; the alpha keeps
    // marching toward FADE_FLOOR so depth still reads visually.
    expect(threadRowStyleFor(INDENT_CAP + 1, ACCENT)).toEqual({
      borderLeftWidth: STRIPE_WIDTH,
      borderLeftColor: rgba(FADE[INDENT_CAP]),
      paddingLeft: STRIPE_GAP + INDENT_CAP * INDENT_STEP,
    })
  })

  it('clamps alpha at the floor for extreme depths', () => {
    // Server's authed `/context` returns up to 4096 rows depth-unbounded
    // (`ThreadLimits::contextLimit()`); the clamp handles arbitrary depth
    // gracefully without throwing or going below the floor.
    const deep = threadRowStyleFor(100, ACCENT)
    const floor = FADE[FADE.length - 1]
    expect(deep).toEqual({
      borderLeftWidth: STRIPE_WIDTH,
      borderLeftColor: rgba(floor),
      paddingLeft: STRIPE_GAP + INDENT_CAP * INDENT_STEP,
    })
  })

  it('hands non-hex colours back unchanged (defensive)', () => {
    // `withAlpha` accepts `#RRGGBB`; an already-rgba string flows through
    // untouched. Verifies the contract from `src/lib/color.ts` is intact.
    const s = threadRowStyleFor(2, 'rgba(0,0,0,0.5)')
    expect(s.borderLeftColor).toBe('rgba(0,0,0,0.5)')
  })

  it('emits a 5-stop fade with smoothstep shape (monotonic decreasing)', () => {
    // The five samples are derived from smoothstep so adjacent steps stay
    // perceptually balanced. Asserting monotonicity catches a future
    // refactor that flips the curve or replaces it with a non-eased table.
    expect(FADE).toHaveLength(5)
    for (let i = 1; i < FADE.length; i++) {
      expect(FADE[i]).toBeLessThan(FADE[i - 1])
    }
  })
})

describe('resolvedLeftInset', () => {
  it('returns the base padding at depth 0', () => {
    expect(resolvedLeftInset(0)).toBe(BASE_LEFT_PADDING)
  })

  it('matches the paddingLeft that threadRowStyleFor would emit', () => {
    for (let d = 1; d <= INDENT_CAP + 2; d++) {
      const s = threadRowStyleFor(d, ACCENT)
      expect(resolvedLeftInset(d)).toBe(s.paddingLeft)
    }
  })
})
