import { z, type ZodType } from 'zod'
import { DescriptionSchema, IconNameSchema, LabelSchema, ModelIdSchema, PathSchema } from './shared'

export const SurfaceTypeSchema = z
  .enum(['page', 'dashboard', 'graph', 'detail', 'list', 'settings'])
  .meta({ label: 'Surface type', color: 'blue' })

export const SurfaceDefinitionSchema = z.object({
  id: ModelIdSchema,
  label: LabelSchema,
  path: PathSchema,
  surfaceType: SurfaceTypeSchema,
  description: DescriptionSchema.optional(),
  enabled: z.boolean().default(true),
  devOnly: z.boolean().optional(),
  icon: IconNameSchema.optional(),
  systemIds: z.array(ModelIdSchema.meta({ ref: 'system' })).default([]),
  entityIds: z.array(ModelIdSchema.meta({ ref: 'entity' })).default([]),
  resourceIds: z.array(ModelIdSchema.meta({ ref: 'resource' })).default([]),
  actionIds: z.array(ModelIdSchema.meta({ ref: 'action' })).default([]),
  parentId: ModelIdSchema.meta({ ref: 'surface' }).optional()
})

export const SidebarSurfaceTargetsSchema = z
  .object({
    systems: z
      .array(ModelIdSchema.meta({ ref: 'system' }))
      .default([])
      .optional(),
    entities: z
      .array(ModelIdSchema.meta({ ref: 'entity' }))
      .default([])
      .optional(),
    resources: z
      .array(ModelIdSchema.meta({ ref: 'resource' }))
      .default([])
      .optional(),
    actions: z
      .array(ModelIdSchema.meta({ ref: 'action' }))
      .default([])
      .optional()
  })
  .default({})

export interface SidebarSurfaceNode {
  type: 'surface'
  label: string
  path: string
  surfaceType: z.infer<typeof SurfaceTypeSchema>
  description?: string
  icon?: string
  order?: number
  targets?: {
    systems?: string[]
    entities?: string[]
    resources?: string[]
    actions?: string[]
  }
  devOnly?: boolean
  requiresAdmin?: boolean
}

export interface SidebarGroupNode {
  type: 'group'
  label: string
  description?: string
  icon?: string
  order?: number
  children: Record<string, SidebarNode>
}

export type SidebarNode = SidebarSurfaceNode | SidebarGroupNode

export const SidebarNodeSchema: ZodType<SidebarNode> = z.lazy(() =>
  z.discriminatedUnion('type', [
    z.object({
      type: z.literal('group'),
      label: LabelSchema,
      description: DescriptionSchema.optional(),
      icon: IconNameSchema.optional(),
      order: z.number().int().optional(),
      children: z.record(z.string(), SidebarNodeSchema).default({})
    }),
    z.object({
      type: z.literal('surface'),
      label: LabelSchema,
      path: PathSchema,
      surfaceType: SurfaceTypeSchema,
      description: DescriptionSchema.optional(),
      icon: IconNameSchema.optional(),
      order: z.number().int().optional(),
      targets: SidebarSurfaceTargetsSchema.optional(),
      devOnly: z.boolean().optional(),
      requiresAdmin: z.boolean().optional()
    })
  ])
)

export const SidebarSectionSchema = z.record(z.string(), SidebarNodeSchema).default({})

export const SidebarNavigationSchema = z
  .object({
    primary: SidebarSectionSchema,
    bottom: SidebarSectionSchema
  })
  .default({ primary: {}, bottom: {} })

/**
 * A leaf node in `navigation.topbar`. Has no `path`, `surfaceType`, or nesting —
 * topbar actions trigger behavior (open a modal, open docs, etc.) rather than navigate to a route.
 * Behavior is bound by a UI registry (`TopbarActionModule`) that matches on `id`.
 */
export const TopbarActionNodeSchema = z.object({
  id: ModelIdSchema,
  label: LabelSchema,
  tooltip: DescriptionSchema.optional(),
  icon: IconNameSchema.optional(),
  order: z.number().int().optional(),
  enabled: z.boolean().default(true),
  devOnly: z.boolean().optional(),
  requiresAdmin: z.boolean().optional(),
  targets: SidebarSurfaceTargetsSchema.optional()
})

/**
 * A flat map of topbar action nodes, keyed by action id.
 * Defaults to an empty record (no topbar actions declared).
 */
export const TopbarSectionSchema = z.record(z.string(), TopbarActionNodeSchema).default({})

export const OrganizationModelNavigationSchema = z
  .object({
    sidebar: SidebarNavigationSchema,
    topbar: TopbarSectionSchema
  })
  .default({ sidebar: { primary: {}, bottom: {} }, topbar: {} })

export const DEFAULT_ORGANIZATION_MODEL_NAVIGATION: z.infer<typeof OrganizationModelNavigationSchema> = {
  sidebar: {
    primary: {},
    bottom: {}
  },
  topbar: {}
}

export function getSortedSidebarEntries<TNode extends SidebarNode>(
  nodes: Record<string, TNode>
): Array<[string, TNode]> {
  return Object.entries(nodes).sort(([leftId, left], [rightId, right]) => {
    const orderDelta = (left.order ?? Number.MAX_SAFE_INTEGER) - (right.order ?? Number.MAX_SAFE_INTEGER)
    return orderDelta === 0 ? leftId.localeCompare(rightId) : orderDelta
  })
}

/**
 * Core placement values: 'primary' | 'secondary' | 'bottom'.
 *
 * `placement` is intentionally open (`z.string()`) so that per-project adapters can
 * introduce additional placement IDs (e.g. 'pinned', 'spotlight', 'contextual') without
 * forking core. Extension pattern: add the new string literal to the project's
 * `foundations/config/organization-model.ts` adapter — no changes to this file required.
 *
 * `surfaceType` and `resourceType` remain closed enums (UI/runtime invariants).
 */
export const CORE_PLACEMENT_VALUES = ['primary', 'secondary', 'bottom'] as const
export type CorePlacement = (typeof CORE_PLACEMENT_VALUES)[number]

export const NavigationGroupSchema = z.object({
  id: ModelIdSchema,
  label: LabelSchema,
  placement: z.string().trim().min(1).max(50),
  surfaceIds: z.array(ModelIdSchema.meta({ ref: 'surface' })).default([])
})

// SurfaceDefinitionSchema and NavigationGroupSchema remain for derived DTOs and
// transitional call sites. They are not authored top-level OrganizationModel fields.
