import { ScaffoldEntityInputSchema, type EntityRelation, type ValidationResult } from './types.js'
import { CORE_WHITELIST_V1 } from './generate.js'

/** FK column name an owning relation (many-to-one / one-to-one) maps to. */
function fkNameOf(rel: EntityRelation): string {
  return rel.foreignKey ?? `${rel.targetEntity}Id`
}

/** Effective referential action: explicit `onDelete` wins, else legacy boolean, else restrict. */
function onDeleteOf(rel: EntityRelation): 'restrict' | 'cascade' | 'set-null' | 'no-action' {
  return rel.onDelete ?? (rel.cascadeDelete ? 'cascade' : 'restrict')
}

export function validate(raw: unknown): ValidationResult {
  const result = ScaffoldEntityInputSchema.safeParse(raw)
  if (!result.success) return { valid: false, errors: result.error.issues.map(i => `[${i.path.join('.')}] ${i.message}`), warnings: [] }
  const spec = result.data
  const warnings: string[] = []
  const errors: string[] = []
  const reserved = ['Id', 'CreatedAt', 'UpdatedAt', 'DeletedAt', 'TenantId']
  for (const f of spec.fields) { if (reserved.includes(f.name)) warnings.push(`"${f.name}" inherited from BaseEntity — skipped`) }

  // ─── Relations: validate the OWNING side (the one that carries the FK column) ───
  const owning = spec.relations.filter(r => r.type === 'many-to-one' || r.type === 'one-to-one')
  const fieldNames = new Map(spec.fields.map(f => [f.name.toLowerCase(), f]))
  const seenFk = new Set<string>()
  for (const rel of owning) {
    const fk = fkNameOf(rel)
    const action = onDeleteOf(rel)
    const scope = rel.targetScope ?? 'same-module'
    if (scope === 'core' && !CORE_WHITELIST_V1.has(rel.targetEntity)) {
      errors.push(
        `relation → ${rel.targetEntity}: targetScope "core" is restricted to the V1 whitelist ` +
        `(User, Role, Tenant, Company, Department, JobTitle, Office, Language, Group). ` +
        `"${rel.targetEntity}" is NOT whitelisted. Inject ICoreDataService (lookup helpers) ` +
        `or open an issue per docs/extensions/whitelist-evolution.md to propose adding it.`
      )
    }
    if (reserved.includes(fk)) errors.push(`relation → ${rel.targetEntity}: foreign key "${fk}" collides with a BaseEntity property`)
    if (action === 'set-null' && !rel.nullable) errors.push(`relation → ${rel.targetEntity}: onDelete "set-null" requires nullable: true`)
    if (seenFk.has(fk.toLowerCase())) errors.push(`duplicate foreign key "${fk}" across relations — give one an explicit distinct foreignKey`)
    seenFk.add(fk.toLowerCase())
    const collide = fieldNames.get(fk.toLowerCase())
    if (collide && collide.type.toLowerCase() !== 'guid') {
      errors.push(`relation → ${rel.targetEntity}: foreign key "${fk}" collides with a non-Guid field of the same name`)
    } else if (collide) {
      warnings.push(`relation → ${rel.targetEntity}: reuses the existing "${fk}" Guid field as the FK column (no duplicate emitted)`)
    }
  }

  // Cross-module / core scope only makes sense on the OWNING side (this entity holds
  // the FK column). A collection relation cannot point outside the module.
  for (const rel of spec.relations) {
    if ((rel.type === 'one-to-many' || rel.type === 'many-to-many') && rel.targetScope && rel.targetScope !== 'same-module') {
      errors.push(`relation → ${rel.targetEntity}: targetScope "${rel.targetScope}" is only valid on a many-to-one / one-to-one (owning) relation`)
    }
  }

  return { valid: errors.length === 0, errors, warnings }
}
