import { z } from 'zod'

// ---------------------------------------------------------------------------
// Kind taxonomy
// ---------------------------------------------------------------------------

/**
 * The kind of scaffold entry:
 *
 * - autogen: fully derivable from source; regen command is the fix
 * - manual-scaffold: hand-authored structure that must be updated on source change
 * - sync-preservation: external-template files with a declared sync tier
 * - validator: drift-detection scripts or CI checks (meta.json pages[] validators, etc.)
 * - other: escape hatch; requires free-form notes
 */
export const ScaffoldEntryKindSchema = z.enum(['autogen', 'manual-scaffold', 'sync-preservation', 'validator', 'other'])

export type ScaffoldEntryKind = z.infer<typeof ScaffoldEntryKindSchema>

// ---------------------------------------------------------------------------
// External sync taxonomy
// ---------------------------------------------------------------------------

export const ExternalSyncCategorySchema = z.enum(['replace', 'merge', 'never-touch', 'generated'])

export type ExternalSyncCategory = z.infer<typeof ExternalSyncCategorySchema>

export const ExternalSyncOwnerSchema = z.enum([
  'template',
  '@elevasis/ui',
  '@elevasis/core',
  '@elevasis/sdk',
  'generator',
  'project'
])

export type ExternalSyncOwner = z.infer<typeof ExternalSyncOwnerSchema>

export const ExternalSyncStrategySchema = z.enum([
  'replace-all',
  'merge-regions',
  'merge-baseline',
  'verify-only',
  'generated-freshness'
])

export type ExternalSyncStrategy = z.infer<typeof ExternalSyncStrategySchema>

export const ExternalSyncDeletePolicySchema = z.enum(['none', 'manifest-only'])

export type ExternalSyncDeletePolicy = z.infer<typeof ExternalSyncDeletePolicySchema>

export const ExternalSyncLaneSeveritySchema = z.enum(['critical-manual-merge', 'optional-feature-selection'])

export type ExternalSyncLaneSeverity = z.infer<typeof ExternalSyncLaneSeveritySchema>

export const ExternalSyncLaneSeverityAnnotationSchema = z.object({
  path: z.string().min(1),
  severity: ExternalSyncLaneSeveritySchema
})

export type ExternalSyncLaneSeverityAnnotation = z.infer<typeof ExternalSyncLaneSeverityAnnotationSchema>

// ---------------------------------------------------------------------------
// Scaffold reference (a single downstream artifact that needs attention)
// ---------------------------------------------------------------------------

export const ScaffoldRefSchema = z.object({
  /**
   * File path, glob, or symbolic target (e.g. "docs: sync-preservation-matrix").
   * Symbolic targets begin with "docs:" or "autogen-target:".
   */
  path: z.string().min(1),

  /**
   * Command to regenerate, or "manual" if human judgment is required.
   */
  regen: z.string().min(1).optional(),

  /**
   * Human-readable hint shown in reminder messages.
   */
  hint: z.string().optional()
})

export type ScaffoldRef = z.infer<typeof ScaffoldRefSchema>

// ---------------------------------------------------------------------------
// Registry entry
// ---------------------------------------------------------------------------

export const ScaffoldRegistryEntrySchema = z
  .object({
    /**
     * Stable slug identifier for this entry (kebab-case).
     * Referenced by hooks, CI checks, and the /scaffold heal skill.
     */
    id: z
      .string()
      .min(1)
      .regex(/^[a-z0-9-]+$/, 'id must be kebab-case'),

    /**
     * The primary kind of this entry.
     */
    kind: ScaffoldEntryKindSchema,

    /**
     * Glob patterns (or exact paths) that, when edited, should trigger a
     * reminder for the associated scaffolds. Uses micromatch / glob semantics.
     */
    sources: z.array(z.string().min(1)).min(1, 'At least one source pattern is required'),

    /**
     * Optional glob patterns that suppress this entry's reminder when the
     * touched file matches. Applied after `sources`: a path that matches any
     * `sources` pattern AND any `excludes` pattern is treated as no-match.
     *
     * Use case: a broad source glob (e.g. `apps/docs/content/docs/**\/*.mdx`)
     * that should not fire on a sub-tree where the dependent scaffold does not
     * apply (e.g. `apps/docs/content/docs/in-progress/**`, where MDX docs do
     * not take meta.json files).
     */
    excludes: z.array(z.string().min(1)).optional(),

    /**
     * Downstream artifacts that need attention when a source changes.
     */
    dependents: z.array(ScaffoldRefSchema).min(1, 'At least one dependent is required'),

    /**
     * Package or app path that owns this scaffold surface.
     * Examples: "packages/core", "apps/api", "external/_template"
     *
     * External-sync-aware entries still use this field, but are constrained to
     * the `ExternalSyncOwnerSchema` enum when any external sync metadata is set.
     */
    owner: z.string().min(1),

    /**
     * Optional external sync classification for entries that participate in the
     * external template ownership model.
     */
    category: ExternalSyncCategorySchema.optional(),

    /**
     * Optional external sync strategy used by compare/apply/verify tooling.
     */
    strategy: ExternalSyncStrategySchema.optional(),

    /**
     * Optional delete behavior for managed external sync surfaces.
     */
    delete_policy: ExternalSyncDeletePolicySchema.optional(),

    /**
     * Optional sync-lane severity tags for project-relative external sync
     * paths. Used by sync-apply lane routing only; classification ownership
     * still comes from category/strategy/delete_policy.
     */
    sync_lane_severity: z.array(ExternalSyncLaneSeverityAnnotationSchema).optional(),

    /**
     * Free-form notes. Required when kind is "other".
     */
    notes: z.string().optional(),

    /**
     * The shell command used to regenerate this entry's outputs.
     *
     * Required for `kind: autogen` entries once Step 6 migration lands.
     * Optional now so harness scaffolding (Step 5) can land before migrating
     * existing generators. Validated as a non-empty string when present.
     *
     * Example: `"pnpm navigation:generate"`, `"pnpm scaffold:sync"`
     */
    regen: z.string().min(1).optional(),

    /**
     * Per-entry reminder throttle override in milliseconds.
     * Defaults to the hook-level default (5 minutes = 300_000 ms) when omitted.
     */
    cooldown_ms: z.number().int().positive().optional(),

    /**
     * When true, the PostToolUse reminder hook will automatically run the `regen`
     * command after emitting the reminder, rather than just advising the user.
     * Defaults to false (advisory only). Only meaningful when `regen` is also set.
     */
    auto_regen: z.boolean().optional()
  })
  .superRefine((entry, ctx) => {
    const usesExternalSyncMetadata =
      entry.category !== undefined || entry.strategy !== undefined || entry.delete_policy !== undefined

    if (usesExternalSyncMetadata) {
      const ownerResult = ExternalSyncOwnerSchema.safeParse(entry.owner)
      if (!ownerResult.success) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          path: ['owner'],
          message: `owner must be one of ${ExternalSyncOwnerSchema.options.join(', ')} when external sync metadata is set`
        })
      }
    }

    if (entry.kind === 'autogen' && !entry.regen) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        path: ['regen'],
        message: 'kind: autogen requires a top-level regen command'
      })
    }

    if (entry.kind === 'validator') {
      const hasVerifier = entry.dependents.some((dependent) => dependent.regen && dependent.regen !== 'manual')
      if (!hasVerifier) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          path: ['dependents'],
          message: 'kind: validator requires at least one executable dependent regen command'
        })
      }
    }

    if (entry.kind === 'other' && !entry.notes) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        path: ['notes'],
        message: 'kind: other requires notes explaining why no narrower kind fits'
      })
    }

    if (entry.kind === 'sync-preservation') {
      if (!entry.category || !entry.strategy || !entry.delete_policy) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          path: ['category'],
          message: 'kind: sync-preservation requires category, strategy, and delete_policy'
        })
      }
    }
  })

export type ScaffoldRegistryEntry = z.infer<typeof ScaffoldRegistryEntrySchema>

// ---------------------------------------------------------------------------
// Top-level registry document
// ---------------------------------------------------------------------------

export const ScaffoldRegistrySchema = z.object({
  /**
   * Schema version for forward-compatibility detection.
   * Bump when the shape changes in a breaking way.
   */
  version: z.string().default('1'),

  entries: z.array(ScaffoldRegistryEntrySchema).min(1)
})

export type ScaffoldRegistry = z.infer<typeof ScaffoldRegistrySchema>
