import type { CaseState, DocumentExhibit, ExhibitRelation, Connection } from '../src/types.js'

function requirementsMet(document: DocumentExhibit, earnedFlags: ReadonlySet<string>) {
  return (document.requiredFlags || []).every(flag => earnedFlags.has(flag))
}

export function filterLevelVisibility(full: CaseState, earnedFlags: Iterable<string>, seenDocumentIds: Iterable<string>, authorMode: boolean): CaseState {
  if (authorMode) return { ...full, newlyVisibleDocumentIds: [] }
  const earned = new Set(earnedFlags)
  const seen = new Set(seenDocumentIds)
  const visibleExhibits = full.exhibits.filter(exhibit => !exhibit.hidden && (exhibit.type !== 'document' || requirementsMet(exhibit, earned)))
  const visibleIds = new Set(visibleExhibits.map(exhibit => exhibit.id))
  const sanitize = (exhibit: typeof visibleExhibits[number]) => {
    if (exhibit.type !== 'document') return exhibit
    const { requiredFlags: _requirements, ...document } = exhibit
    return document
  }
  return {
    ...full,
    exhibits: visibleExhibits.map(sanitize),
    relations: full.relations.filter(relation => visibleIds.has(relation.fromExhibitId) && visibleIds.has(relation.toExhibitId)),
    connections: full.connections.filter(connection => visibleIds.has(connection.fromExhibitId) && visibleIds.has(connection.toExhibitId)),
    newlyVisibleDocumentIds: visibleExhibits.flatMap(exhibit => exhibit.type === 'document' && !seen.has(exhibit.id) ? [exhibit.id] : []),
  }
}

function appendMissingById<T extends { id: string }>(submitted: T[], preserved: T[]) {
  const ids = new Set(submitted.map(item => item.id))
  return [...submitted, ...preserved.filter(item => !ids.has(item.id))]
}

/** Preserve server-hidden objects during the legacy whole-board PUT used by play mode. */
export function mergePlayerStateForPersistence(full: CaseState, visible: CaseState, submitted: CaseState): CaseState {
  const visibleIds = new Set(visible.exhibits.map(exhibit => exhibit.id))
  const unavailableIds = new Set(full.exhibits.filter(exhibit => !visibleIds.has(exhibit.id)).map(exhibit => exhibit.id))
  const fullDocuments = new Map(full.exhibits.flatMap(exhibit => exhibit.type === 'document' ? [[exhibit.id, exhibit] as const] : []))
  const submittedExhibits = submitted.exhibits.map(exhibit => exhibit.type === 'document'
    ? { ...exhibit, requiredFlags: fullDocuments.get(exhibit.id)?.requiredFlags || exhibit.requiredFlags || [] }
    : exhibit)
  const preservedExhibits = full.exhibits.filter(exhibit => unavailableIds.has(exhibit.id))
  const touchesUnavailable = (item: ExhibitRelation | Connection) => unavailableIds.has(item.fromExhibitId) || unavailableIds.has(item.toExhibitId)
  return {
    ...submitted,
    report: full.report,
    exhibits: appendMissingById(submittedExhibits, preservedExhibits),
    relations: appendMissingById(submitted.relations, full.relations.filter(touchesUnavailable)),
    connections: appendMissingById(submitted.connections, full.connections.filter(touchesUnavailable)),
    newlyVisibleDocumentIds: [],
  }
}
