import { formatMessage, messages } from './messages'
import type { StreamEvent, TrackCard } from './types'
import { parseSpotifyUrl } from './spotifyUrl'

export class StreamParseError extends Error {
  constructor(message: string) {
    super(message)
    this.name = 'StreamParseError'
  }
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value)
}

function stringField(value: Record<string, unknown>, key: string): string {
  const field = value[key]
  if (typeof field !== 'string') {
    throw new StreamParseError(formatMessage('streamParserInvalidField', { field: key }))
  }
  return field
}

function numberField(value: Record<string, unknown>, key: string): number {
  const field = value[key]
  if (typeof field !== 'number' || !Number.isFinite(field)) {
    throw new StreamParseError(formatMessage('streamParserInvalidField', { field: key }))
  }
  return field
}

function stringArrayField(value: Record<string, unknown>, key: string): string[] {
  const field = value[key]
  if (!Array.isArray(field) || !field.every((entry) => typeof entry === 'string')) {
    throw new StreamParseError(formatMessage('streamParserInvalidField', { field: key }))
  }
  return field
}

function nullableStringField(value: Record<string, unknown>, key: string): string | null {
  const field = value[key]
  if (field !== null && typeof field !== 'string') {
    throw new StreamParseError(formatMessage('streamParserInvalidField', { field: key }))
  }
  return field
}

function spotifyUrlField(value: Record<string, unknown>, key: string): string | null {
  const field = nullableStringField(value, key)
  if (field === null) return null
  return parseSpotifyUrl(field)
}

function parseTrackCard(value: unknown): TrackCard {
  if (!isRecord(value)) throw new StreamParseError(messages.streamParserInvalidTrack)
  return {
    id: stringField(value, 'id'),
    uri: stringField(value, 'uri'),
    title: stringField(value, 'title'),
    artists: stringArrayField(value, 'artists'),
    album_name: stringField(value, 'album_name'),
    album_art_url: nullableStringField(value, 'album_art_url'),
    external_url: spotifyUrlField(value, 'external_url'),
  }
}

/** Parse one untrusted NDJSON value into the frozen event union. */
export function parseStreamEvent(value: unknown): StreamEvent {
  if (!isRecord(value) || typeof value.type !== 'string') {
    throw new StreamParseError(messages.streamParserMissingType)
  }

  switch (value.type) {
    case 'metadata':
      return {
        type: 'metadata',
        request_id: stringField(value, 'request_id'),
        intent_summary: stringField(value, 'intent_summary'),
        candidate_count: numberField(value, 'candidate_count'),
      }
    case 'track':
      return {
        type: 'track',
        rank: numberField(value, 'rank'),
        track: parseTrackCard(value.track),
        justification: stringField(value, 'justification'),
      }
    case 'warning':
      return {
        type: 'warning',
        code: stringField(value, 'code'),
        message: stringField(value, 'message'),
      }
    case 'error':
      return {
        type: 'error',
        code: stringField(value, 'code'),
        message: stringField(value, 'message'),
      }
    case 'done':
      return {
        type: 'done',
        track_count: numberField(value, 'track_count'),
        total_ms: numberField(value, 'total_ms'),
      }
    default:
      throw new StreamParseError(formatMessage('streamParserUnknownEvent', { type: value.type }))
  }
}
