import { z } from 'zod'
import { DescriptionSchema, LabelSchema, ModelIdSchema } from './shared'
import { SystemPathSchema, SystemLifecycleSchema } from './systems'
import { ActionInvocationSchema } from './actions'
import { OntologyIdSchema } from '../ontology'

// ---------------------------------------------------------------------------
// ContractRef — portable, browser-safe ref string for workflow I/O contracts
// ---------------------------------------------------------------------------
//
// Format: `package/subpath#ExportName`
// Examples:
//   `@repo/elevasis-core/contracts/apollo-import#inputSchema`
//   `@repo/elevasis-core/contracts/dtc-subscription-score#outputSchema`
//
// Tier-1 validation (schema.ts superRefine): ref-string SHAPE only — no import
// Tier-2 validation (om:verify): intra-@repo/elevasis-core typed-map resolution

/** Portable `package/subpath#ExportName` contract reference string. Browser-safe; no imports. */
export const ContractRefSchema = z
  .string()
  .trim()
  .min(1)
  .max(500)
  .regex(
    /^[A-Za-z0-9@](?:[A-Za-z0-9_./@-]*[A-Za-z0-9_])?\/?[A-Za-z0-9_./@-]*#[A-Za-z_$][A-Za-z0-9_$]*$/,
    'ContractRef must be in the format package/subpath#ExportName (e.g. @repo/elevasis-core/contracts/apollo-import#inputSchema)'
  )

export type ContractRef = z.infer<typeof ContractRefSchema>

/** Parsed representation of a ContractRef string. */
export interface ParsedContractRef {
  /** The full package + subpath portion before `#`. */
  moduleSpecifier: string
  /** The export name after `#`. */
  exportName: string
}

/**
 * Parse a `package/subpath#ExportName` contract ref string.
 * Browser-safe — no module system interaction.
 *
 * @throws {Error} if the ref string does not match the expected format.
 */
export function parseContractRef(ref: string): ParsedContractRef {
  const hashIndex = ref.lastIndexOf('#')
  if (hashIndex === -1) {
    throw new Error(`ContractRef "${ref}" is missing the required "#ExportName" suffix`)
  }
  const moduleSpecifier = ref.slice(0, hashIndex)
  const exportName = ref.slice(hashIndex + 1)
  if (!moduleSpecifier) {
    throw new Error(`ContractRef "${ref}" has an empty module specifier before "#"`)
  }
  if (!exportName) {
    throw new Error(`ContractRef "${ref}" has an empty export name after "#"`)
  }
  return { moduleSpecifier, exportName }
}

// ---------------------------------------------------------------------------
// Resources domain
// ---------------------------------------------------------------------------
//
// Resources are governance-only OM descriptors. Runtime code imports these
// descriptors and attaches executable behavior; it does not re-author identity.

export const ResourceKindSchema = z
  .enum(['workflow', 'agent', 'integration', 'script'])
  .meta({ label: 'Resource kind', color: 'orange' })
export const ResourceGovernanceStatusSchema = z
  .enum(['active', 'deprecated', 'archived'])
  .meta({ label: 'Governance status', color: 'teal' })
export const AgentKindSchema = z
  .enum(['orchestrator', 'specialist', 'utility', 'platform'])
  .meta({ label: 'Agent kind', color: 'violet' })
export const ScriptResourceLanguageSchema = z.enum(['shell', 'sql', 'typescript', 'python']).meta({ label: 'Language' })
export const CodeReferenceRoleSchema = z
  .enum(['entrypoint', 'handler', 'schema', 'test', 'docs', 'config'])
  .meta({ label: 'Code reference role', color: 'blue' })

export const ResourceIdSchema = z
  .string()
  .trim()
  .min(1)
  .max(255)
  .regex(/^[A-Za-z0-9]+(?:[-._][A-Za-z0-9]+)*$/, 'Resource IDs must use letters, numbers, -, _, or . separators')

export const EventIdSchema = z
  .string()
  .trim()
  .min(1)
  .max(300)
  .regex(
    /^[A-Za-z0-9]+(?:[-._][A-Za-z0-9]+)*:[a-z0-9]+(?:[-._][a-z0-9]+)*$/,
    'Event IDs must use <owner-id>:<event-key>'
  )

export const EventKeySchema = ModelIdSchema

export const EventEmissionDescriptorSchema = z.object({
  eventKey: EventKeySchema,
  label: z.string().trim().min(1).max(120),
  payloadSchema: ModelIdSchema.optional(),
  lifecycle: SystemLifecycleSchema.optional()
})

export const EventDescriptorSchema = EventEmissionDescriptorSchema.extend({
  id: EventIdSchema,
  ownerId: z.union([ResourceIdSchema, ModelIdSchema]),
  ownerKind: z.enum(['resource', 'entity']).meta({ label: 'Owner kind' })
})

export const ResourceOntologyBindingSchema = z
  .object({
    actions: z.array(OntologyIdSchema).optional(),
    primaryAction: OntologyIdSchema.optional(),
    reads: z.array(OntologyIdSchema).optional(),
    writes: z.array(OntologyIdSchema).optional(),
    usesCatalogs: z.array(OntologyIdSchema).optional(),
    emits: z.array(OntologyIdSchema).optional(),
    /**
     * Optional typed contract binding for this resource's workflow I/O.
     * Each ref is a `package/subpath#ExportName` string that resolves to a
     * Zod schema in the tenant-owned organization model package.
     *
     * Absence of this field preserves all existing behavior — it is additive + optional.
     * Tier-1 validation (schema.ts): ref-string shape only (browser-safe, no imports).
     * Tier-2 validation (om:verify): intra-package typed-map resolution asserts ZodType.
     */
    contract: z
      .object({
        input: ContractRefSchema.optional(),
        output: ContractRefSchema.optional()
      })
      .optional()
  })
  .superRefine((binding, ctx) => {
    if (binding.primaryAction === undefined) return
    if (binding.actions?.includes(binding.primaryAction)) return

    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      path: ['primaryAction'],
      message: 'Resource ontology primaryAction must be included in actions'
    })
  })

type OntologyRefInput = string | { id: string }
type ResourceOntologyBindingInput = {
  actions?: OntologyRefInput[]
  primaryAction?: OntologyRefInput
  reads?: OntologyRefInput[]
  writes?: OntologyRefInput[]
  usesCatalogs?: OntologyRefInput[]
  emits?: OntologyRefInput[]
}

export const CodeReferenceSchema = z.object({
  path: z
    .string()
    .trim()
    .min(1)
    .max(500)
    .regex(/^[A-Za-z0-9_./$@()[\] -]+$/, 'Code reference paths must be repo-relative paths'),
  role: CodeReferenceRoleSchema,
  symbol: z.string().trim().min(1).max(200).optional(),
  description: z.string().trim().min(1).max(300).optional()
})

const ResourceEntryBaseSchema = z.object({
  /** Canonical resource id; runtime resourceId derives from this value. */
  id: ResourceIdSchema,
  /** Domain-map iteration order. Convention: multiples of 10 (10, 20, 30, ...) to allow easy insertion. */
  order: z.number().default(0),
  /** Required single System membership — value is a dot-separated system path (e.g. "sales.lead-gen"). */
  systemPath: SystemPathSchema.meta({ ref: 'system' }),
  /** Executable display title owned by the OM Resource descriptor. */
  title: LabelSchema.optional(),
  /** Executable display description owned by the OM Resource descriptor. */
  description: DescriptionSchema.optional(),
  /** Optional role responsible for maintaining this resource. */
  ownerRoleId: ModelIdSchema.meta({ ref: 'role' }).optional(),
  status: ResourceGovernanceStatusSchema,
  /**
   * Ontology contract bindings for the semantic work this resource performs.
   * `emits` stays nested here so top-level resource emits descriptors remain
   * compatible with graph event projection during the bridge.
   */
  ontology: ResourceOntologyBindingSchema.optional(),
  /** Repo-relative implementation breadcrumbs for agents and operators. */
  codeRefs: z.array(CodeReferenceSchema).default([])
})

export const WorkflowResourceEntrySchema = ResourceEntryBaseSchema.extend({
  kind: z.literal('workflow'),
  emits: z.array(EventEmissionDescriptorSchema).optional()
})

export const AgentResourceEntrySchema = ResourceEntryBaseSchema.extend({
  kind: z.literal('agent'),
  /** Mirrors code-side AgentConfig.kind. */
  agentKind: AgentKindSchema,
  /** Role this agent embodies, if any. */
  actsAsRoleId: ModelIdSchema.meta({ ref: 'role' }).optional(),
  /** Mirrors AgentConfig.sessionCapable. */
  sessionCapable: z.boolean(),
  /** Broad/composite callable entry points orchestrated by this agent. */
  invocations: z.array(ActionInvocationSchema).default([]),
  emits: z.array(EventEmissionDescriptorSchema).optional()
})

export const IntegrationResourceEntrySchema = ResourceEntryBaseSchema.extend({
  kind: z.literal('integration'),
  provider: z.string().trim().min(1).max(100)
})

export const ScriptResourceSourceSchema = z.union([
  z.string().trim().min(1).max(50_000),
  z.object({
    file: z.string().trim().min(1).max(500)
  })
])

export const ScriptResourceEntrySchema = ResourceEntryBaseSchema.extend({
  kind: z.literal('script'),
  language: ScriptResourceLanguageSchema,
  source: ScriptResourceSourceSchema
})

export const ResourceEntrySchema = z.discriminatedUnion('kind', [
  WorkflowResourceEntrySchema,
  AgentResourceEntrySchema,
  IntegrationResourceEntrySchema,
  ScriptResourceEntrySchema
])

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

export const DEFAULT_ORGANIZATION_MODEL_RESOURCES: z.infer<typeof ResourcesDomainSchema> = {}

export function defineResource<const TResource extends ResourceEntry>(resource: TResource): TResource {
  return ResourceEntrySchema.parse(resource) as TResource
}

export function defineResources<const TResources extends Record<string, ResourceEntry>>(
  resources: TResources
): TResources {
  return Object.fromEntries(
    Object.entries(resources).map(([key, resource]) => [key, ResourceEntrySchema.parse(resource)])
  ) as TResources
}

function ontologyIdFrom(input: OntologyRefInput): string {
  return typeof input === 'string' ? input : input.id
}

function ontologyIdArrayFrom(input: OntologyRefInput[] | undefined): string[] | undefined {
  return input?.map(ontologyIdFrom)
}

export function defineResourceOntology(input: ResourceOntologyBindingInput): ResourceOntologyBinding {
  return ResourceOntologyBindingSchema.parse({
    actions: ontologyIdArrayFrom(input.actions),
    primaryAction: input.primaryAction === undefined ? undefined : ontologyIdFrom(input.primaryAction),
    reads: ontologyIdArrayFrom(input.reads),
    writes: ontologyIdArrayFrom(input.writes),
    usesCatalogs: ontologyIdArrayFrom(input.usesCatalogs),
    emits: ontologyIdArrayFrom(input.emits)
  })
}

export type ResourceId = z.infer<typeof ResourceIdSchema>
export type ResourceKind = z.infer<typeof ResourceKindSchema>
export type ResourceGovernanceStatus = z.infer<typeof ResourceGovernanceStatusSchema>
export type ResourceAgentKind = z.infer<typeof AgentKindSchema>
export type CodeReferenceRole = z.infer<typeof CodeReferenceRoleSchema>
export type ResourceOntologyBinding = z.infer<typeof ResourceOntologyBindingSchema>
export type ResourceOntologyBindingContract = NonNullable<ResourceOntologyBinding['contract']>
export type CodeReference = z.infer<typeof CodeReferenceSchema>
export type ScriptResourceLanguage = z.infer<typeof ScriptResourceLanguageSchema>
export type ScriptResourceSource = z.infer<typeof ScriptResourceSourceSchema>
export type WorkflowResourceEntry = z.infer<typeof WorkflowResourceEntrySchema>
export type AgentResourceEntry = z.infer<typeof AgentResourceEntrySchema>
export type IntegrationResourceEntry = z.infer<typeof IntegrationResourceEntrySchema>
export type ScriptResourceEntry = z.infer<typeof ScriptResourceEntrySchema>
export type ResourceEntry = z.infer<typeof ResourceEntrySchema>
export type ResourcesDomain = z.infer<typeof ResourcesDomainSchema>
