// Radial menu action tree. To add a new menu action:
//   - Add a `RadialAction` node to MASCOT_ACTIONS below (or call
//     `registerMascotAction(a, parentId)` at runtime to insert under an
//     existing node).
//   - The leaf `action(ctx)` mutates the model/runtime via ctx; nested
//     `children` render as a sub-ring.
//   - `visible(model)` gates visibility (e.g. "Rename" only once hatched).
// RadialMenu.svelte renders whatever tree it's given, including
// arbitrary nesting depth — no engine change is needed for a new node.

import type { MascotActionCtx, RadialAction } from './types'
import { REACTIONS } from './stimuli'

// v1 tree: Interact [Pet, Feed → [Seeds, Worm]], Care [Sleep, Wake],
// Identity [Rename], Debug [Lifecycle → [...], Reactions → [...], Force
// fall, Reset]. The Pet action is the same as a plain click — included in
// the menu for discoverability.
//
// Every Debug leaf carries a `description` naming the real, non-debug
// trigger it's simulating (rendered as a muted second line by
// RadialMenu.svelte) — the point of a debug menu is to let you fire
// something without waiting for the real condition, but that's only
// useful for testing if you also know what condition it's standing in for.

/** Play a REACTIONS entry (see stimuli.ts) exactly as the real stimulus bus would — same anim/bubble/duration/effect — without needing to fake the chat/activity/event stream that normally triggers it. */
function triggerReaction(ctx: MascotActionCtx, id: keyof typeof REACTIONS): void {
  const r = REACTIONS[id]
  ctx.runtime.bubbleText = r.bubble ?? null
  ctx.runtime.bubbleUntil = performance.now() + r.durationMs
  ctx.force('react', { anim: r.anim, durationMs: r.durationMs })
  r.effect?.()
}

/** Show a speech-bubble line (text and/or emoji — see Mascot.svelte's template) above the mascot for `ms`. */
function showBubble(ctx: MascotActionCtx, text: string, ms: number): void {
  ctx.runtime.bubbleText = text
  ctx.runtime.bubbleUntil = performance.now() + ms
}

export const MASCOT_ACTIONS: RadialAction[] = [
  {
    id: 'interact',
    label: 'Interact',
    children: [
      {
        id: 'pet',
        label: 'Pet',
        action: (ctx) => {
          showBubble(ctx, '❤️', 1500)
          ctx.force('react', { anim: 'react-happy', durationMs: 1500 })
          ctx.refresh()
        }
      },
      {
        id: 'feed',
        label: 'Feed',
        children: [
          {
            id: 'feed-seeds',
            label: 'Seeds',
            action: (ctx) => {
              // Feeding: small happiness + xp boost.
              ctx.model.happiness = Math.min(100, ctx.model.happiness + 8)
              showBubble(ctx, '🌾 Yum!', 1800)
              // Force the dedicated 'peck' behavior, not 'idle' with an anim
              // override — stepMascot() re-derives the anim from the active
              // behavior's own anim() every tick (except for 'react', which
              // has a special-cased override via reactAnim), so an anim
              // override on any other behavior gets silently clobbered
              // within one frame. 'peck' always resolves to the peck anim.
              ctx.force('peck', { durationMs: 1800 })
              ctx.refresh()
            }
          },
          {
            id: 'feed-worm',
            label: 'Worm',
            action: (ctx) => {
              // Worm: bigger boost.
              ctx.model.happiness = Math.min(100, ctx.model.happiness + 16)
              showBubble(ctx, '🐛 Yum!', 1800)
              // Force the dedicated 'peck' behavior, not 'idle' with an anim
              // override — stepMascot() re-derives the anim from the active
              // behavior's own anim() every tick (except for 'react', which
              // has a special-cased override via reactAnim), so an anim
              // override on any other behavior gets silently clobbered
              // within one frame. 'peck' always resolves to the peck anim.
              ctx.force('peck', { durationMs: 1800 })
              ctx.refresh()
            }
          }
        ]
      }
    ]
  },
  {
    id: 'care',
    label: 'Care',
    children: [
      {
        id: 'sleep',
        label: 'Sleep',
        visible: (m) => m.stage !== 'egg',
        action: (ctx) => {
          showBubble(ctx, '😴 Zzz…', 2000)
          ctx.force('sleep', { durationMs: 8000 })
          ctx.refresh()
        }
      },
      {
        id: 'wake',
        label: 'Wake',
        visible: () => true, // visible always; only useful when asleep but harmless otherwise
        action: (ctx) => {
          ctx.force('idle')
          ctx.refresh()
        }
      }
    ]
  },
  {
    id: 'identity',
    label: 'Identity',
    children: [
      {
        id: 'rename',
        label: 'Rename',
        visible: (m) => m.stage !== 'egg',
        action: (ctx) => {
          ctx.requestRename()
          ctx.refresh()
        }
      }
    ]
  },
  {
    id: 'debug',
    label: 'Debug',
    children: [
      {
        id: 'debug-lifecycle',
        label: 'Lifecycle',
        children: [
          {
            id: 'force-hatch',
            label: 'Force hatch',
            description:
              'Normally fires the instant you submit a name for a fresh egg — hatching is tied to naming, not a timer.',
            visible: (m) => m.stage === 'egg',
            action: (ctx) => {
              ctx.forceHatch()
              ctx.refresh()
            }
          },
          {
            id: 'force-chick',
            label: 'Force chick',
            description:
              'Sets the stage directly. Normally happens automatically the moment a freshly-named egg hatches.',
            visible: (m) => m.stage !== 'chick',
            action: (ctx) => {
              ctx.forceStage('chick')
              ctx.force('idle')
              ctx.refresh()
            }
          },
          {
            id: 'force-adult',
            label: 'Force adult',
            description:
              'Sets the stage directly. Normally happens once xp reaches 200 — earned by petting, feeding, and reactions like Eureka.',
            visible: (m) => m.stage !== 'adult',
            action: (ctx) => {
              ctx.forceStage('adult')
              ctx.force('idle')
              ctx.refresh()
            }
          }
        ]
      },
      {
        id: 'debug-reactions',
        label: 'Reactions',
        visible: (m) => m.stage !== 'egg', // reactions are suppressed while an egg — see MascotLayer's attachStimuli callback
        children: [
          {
            id: 'trigger-thinking',
            label: 'Trigger: Thinking',
            description:
              'Normally a continuous state (not a timed pulse like this button): the focused task window\'s session starts streaming a reply, before any text has arrived yet. Switches to the "talk" sprite the moment text starts appearing.',
            action: (ctx) => {
              triggerReaction(ctx, 'thinking')
              ctx.refresh()
            }
          },
          {
            id: 'trigger-eureka',
            label: 'Trigger: Eureka',
            description:
              'Normally fires when the focused task window records new knowledge (an "upsert_knowledge" tool result) — the real bubble shows the knowledge\'s own title. Grants +5 xp.',
            action: (ctx) => {
              triggerReaction(ctx, 'eureka')
              ctx.refresh()
            }
          },
          {
            id: 'trigger-alarmed',
            label: 'Trigger: Alarmed',
            description:
              'Normally fires when the focused task window raises a new operator question (permission needed to proceed). The only reaction that wakes the mascot from sleep.',
            action: (ctx) => {
              triggerReaction(ctx, 'alarmed')
              ctx.refresh()
            }
          },
          {
            id: 'trigger-happy',
            label: 'Trigger: Happy',
            description:
              "Normally fires when the focused task window's task completes successfully — the real bubble shows the task's own summary.",
            action: (ctx) => {
              triggerReaction(ctx, 'happy')
              ctx.refresh()
            }
          }
        ]
      },
      {
        id: 'force-fall',
        label: 'Force fall',
        description:
          'Normally starts when you drop the mascot mid-air, it walks off the edge of a window, or the surface beneath it disappears (a window closes or moves away). Lifts it up first so there’s room to actually fall.',
        visible: (m) => m.stage !== 'egg',
        action: (ctx) => {
          ctx.runtime.y = Math.max(0, ctx.runtime.y - 160)
          ctx.force('falling')
          ctx.refresh()
        }
      },
      {
        id: 'reset',
        label: 'Reset',
        description:
          'No natural trigger — clears all mascot state (stage, name, stats, position) back to a fresh, unnamed egg.',
        action: (ctx) => {
          ctx.reset()
          ctx.refresh()
        }
      }
    ]
  }
]

/** Insert an action at runtime, optionally nested under a parent id. Root insertion if parentId is undefined. */
export function registerMascotAction(a: RadialAction, parentId?: string): void {
  if (!parentId) {
    MASCOT_ACTIONS.push(a)
    return
  }
  function findAndInsert(nodes: RadialAction[]): boolean {
    for (const n of nodes) {
      if (n.id === parentId) {
        n.children = n.children ?? []
        n.children.push(a)
        return true
      }
      if (n.children && findAndInsert(n.children)) return true
    }
    return false
  }
  findAndInsert(MASCOT_ACTIONS)
}
