import { z } from 'zod';
import { SPORE_STATUSES, type SporeStatus } from '@myco/constants/spore-status.js';

export const SessionFrontmatterSchema = z.object({
  type: z.literal('session'),
  id: z.string(),
  agent: z.string(),
  user: z.string(),
  started: z.string(),
  ended: z.string().optional(),
  parent: z.string().optional(),
  parent_reason: z.string().optional(),
  plan: z.string().optional(),          // backward compat read path
  plans: z.array(z.string()).optional(), // new: multiple plans
  branch: z.string().optional(),
  tags: z.array(z.string()).default([]),
  tools_used: z.number().int().optional(),
  files_changed: z.number().int().optional(),
});

export const PLAN_STATUSES = ['active', 'in_progress', 'completed', 'abandoned'] as const;

export const PlanFrontmatterSchema = z.object({
  type: z.literal('plan'),
  id: z.string(),
  status: z.enum(PLAN_STATUSES).default('active'),
  created: z.string(),
  author: z.string().optional(),
  tags: z.array(z.string()).default([]),
});

/**
 * Canonical set of spore kinds the renderable + writable surface supports.
 *
 * Two clusters:
 * - **Direct-extraction** (capture-pipeline / coding agent writes these):
 *   `gotcha`, `bug_fix`, `decision`, `discovery`, `trade_off`, `cross-cutting`.
 * - **Synthesized** (intelligence agent writes these during vault-seed /
 *   vault-evolve consolidation):
 *   `wisdom`, `pattern`, `architecture`.
 *
 * Both clusters are first-class kinds — the UI maps every entry to a tone,
 * search ranks them uniformly, and downstream consumers should treat the
 * whole set as the renderable universe.
 */
export const OBSERVATION_TYPES = [
  'gotcha',
  'bug_fix',
  'decision',
  'discovery',
  'trade_off',
  'cross-cutting',
  'wisdom',
  'pattern',
  'architecture',
] as const;

// Canonical spore status set lives in constants/spore-status.ts; re-exported
// here for the many call sites that import it from vault/types.
export { SPORE_STATUSES };
export type { SporeStatus };

export const SporeFrontmatterSchema = z.object({
  type: z.literal('spore'),
  id: z.string(),
  observation_type: z.enum(OBSERVATION_TYPES),
  status: z.enum(SPORE_STATUSES).default('active'),
  session: z.string().optional(),
  plan: z.string().optional(),
  superseded_by: z.string().optional(),
  consolidated_from: z.array(z.string()).optional(),
  created: z.string(),
  tags: z.array(z.string()).default([]),
});

export const ARTIFACT_TYPES = ['spec', 'plan', 'rfc', 'doc', 'other'] as const;
export type ArtifactType = (typeof ARTIFACT_TYPES)[number];

export const ArtifactFrontmatterSchema = z.object({
  type: z.literal('artifact'),
  id: z.string(),
  artifact_type: z.enum(ARTIFACT_TYPES).default('other'),
  source_path: z.string(),
  title: z.string(),
  last_captured_by: z.string(),
  created: z.string(),
  updated: z.string(),
  tags: z.array(z.string()).default([]),
});

export const TeamMemberFrontmatterSchema = z.object({
  type: z.literal('team-member'),
  user: z.string(),
  joined: z.string(),
  role: z.string().optional(),
  tags: z.array(z.string()).default([]),
});

export type SessionFrontmatter = z.infer<typeof SessionFrontmatterSchema>;
export type PlanFrontmatter = z.infer<typeof PlanFrontmatterSchema>;
export type SporeFrontmatter = z.infer<typeof SporeFrontmatterSchema>;
export type ObservationType = SporeFrontmatter['observation_type'];
export type ArtifactFrontmatter = z.infer<typeof ArtifactFrontmatterSchema>;
export type TeamMemberFrontmatter = z.infer<typeof TeamMemberFrontmatterSchema>;

export type NoteFrontmatter =
  | SessionFrontmatter
  | PlanFrontmatter
  | SporeFrontmatter
  | ArtifactFrontmatter
  | TeamMemberFrontmatter;

export interface VaultNote<T extends NoteFrontmatter = NoteFrontmatter> {
  path: string;
  frontmatter: T;
  content: string;
}

const schemasByType: Record<string, z.ZodSchema> = {
  session: SessionFrontmatterSchema,
  plan: PlanFrontmatterSchema,
  spore: SporeFrontmatterSchema,
  artifact: ArtifactFrontmatterSchema,
  'team-member': TeamMemberFrontmatterSchema,
};

export function parseNoteFrontmatter(data: Record<string, unknown>): NoteFrontmatter {
  const type = data.type as string;
  const schema = schemasByType[type];
  if (!schema) {
    throw new Error(`Unknown note type: ${type}. Known types: ${Object.keys(schemasByType).join(', ')}`);
  }
  // gray-matter and YAML.parse return Date objects for ISO date strings.
  // Coerce them to strings before Zod validation.
  const coerced = coerceDatesToStrings(data);
  return schema.parse(coerced) as NoteFrontmatter;
}

function coerceDatesToStrings(obj: Record<string, unknown>): Record<string, unknown> {
  const result: Record<string, unknown> = {};
  for (const [key, value] of Object.entries(obj)) {
    if (value instanceof Date) {
      result[key] = value.toISOString();
    } else if (Array.isArray(value)) {
      result[key] = value.map((item) => item instanceof Date ? item.toISOString() : item);
    } else if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
      result[key] = coerceDatesToStrings(value as Record<string, unknown>);
    } else {
      result[key] = value;
    }
  }
  return result;
}
