/**
 * Lane J — the slot catalogue the editor's left panel renders.
 *
 * The VOCABULARY is not defined here. `SLOT_IDS` and `SLOT_LABELS` come from
 * src/assets/slots.ts, which is the same list the runtime registry answers to —
 * an editor that offered a slot the game discards (or hid one the game supports)
 * would be lying to the user, and the two lists drifted apart exactly that way
 * before this import existed.
 *
 * What lives here is PRESENTATION only: help text, camera framing, grouping,
 * ordering. It is keyed by `SlotId`, so describing a slot the runtime does not
 * have is a compile error rather than a dead row in the list, and a slot the
 * runtime adds shows up automatically with its runtime label.
 *
 * THREE-free on purpose: the stage maps ids to objects, this file only names
 * them, so the catalogue is testable and cheap to import anywhere.
 */
import { SLOT_IDS as RUNTIME_SLOT_IDS, SLOT_LABELS, SLOT_NOTES } from '../assets/slots'
import type { SlotId } from '../assets/slots'

export type { SlotId }
export { isSlotId } from '../assets/slots'

export type SlotGroupName = 'The blob' | 'Machines' | 'The course' | 'Effects'

export interface SlotSpec {
  id: SlotId
  group: SlotGroupName
  /** Plain-language name shown in the list. */
  label: string
  /** One sentence under the name. */
  hint: string
  /** Where the camera should sit / look when you press "Show me". */
  focus: { target: [number, number, number]; distance: number }
  /** True for the paintable body — unlocks the paintability report. */
  paintable?: boolean
  /**
   * Derived slots have no drop zone of their own: they copy another slot.
   * (The ghost is a see-through copy of the blob, per the plan.)
   */
  derivedFrom?: SlotId
  /**
   * True when the game builds SEVERAL of this thing (nine puddles, one barrel
   * per cannon). A drop replaces every one of them, each at its own place —
   * which is what the runtime does too.
   */
  multi?: boolean
}

interface Presentation {
  label?: string
  hint: string
  focus: { target: [number, number, number]; distance: number }
  paintable?: boolean
  derivedFrom?: SlotId
  multi?: boolean
}

/**
 * Keyed by SlotId: a typo, or a slot the runtime dropped, fails `tsc`.
 * Partial on purpose — a slot with no entry still appears, using its runtime
 * label and note, so the editor can never silently hide part of the game.
 */
const PRESENTATION: Partial<Record<SlotId, Presentation>> = {
  'blob.body': {
    label: 'Blob body',
    hint: 'The thing you play as. Paint sticks to this one, so it needs tidy UVs.',
    focus: { target: [0, 3.5, 30], distance: 6 },
    paintable: true,
  },
  'blob.face': {
    label: 'Blob face',
    hint: 'Just the eyes. Kept separate so paint never covers them.',
    focus: { target: [0, 3.7, 30], distance: 4 },
  },
  'ghost.body': {
    label: 'Ghost racer',
    hint: 'Your best-run ghost. It copies the blob body automatically.',
    focus: { target: [3, 3.5, 30], distance: 6 },
    derivedFrom: 'blob.body',
  },
  'cannon.barrel': {
    label: 'Paint cannon barrel',
    hint: 'The swivelling tube. Every cannon on the course gets your model, ' +
      'each one tinted the colour it shoots.',
    focus: { target: [-9, 3, 14], distance: 6 },
    multi: true,
  },
  'machine.plate': {
    label: 'Pressure plate',
    hint: 'The pad a heavy blob stands on to set the boot off. Frame only — the ' +
      'pressed pad stays as it is so it can light up.',
    focus: { target: [-6, 0.5, -47], distance: 9 },
  },
  'machine.boot': {
    label: 'Spring boot',
    hint: 'Kicks you down the course. Model it standing on the floor, origin at the base.',
    focus: { target: [-6, 1, -49.5], distance: 9 },
  },
  'machine.bucket': {
    label: 'Paint bucket',
    hint: 'Tips a colour over you. Its model must pivot at the lip and pour toward +X.',
    focus: { target: [-1.4, 6, -50], distance: 10 },
  },
  'machine.arch': {
    label: 'Bubble wash',
    hint: 'Walk through it and paint comes off.',
    focus: { target: [-4.5, 2, -56], distance: 9 },
  },
  'machine.belt': {
    label: 'Conveyor belt',
    hint: 'Slow-carries you along. Model the slab only — the moving stripes stay animated.',
    focus: { target: [0, 0.5, -50], distance: 12 },
  },
  'machine.fan': {
    label: 'Fan',
    hint: 'Blows you sideways. Face it toward +Z; the blades stay as they are so they keep spinning.',
    focus: { target: [0, 2, -30], distance: 10 },
  },
  'machine.seesaw': {
    label: 'See-saw plank',
    hint: 'Tips under your weight. Plank only, long axis left-to-right, origin in the middle.',
    focus: { target: [0, 1, -20], distance: 12 },
  },
  'course.scenery.cereal': {
    label: 'Giant cereal box',
    hint: 'Big silly prop next to the start. Nothing depends on it — go wild.',
    focus: { target: [-20, 7, 26], distance: 22 },
  },
  'course.scenery.block': {
    label: 'Purple block',
    hint: 'The other roadside prop. Also purely decorative.',
    focus: { target: [20, 5, 12], distance: 20 },
  },
  'fx.puddle': {
    label: 'Paint puddle',
    hint: 'The glossy colour patches on the ground. Every puddle gets your model, ' +
      'stretched to fit its own patch.',
    focus: { target: [0, 1.1, 12], distance: 10 },
    multi: true,
  },
}

const GROUP_OF: [prefix: string, group: SlotGroupName][] = [
  ['blob.', 'The blob'],
  ['ghost.', 'The blob'],
  ['cannon.', 'Machines'],
  ['machine.', 'Machines'],
  ['course.', 'The course'],
  ['fx.', 'Effects'],
]

const groupFor = (id: SlotId): SlotGroupName =>
  GROUP_OF.find(([prefix]) => id.startsWith(prefix))?.[1] ?? 'The course'

/** A slot with no bespoke help text still gets a usable line, never a blank. */
const fallbackHint = (id: SlotId): string =>
  SLOT_NOTES[id] ?? `Drop a model here to replace the ${SLOT_LABELS[id].toLowerCase()}.`

export const SLOTS: SlotSpec[] = RUNTIME_SLOT_IDS.map((id): SlotSpec => {
  const p = PRESENTATION[id]
  return {
    id,
    group: groupFor(id),
    label: p?.label ?? SLOT_LABELS[id],
    hint: p?.hint ?? fallbackHint(id),
    focus: p?.focus ?? { target: [0, 2, 0], distance: 16 },
    ...(p?.paintable ? { paintable: true } : {}),
    ...(p?.derivedFrom ? { derivedFrom: p.derivedFrom } : {}),
    ...(p?.multi ? { multi: true } : {}),
  }
})

export const SLOT_IDS: SlotId[] = SLOTS.map((s) => s.id)

export const slotById = (id: string): SlotSpec | undefined =>
  SLOTS.find((s) => s.id === id)

export const SLOT_GROUPS: SlotGroupName[] =
  ['The blob', 'Machines', 'The course', 'Effects']
