import { LLMEvent, type FinishReason, type ProviderMetadata, type Usage } from "../../schema"

export interface State {
  readonly stepStarted: boolean
  readonly text: ReadonlySet<string>
  readonly reasoning: ReadonlySet<string>
}

export const initial = (): State => ({ stepStarted: false, text: new Set(), reasoning: new Set() })

export const stepStart = (state: State, events: LLMEvent[]): State => {
  if (state.stepStarted) return state
  events.push(LLMEvent.stepStart({ index: 0 }))
  return { ...state, stepStarted: true }
}

export const textDelta = (state: State, events: LLMEvent[], id: string, text: string): State => {
  const stepped = stepStart(state, events)
  if (stepped.text.has(id)) {
    events.push(LLMEvent.textDelta({ id, text }))
    return stepped
  }
  events.push(LLMEvent.textStart({ id }), LLMEvent.textDelta({ id, text }))
  return { ...stepped, text: new Set([...stepped.text, id]) }
}

export const reasoningStart = (
  state: State,
  events: LLMEvent[],
  id: string,
  providerMetadata?: ProviderMetadata,
): State => {
  if (state.reasoning.has(id)) return state
  const stepped = stepStart(state, events)
  events.push(LLMEvent.reasoningStart({ id, providerMetadata }))
  return { ...stepped, reasoning: new Set([...stepped.reasoning, id]) }
}

export const reasoningDelta = (
  state: State,
  events: LLMEvent[],
  id: string,
  text: string,
  providerMetadata?: ProviderMetadata,
): State => {
  const started = reasoningStart(state, events, id, providerMetadata)
  events.push(LLMEvent.reasoningDelta({ id, text }))
  return started
}

export const reasoningEnd = (
  state: State,
  events: LLMEvent[],
  id: string,
  providerMetadata?: ProviderMetadata,
): State => {
  if (!state.reasoning.has(id)) return state
  const stepped = stepStart(state, events)
  events.push(LLMEvent.reasoningEnd({ id, providerMetadata }))
  const reasoning = new Set(stepped.reasoning)
  reasoning.delete(id)
  return { ...stepped, reasoning }
}

export const textEnd = (state: State, events: LLMEvent[], id: string, providerMetadata?: ProviderMetadata): State => {
  if (!state.text.has(id)) return state
  const stepped = stepStart(state, events)
  events.push(LLMEvent.textEnd({ id, providerMetadata }))
  const text = new Set(stepped.text)
  text.delete(id)
  return { ...stepped, text }
}

const closeOpenBlocks = (state: State, events: LLMEvent[]): State => {
  for (const id of state.reasoning) events.push(LLMEvent.reasoningEnd({ id }))
  for (const id of state.text) events.push(LLMEvent.textEnd({ id }))
  return { ...state, text: new Set(), reasoning: new Set() }
}

export const finish = (
  state: State,
  events: LLMEvent[],
  input: {
    readonly reason: FinishReason
    readonly usage?: Usage
    readonly providerMetadata?: ProviderMetadata
  },
): State => {
  const stepped = closeOpenBlocks(stepStart(state, events), events)
  events.push(
    LLMEvent.stepFinish({
      index: 0,
      reason: input.reason,
      usage: input.usage,
      providerMetadata: input.providerMetadata,
    }),
    LLMEvent.finish(input),
  )
  return { ...stepped, stepStarted: false }
}

export * as Lifecycle from "./lifecycle"
