/**
 * Goal surface plugin, browser half: the GoalBar entry in the
 * conversation.input.dock strip. Projection-mode surface — the live goal
 * arrives through `useProjection('goal')` (seeded by the history tail page,
 * updated by session/projection frames), so this plugin owns no store, no
 * refresh chain, and no event listener. The inject face carries only the
 * three mutation verbs (edit/resume/clear over the goal.* wire domain);
 * their CAS ref reads the session's current projected value at call time.
 * Goal creation stays on the /goal host command.
 */
import type { ConnectionHandle, GoalRef, SessionId } from '@deepseek-ai/dsh-client-connection/client'
import type { RpcResult } from '@deepseek-ai/dsh-client-connection/client'
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
// Type-only: pulls the ui-conversation SlotMap merge (the input.dock entry).
import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
// Type-only: the `goal` SessionProjectionMap key merge (single source, the domain's pure outlet).
import type { GoalProjection } from '@deepseek-ai/dsh-goal/client'
import type { GoalActionResult, GoalBarActions } from './slots.ts'
import { GoalDock } from './GoalBar.tsx'

export { GoalBar, GoalDock } from './GoalBar.tsx'
export type { GoalActionResult, GoalBarActions } from './slots.ts'

/** Required services: slots for the dock entry, sessions for the projected ref, connection for the wire verbs. */
export const inject = ['slots', 'sessions', 'connection']

/** Map one settled RPC result onto the strip's inline-render shape. */
function settle<T>(result: RpcResult<T>): GoalActionResult {
  if (result.ok) return { ok: true }
  return { ok: false, error: { code: result.error.code, message: result.error.message } }
}

/**
 * Client plugin body: the GoalBar dock entry with its mutation verbs.
 * @param ctx - client root context.
 */
export function apply(ctx: ClientContext): void {
  const { goals } = (ctx.get('connection') as ConnectionHandle).api

  // Conditional mount: 'conversation.input.dock' is declared by the
  // conversation entry; the conversation service being up is the
  // registration-safe signal (the TodoDock/QueueDock seam).
  ctx.inject(['slots', 'conversation', 'sessions'], (scope: ClientContext) => {
    const sessions = scope.sessions

    /** The session's current projected CAS ref, read at verb call time (no staleness fence: the RPC's CAS is the guard). */
    const refOf = (sessionId: SessionId): GoalRef | undefined => {
      const face = sessions.binding(sessionId)?.session.projections.faceOf('goal')
      const projection = face?.getSnapshot() as GoalProjection | null | undefined
      if (projection == null) return undefined
      return { id: projection.goal.id, revision: projection.goal.revision }
    }

    const noCurrentGoal: GoalActionResult = {
      ok: false,
      error: { code: 'no-current-goal', message: 'no current goal to mutate' },
    }

    scope.effect(() => scope.slots.register({
      name: 'conversation.input.dock',
      id: 'goal',
      order: 1,
      inject: (sessionId): GoalBarActions => ({
        onEdit: async (objective) => {
          const ref = refOf(sessionId)
          if (ref === undefined) return noCurrentGoal
          return settle((await goals.edit({ sessionId, ref, objective })).result)
        },
        onResume: async () => {
          const ref = refOf(sessionId)
          if (ref === undefined) return noCurrentGoal
          return settle((await goals.resume({ sessionId, ref })).result)
        },
        onClear: async () => {
          const ref = refOf(sessionId)
          if (ref === undefined) return noCurrentGoal
          return settle((await goals.clear({ sessionId, ref })).result)
        },
      }),
    }, GoalDock), 'ui-goal: GoalBar dock registration')
  })
}
