import { z, type ZodType } from 'zod'
import { ActionRefSchema } from './actions'
import {
  DescriptionSchema,
  IconNameSchema,
  LabelSchema,
  ModelIdSchema,
  PathSchema,
  ReferenceIdsSchema
} from './shared'
import { OntologyScopeSchema, type OntologyScope } from '../ontology'
import { defineDomainRecord } from '../helpers'

// ---------------------------------------------------------------------------
// Systems domain
// ---------------------------------------------------------------------------
//
// A System is a tenant-defined bounded context. It can carry UI presence,
// hierarchy, and governance metadata; non-UI systems simply omit `ui`.

export const SystemKindSchema = z
  .enum(['product', 'operational', 'platform', 'diagnostic'])
  .meta({ label: 'System kind', color: 'blue' })
export const SystemLifecycleSchema = z
  .enum(['draft', 'beta', 'active', 'deprecated', 'archived'])
  .meta({ label: 'Lifecycle', color: 'teal' })
/** @deprecated Use SystemLifecycleSchema. Accepted for one publish cycle. */
export const SystemStatusSchema = z.enum(['active', 'deprecated', 'archived']).meta({ label: 'Status', color: 'teal' })
export const SystemIdSchema = ModelIdSchema
/**
 * Validates a dot-separated system path (e.g. "sales.lead-gen", "sales.crm").
 * Each segment is lowercase, starts with a letter or digit, and may contain hyphens.
 * This is the canonical form used in `resource.systemPath`.
 */
export const SystemPathSchema = z
  .string()
  .trim()
  .min(1)
  .regex(
    /^[a-z0-9][a-z0-9-]*(?:\.[a-z0-9][a-z0-9-]*)*$/,
    'must be a dotted lowercase path (e.g. "sales.lead-gen" or "sales.crm")'
  )
export const UiPositionSchema = z.enum(['sidebar-primary', 'sidebar-bottom']).meta({ label: 'UI position' })
export const NodeIdPathSchema = SystemIdSchema
export const NodeIdStringSchema = z
  .string()
  .trim()
  .min(1)
  .max(200)
  // Kind prefix allows hyphens; id allows scoped references such as ontology targets.
  .regex(
    /^[a-z][a-z-]*:([a-z0-9-]+)(\.[a-z0-9-]+)*(:[a-z0-9.-]+)*$/,
    'Node references must use kind:dotted-path (e.g. system:sales.crm or resource:lead-gen.company.qualify)'
  )

export const SystemUiSchema = z.object({
  path: PathSchema,
  surfaces: ReferenceIdsSchema,
  icon: IconNameSchema.optional(),
  order: z.number().int().optional()
})

export const SystemInterfaceKeySchema = ModelIdSchema
export const SystemInterfaceLifecycleSchema = z
  .enum(['draft', 'active', 'disabled', 'deprecated', 'archived'])
  .meta({ label: 'System interface lifecycle', color: 'teal' })
export const SYSTEM_INTERFACE_PROFILES = [
  {
    systemPath: 'sales.lead-gen',
    interfaceKey: 'api',
    readinessProfile: 'sales.lead-gen.api'
  },
  {
    systemPath: 'sales.crm',
    interfaceKey: 'api',
    readinessProfile: 'sales.crm.api'
  },
  {
    systemPath: 'sales.lead-gen',
    interfaceKey: 'crm-handoff',
    readinessProfile: 'sales.lead-gen.crm-handoff'
  }
] as const
type SystemInterfaceReadinessProfileValue = (typeof SYSTEM_INTERFACE_PROFILES)[number]['readinessProfile']
export const SYSTEM_INTERFACE_READINESS_PROFILES = SYSTEM_INTERFACE_PROFILES.map(
  (profile) => profile.readinessProfile
) as [SystemInterfaceReadinessProfileValue, ...SystemInterfaceReadinessProfileValue[]]
export const SystemInterfaceReadinessProfileSchema = z.enum(SYSTEM_INTERFACE_READINESS_PROFILES)
export const SystemInterfaceResourceScopeSchema = z
  .array(ModelIdSchema)
  .default([])

export const SystemApiInterfaceSchema = z
  .object({
    lifecycle: SystemInterfaceLifecycleSchema.default('active'),
    readinessProfile: SystemInterfaceReadinessProfileSchema.optional(),
    /**
     * Resource ids that participate in this API interface. This scopes readiness
     * derivation without duplicating authored required/provided contract refs.
     */
    resourceIds: SystemInterfaceResourceScopeSchema.optional()
  })
  .strict()

export const SystemInterfaceRefSchema = z
  .object({
    systemPath: SystemPathSchema,
    interfaceKey: SystemInterfaceKeySchema
  })
  .strict()

// ---------------------------------------------------------------------------
// Recursive SystemEntry schema.
//
// TypeScript cannot infer the type of a schema that references itself through
// z.lazy(). The standard Zod fix is to:
//   1. Declare the output type as an explicit interface BEFORE the schema.
//   2. Annotate the schema variable with `ZodType<SystemEntry>` so TS uses
//      the declared interface instead of trying to infer through the cycle.
// ---------------------------------------------------------------------------

export type JsonPrimitive = string | number | boolean | null
export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue }

export const JsonValueSchema: ZodType<JsonValue> = z.lazy(() =>
  z.union([
    z.string(),
    z.number(),
    z.boolean(),
    z.null(),
    z.array(JsonValueSchema),
    z.record(z.string(), JsonValueSchema)
  ])
)

export const SystemConfigSchema = z.record(z.string().trim().min(1).max(200), JsonValueSchema).default({}).optional()

/** Explicit interface needed to annotate the recursive SystemEntrySchema. */
export interface SystemEntry {
  id: string
  label?: string
  title?: string
  description?: string
  kind?: 'product' | 'operational' | 'platform' | 'diagnostic'
  parentSystemId?: string
  ui?: { path: string; surfaces: string[]; icon?: string; order?: number }
  lifecycle?: 'draft' | 'beta' | 'active' | 'deprecated' | 'archived'
  responsibleRoleId?: string
  governedByKnowledge?: string[]
  actions?: { actionId: string; intent: 'exposes' | 'consumes'; invocation?: unknown }[]
  policies?: string[]
  drivesGoals?: string[]
  apiInterface?: z.infer<typeof SystemApiInterfaceSchema>
  /** @deprecated Use lifecycle. Accepted for one publish cycle. */
  status?: 'active' | 'deprecated' | 'archived'
  path?: string
  icon?: string
  uiPosition?: 'sidebar-primary' | 'sidebar-bottom'
  enabled?: boolean
  devOnly?: boolean
  requiresAdmin?: boolean
  order: number
  config?: Record<string, JsonValue>
  ontology?: OntologyScope
  systems?: Record<string, SystemEntry>
  subsystems?: Record<string, SystemEntry>
}

export const SystemEntrySchema: ZodType<SystemEntry> = z
  .object({
    /** Stable tenant-defined system id (e.g. "sys.lead-gen" or "sales.crm"). */
    id: SystemIdSchema,
    /** Human-readable system label shown in UI, governance, and operations surfaces. */
    label: LabelSchema.optional(),
    /** @deprecated Use label. Accepted for pre-consolidation System declarations. */
    title: LabelSchema.optional(),
    /** One-paragraph purpose statement for the bounded context. */
    description: DescriptionSchema.optional(),
    /** Closed system shape enum; catalog values remain tenant-defined. */
    kind: SystemKindSchema.optional(),
    /** Optional self-reference for System hierarchy. */
    parentSystemId: SystemIdSchema.optional(),
    /** Optional UI presence. Systems without UI omit this. */
    ui: SystemUiSchema.optional(),
    /** Canonical lifecycle state. Replaces Feature.enabled/devOnly and System.status. */
    lifecycle: SystemLifecycleSchema.optional(),
    /** Optional role responsible for this system. */
    responsibleRoleId: ModelIdSchema.meta({ ref: 'role' }).optional(),
    /** Optional knowledge nodes that govern this system. */
    governedByKnowledge: z
      .array(ModelIdSchema.meta({ ref: 'knowledge' }))
      .default([])
      .optional(),
    /** Optional actions this system exposes or consumes. */
    actions: z.array(ActionRefSchema).optional(),
    /** Optional operational policies that apply to this system. */
    policies: z
      .array(ModelIdSchema.meta({ ref: 'policy' }))
      .default([])
      .optional(),
    /** Optional goals this system contributes to. */
    drivesGoals: z
      .array(ModelIdSchema.meta({ ref: 'goal' }))
      .default([])
      .optional(),
    /** Thin API runtime-boundary marker. Readiness is derived from scoped resources and topology. */
    apiInterface: SystemApiInterfaceSchema.optional(),
    /** @deprecated Use lifecycle. Accepted for one publish cycle. */
    status: SystemStatusSchema.optional(),
    /** @deprecated Use ui.path. Kept for one-cycle Feature compatibility. */
    path: PathSchema.optional(),
    /** @deprecated Use ui.icon. Kept for one-cycle Feature compatibility. */
    icon: IconNameSchema.optional(),
    /** @deprecated UI placement hint, retained for one-cycle compatibility. */
    uiPosition: UiPositionSchema.optional(),
    /** @deprecated Use lifecycle. */
    enabled: z.boolean().optional(),
    /** @deprecated Use lifecycle: "beta". */
    devOnly: z.boolean().optional(),
    requiresAdmin: z.boolean().optional(),
    /** Domain-map iteration order. Convention: multiples of 10 (10, 20, 30, ...) to allow easy insertion. */
    order: z.number(),
    /**
     * System-local JSON settings and defaults. Strongly typed OM fields,
     * secrets, credentials, and runtime state stay outside this bucket.
     */
    config: SystemConfigSchema,
    /**
     * System-owned ontology declarations. `systems` is now the canonical child
     * key; this scope holds the object, action, catalog, link, event, and
     * shared contract records owned by this system.
     */
    ontology: OntologyScopeSchema.optional(),
    /**
     * Recursive child systems, authored via nesting (per L11).
     * The key is the local system id; the full path is computed by joining
     * ancestor keys with `.` (e.g. parent key `'sales'` + child key `'crm'` → `'sales.crm'`).
     * Per Phase 4: `id` and `parentSystemId` fields will be removed in favour of
     * position-derived paths. Both still exist on this schema for backward compat.
     */
    systems: z.lazy(() => z.record(z.string().trim().min(1).max(100), SystemEntrySchema)).optional(),
    /** @deprecated Use systems. Accepted as a compatibility alias during the ontology bridge. */
    subsystems: z.lazy(() => z.record(z.string().trim().min(1).max(100), SystemEntrySchema)).optional()
  })
  .strict()
  .refine((system: SystemEntry) => system.label !== undefined || system.title !== undefined, {
    path: ['label'],
    message: 'System must provide label or title'
  })
  .transform((system: SystemEntry) => {
    const normalizedSystem =
      system.systems !== undefined && system.subsystems === undefined
        ? { ...system, subsystems: system.systems }
        : system

    if (normalizedSystem.status === undefined) return normalizedSystem

    console.warn('[organization-model] System.status is deprecated; use System.lifecycle instead.')
    return normalizedSystem.lifecycle === undefined
      ? { ...normalizedSystem, lifecycle: normalizedSystem.status }
      : normalizedSystem
  })

export const SystemsDomainSchema = z
  .record(z.string(), SystemEntrySchema)
  .refine((record) => Object.entries(record).every(([key, entry]) => entry.id === key), {
    message: 'Each system entry id must match its map key'
  })
  .default({})

export const DEFAULT_ORGANIZATION_MODEL_SYSTEMS: z.infer<typeof SystemsDomainSchema> = {}

/** Validate and return a single system entry. */
export function defineSystem(entry: z.input<typeof SystemEntrySchema>): z.infer<typeof SystemEntrySchema> {
  return SystemEntrySchema.parse(entry)
}

/** Validate and return an id-keyed map of system entries. */
export function defineSystems(
  entries: readonly z.input<typeof SystemEntrySchema>[]
): Record<string, z.infer<typeof SystemEntrySchema>> {
  return defineDomainRecord(SystemEntrySchema, entries)
}

export type SystemId = z.infer<typeof SystemIdSchema>
export type SystemKind = z.infer<typeof SystemKindSchema>
export type SystemLifecycle = z.infer<typeof SystemLifecycleSchema>
/** @deprecated Use SystemLifecycle. Accepted for one publish cycle. */
export type SystemStatus = z.infer<typeof SystemStatusSchema>
export type SystemLocalConfig = z.infer<typeof SystemConfigSchema>
export type SystemInterfaceKey = z.infer<typeof SystemInterfaceKeySchema>
export type SystemInterfaceLifecycle = z.infer<typeof SystemInterfaceLifecycleSchema>
export type SystemInterfaceReadinessProfile = z.infer<typeof SystemInterfaceReadinessProfileSchema>
export type SystemApiInterface = z.infer<typeof SystemApiInterfaceSchema>
export type SystemInterfaceRef = z.infer<typeof SystemInterfaceRefSchema>
// SystemEntry is declared as an explicit interface above the schema (required for
// recursive z.lazy() type inference). Re-export omitted — the interface IS the type.
export type SystemsDomain = z.infer<typeof SystemsDomainSchema>
